ScopedValue structured forking/forwarding API
Andrew Haley
aph-open at littlepinkcloud.com
Sat Jul 12 09:18:30 UTC 2025
Thank you for continuing to engage. This is really interesting.
On 11/07/2025 20:05, Nikita Bobko wrote:
> Andrew Haley wrote:
>> handshake to make sure that
>> every Snapshot has been join()ed at the end of Carrier.run(). I guess
>> that would work if we could figure out a sound way to do it
>
> I'm curious as to what makes you doubt that it can be done in a sound
> way. StructuredTaskScope already does that, doesn't it?
StructuredTaskScope is part of the system, and is trusted code. It uses
facilities that are necessary in the standard library that we don't want
to expose. If any bugs are found in StructuredTaskScope we can fix it.
> Andrew Haley wrote:
>> Given that a StructuredTaskScope can work with any ThreadFactory, why do
>> you need to replicate its working? What do you need to do differently?
>
> Great question. I'm trying to make kotlinx.coroutines seamlessly work
> together with ScopedValues. Similar to how StructuredTaskScope endorses
> structural concurrency, kotlinx.coroutines endorses structural
> concurrency as well.
>
> But StructuredTaskScope and kotlinx.coroutines use threads in a
> different way. For StructuredTaskScope, thread is *the unit* of
> structural work. For kotlinx.coroutines, thread is just a resource (like
> memory) that we use to run coroutines on.
>
> Coroutine is *the unit* of structural work in the kotlinx.coroutine
> world. Coroutines are lightweight threads, and they are implemented via
> async/await code-coloring and Continuation Passing Style transformation
> (CPS-transformation) made by the Kotlin compiler.
>
> kotlinx.coroutines framework allows to choose which threads users want
> to run their Coroutines on. For example, UI main thread is an important
> case where users care on which thread Coroutines run on.
>
> Unlike in StructuredTaskScope, threads that we run Coroutines on could
> had existed long before ScopedValue.Carrier.run() was called.
>
> We don't use threads structurally. But the jobs (coroutines) that we
> submit on those threads are structural. And the child coroutines are the
> place where I want to forward ScopedValues to. Not child threads, but
> child coroutines. Unfortunately for us, StructuredTaskScope assumes that
> *the unit* of structural work is thread.
OK, thank you. It really helps me to understand the root problem.
I think I've got a fair idea of how Kotlin coroutines work under the
hood. Is it your intention that a set of ScopedValue bindings be
associated with a coroutine, and that ScopedValue bindings be captured
at the point at which the coroutine is suspended, and re-bound later
when the coroutine is resumed?
> Andrew Haley wrote:
>> I'm still curious about this:
>>
>> > However, it is straightforward to create a scoped value class of your
>> > own that has any properties you wish, including different inheritance
>> > rules. Given a ScopedValue.Carrier that binds some values, start your
>> > thread with aCarrier.run(task). Would this work for you?
>
> My task is to seamlessly integrate kotlinx.coroutines and ScopedValues.
> Unfortunately, your suggestion doesn't work for us, because we would
> need to ask users to manually pass ScopedValue.Carrier bindings to
> kotlinx.coroutines.
I'm baffled. Your proposal involves invoking bindings.forward() when
(re)starting a task:
pool.submit(() -> {
bindings.forward(() -> {
This has to be explicit, and has to be done when there are no bound
scoped values. Why can't your implementation do it?
--
Andrew Haley (he/him)
Java Platform Lead Engineer
Red Hat UK Ltd. <https://www.redhat.com>
https://keybase.io/andrewhaley
EAC8 43EB D3EF DB98 CC77 2FAD A5CD 6035 332F A671
More information about the loom-dev
mailing list