Skip to content

Commit 3a09da3

Browse files
committed
Fix setPendingFlagForVirtualThread() to not accidentally clear the flag
* Before this TruffleSafepointTest#testContextSafepoint[VIRTUAL_THREADS] would occasionally fail because of this race: with VT=VirtualThread MT=MainThread (or the thread that submits the ThreadLocalAction): VT: boolean pending = this.fastPendingSet; // false MT: fastPendingSet = true; MT: carrierThread.pending = true; VT: carrierThread.pending = pending; // false
1 parent b7a2396 commit 3a09da3

File tree

1 file changed

+36
-3
lines changed

1 file changed

+36
-3
lines changed

truffle/src/com.oracle.truffle.runtime/src/com/oracle/truffle/runtime/hotspot/HotSpotThreadLocalHandshake.java

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,44 @@ protected void setFastPending(Thread thread) {
148148
static void setPendingFlagForVirtualThread() {
149149
TruffleSafepointImpl safepoint = STATE.get();
150150
if (safepoint != null) {
151-
boolean pending = safepoint.isFastPendingSet();
152-
153151
Thread carrierThread = JAVA_LANG_ACCESS.currentCarrierThread();
154152
long eetop = UNSAFE.getLongVolatile(carrierThread, THREAD_EETOP_OFFSET);
155-
UNSAFE.putIntVolatile(null, eetop + PENDING_OFFSET, pending ? 1 : 0);
153+
154+
/*
155+
* When this method and setFastPending() are run concurrently, there is a possibility
156+
* that we set the carrier pending flag to 0 here based on pendingBefore==false, after
157+
* setFastPending() sets the flag to 1 (which would lose the flag):
158+
*
159+
* @formatter:off
160+
* with VT=VirtualThread MT=the thread that submits the ThreadLocalAction:
161+
* VT: boolean pending = this.fastPendingSet; // false
162+
* MT: fastPendingSet = true;
163+
* MT: carrierThread.pending = true;
164+
* VT: carrierThread.pending = pending; // false
165+
* @formatter:on
166+
*
167+
* So we check for that case with pendingAfter and fix it. This is correct because
168+
* either we wrote the 0 before setFastPending() wrote the 1 (harmless), or we wrote it
169+
* after and then we must see pendingAfter==true, because (the caller of)
170+
* setFastPending() sets fastPendingSet before writing to the carrier pending flag.
171+
*
172+
* If this method and setFastPending() are not run concurrently, then it is as if both
173+
* methods were executed one after another (since all accesses are volatile and so
174+
* sequentially consistent) and it works trivially.
175+
*
176+
* This solution is better than `if (isFastPendingSet()) { carrierThread.pending = 1; }`
177+
* because that can leave the carrier flag as true after an unmount of a virtual thread
178+
* which did not process safepoints yet and the newly-mounted virtual thread would not
179+
* clear it, causing extra stub calls (clearing is only done if fastPendingSet is true,
180+
* necessary for DefaultThreadLocalHandshake where clearFastPending() is not
181+
* idempotent).
182+
*/
183+
boolean pendingBefore = safepoint.isFastPendingSet();
184+
UNSAFE.putIntVolatile(null, eetop + PENDING_OFFSET, pendingBefore ? 1 : 0);
185+
boolean pendingAfter = safepoint.isFastPendingSet();
186+
if (!pendingBefore && pendingAfter) {
187+
UNSAFE.putIntVolatile(null, eetop + PENDING_OFFSET, 1);
188+
}
156189
}
157190
}
158191

0 commit comments

Comments
 (0)