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 }