JIT: always throw scoped async exceptions via new async category#23367
JIT: always throw scoped async exceptions via new async category#23367babsingh wants to merge 1 commit intoeclipse-openj9:masterfrom
Conversation
Scoped async exceptions were previously tagged under J9_CHECK_ASYNC_THROW_EXCEPTION. Under that category, the JIT paths do not always guarantee exception delivery. This is incorrect for scoped exceptions, which must be thrown immediately when observed. Introduce a new async action category, J9_CHECK_ASYNC_SCOPED_EXCEPTION, and update the JIT async check and dispatch paths to treat it as a must-throw condition from compiled code. This ensures scoped exceptions are raised immediately from the JIT side rather than being merged with the general async throw logic. Changes include: - Define new J9_CHECK_ASYNC_SCOPED_EXCEPTION async flag - Classify scoped exceptions under the new category instead of J9_CHECK_ASYNC_THROW_EXCEPTION - Update JIT async check handling to always trigger a throw for the scoped exception category - Adjust helper / dispatch logic accordingly This preserves existing behavior for normal async exceptions while tightening semantics for scoped exceptions. Related: eclipse-openj9#22934 Signed-off-by: Babneet Singh <sbabneet@ca.ibm.com>
It is fixed by the below PRs. Extension Repo PRs (Test fix): - JDK-next: ibmruntimes/openj9-openjdk-jdk#1174 - JDK26: ibmruntimes/openj9-openjdk-jdk26#22 OpenJ9 PR (VM/JIT fix): eclipse-openj9/openj9#23367 Related: eclipse-openj9/openj9#22934 Signed-off-by: Babneet Singh <sbabneet@ca.ibm.com>
TestSharedCloseJvmti.java is fixed by the below PRs. Extension Repo PRs (Test fix): - JDK-next: ibmruntimes/openj9-openjdk-jdk#1174 - JDK26: ibmruntimes/openj9-openjdk-jdk26#22 OpenJ9 PR (VM/JIT fix): eclipse-openj9/openj9#23367 Related: eclipse-openj9/openj9#22934 Signed-off-by: Babneet Singh <sbabneet@ca.ibm.com>
|
I have not looked at this in detail, but the JIT does not support throwing exceptions from the async handler called by compiled code. Adding new categories of exceptions does not fix this. JIT changes would be required to support this (in which case, we should probably just allow it in general). |
|
JIT was repeatedly re-entering @gacholio Could you share what JIT-side changes would be required to properly support this case? Looping in @hzongaro as well for JIT perspective. |
|
I believe the restriction is due to control flow complexity - if every async can potentially throw, the JIT needs to do more bookkeeping on all the paths to the async, potentially resulting in excessive register spills. I think it would be a simple enough experiment to implement this and see what the costs are. |
@hzongaro Thoughts on implementing the above? @gacholio Just to confirm, once the JIT guarantees that async exceptions are thrown immediately, the synchronization below can be removed, correct? There shouldn’t be any other gaps where async exception processing could be delayed, right? openj9/runtime/jcl/common/jdk_internal_misc_ScopedMemoryAccess.cpp Lines 105 to 117 in 9698be0 |
|
I don't think so at first glance - is there a guarantee that the scope exception will not be caught inside code that's sill inside the scope? I'm not familiar enough with this feature - the code above appears to either be assuming there's only ever one close going on, or that we wait until all closes are complete before allowing a single close to complete, which seems incorrect. It would be simple to construct a case where close never completes. |
|
This seems an obvious hole in the specification - can we interrupt the thread and throw from the point where the interrupt is detected? This doesn't seem like an OpenJ9-specific problem - what does the RI do? |
if my understanding is correct, they have handshake mechanisms where threads do extra JVM internal work (e.g. inspections) at safepoints via adding things on top of the stack similar to their virtual thread handling. we swap the stack, but they have the ability to build on top of the stack. this allows them to throw the scoped exception even when the thread is indefinitely waiting on a lock.
the public API for close doesn't promise non-blocking behavior or progress guarantees. it only promises temporal safety (no use-after-close). so, it is left to the JVM's internal implementation on how it should be handled. for the interrupt solution, not sure if it will be possible to distinguish between an actual interrupt that should yield InterruptedException vs. the scoped exception. also, forcing a thread out of CountDownLatch.await() without countDown() changes Java-level semantics; would this be acceptable by a Java user? for java 26, will there be much value in supporting such an extreme corner case where the Java user itself is responsible for the deadlock? can this be a backlog task that can be supported in the future? |
|
As long as the java code has invalidated the scope (i.e. no one new can enter it), then there's no reason close couldn't be completely asynchronous. This would require maintaining a list of closing scopes with an entry count so when the last thread finally throws the exception, we can perform the final cleanup. |
Scoped async exceptions were previously tagged under
J9_CHECK_ASYNC_THROW_EXCEPTION. Under that category, the JIT paths
do not always guarantee exception delivery.
This is incorrect for scoped exceptions, which must be thrown
immediately when observed.
Introduce a new async action category,
J9_CHECK_ASYNC_SCOPED_EXCEPTION, and update the JIT async check and
dispatch paths to treat it as a must-throw condition from compiled
code. This ensures scoped exceptions are raised immediately from the
JIT side rather than being merged with the general async throw logic.
Changes include:
J9_CHECK_ASYNC_THROW_EXCEPTION
scoped exception category
This preserves existing behavior for normal async exceptions while
tightening semantics for scoped exceptions.
Related: #22934