Skip to content

Commit 08c0c19

Browse files
committed
Fix lockups in GIL release flag
1 parent 595bcfb commit 08c0c19

File tree

1 file changed

+62
-41
lines changed
  • graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime

1 file changed

+62
-41
lines changed

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

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import java.util.concurrent.Executors;
5454
import java.util.concurrent.ScheduledExecutorService;
5555
import java.util.concurrent.TimeUnit;
56-
import java.util.concurrent.atomic.AtomicBoolean;
5756

5857
import org.graalvm.nativeimage.ImageInfo;
5958

@@ -75,7 +74,6 @@
7574
import com.oracle.truffle.api.RootCallTarget;
7675
import com.oracle.truffle.api.ThreadLocalAction;
7776
import com.oracle.truffle.api.TruffleLanguage;
78-
import com.oracle.truffle.api.TruffleLanguage.Env;
7977
import com.oracle.truffle.api.TruffleLogger;
8078
import com.oracle.truffle.api.debug.Debugger;
8179
import com.oracle.truffle.api.frame.VirtualFrame;
@@ -339,51 +337,74 @@ void poll() {
339337
}
340338
}
341339

340+
private static class GilReleaseScheduler implements Runnable {
341+
private final PythonContext ctx;
342+
private volatile boolean gilReleaseRequested;
343+
private Thread lastGilOwner;
344+
345+
private GilReleaseScheduler(PythonContext ctx) {
346+
this.ctx = ctx;
347+
}
348+
349+
@Override
350+
public void run() {
351+
Thread gilOwner = ctx.getGilOwner();
352+
if (gilOwner != null) {
353+
synchronized (this) {
354+
if (!gilReleaseRequested) {
355+
gilReleaseRequested = true;
356+
/*
357+
* There is a race, but that's no problem. The gil owner may release the gil
358+
* before getting to run this safepoint. In that case, it just ignores it.
359+
* Some other thread will run and eventually get another gil release
360+
* request.
361+
*/
362+
ctx.getEnv().submitThreadLocal(new Thread[]{gilOwner}, new ThreadLocalAction(false, false) {
363+
@Override
364+
protected void perform(ThreadLocalAction.Access access) {
365+
// it may happen that we request a GIL release and no thread is
366+
// currently holding the GIL (e.g. all are sleeping). We still need
367+
// to tick again later, so we reset the gilReleaseRequested flag
368+
// even
369+
// when the thread in question isn't actually holding it.
370+
gilReleaseRequested = false;
371+
RootNode rootNode = access.getLocation().getRootNode();
372+
if (rootNode instanceof PRootNode) {
373+
if (rootNode.isInternal()) {
374+
return;
375+
}
376+
if (((PRootNode) rootNode).isPythonInternal()) {
377+
return;
378+
}
379+
// we only release the gil in ordinary Python code nodes
380+
GilNode gil = GilNode.getUncached();
381+
if (gil.tryRelease()) {
382+
gil.acquire(access.getLocation());
383+
}
384+
}
385+
}
386+
});
387+
} else if (gilOwner != lastGilOwner) {
388+
/*
389+
* If the gil changed owner since the last time we observed it, clear the
390+
* flag to make sure we don't get stuck if the last owner exits before
391+
* executing the safepoint.
392+
*/
393+
gilReleaseRequested = false;
394+
}
395+
lastGilOwner = gilOwner;
396+
}
397+
}
398+
}
399+
}
400+
342401
void activateGIL() {
343402
CompilerAsserts.neverPartOfCompilation();
344403
final PythonContext ctx = context.get();
345404
if (ctx == null) {
346405
return;
347406
}
348-
final Env env = ctx.getEnv();
349-
final AtomicBoolean gilReleaseRequested = new AtomicBoolean(false);
350-
final Runnable gilReleaseRunnable = () -> {
351-
if (gilReleaseRequested.compareAndSet(false, true)) {
352-
Thread gilOwner = ctx.getGilOwner();
353-
// There is a race, but that's no problem. The gil owner may release the gil before
354-
// getting to run this safepoint. In that case, it just ignores it. Some other
355-
// thread will run and eventually get another gil release request.
356-
if (gilOwner != null) {
357-
env.submitThreadLocal(new Thread[]{gilOwner}, new ThreadLocalAction(false, false) {
358-
@Override
359-
protected void perform(ThreadLocalAction.Access access) {
360-
// it may happen that we request a GIL release and no thread is
361-
// currently holding the GIL (e.g. all are sleeping). We still need
362-
// to tick again later, so we reset the gilReleaseRequested flag even
363-
// when the thread in question isn't actually holding it.
364-
gilReleaseRequested.set(false);
365-
RootNode rootNode = access.getLocation().getRootNode();
366-
if (rootNode instanceof PRootNode) {
367-
if (rootNode.isInternal()) {
368-
return;
369-
}
370-
if (((PRootNode) rootNode).isPythonInternal()) {
371-
return;
372-
}
373-
// we only release the gil in ordinary Python code nodes
374-
GilNode gil = GilNode.getUncached();
375-
if (gil.tryRelease()) {
376-
Thread.yield();
377-
gil.acquire(access.getLocation());
378-
}
379-
}
380-
}
381-
});
382-
} else {
383-
gilReleaseRequested.set(false);
384-
}
385-
}
386-
};
407+
final Runnable gilReleaseRunnable = new GilReleaseScheduler(ctx);
387408
if (PythonOptions.AUTOMATIC_ASYNC_ACTIONS) {
388409
executorService.scheduleWithFixedDelay(gilReleaseRunnable, GIL_RELEASE_DELAY, GIL_RELEASE_DELAY, TimeUnit.MILLISECONDS);
389410
} else {

0 commit comments

Comments
 (0)