|
53 | 53 | import java.util.concurrent.Executors; |
54 | 54 | import java.util.concurrent.ScheduledExecutorService; |
55 | 55 | import java.util.concurrent.TimeUnit; |
56 | | -import java.util.concurrent.atomic.AtomicBoolean; |
57 | 56 |
|
58 | 57 | import org.graalvm.nativeimage.ImageInfo; |
59 | 58 |
|
|
75 | 74 | import com.oracle.truffle.api.RootCallTarget; |
76 | 75 | import com.oracle.truffle.api.ThreadLocalAction; |
77 | 76 | import com.oracle.truffle.api.TruffleLanguage; |
78 | | -import com.oracle.truffle.api.TruffleLanguage.Env; |
79 | 77 | import com.oracle.truffle.api.TruffleLogger; |
80 | 78 | import com.oracle.truffle.api.debug.Debugger; |
81 | 79 | import com.oracle.truffle.api.frame.VirtualFrame; |
@@ -339,51 +337,74 @@ void poll() { |
339 | 337 | } |
340 | 338 | } |
341 | 339 |
|
| 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 | + |
342 | 401 | void activateGIL() { |
343 | 402 | CompilerAsserts.neverPartOfCompilation(); |
344 | 403 | final PythonContext ctx = context.get(); |
345 | 404 | if (ctx == null) { |
346 | 405 | return; |
347 | 406 | } |
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); |
387 | 408 | if (PythonOptions.AUTOMATIC_ASYNC_ACTIONS) { |
388 | 409 | executorService.scheduleWithFixedDelay(gilReleaseRunnable, GIL_RELEASE_DELAY, GIL_RELEASE_DELAY, TimeUnit.MILLISECONDS); |
389 | 410 | } else { |
|
0 commit comments