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