1 /*
  2  *  Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
  3  *  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  *  This code is free software; you can redistribute it and/or modify it
  6  *  under the terms of the GNU General Public License version 2 only, as
  7  *  published by the Free Software Foundation.  Oracle designates this
  8  *  particular file as subject to the "Classpath" exception as provided
  9  *  by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  *  This code is distributed in the hope that it will be useful, but WITHOUT
 12  *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  *  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  *  version 2 for more details (a copy is included in the LICENSE file that
 15  *  accompanied this code).
 16  *
 17  *  You should have received a copy of the GNU General Public License version
 18  *  2 along with this work; if not, write to the Free Software Foundation,
 19  *  Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  *   Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  *  or visit www.oracle.com if you need additional information or have any
 23  *  questions.
 24  *
 25  */
 26 
 27 package jdk.internal.foreign;
 28 
 29 import java.lang.foreign.MemorySegment;
 30 import java.lang.foreign.Arena;
 31 import java.lang.foreign.MemorySegment.Scope;
 32 import java.lang.invoke.MethodHandles;
 33 import java.lang.invoke.VarHandle;
 34 import java.lang.ref.Cleaner;
 35 import java.util.Objects;
 36 import jdk.internal.misc.ScopedMemoryAccess;
 37 import jdk.internal.vm.annotation.ForceInline;
 38 
 39 /**
 40  * This class manages the temporal bounds associated with a memory segment as well
 41  * as thread confinement. A session has a liveness bit, which is updated when the session is closed
 42  * (this operation is triggered by {@link MemorySessionImpl#close()}). This bit is consulted prior
 43  * to memory access (see {@link #checkValidStateRaw()}).
 44  * There are two kinds of memory session: confined memory session and shared memory session.
 45  * A confined memory session has an associated owner thread that confines some operations to
 46  * associated owner thread such as {@link #close()} or {@link #checkValidStateRaw()}.
 47  * Shared sessions do not feature an owner thread - meaning their operations can be called, in a racy
 48  * manner, by multiple threads. To guarantee temporal safety in the presence of concurrent thread,
 49  * shared sessions use a more sophisticated synchronization mechanism, which guarantees that no concurrent
 50  * access is possible when a session is being closed (see {@link jdk.internal.misc.ScopedMemoryAccess}).
 51  */
 52 public abstract sealed class MemorySessionImpl
 53         implements Scope
 54         permits ConfinedSession, GlobalSession, SharedSession {
 55     static final int OPEN = 0;
 56     static final int CLOSING = -1;
 57     static final int CLOSED = -2;
 58 
 59     static final VarHandle STATE;
 60     static final int MAX_FORKS = Integer.MAX_VALUE;
 61 
 62     public static final MemorySessionImpl GLOBAL = new GlobalSession(null);
 63 
 64     static final ScopedMemoryAccess.ScopedAccessError ALREADY_CLOSED = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::alreadyClosed);
 65     static final ScopedMemoryAccess.ScopedAccessError WRONG_THREAD = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::wrongThread);
 66 
 67     final ResourceList resourceList;
 68     final Thread owner;
 69     int state = OPEN;
 70 
 71     static {
 72         try {
 73             STATE = MethodHandles.lookup().findVarHandle(MemorySessionImpl.class, "state", int.class);
 74         } catch (Exception ex) {
 75             throw new ExceptionInInitializerError(ex);
 76         }
 77     }
 78 
 79     public Arena asArena() {
 80         return new Arena() {
 81             @Override
 82             public Scope scope() {
 83                 return MemorySessionImpl.this;
 84             }
 85 
 86             @Override
 87             public void close() {
 88                 MemorySessionImpl.this.close();
 89             }
 90         };
 91     }
 92 
 93     @ForceInline
 94     public static final MemorySessionImpl toMemorySession(Arena arena) {
 95         return (MemorySessionImpl) arena.scope();
 96     }
 97 
 98     public final boolean isCloseableBy(Thread thread) {
 99         Objects.requireNonNull(thread);
100         return isCloseable() &&
101                 (owner == null || owner == thread);
102     }
103 
104     public void addCloseAction(Runnable runnable) {
105         Objects.requireNonNull(runnable);
106         addInternal(ResourceList.ResourceCleanup.ofRunnable(runnable));
107     }
108 
109     /**
110      * Add a cleanup action. If a failure occurred (because of a add vs. close race), call the cleanup action.
111      * This semantics is useful when allocating new memory segments, since we first do a malloc/mmap and _then_
112      * we register the cleanup (free/munmap) against the session; so, if registration fails, we still have to
113      * cleanup memory. From the perspective of the client, such a failure would manifest as a factory
114      * returning a segment that is already "closed" - which is always possible anyway (e.g. if the session
115      * is closed _after_ the cleanup for the segment is registered but _before_ the factory returns the
116      * new segment to the client). For this reason, it's not worth adding extra complexity to the segment
117      * initialization logic here - and using an optimistic logic works well in practice.
118      */
119     public void addOrCleanupIfFail(ResourceList.ResourceCleanup resource) {
120         try {
121             addInternal(resource);
122         } catch (Throwable ex) {
123             resource.cleanup();
124             throw ex;
125         }
126     }
127 
128     void addInternal(ResourceList.ResourceCleanup resource) {
129         checkValidState();
130         // Note: from here on we no longer check the session state. Two cases are possible: either the resource cleanup
131         // is added to the list when the session is still open, in which case everything works ok; or the resource
132         // cleanup is added while the session is being closed. In this latter case, what matters is whether we have already
133         // called `ResourceList::cleanup` to run all the cleanup actions. If not, we can still add this resource
134         // to the list (and, in case of an add vs. close race, it might happen that the cleanup action will be
135         // called immediately after).
136         resourceList.add(resource);
137     }
138 
139     protected MemorySessionImpl(Thread owner, ResourceList resourceList) {
140         this.owner = owner;
141         this.resourceList = resourceList;
142     }
143 
144     public static MemorySessionImpl createConfined(Thread thread) {
145         return new ConfinedSession(thread);
146     }
147 
148     public static MemorySessionImpl createShared() {
149         return new SharedSession();
150     }
151 
152     public static MemorySessionImpl createImplicit(Cleaner cleaner) {
153         return new ImplicitSession(cleaner);
154     }
155 
156     public MemorySegment allocate(long byteSize, long byteAlignment) {
157         Utils.checkAllocationSizeAndAlign(byteSize, byteAlignment);
158         return NativeMemorySegmentImpl.makeNativeSegment(byteSize, byteAlignment, this);
159     }
160 
161     public abstract void release0();
162 
163     public abstract void acquire0();
164 
165     public void whileAlive(Runnable action) {
166         Objects.requireNonNull(action);
167         acquire0();
168         try {
169             action.run();
170         } finally {
171             release0();
172         }
173     }
174 
175     public final Thread ownerThread() {
176         return owner;
177     }
178 
179     public final boolean isAccessibleBy(Thread thread) {
180         Objects.requireNonNull(thread);
181         return owner == null || owner == thread;
182     }
183 
184     /**
185      * Returns true, if this session is still open. This method may be called in any thread.
186      * @return {@code true} if this session is not closed yet.
187      */
188     public boolean isAlive() {
189         return state >= OPEN;
190     }
191 
192     /**
193      * This is a faster version of {@link #checkValidState()}, which is called upon memory access, and which
194      * relies on invariants associated with the memory session implementations (volatile access
195      * to the closed state bit is replaced with plain access). This method should be monomorphic,
196      * to avoid virtual calls in the memory access hot path. This method is not intended as general purpose method
197      * and should only be used in the memory access handle hot path; for liveness checks triggered by other API methods,
198      * please use {@link #checkValidState()}.
199      */
200     @ForceInline
201     public void checkValidStateRaw() {
202         if (owner != null && owner != Thread.currentThread()) {
203             throw WRONG_THREAD;
204         }
205         if (state < OPEN) {
206             throw ALREADY_CLOSED;
207         }
208     }
209 
210     /**
211      * Checks that this session is still alive (see {@link #isAlive()}).
212      * @throws IllegalStateException if this session is already closed or if this is
213      * a confined session and this method is called outside of the owner thread.
214      */
215     public void checkValidState() {
216         try {
217             checkValidStateRaw();
218         } catch (ScopedMemoryAccess.ScopedAccessError error) {
219             throw error.newRuntimeException();
220         }
221     }
222 
223     public static final void checkValidState(MemorySegment segment) {
224         ((AbstractMemorySegmentImpl)segment).sessionImpl().checkValidState();
225     }
226 
227     @Override
228     protected Object clone() throws CloneNotSupportedException {
229         throw new CloneNotSupportedException();
230     }
231 
232     public boolean isCloseable() {
233         return true;
234     }
235 
236     /**
237      * Closes this session, executing any cleanup action (where provided).
238      * @throws IllegalStateException if this session is already closed or if this is
239      * a confined session and this method is called outside of the owner thread.
240      */
241     public void close() {
242         justClose();
243         resourceList.cleanup();
244     }
245 
246     abstract void justClose();
247 
248     public static MemorySessionImpl heapSession(Object ref) {
249         return new GlobalSession(ref);
250     }
251 
252     /**
253      * A list of all cleanup actions associated with a memory session. Cleanup actions are modelled as instances
254      * of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a session
255      * is shared or confined, different implementations of this class will be used, see {@link ConfinedSession.ConfinedResourceList}
256      * and {@link SharedSession.SharedResourceList}.
257      */
258     public abstract static class ResourceList implements Runnable {
259         ResourceCleanup fst;
260 
261         abstract void add(ResourceCleanup cleanup);
262 
263         abstract void cleanup();
264 
265         public final void run() {
266             cleanup(); // cleaner interop
267         }
268 
269         static void cleanup(ResourceCleanup first) {
270             ResourceCleanup current = first;
271             while (current != null) {
272                 current.cleanup();
273                 current = current.next;
274             }
275         }
276 
277         public abstract static class ResourceCleanup {
278             ResourceCleanup next;
279 
280             public abstract void cleanup();
281 
282             static final ResourceCleanup CLOSED_LIST = new ResourceCleanup() {
283                 @Override
284                 public void cleanup() {
285                     throw new IllegalStateException("This resource list has already been closed!");
286                 }
287             };
288 
289             static ResourceCleanup ofRunnable(Runnable cleanupAction) {
290                 return new ResourceCleanup() {
291                     @Override
292                     public void cleanup() {
293                         cleanupAction.run();
294                     }
295                 };
296             }
297         }
298     }
299 
300     // helper functions to centralize error handling
301 
302     static IllegalStateException tooManyAcquires() {
303         return new IllegalStateException("Session acquire limit exceeded");
304     }
305 
306     static IllegalStateException alreadyAcquired(int acquires) {
307         return new IllegalStateException(String.format("Session is acquired by %d clients", acquires));
308     }
309 
310     static IllegalStateException alreadyClosed() {
311         return new IllegalStateException("Already closed");
312     }
313 
314     static WrongThreadException wrongThread() {
315         return new WrongThreadException("Attempted access outside owning thread");
316     }
317 
318     static UnsupportedOperationException nonCloseable() {
319         return new UnsupportedOperationException("Attempted to close a non-closeable session");
320     }
321 
322 }