Skip to content

Commit 07b7f6e

Browse files
committed
document the race in trigger async actions and why it's ok
1 parent 02c2051 commit 07b7f6e

File tree

1 file changed

+32
-8
lines changed
  • graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime

1 file changed

+32
-8
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/AsyncHandler.java

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,40 @@ void triggerAsyncActions() {
161161
}
162162
}
163163

164+
/**
165+
* It's fine that there is a race between checking the hasScheduledAction flag and processing
166+
* actions, we use the executingScheduledActions lock to ensure that only one thread is
167+
* processing and that no asynchronous handler thread would set it again while we're processing.
168+
* While the nice scenario would be any variation of:
169+
*
170+
* <ul>
171+
* <li>Thread2 - acquireLock, pushWork, setFlag, releaseLock</li>
172+
* <li>Thread1 - checkFlag, acquireLock, resetFlag, processActions, releaseLock</li>
173+
* </ul>
174+
*
175+
* <ul>
176+
* <li>Thread1 - checkFlag</li>
177+
* <li>Thread2 - acquireLock, pushWork, setFlag, releaseLock</li>
178+
* <li>Thread1 - acquireLock, resetFlag, processActions, releaseLock</li>
179+
* </ul>
180+
*
181+
* it's also fine if we get into a race for example like this:
182+
*
183+
* <ul>
184+
* <li>Thread2 - acquireLock, pushWork, setFlag</li>
185+
* <li>Thread1 - checkFlag, tryAcquireLock, bail out</li>
186+
* <li>Thread2 - releaseLock</li>
187+
* </ul>
188+
*
189+
* because Thread1 is sure to check the flag again soon enough, and very likely much sooner than
190+
* the {@value #ASYNC_ACTION_DELAY} ms delay between successive runs of the async handler
191+
* threads (Thread2 in this example). Of course, there can be more than one handler thread, but
192+
* it's unlikely that there are so many that it would completely saturate the ability to process
193+
* async actions on the main thread, because there's only one per "type" of async thing (e.g. 1
194+
* for weakref finalizers, 1 for signals, 1 for destructors).
195+
*/
164196
@TruffleBoundary
165197
private void processAsyncActions() {
166-
// We'll likely be able to get the lock and start working through the async actions. But
167-
// there could be a race between the atomic switch of the hasScheduledAction flag, an
168-
// AsyncRunnable thread and the acquisition of the lock, so a second thread may end up
169-
// clearing the flag again. In that case, both would get here, but only will get the lock
170-
// and the other will skip over this and return. In any case, by the time a thread has
171-
// this lock and is handling async actions, nothing new will be pushed to the
172-
// scheduledActions queue, so we won't have a race between finishing the while loop and
173-
// returning from this method.
174198
if (executingScheduledActions.tryLock()) {
175199
hasScheduledAction = false;
176200
try {

0 commit comments

Comments
 (0)