|
26 | 26 |
|
27 | 27 | package jdk.internal.foreign;
|
28 | 28 |
|
| 29 | +import jdk.internal.vm.annotation.ForceInline; |
| 30 | + |
29 | 31 | import java.lang.invoke.MethodHandles;
|
30 | 32 | import java.lang.invoke.VarHandle;
|
| 33 | +import java.util.Objects; |
| 34 | +import java.util.concurrent.atomic.LongAdder; |
| 35 | +import java.util.concurrent.locks.StampedLock; |
31 | 36 |
|
32 | 37 | /**
|
33 |
| - * This class manages the temporal bounds associated with a memory segment. A scope has a liveness bit, which is updated |
34 |
| - * when the scope is closed (this operation is triggered by {@link AbstractMemorySegmentImpl#close()}). Furthermore, a scope is |
35 |
| - * associated with an <em>atomic</em> counter which can be incremented (upon calling the {@link #acquire()} method), |
36 |
| - * and is decremented (when a previously acquired segment is later closed). |
| 38 | + * This class manages the temporal bounds associated with a memory segment as well |
| 39 | + * as thread confinement. |
| 40 | + * A scope has a liveness bit, which is updated when the scope is closed |
| 41 | + * (this operation is triggered by {@link AbstractMemorySegmentImpl#close()}). |
| 42 | + * A scope may also have an associated "owner" thread that confines some operations to |
| 43 | + * associated owner thread such as {@link #close()} or {@link #dup(Thread)}. |
| 44 | + * Furthermore, a scope is either root scope ({@link #create(Object, Runnable) created} |
| 45 | + * when memory segment is allocated) or child scope ({@link #acquire() acquired} from root scope). |
| 46 | + * When a child scope is acquired from another child scope, it is actually acquired from |
| 47 | + * the root scope. There is only a single level of children. All child scopes are peers. |
| 48 | + * A child scope can be {@link #close() closed} at any time, but root scope can only |
| 49 | + * be closed after all its children have been closed, at which time any associated |
| 50 | + * cleanup action is executed (the associated memory segment is freed). |
| 51 | + * Besides thread-confined checked scopes, {@linkplain #createUnchecked(Thread, Object, Runnable)} |
| 52 | + * method may be used passing {@code null} as the "owner" thread to create a |
| 53 | + * scope that doesn't check for thread-confinement while its temporal bounds are |
| 54 | + * enforced reliably only under condition that thread that closes the scope is also |
| 55 | + * the single thread performing the checked access or there is an external synchronization |
| 56 | + * in place that prevents concurrent access and closing of the scope. |
37 | 57 | */
|
38 |
| -public final class MemoryScope { |
| 58 | +abstract class MemoryScope { |
39 | 59 |
|
40 |
| - //reference to keep hold onto |
41 |
| - final Object ref; |
| 60 | + /** |
| 61 | + * Creates a root MemoryScope with given ref, cleanupAction and current |
| 62 | + * thread as the "owner" thread. |
| 63 | + * This method may be called in any thread. |
| 64 | + * The returned instance may be published unsafely to and used in any thread, |
| 65 | + * but methods that explicitly state that they may only be called in "owner" thread, |
| 66 | + * must strictly be called in the thread that created the scope |
| 67 | + * or else IllegalStateException is thrown. |
| 68 | + * |
| 69 | + * @param ref an optional reference to an instance that needs to be kept reachable |
| 70 | + * @param cleanupAction an optional cleanup action to be executed when returned scope is closed |
| 71 | + * @return a root MemoryScope |
| 72 | + */ |
| 73 | + static MemoryScope create(Object ref, Runnable cleanupAction) { |
| 74 | + return new Root(Thread.currentThread(), ref, cleanupAction); |
| 75 | + } |
42 | 76 |
|
43 |
| - int activeCount = UNACQUIRED; |
| 77 | + /** |
| 78 | + * Creates a root MemoryScope with given ref, cleanupAction and "owner" thread. |
| 79 | + * This method may be called in any thread. |
| 80 | + * The returned instance may be published unsafely to and used in any thread, |
| 81 | + * but methods that explicitly state that they may only be called in "owner" thread, |
| 82 | + * must strictly be called in given owner thread or else IllegalStateException is thrown. |
| 83 | + * If given owner thread is null, the returned MemoryScope is unchecked, meaning |
| 84 | + * that all methods may be called in any thread and that {@link #checkValidState()} |
| 85 | + * does not check for temporal bounds. |
| 86 | + * |
| 87 | + * @param owner the desired owner thread. If {@code owner == null}, |
| 88 | + * the returned scope is <em>not</em> thread-confined and not checked. |
| 89 | + * @param ref an optional reference to an instance that needs to be kept reachable |
| 90 | + * @param cleanupAction an optional cleanup action to be executed when returned scope is closed |
| 91 | + * @return a root MemoryScope |
| 92 | + */ |
| 93 | + static MemoryScope createUnchecked(Thread owner, Object ref, Runnable cleanupAction) { |
| 94 | + return new Root(owner, ref, cleanupAction); |
| 95 | + } |
44 | 96 |
|
45 |
| - final static VarHandle COUNT_HANDLE; |
| 97 | + private final Thread owner; |
| 98 | + private boolean closed; // = false |
| 99 | + private static final VarHandle CLOSED; |
46 | 100 |
|
47 | 101 | static {
|
48 | 102 | try {
|
49 |
| - COUNT_HANDLE = MethodHandles.lookup().findVarHandle(MemoryScope.class, "activeCount", int.class); |
| 103 | + CLOSED = MethodHandles.lookup().findVarHandle(MemoryScope.class, "closed", boolean.class); |
50 | 104 | } catch (Throwable ex) {
|
51 | 105 | throw new ExceptionInInitializerError(ex);
|
52 | 106 | }
|
53 | 107 | }
|
54 | 108 |
|
55 |
| - final static int UNACQUIRED = 0; |
56 |
| - final static int CLOSED = -1; |
57 |
| - final static int MAX_ACQUIRE = Integer.MAX_VALUE; |
| 109 | + private MemoryScope(Thread owner) { |
| 110 | + this.owner = owner; |
| 111 | + } |
58 | 112 |
|
59 |
| - final Runnable cleanupAction; |
| 113 | + /** |
| 114 | + * Acquires a child scope (or peer scope if this is a child) with current |
| 115 | + * thread as the "owner" thread. |
| 116 | + * This method may be called in any thread. |
| 117 | + * The returned instance may be published unsafely to and used in any thread, |
| 118 | + * but methods that explicitly state that they may only be called in "owner" thread, |
| 119 | + * must strictly be called in the thread that acquired the scope |
| 120 | + * or else IllegalStateException is thrown. |
| 121 | + * |
| 122 | + * @return a child (or peer) scope |
| 123 | + * @throws IllegalStateException if root scope is already closed |
| 124 | + */ |
| 125 | + abstract MemoryScope acquire(); |
60 | 126 |
|
61 |
| - final static MemoryScope GLOBAL = new MemoryScope(null, null); |
| 127 | + /** |
| 128 | + * Closes this scope, executing any cleanup action if this is the root scope. |
| 129 | + * This method may only be called in the "owner" thread of this scope unless the |
| 130 | + * scope is a root scope with no owner thread - i.e. is not checked. |
| 131 | + * |
| 132 | + * @throws IllegalStateException if this scope is already closed or if this is |
| 133 | + * a root scope and there is/are still active child |
| 134 | + * scope(s) or if this method is called outside of |
| 135 | + * owner thread in checked scope |
| 136 | + */ |
| 137 | + abstract void close(); |
62 | 138 |
|
63 |
| - public MemoryScope(Object ref, Runnable cleanupAction) { |
64 |
| - this.ref = ref; |
65 |
| - this.cleanupAction = cleanupAction; |
| 139 | + /** |
| 140 | + * Duplicates this scope with given new "owner" thread and {@link #close() closes} it. |
| 141 | + * If this is a root scope, a new root scope is returned; this root scope is closed, but |
| 142 | + * without executing the cleanup action, which is instead transferred to the duped scope. |
| 143 | + * If this is a child scope, a new child scope is returned. |
| 144 | + * This method may only be called in the "owner" thread of this scope unless the |
| 145 | + * scope is a root scope with no owner thread - i.e. is not checked. |
| 146 | + * The returned instance may be published unsafely to and used in any thread, |
| 147 | + * but methods that explicitly state that they may only be called in "owner" thread, |
| 148 | + * must strictly be called in given new "owner" thread |
| 149 | + * or else IllegalStateException is thrown. |
| 150 | + * |
| 151 | + * @param newOwner new owner thread of the returned MemoryScope |
| 152 | + * @return a duplicate of this scope |
| 153 | + * @throws NullPointerException if given owner thread is null |
| 154 | + * @throws IllegalStateException if this scope is already closed or if this is |
| 155 | + * a root scope and there is/are still active child |
| 156 | + * scope(s) or if this method is called outside of |
| 157 | + * owner thread in checked scope |
| 158 | + */ |
| 159 | + abstract MemoryScope dup(Thread newOwner); |
| 160 | + |
| 161 | + /** |
| 162 | + * Returns "owner" thread of this scope. |
| 163 | + * |
| 164 | + * @return owner thread (or null for unchecked scope) |
| 165 | + */ |
| 166 | + final Thread ownerThread() { |
| 167 | + return owner; |
66 | 168 | }
|
67 | 169 |
|
68 | 170 | /**
|
69 |
| - * This method performs a full, thread-safe liveness check; can be used outside confinement thread. |
| 171 | + * This method may be called in any thread. |
| 172 | + * |
| 173 | + * @return {@code true} if this scope is not closed yet. |
70 | 174 | */
|
71 |
| - final boolean isAliveThreadSafe() { |
72 |
| - return ((int)COUNT_HANDLE.getVolatile(this)) != CLOSED; |
| 175 | + final boolean isAlive() { |
| 176 | + return !((boolean)CLOSED.getVolatile(this)); |
73 | 177 | }
|
74 | 178 |
|
75 | 179 | /**
|
76 |
| - * This method performs a quick liveness check; must be called from the confinement thread. |
| 180 | + * Checks that this scope is still alive and that this method is executed |
| 181 | + * in the "owner" thread of this scope or this scope is unchecked (not associated |
| 182 | + * with owner thread). |
| 183 | + * |
| 184 | + * @throws IllegalStateException if this scope is already closed or this |
| 185 | + * method is executed outside owning thread |
| 186 | + * in checked scope |
77 | 187 | */
|
78 |
| - final void checkAliveConfined() { |
79 |
| - if (activeCount == CLOSED) { |
80 |
| - throw new IllegalStateException("Segment is not alive"); |
| 188 | + @ForceInline |
| 189 | + final void checkValidState() { |
| 190 | + if (owner != null && owner != Thread.currentThread()) { |
| 191 | + throw new IllegalStateException("Attempted access outside owning thread"); |
81 | 192 | }
|
| 193 | + checkAliveConfined(this); |
82 | 194 | }
|
83 | 195 |
|
84 |
| - MemoryScope acquire() { |
85 |
| - int value; |
86 |
| - do { |
87 |
| - value = (int)COUNT_HANDLE.getVolatile(this); |
88 |
| - if (value == CLOSED) { |
89 |
| - //segment is not alive! |
90 |
| - throw new IllegalStateException("Segment is not alive"); |
91 |
| - } else if (value == MAX_ACQUIRE) { |
92 |
| - //overflow |
93 |
| - throw new IllegalStateException("Segment acquire limit exceeded"); |
94 |
| - } |
95 |
| - } while (!COUNT_HANDLE.compareAndSet(this, value, value + 1)); |
96 |
| - return new MemoryScope(ref, this::release); |
| 196 | + /** |
| 197 | + * Checks that this scope is still alive. |
| 198 | + * |
| 199 | + * @throws IllegalStateException if this scope is already closed |
| 200 | + */ |
| 201 | + @ForceInline |
| 202 | + private static void checkAliveConfined(MemoryScope scope) { |
| 203 | + if (scope.closed) { |
| 204 | + throw new IllegalStateException("This segment is already closed"); |
| 205 | + } |
97 | 206 | }
|
98 | 207 |
|
99 |
| - private void release() { |
100 |
| - int value; |
101 |
| - do { |
102 |
| - value = (int)COUNT_HANDLE.getVolatile(this); |
103 |
| - if (value <= UNACQUIRED) { |
104 |
| - //cannot get here - we can't close segment twice |
105 |
| - throw new IllegalStateException(); |
| 208 | + private static final class Root extends MemoryScope { |
| 209 | + private final StampedLock lock = new StampedLock(); |
| 210 | + private final LongAdder acquired = new LongAdder(); |
| 211 | + private final Object ref; |
| 212 | + private final Runnable cleanupAction; |
| 213 | + |
| 214 | + private Root(Thread owner, Object ref, Runnable cleanupAction) { |
| 215 | + super(owner); |
| 216 | + this.ref = ref; |
| 217 | + this.cleanupAction = cleanupAction; |
| 218 | + } |
| 219 | + |
| 220 | + @Override |
| 221 | + MemoryScope acquire() { |
| 222 | + // try to optimistically acquire the lock |
| 223 | + long stamp = lock.tryOptimisticRead(); |
| 224 | + try { |
| 225 | + for (; ; stamp = lock.readLock()) { |
| 226 | + if (stamp == 0L) |
| 227 | + continue; |
| 228 | + checkAliveConfined(this); // plain read is enough here (either successful optimistic read, or full read lock) |
| 229 | + |
| 230 | + // increment acquires |
| 231 | + acquired.increment(); |
| 232 | + // did a call to close() occur since we acquired the lock? |
| 233 | + if (lock.validate(stamp)) { |
| 234 | + // no, just return the acquired scope |
| 235 | + return new Child(Thread.currentThread()); |
| 236 | + } else { |
| 237 | + // yes, just back off and retry (close might have failed, after all) |
| 238 | + acquired.decrement(); |
| 239 | + } |
| 240 | + } |
| 241 | + } finally { |
| 242 | + if (StampedLock.isReadLockStamp(stamp)) |
| 243 | + lock.unlockRead(stamp); |
106 | 244 | }
|
107 |
| - } while (!COUNT_HANDLE.compareAndSet(this, value, value - 1)); |
108 |
| - } |
| 245 | + } |
109 | 246 |
|
110 |
| - void close(boolean doCleanup) { |
111 |
| - if (!COUNT_HANDLE.compareAndSet(this, UNACQUIRED, CLOSED)) { |
112 |
| - //first check if already closed... |
113 |
| - checkAliveConfined(); |
114 |
| - //...if not, then we have acquired views that are still active |
115 |
| - throw new IllegalStateException("Cannot close a segment that has active acquired views"); |
| 247 | + @Override |
| 248 | + MemoryScope dup(Thread newOwner) { |
| 249 | + Objects.requireNonNull(newOwner, "newOwner"); |
| 250 | + // pre-allocate duped scope so we don't get OOME later and be left with this scope closed |
| 251 | + var duped = new Root(newOwner, ref, cleanupAction); |
| 252 | + justClose(); |
| 253 | + return duped; |
116 | 254 | }
|
117 |
| - if (doCleanup && cleanupAction != null) { |
118 |
| - cleanupAction.run(); |
| 255 | + |
| 256 | + @Override |
| 257 | + void close() { |
| 258 | + justClose(); |
| 259 | + if (cleanupAction != null) { |
| 260 | + cleanupAction.run(); |
| 261 | + } |
119 | 262 | }
|
120 |
| - } |
121 | 263 |
|
122 |
| - MemoryScope dup() { |
123 |
| - close(false); |
124 |
| - return new MemoryScope(ref, cleanupAction); |
| 264 | + @ForceInline |
| 265 | + private void justClose() { |
| 266 | + // enter critical section - no acquires are possible past this point |
| 267 | + long stamp = lock.writeLock(); |
| 268 | + try { |
| 269 | + checkValidState(); // plain read is enough here (full write lock) |
| 270 | + // check for absence of active acquired children |
| 271 | + if (acquired.sum() > 0) { |
| 272 | + throw new IllegalStateException("Cannot close this scope as it has active acquired children"); |
| 273 | + } |
| 274 | + // now that we made sure there's no active acquired children, we can mark scope as closed |
| 275 | + CLOSED.set(this, true); // plain write is enough here (full write lock) |
| 276 | + } finally { |
| 277 | + // leave critical section |
| 278 | + lock.unlockWrite(stamp); |
| 279 | + } |
| 280 | + } |
| 281 | + |
| 282 | + private final class Child extends MemoryScope { |
| 283 | + |
| 284 | + private Child(Thread owner) { |
| 285 | + super(owner); |
| 286 | + } |
| 287 | + |
| 288 | + @Override |
| 289 | + MemoryScope acquire() { |
| 290 | + return Root.this.acquire(); |
| 291 | + } |
| 292 | + |
| 293 | + @Override |
| 294 | + MemoryScope dup(Thread newOwner) { |
| 295 | + checkValidState(); // child scope is always checked |
| 296 | + // pre-allocate duped scope so we don't get OOME later and be left with this scope closed |
| 297 | + var duped = new Child(newOwner); |
| 298 | + CLOSED.setVolatile(this, true); |
| 299 | + return duped; |
| 300 | + } |
| 301 | + |
| 302 | + @Override |
| 303 | + void close() { |
| 304 | + checkValidState(); // child scope is always checked |
| 305 | + CLOSED.set(this, true); |
| 306 | + // following acts as a volatile write after plain write above so |
| 307 | + // plain write gets flushed too (which is important for isAliveThreadSafe()) |
| 308 | + Root.this.acquired.decrement(); |
| 309 | + } |
| 310 | + } |
125 | 311 | }
|
126 | 312 | }
|
0 commit comments