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 >