JDK-8306324 : StopThread results in thread being marked as interrupted, leading to unexpected InterruptedException
  • Type: Bug
  • Component: hotspot
  • Sub-Component: jvmti
  • Affected Version: 21
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • Submitted: 2023-04-18
  • Updated: 2025-05-23
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 26
26Unresolved
Related Reports
Relates :  
Description
If StopThread is done when the thread is in certain various states (but not all states), after the async exception is delivered and handled, hotspot leaves the thread's "interrupted" flag set. The end result is the next time the thread does something like Thread.sleep(), it will immediately get an InterruptedException.

I've only been observing this with platform threads, not virtual threads, but that might be because the carrier thread is getting the flag set, and it is not seen from the virtual thread.

I'm seeing the "interrupted" flag being left set after StopThread (and the thread resumed if it was previously suspended) in each of the following situations:
 - thread suspended at a breakpoint
 - thread in a loop and suspended
 - thread in a loop and not suspended

I do not see the "interrupted" flag being left set after:
 - thread suspended in Thread.sleep()

The workaround for this is to call Thread.interrupted() to clear the pending interrupt, but of course this is only possible if the code knows when/where to expect an async exception.

The "interrupted" flag seems to be getting set here:

void JavaThread::install_async_exception(AsyncExceptionHandshake* aeh) {
 // Do not throw asynchronous exceptions against the compiler thread
 // or if the thread is already exiting.
 if (!can_call_java() || is_exiting()) {
  delete aeh;
  return;
 }
 . . .
 // Interrupt thread so it will wake up from a potential wait()/sleep()/park()
 java_lang_Thread::set_interrupted(threadObj(), true);
 this->interrupt();
}

The following seems to fix the issue, but it is not known if this is the correct fix, or if it may cause other problems:

diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp
index 473ce59c751..16e794d3474 100644
--- a/src/hotspot/share/runtime/javaThread.cpp
+++ b/src/hotspot/share/runtime/javaThread.cpp
@@ -1062,6 +1062,7 @@ void JavaThread::handle_async_exception(oop java_throwable) {
 
  // We cannot call Exceptions::_throw(...) here because we cannot block
  set_pending_exception(java_throwable, __FILE__, __LINE__);
+ java_lang_Thread::set_interrupted(threadObj(), false);
 
  clear_scopedValueBindings();
Comments
It looks like the kill001 test also results in odd behavior because of the pending interrupt after doing a ThreadRefrence.stop(). The test does a ThreadReference.stop() on the target thread, which does seem to work. However, I noticed the following code in the debug agent: void threadControl_setPendingInterrupt(jthread thread) { ThreadNode *node; /* * vmTestbase/nsk/jdb/kill/kill001/kill001.java seems to be the only code * that triggers this function. Is uses ThreadReference.Stop. * * Since ThreadReference.Stop is not supported for vthreads, we should never * get here with a vthread. */ JDI_ASSERT(!isVThread(thread)); ... The comment and assert are clearly bit rotted, since we now support ThreadReference.Stop for virtual threads, and it is tested and works (kill001 passes). So the question is why is this assert not hit. I made it to always assert, and it does assert if the target thread is a platform thread. The call chain is: threadControl_setPendingInterrupt handleInterrupt debugMonitorWait enqueueCommand eventHelper_reportEvents reportEvents filterAndHandleEvent event_callback cbException JvmtiExport::post_exception_throw InterpreterRuntime::exception_handler_for_exception So an exception is being thrown (I assume because of the TR.stop(), but I'm not certain). debugMonitorWait calls JVMTI RawMonitorWait to block until the command is complete. RawMonitorWait is returning with JVMTI_ERROR_INTERRUPT. The debug agent is setup to handle this. It sets the "pendingInterrupt" flag on the ThreadNode (that's why threadControl_setPendingInterrupt() is called), and then it re-enters the JVMTI RawMonitorWait. I assume the interrupt is due to the ThreadReference.Stop. JVMTI sets the interrupt flag on the thread in this case as mentioned above. Although probably this interrupt should not be happening, the debug agent is setup to handle it and it does the right thing...sort of. The problem is eventually the debug agent checks the node->pendingInterrupt flag, and if set it calls JVMTI InterruptThread. This then makes the interrupt visible to the java app, something that should not happen since the interrupt is superfluous. In other words, attaching to the debugger is changing program behavior. I got into all this is because threadControl_setPendingInterrupt() is only getting called for platform threads, not virtual threads. It seems the reason why is what was noted above. The interrupt status is set on the carrier thread, not the virtual thread. That means the RawMonitorWait mentioned above when called from a virtual thread does not fail with JVMTI_ERROR_INTERRUPT. However, next time the carrier thread executes something that is interruptible, at that point the InterruptedException will be thrown. This is again changing java app behavior when using a debugger. However, I'm not seeing any ill effects when running the kill001 test.
05-01-2024

> the proposed fix may be okay We may not care but the proposed fix may clear the interrupt state when it was already set prior to the issuing of the StopThread.
26-04-2023

ILW=MLM=P4
25-04-2023

Chris, suggested to call Thread.interrupted() in the test to work around this issue. So, this bug is not a blocker for JDK-8306034.
25-04-2023

No need for this to block JDK-8306034. Just add Thread.interrupted() calls to the test where needed.
25-04-2023

This issue is a blocker for enhancement JDK-8306034. The issue causes new test StopThreadTest to fail with an unexpected interruption from Thread.sleep(millis) when a BoundVirtualThread is used for the TestTask.
25-04-2023

Now, the interrupt bit is only used by the JVMTI StopThread only. Probably, test coverage plus test runs have to be good enough to make sure there are no surprises.
25-04-2023

Thread.stop() and JVMTI StopThread have always interrupted the thread as per the code cited as that was the only mechanism to have the thread unblock from sleep/wait/park etc. This behaviour has never been considered a problem. That said the proposed fix may be okay but I wonder about the sudden change in behaviour - very hard to evaluate the impact.
18-04-2023