ScopedValue structured forking/forwarding API

Nikita Bobko nikita.bobko at jetbrains.com
Fri Jul 11 10:57:52 UTC 2025


> Those Java rules
> apply to scoped values, but we add another: if a field is not
> accessible to you, you may not extend its lifetime, or cause it to be
> accessible in some other context.

Yes, I understand what you mean, and I agree. The lifetime of a
ScopedValue variable belongs to its owner.

Who defines the lifetime? The current stack inside Carrier.run defines
the lifetime. Whilst the stack is alive, all so far bound ScopedValues
remain alive. StructuredTaskScope takes advantage of this fact. Whenever
users call StructuredTaskScope.join (and block the parent thread by
doing that), they effectively make sure that the stack will live long
enough, so that all ScopedValues could be safely accessed in child
virtual threads.

Please note that the API shape that I'm suggesting replicates
StructuredTaskScope, and it doesn't do anything that couldn't be already
achieved with the existing StructuredTaskScope API (Or I explained it
poorly).

Unfortunately for me, StructuredTaskScope is two things in one package.
It's structural virtual thread factory and it's ScopedValues structural
forking API. I don't want the first thing, I only want the second one.

So let's take a look at what happens if you try to extend the lifetime
of a ScopedValue beyond its proper boundaries by using
StructuredTaskScope:

    var value = ScopedValue.<String>newInstance();
    var reference = new StructuredTaskScope[]{null};
    ScopedValue.where(value, "Duke")
            // java.util.concurrent.StructureViolationException
            .run(() -> {
                reference[0] = StructuredTaskScope.open();
            });

You get a StructureViolationException from Carrier.run. It means that
the very same thing should happen with the API that I'm suggesting:

    var value = ScopedValue.<String>newInstance();
    var reference = new ScopedValue.Snapshot[]{null};
    ScopedValue.where(value, "Duke")
            // java.util.concurrent.StructureViolationException
            .run(() -> {
                reference[0] = ScopedValue.snapshot();
            });

Note: I snatch the StructuredTaskScope/ScopedValue.Snapshot into a
reference variable for extra clarity. We would get the same exception
even without doing that because we didn't close our AutoCloseables.



More information about the loom-dev mailing list