On Fri, 3 Jul 2026 00:49:43 GMT, Patricio Chilano Mateo 
<[email protected]> wrote:

> An asynchronous exception sent by JVMTI `StopThread` to a virtual thread can 
> be processed and thrown at a point where it is unsafe to do so, leaving the 
> virtual thread in an invalid state. The unsafe nature of `StopThread` is not 
> an issue exclusive to virtual threads, but due to extra code executed during 
> mount/unmount transitions, some tests that are safe for platform threads are 
> not for virtual threads. In the reported bug, where the async exception is 
> sent to a thread executing `Thread.yield` in a loop, the exception ends up 
> being thrown on return from `VirtualThread.startTransition`. Since the 
> transition bits remain set, the virtual thread hits the reported assert in 
> `MountUnmountDisabler::start_transition` at the next unmount attempt.
> 
> A similar issue can happen if the exception is thrown right after executing 
> `VirtualThread.endFirstTransition` or right before 
> `VirtualThread.startFinalTransition` which can be reproduced by calling 
> `StopThread` on virtual threads with empty tasks.
> 
> The patch fixes these cases and potentially others that can happen if an 
> async exception is thrown while executing a method in the `VirtualThread` 
> class, i.e. it improves the robustness of the implementation in the presence 
> of async exceptions. It is not an attempt to make `StopThread` bulletproof as 
> that would be a more ambitious task.
> 
> The proposed changes add two extra checks before installing the asynchronous 
> exception handshake. The first verifies that the top method is not a 
> `VirtualThread` method. The second verifies that the exception will be thrown 
> at the current bytecode, i.e. that exception processing will not be deferred 
> to a later safepoint poll where the target might already be in one of the 
> unsafe methods.
> 
> A less restrictive alternative that avoids that second check is to have the 
> target defer processing of the handshake as long as it’s unsafe to do so (as 
> based on the first check above). If the handshake is still pending when an 
> unmount transition begins, we process it and save the exception in the 
> virtual thread’s `JvmtiThreadState` to be thrown at the end of the next 
> mount. I have a patch implementing this approach, but the code is a bit more 
> involved and I wasn’t convinced it was worth it.
> 
> I also refactored `StopThread` by introducing `StopThreadClosure` and 
> `StopThreadAsyncClosure` handshake classes. This aligns it with the other 
> JVMTI methods that use `JvmtiHandshake`, and also keeps the `StopThread` 
> specific logic local to the JVMTI code.
> 
> The changes...

For other readers, Patricio has given me (and others) a solid walk-through of 
the code changes and the refactoring so I am no longer concerned about 
separating them out.

I still have some concerns about some of the details though. And a number of 
minor nits.

Thanks

src/hotspot/share/prims/jvmtiEnvBase.cpp line 2428:

> 2426: 
> 2427: void
> 2428: StopThreadClosure::doit(JavaThread *target) {

Suggestion:

StopThreadClosure::doit(JavaThread* target) {

src/hotspot/share/prims/jvmtiEnvBase.cpp line 2435:

> 2433: 
> 2434: void
> 2435: StopThreadClosure::do_thread(Thread *target) {

Suggestion:

StopThreadClosure::do_thread(Thread* target) {

src/hotspot/share/prims/jvmtiEnvBase.cpp line 2469:

> 2467:   vframeStream vfst(_target_jt);
> 2468:   Method* m = vfst.method();
> 2469:   if (is_async_unsafe_method(m)) {

I assume `m` is the current Java method. Simply checking if that method is 
unsafe seems inadequate as we may be in the VT code but calling a method of 
another class when we process the async-exception. Doesn't this need to be an 
actual walk of the stack to look for any unsafe method?

src/hotspot/share/prims/jvmtiEnvBase.cpp line 2476:

> 2474:   }
> 2475: 
> 2476:   if (_target_jt->at_no_async_entry_count() > 0

The count is set by the target thread but is read here by the current thread - 
we need some use of atomics here.

src/hotspot/share/prims/jvmtiEnvBase.hpp line 545:

> 543:      _exception(thread, exception) {}
> 544:   void doit(JavaThread *target);
> 545:   void do_thread(Thread *target);

Suggestion:

  void doit(JavaThread* target);
  void do_thread(Thread* target);

src/hotspot/share/runtime/javaThread.hpp line 236:

> 234:   friend class HandshakeState;
> 235: 
> 236:   int _at_no_async_entry_count;

Suggestion:

  int _at_no_async_entry_count;  // Tracks whether we are within a 
JRT_ENTRY_NOASYNC function.

src/hotspot/share/runtime/javaThread.hpp line 1355:

> 1353: 
> 1354: class AtNoAsyncEntryMark : public StackObj {
> 1355:   JavaThread *_target;

Suggestion:

  JavaThread* _target;

src/hotspot/share/runtime/javaThread.hpp line 1356:

> 1354: class AtNoAsyncEntryMark : public StackObj {
> 1355:   JavaThread *_target;
> 1356:   bool _count;

Suggestion:

  bool _no_async;

src/hotspot/share/runtime/javaThread.hpp line 1358:

> 1356:   bool _count;
> 1357:  public:
> 1358:   AtNoAsyncEntryMark(JavaThread *t, bool b)

Suggestion:

  AtNoAsyncEntryMark(JavaThread* t, bool allows_async)

test/hotspot/jtreg/serviceability/jvmti/vthread/StopThreadTest2/StopThreadTest2.java
 line 63:

> 61:         suspendAllVirtualThreads();
> 62:         for (Thread vthread : vthreads) {
> 63:             stopThread(vthread, new ThreadDeath());

`ThreadDeath` is deprecated for removal so perhaps better to define a custom 
exception type, or just use a generic type.

-------------

Changes requested by dholmes (Reviewer).

PR Review: https://git.openjdk.org/jdk/pull/31759#pullrequestreview-4651046188
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541332382
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541342130
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541372665
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541395355
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541377408
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541386770
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541399581
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541411660
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541400291
PR Review Comment: https://git.openjdk.org/jdk/pull/31759#discussion_r3541429023

Reply via email to