Question about mapConcurrent() Behavior and Happens-Before Guarantees

Jige Yu yujige at gmail.com
Tue Jul 8 02:26:24 UTC 2025


Thanks for the quick reply, Viktor!

On Mon, Jul 7, 2025 at 2:35 AM Viktor Klang <viktor.klang at 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-retn at openjdk.org> on behalf of Jige
> Yu <yujige at gmail.com>
> *Sent:* Thursday, 3 July 2025 16:36
> *To:* core-libs-dev at openjdk.org <core-libs-dev at 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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20250707/ee6028aa/attachment.htm>


More information about the core-libs-dev mailing list