1 /*
  2  *  Copyright (c) 2019, 2020, 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 jdk.incubator.foreign.MemorySegment;
 30 import jdk.incubator.foreign.ResourceScope;
 31 import jdk.incubator.foreign.SegmentAllocator;
 32 import jdk.internal.misc.ScopedMemoryAccess;
 33 import jdk.internal.vm.annotation.ForceInline;
 34 
 35 import java.lang.ref.Cleaner;
 36 import java.lang.ref.Reference;
 37 import java.util.Objects;
 38 
 39 /**
 40  * This class manages the temporal bounds associated with a memory segment as well
 41  * as thread confinement. A scope has a liveness bit, which is updated when the scope is closed
 42  * (this operation is triggered by {@link ResourceScope#close()}). This bit is consulted prior
 43  * to memory access (see {@link #checkValidState()}).
 44  * There are two kinds of memory scope: confined memory scope and shared memory scope.
 45  * A confined memory scope has an associated owner thread that confines some operations to
 46  * associated owner thread such as {@link #close()} or {@link #checkValidState()}.
 47  * Shared scopes 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 scopes use a more sophisticated synchronization mechanism, which guarantees that no concurrent
 50  * access is possible when a scope is being closed (see {@link jdk.internal.misc.ScopedMemoryAccess}).
 51  */
 52 public abstract non-sealed class ResourceScopeImpl implements ResourceScope, SegmentAllocator, ScopedMemoryAccess.Scope {
 53 
 54     final ResourceList resourceList;
 55     final Cleaner.Cleanable cleanable;
 56 
 57     static final int MAX_FORKS = Integer.MAX_VALUE;
 58 
 59     @Override
 60     public void addCloseAction(Runnable runnable) {
 61         Objects.requireNonNull(runnable);
 62         addInternal(ResourceList.ResourceCleanup.ofRunnable(runnable));
 63     }
 64 
 65     /**
 66      * Add a cleanup action. If a failure occurred (because of a add vs. close race), call the cleanup action.
 67      * This semantics is useful when allocating new memory segments, since we first do a malloc/mmap and _then_
 68      * we register the cleanup (free/munmap) against the scope; so, if registration fails, we still have to
 69      * cleanup memory. From the perspective of the client, such a failure would manifest as a factory
 70      * returning a segment that is already "closed" - which is always possible anyway (e.g. if the scope
 71      * is closed _after_ the cleanup for the segment is registered but _before_ the factory returns the
 72      * new segment to the client). For this reason, it's not worth adding extra complexity to the segment
 73      * initialization logic here - and using an optimistic logic works well in practice.
 74      */
 75     public void addOrCleanupIfFail(ResourceList.ResourceCleanup resource) {
 76         try {
 77             addInternal(resource);
 78         } catch (Throwable ex) {
 79             resource.cleanup();
 80         }
 81     }
 82 
 83     void addInternal(ResourceList.ResourceCleanup resource) {
 84         try {
 85             checkValidStateSlow();
 86             resourceList.add(resource);
 87         } catch (ScopedMemoryAccess.Scope.ScopedAccessError err) {
 88             throw new IllegalStateException("Already closed");
 89         }
 90     }
 91 
 92     protected ResourceScopeImpl(ResourceList resourceList, Cleaner cleaner) {
 93         this.resourceList = resourceList;
 94         cleanable = (cleaner != null) ?
 95             cleaner.register(this, resourceList) : null;
 96     }
 97 
 98     public static ResourceScopeImpl createConfined(Thread thread, Cleaner cleaner) {
 99         return new ConfinedScope(thread, cleaner);
100     }
101 
102     public static ResourceScopeImpl createShared(Cleaner cleaner) {
103         return new SharedScope(cleaner);
104     }
105 
106     @Override
107     public MemorySegment allocate(long bytesSize, long bytesAlignment) {
108         return MemorySegment.allocateNative(bytesSize, bytesAlignment, this);
109     }
110 
111     public abstract void release0();
112 
113     public abstract void acquire0();
114 
115     @Override
116     public void keepAlive(ResourceScope target) {
117         if (target == this) {
118             throw new IllegalArgumentException("Invalid target scope.");
119         }
120         ResourceScopeImpl targetImpl = (ResourceScopeImpl)target;
121         targetImpl.acquire0();
122         addCloseAction(targetImpl::release0);
123     }
124 
125     /**
126      * Closes this scope, executing any cleanup action (where provided).
127      * @throws IllegalStateException if this scope is already closed or if this is
128      * a confined scope and this method is called outside of the owner thread.
129      */
130     public void close() {
131         try {
132             justClose();
133             if (cleanable != null) {
134                 cleanable.clean();
135             } else {
136                 resourceList.cleanup();
137             }
138         } finally {
139             Reference.reachabilityFence(this);
140         }
141     }
142 
143     abstract void justClose();
144 
145     /**
146      * Returns "owner" thread of this scope.
147      * @return owner thread (or null for a shared scope)
148      */
149     public abstract Thread ownerThread();
150 
151     /**
152      * Returns true, if this scope is still alive. This method may be called in any thread.
153      * @return {@code true} if this scope is not closed yet.
154      */
155     public abstract boolean isAlive();
156 
157 
158     /**
159      * This is a faster version of {@link #checkValidStateSlow()}, which is called upon memory access, and which
160      * relies on invariants associated with the memory scope implementations (typically, volatile access
161      * to the closed state bit is replaced with plain access, and ownership check is removed where not needed.
162      * Should be used with care.
163      */
164     public abstract void checkValidState();
165 
166     /**
167      * Checks that this scope is still alive (see {@link #isAlive()}).
168      * @throws IllegalStateException if this scope is already closed or if this is
169      * a confined scope and this method is called outside of the owner thread.
170      */
171     public final void checkValidStateSlow() {
172         if (ownerThread() != null && Thread.currentThread() != ownerThread()) {
173             throw new IllegalStateException("Attempted access outside owning thread");
174         } else if (!isAlive()) {
175             throw new IllegalStateException("Already closed");
176         }
177     }
178 
179     @Override
180     protected Object clone() throws CloneNotSupportedException {
181         throw new CloneNotSupportedException();
182     }
183 
184     /**
185      * The global, always alive, non-closeable, shared scope. Similar to a shared scope, but its {@link #close()} method throws unconditionally.
186      * Adding new resources to the global scope, does nothing: as the scope can never become not-alive, there is nothing to track.
187      * Acquiring and or releasing a resource scope similarly does nothing.
188      */
189     static class GlobalScopeImpl extends SharedScope {
190 
191         public GlobalScopeImpl() {
192             super(null);
193         }
194 
195         @Override
196         public void close() {
197             throw new UnsupportedOperationException("Scope cannot be closed");
198         }
199 
200         @Override
201         @ForceInline
202         public void release0() {
203             // do nothing
204         }
205 
206         @Override
207         @ForceInline
208         public void acquire0() {
209             // do nothing
210         }
211 
212         @Override
213         void addInternal(ResourceList.ResourceCleanup resource) {
214             // do nothing
215         }
216     }
217 
218     public static final ResourceScopeImpl GLOBAL = new GlobalScopeImpl();
219 
220     /**
221      * A list of all cleanup actions associated with a resource scope. Cleanup actions are modelled as instances
222      * of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a scope
223      * is shared or confined, different implementations of this class will be used, see {@link ConfinedScope.ConfinedResourceList}
224      * and {@link SharedScope.SharedResourceList}.
225      */
226     public abstract static class ResourceList implements Runnable {
227         ResourceCleanup fst;
228 
229         abstract void add(ResourceCleanup cleanup);
230 
231         abstract void cleanup();
232 
233         public final void run() {
234             cleanup(); // cleaner interop
235         }
236 
237         static void cleanup(ResourceCleanup first) {
238             ResourceCleanup current = first;
239             while (current != null) {
240                 current.cleanup();
241                 current = current.next;
242             }
243         }
244 
245         public static abstract class ResourceCleanup {
246             ResourceCleanup next;
247 
248             public abstract void cleanup();
249 
250             static final ResourceCleanup CLOSED_LIST = new ResourceCleanup() {
251                 @Override
252                 public void cleanup() {
253                     throw new IllegalStateException("This resource list has already been closed!");
254                 }
255             };
256 
257             static ResourceCleanup ofRunnable(Runnable cleanupAction) {
258                 return new ResourceCleanup() {
259                     @Override
260                     public void cleanup() {
261                         cleanupAction.run();
262                     }
263                 };
264             }
265         }
266 
267     }
268 }