|
11 | 11 | import java.util.Queue; |
12 | 12 | import java.util.concurrent.ConcurrentLinkedDeque; |
13 | 13 | import java.util.concurrent.ConcurrentLinkedQueue; |
| 14 | +import java.util.concurrent.ExecutionException; |
| 15 | +import java.util.concurrent.ExecutorService; |
| 16 | +import java.util.concurrent.Executors; |
14 | 17 | import java.util.concurrent.Semaphore; |
15 | 18 | import java.util.concurrent.atomic.AtomicReference; |
16 | 19 | import java.util.function.BiConsumer; |
@@ -89,6 +92,7 @@ public class Bosk<R extends StateTreeNode> implements BoskInfo<R> { |
89 | 92 | private final RootRef rootRef; |
90 | 93 | private final ThreadLocal<R> rootSnapshot = new ThreadLocal<>(); |
91 | 94 | private final Queue<HookRegistration<?>> hooks = new ConcurrentLinkedQueue<>(); |
| 95 | + private final ExecutorService hookExecutor = Executors.newVirtualThreadPerTaskExecutor(); |
92 | 96 | private final PathCompiler pathCompiler; |
93 | 97 |
|
94 | 98 | // Mutable state |
@@ -542,8 +546,23 @@ private void drainQueueIfAllowed() { |
542 | 546 | if (hookExecutionPermit.tryAcquire()) { |
543 | 547 | try { |
544 | 548 | for (Runnable ex = hookExecutionQueue.pollFirst(); ex != null; ex = hookExecutionQueue.pollFirst()) { |
545 | | - ex.run(); |
| 549 | + // Run the task in a separate virtual thread to prevent ThreadLocals from propagating. |
| 550 | + // This is slightly tragic, because usually ThreadLocal propagation works just the |
| 551 | + // way we'd want, but not always. Given the choices "always, sometimes, never", if |
| 552 | + // we can't achieve "always", then the bosk philosophy prefers "never" over "sometimes". |
| 553 | + hookExecutor.submit(ex).get(); |
546 | 554 | } |
| 555 | + } catch (ExecutionException e) { |
| 556 | + if (e.getCause() instanceof RuntimeException r) { |
| 557 | + throw r; |
| 558 | + } else if (e.getCause() instanceof Error error) { |
| 559 | + throw error; |
| 560 | + } else { |
| 561 | + throw new AssertionError("Hook runnable should catch and wrap checked exceptions", e); |
| 562 | + } |
| 563 | + } catch (InterruptedException e) { |
| 564 | + LOGGER.warn("Interrupted while running hooks", e); |
| 565 | + return; |
547 | 566 | } finally { |
548 | 567 | hookExecutionPermit.release(); |
549 | 568 | } |
@@ -622,7 +641,7 @@ public <T> void registerHook(String name, @NonNull Reference<T> scope, @NonNull |
622 | 641 |
|
623 | 642 | @Override |
624 | 643 | public void registerHooks(Object receiver) throws InvalidTypeException { |
625 | | - HookRegistrar.registerHooks(receiver, this); |
| 644 | + HookScanner.registerHooks(receiver, this); |
626 | 645 | } |
627 | 646 |
|
628 | 647 | @Override |
|
0 commit comments