Style difference between StructuredTaskScope and ScopedValue APIs
Viktor Klang
viktor.klang at oracle.com
Thu Aug 21 15:43:44 UTC 2025
Hi Subrahmanyam,
There are a multitude of possible Structured Concurrency APIs, all with different trade-offs in discoverability, ease-of-use, difficulty-of-misuse, performance, composability, maintainability, reviewability, and more. (Alan and I have prototyped dozens of different ones.)
If an API with the shape you outline above would suit your use-case(s), it does look tractable for you to create that on top of Structured Concurrency APIs from the JEP.
Cheers,
√
Viktor Klang
Software Architect, Java Platform Group
Oracle
________________________________
From: loom-dev <loom-dev-retn at openjdk.org> on behalf of Subrahmanyam V <vemana.github at gmail.com>
Sent: Thursday, 21 August 2025 13:07
To: loom-dev at openjdk.org <loom-dev at openjdk.org>
Subject: Style difference between StructuredTaskScope and ScopedValue APIs
Hello all, I am new here but just wanted to ask a question & share a thought on StructuredTaskScope API.
What's the reason for the asymmetry between ScopedValue and StructuredTaskScope APIs? Symmetry would suggest something like `StructuredTaskPolicy.create(Supplier<Joiner>).call(Function<StructuredTaskScope, T>)`.
Note
(1) StructuredTask_Policy_. The _call_ method on a Policy instance creates a StructuredTaskScope, passes it to the Consumer/Function and closes it before returning. Scoped Value propagation should behave like in the current API.
(2) Supplier<Joiner> in constructor because Joiner is stateful.
On the surface, there are a few advantages to the symmetric API
1. [Immutable, well-defined Policies] StructuredTaskPolicy instances can be immutable and shared across the application and its many threads. I can imagine applications that use just 2 different policies {all-succeed, any-succeed} across tens or hundreds of call sites
2. [Expressions] This API produces expressions instead of imperative constructs and thus composes better. For example, I can do: `return awaitUniterruptibly(() -> ALL_SUCCEED_POLICY.call(scope -> {...}));`.
3. [Managed Scope lifecycle] Let the API manage scope.close() to free resources instead of relying on the user. Hide lifecycle methods like open() and close() and expose only fork() and join() on StructuredTaskScope - the only ones that the user really cares about.
4. [Brevity that leads to Clarity] Nesting is feasible given brevity and expressions (#2). For example, a single readable expression can express that two subtasks should each succeed where the first task is itself a StructuredTask made up of three subtasks any of which succeeds,
5. [Easier evolution] If StructuredTaskScope.Config API evolves (this seems an active area of change), only Policy classes need to be evaluated by users for any potential migration instead of every call-site. A handful of immutable Policy classes is easier to manage than 10s or 100s of call-sites.
But, on reflection, an application can get all of the above benefits if it sticks exclusively to the _race()_ or _runConcurrently()_ constructs from the JEP. That is, instead of _Policy Classes_, it uses _Policy Methods_ like race(). Which begs the question - should the structured concurrency API also expose Policy Methods as a first class API, one Policy Method per Joiner?
Subrahmanyam
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20250821/db248f4a/attachment-0001.htm>
More information about the loom-dev
mailing list