ScopedValue structured forking/forwarding API

Andrew Haley aph-open at littlepinkcloud.com
Tue Jul 22 16:46:26 UTC 2025


On 13/07/2025 21:30, Nikita Bobko wrote:
> *Integration 2.* Similar to how child threads in StructuredTaskScope
> have access to all ScopedValues of their parent thread, we want to
> forward all ScopedValues from the blocked parent thread (yes, parent
> thread, not parent coroutine, please read further) to children
> coroutines. For that kind of integration, we need something along the
> lines of what I've described at the beginning of the thread.

Thinking about this some more, here is your example:

         public static void main(String[] args) {
             ScopedValue.where(userName, "Duke").call(() -> {
                 try (ScopedValue.Snapshot bindings =
                          ScopedValue.snapshot()) {
                     try (var pool = Executors.newFixedThreadPool(1)) {
                         pool.submit(() -> {
                             bindings.forward(() -> {
                                 // Should prints "Duke"
                                 System.out.println(userName.get());
                             });
                             // Throws
                             //
                             // java.util.NoSuchElementException:
                             //     ScopedValue not bound
                             System.out.println(userName.get());
                         }).get();
                     }
                     bindings.join(); // may block
                 }
                 return null;
             }); // may block
         }

I think we can make this robust if and only if ScopedValue call() blocks 
until every forward() (or, equivalently, every bindings.join()) has run 
to completion. This approach may also give us a way to support parallel 
streams, etc., without compromising scoped values' temporal and 
structural invariants. This is an attractive thought.

Unfortunately, even though failing to wait for children to terminate 
would be a coding error, it couldn't cause an exception to be thrown 
because that would leave threads running after the scope that launched 
them had exited. We could try to interrupt the blocking threads, but if 
they didn't respond we'd be stuck.

Also, doing this does sort-of imply that there has to be a handshake at 
every scoped value binding. It also means that ScopedValue.Snapshot 
would need to be mutable in order to ensure that a Snapshot instance 
couldn't be reused once it was join()ed.

-- 
Andrew Haley  (he/him)
Java Platform Lead Engineer
https://keybase.io/andrewhaley
EAC8 43EB D3EF DB98 CC77 2FAD A5CD 6035 332F A671



More information about the loom-dev mailing list