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 }