Thanks for pressing on that, Viktor!

I think I was fooled by my quick-and-dirty test. As I tried to harden it, I
failed to deduce consistent behavior about what parallel stream does when
unchecked exceptions happen. And in fact, it seems like it does *not*
interrupt pending threads (?).

With this in mind, do you consider this behavior of mapConcurrent()
cancelling and joining the virtual threads on a best-effort basis
acceptable trade-off?

I wonder then if it's even worth it for mapConcurrent() to try to join the
threads at all? If it can sometimes join and sometimes not, why not just
always fail fast? At least then you get consistent fail-fast behavior: if a
thread fails to respond to interruption and hangs, the main thread would
still be able to respond to the exception.

Cheers,



On Tue, Jul 8, 2025 at 2:34 AM Viktor Klang <viktor.kl...@oracle.com> wrote:

> >I understand that explicit API contracts are what matters. My concern,
> however, is that even if the API contract explicitly states *no* 
> happens-before
> guarantee upon an unchecked exception, this behavior would still be a
> significant deviation from established visibility standards in other JDK
> APIs.
>
> Would you mind clariying *exactly* what you mean here—*what* happens-before
> completion/exception?
>
> Cheers,
> √
>
>
> *Viktor Klang*
> Software Architect, Java Platform Group
> Oracle
>
> ------------------------------
> *From:* Jige Yu <yuj...@gmail.com>
> *Sent:* Tuesday, 8 July 2025 04:26
> *To:* Viktor Klang <viktor.kl...@oracle.com>
> *Cc:* core-libs-dev@openjdk.org <core-libs-dev@openjdk.org>
> *Subject:* [External] : Re: Question about mapConcurrent() Behavior and
> Happens-Before Guarantees
>
> Thanks for the quick reply, Viktor!
>
> On Mon, Jul 7, 2025 at 2:35 AM Viktor Klang <viktor.kl...@oracle.com>
> wrote:
>
> Hi Jige,
>
> >Initially, I thought this design choice might provide a strong
> happens-before guarantee. My assumption was that an application catching a
> RuntimeException would be able to *observe all side effects* from the
> virtual threads, even though this practice is generally discouraged. This
> seemed like a potentially significant advantage, outweighing the risk of a
> virtual thread failing to respond to interruption or responding slowly.
>
> Unless explicitly stated in the API contract, no such guarantees should be
> presumed to exist.
>
>
> I understand that explicit API contracts are what matters. My concern,
> however, is that even if the API contract explicitly states *no* 
> happens-before
> guarantee upon an unchecked exception, this behavior would still be a
> significant deviation from established visibility standards in other JDK
> APIs.
>
> For instance, both *parallel streams* and Future.get() provide a
> happens-before guarantee upon completion (or exceptional completion in the
> case of Future.get()). So users will most likely take it for granted. If
> mapConcurrent() were to *not* offer this, it would potentially be the *first
> blocking JDK API that doesn't honor happens-before* in such a scenario.
> This inconsistency would likely be surprising and potentially confusing to
> users who have come to expect this behavior in concurrent programming
> constructs within the JDK.
>
>
>
> As for general resource-management in Stream, I have contemplated designs
> for Gatherer (and Collector) to be able to participate in the onClose
> actions, but there's a lot of ground to cover to ensure correct ordering
> and sufficiently-encompassing of cleanup action execution.
>
>
> Yeah. I agree that hooking into onClose() could provide a more reliable
> mechanism for cleanup.
>
> My primary concern though, is the change it imposes on the call-site
> contract. Requiring all users of mapConcurrent() to adopt a
> try-with-resources syntax, while ideal for correctness, introduces a
> burden and is more subject to users forgetting to do so, potentially
> leading to resource leaks.
>
> My previously proposed collectingAndThen(toList(), list ->
> list.stream().gather(mapConcurrent())) idea, on the other hand, avoids
> this call-site contract change. Being a collector, it needs to first
> consume the input, similar to how most Collectors operate. So it might be
> a less intrusive path to ensure proper resource handling without altering
> usage patterns.
>
>
> Cheers,
> √
>
>
> *Viktor Klang*
> Software Architect, Java Platform Group
> Oracle
> ------------------------------
> *From:* core-libs-dev <core-libs-dev-r...@openjdk.org> on behalf of Jige
> Yu <yuj...@gmail.com>
> *Sent:* Thursday, 3 July 2025 16:36
> *To:* core-libs-dev@openjdk.org <core-libs-dev@openjdk.org>
> *Subject:* Question about mapConcurrent() Behavior and Happens-Before
> Guarantees
>
>
> Hi JDK Core Devs,
>
> I'm writing to you today with a question about the behavior of
> mapConcurrent() and its interaction with unchecked exceptions. I've been
> experimenting with the API and observed that mapConcurrent() blocks and
> joins all virtual threads upon an unchecked exception before propagating it.
>
> Initially, I thought this design choice might provide a strong
> happens-before guarantee. My assumption was that an application catching a
> RuntimeException would be able to *observe all side effects* from the
> virtual threads, even though this practice is generally discouraged. This
> seemed like a potentially significant advantage, outweighing the risk of a
> virtual thread failing to respond to interruption or responding slowly.
>
> However, I've since realized that mapConcurrent() cannot fully guarantee
> a strong happens-before relationship when an unchecked exception occurs
> *somewhere* in the stream pipeline. While it can block and wait for
> exceptions thrown by the mapper function or downstream operations, it
> appears unable to intercept unchecked exceptions *thrown by an upstream*
>  source.
>
> Consider a scenario with two input elements: if the first element starts a
> virtual thread, and then the second element causes an unchecked exception
> from the upstream *before* reaching the gather() call, the virtual thread
> initiated by the first element would not be interrupted. This makes the
> "happens-before" guarantee quite nuanced in practice.
>
> This brings me to my core questions:
>
>    1.
>
>    Is providing a happens-before guarantee upon an unchecked exception a
>    design goal for mapConcurrent()?
>    2.
>
>    If not, would it be more desirable to *not* join on virtual threads
>    when unchecked exceptions occur? This would allow the application code to
>    catch the exception sooner and avoid the risk of being blocked 
> indefinitely.
>
> Thank you for your time and insights.
>
> Best regards,
>
> Ben Yu
>
>

Reply via email to