8324868: debug agent does not properly handle interrupts of a virtual thread
Reviewed-by: sspitsyn, amenkov
This commit is contained in:
parent
22f10e045b
commit
966a42f9b3
@ -1549,7 +1549,7 @@ threadControl_resumeAll(void)
|
||||
* resume any vthread with a suspendCount == 1, and we want to ignore
|
||||
* vthreads with a suspendCount > 0. Therefore we don't want
|
||||
* ResumeAllVirtualThreads resuming these vthreads. We must first
|
||||
* build a list of them to pass to as the exclude list.
|
||||
* build a list of them to pass as the exclude list.
|
||||
*/
|
||||
enumerateOverThreadList(env, &runningVThreads, excludeCountHelper,
|
||||
&excludeCnt);
|
||||
@ -2117,28 +2117,25 @@ threadControl_onEventHandlerEntry(jbyte sessionID, EventInfo *evinfo, jobject cu
|
||||
}
|
||||
|
||||
static void
|
||||
doPendingTasks(JNIEnv *env, ThreadNode *node)
|
||||
doPendingTasks(JNIEnv *env, jthread thread, int pendingInterrupt, jobject pendingStop)
|
||||
{
|
||||
/*
|
||||
* Take care of any pending interrupts/stops, and clear out
|
||||
* info on pending interrupts/stops.
|
||||
* Take care of any pending interrupts/stops.
|
||||
*/
|
||||
if (node->pendingInterrupt) {
|
||||
if (pendingInterrupt) {
|
||||
JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread)
|
||||
(gdata->jvmti, node->thread);
|
||||
(gdata->jvmti, thread);
|
||||
/*
|
||||
* TO DO: Log error
|
||||
*/
|
||||
node->pendingInterrupt = JNI_FALSE;
|
||||
}
|
||||
|
||||
if (node->pendingStop != NULL) {
|
||||
if (pendingStop != NULL) {
|
||||
JVMTI_FUNC_PTR(gdata->jvmti,StopThread)
|
||||
(gdata->jvmti, node->thread, node->pendingStop);
|
||||
(gdata->jvmti, thread, pendingStop);
|
||||
/*
|
||||
* TO DO: Log error
|
||||
*/
|
||||
tossGlobalRef(env, &(node->pendingStop));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2147,35 +2144,44 @@ threadControl_onEventHandlerExit(EventIndex ei, jthread thread,
|
||||
struct bag *eventBag)
|
||||
{
|
||||
ThreadNode *node;
|
||||
JNIEnv *env = getEnv();
|
||||
|
||||
log_debugee_location("threadControl_onEventHandlerExit()", thread, NULL, 0);
|
||||
|
||||
if (ei == EI_THREAD_END) {
|
||||
eventHandler_lock(); /* for proper lock order */
|
||||
}
|
||||
debugMonitorEnter(threadLock);
|
||||
|
||||
node = findRunningThread(thread);
|
||||
if (node == NULL) {
|
||||
EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted");
|
||||
} else {
|
||||
JNIEnv *env;
|
||||
|
||||
env = getEnv();
|
||||
if (ei == EI_THREAD_END) {
|
||||
removeThread(env, node);
|
||||
node = NULL; /* has been freed */
|
||||
} else {
|
||||
/* No point in doing this if the thread is about to die.*/
|
||||
doPendingTasks(env, node);
|
||||
node->eventBag = eventBag;
|
||||
node->current_ei = 0;
|
||||
eventHandler_lock(); /* for proper lock order - see removeThread() call below */
|
||||
debugMonitorEnter(threadLock);
|
||||
node = findRunningThread(thread);
|
||||
if (node == NULL) {
|
||||
EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted");
|
||||
}
|
||||
}
|
||||
|
||||
debugMonitorExit(threadLock);
|
||||
if (ei == EI_THREAD_END) {
|
||||
removeThread(env, node); // Grabs handlerLock, thus the reason for first grabbing it above.
|
||||
node = NULL; // We exiting the threadLock. No longer safe to access.
|
||||
debugMonitorExit(threadLock);
|
||||
eventHandler_unlock();
|
||||
} else {
|
||||
debugMonitorEnter(threadLock);
|
||||
node = findRunningThread(thread);
|
||||
if (node == NULL) {
|
||||
EXIT_ERROR(AGENT_ERROR_NULL_POINTER,"thread list corrupted");
|
||||
}
|
||||
/* No need to do the following if the thread is about to die.*/
|
||||
int pendingInterrupt = node->pendingInterrupt;
|
||||
jobject pendingStop = node->pendingStop;
|
||||
jthread thread = node->thread;
|
||||
node->pendingInterrupt = JNI_FALSE;
|
||||
node->pendingStop = NULL;
|
||||
node->eventBag = eventBag;
|
||||
node->current_ei = 0;
|
||||
node = NULL; // We exiting the threadLock. No longer safe to access.
|
||||
// doPendingTasks() may do an upcall to java, and we don't want to hold any
|
||||
// locks when doing that. Thus we got all our node updates done first
|
||||
// and can now exit the threadLock.
|
||||
debugMonitorExit(threadLock);
|
||||
doPendingTasks(env, thread, pendingInterrupt, pendingStop);
|
||||
if (pendingStop != NULL) {
|
||||
tossGlobalRef(env, &pendingStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2219,29 +2225,9 @@ threadControl_applicationThreadStatus(jthread thread,
|
||||
jvmtiError
|
||||
threadControl_interrupt(jthread thread)
|
||||
{
|
||||
ThreadNode *node;
|
||||
jvmtiError error;
|
||||
|
||||
error = JVMTI_ERROR_NONE;
|
||||
|
||||
log_debugee_location("threadControl_interrupt()", thread, NULL, 0);
|
||||
|
||||
debugMonitorEnter(threadLock);
|
||||
|
||||
node = findThread(&runningThreads, thread);
|
||||
if ((node == NULL) || !HANDLING_EVENT(node)) {
|
||||
error = JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread)
|
||||
(gdata->jvmti, thread);
|
||||
} else {
|
||||
/*
|
||||
* Hold any interrupts until after the event is processed.
|
||||
*/
|
||||
node->pendingInterrupt = JNI_TRUE;
|
||||
}
|
||||
|
||||
debugMonitorExit(threadLock);
|
||||
|
||||
return error;
|
||||
return JVMTI_FUNC_PTR(gdata->jvmti,InterruptThread)(gdata->jvmti, thread);
|
||||
}
|
||||
|
||||
void
|
||||
@ -2308,26 +2294,20 @@ threadControl_saveCLEInfo(JNIEnv *env, jthread thread, EventIndex ei,
|
||||
debugMonitorExit(threadLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Support for getting an interrupt in an application thread while doing
|
||||
* a debugMonitorWait().
|
||||
*/
|
||||
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));
|
||||
|
||||
debugMonitorEnter(threadLock);
|
||||
|
||||
node = findThread(&runningThreads, thread);
|
||||
if (node != NULL) {
|
||||
node->pendingInterrupt = JNI_TRUE;
|
||||
}
|
||||
node = findRunningThread(thread);
|
||||
JDI_ASSERT(node != NULL);
|
||||
node->pendingInterrupt = JNI_TRUE;
|
||||
|
||||
debugMonitorExit(threadLock);
|
||||
}
|
||||
@ -2512,23 +2492,13 @@ threadControl_setEventMode(jvmtiEventMode mode, EventIndex ei, jthread thread)
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the current thread, if the thread has generated at least
|
||||
* one event, and has not generated a thread end event.
|
||||
* Returns the current thread.
|
||||
*/
|
||||
jthread
|
||||
threadControl_currentThread(void)
|
||||
{
|
||||
jthread thread;
|
||||
|
||||
debugMonitorEnter(threadLock);
|
||||
{
|
||||
ThreadNode *node;
|
||||
|
||||
node = findThread(&runningThreads, NULL);
|
||||
thread = (node == NULL) ? NULL : node->thread;
|
||||
}
|
||||
debugMonitorExit(threadLock);
|
||||
|
||||
jthread thread = NULL;
|
||||
jvmtiError error = JVMTI_FUNC_PTR(gdata->jvmti,GetCurrentThread)(gdata->jvmti, &thread);
|
||||
return thread;
|
||||
}
|
||||
|
||||
@ -2670,6 +2640,7 @@ dumpThread(ThreadNode *node) {
|
||||
tty_message("\tthreadState: 0x%x", getThreadState(node));
|
||||
tty_message("\ttoBeResumed: %d", node->toBeResumed);
|
||||
tty_message("\tis_vthread: %d", node->is_vthread);
|
||||
tty_message("\tpendingInterrupt: %d", node->pendingInterrupt);
|
||||
tty_message("\tcurrentInvoke.pending: %d", node->currentInvoke.pending);
|
||||
tty_message("\tcurrentInvoke.started: %d", node->currentInvoke.started);
|
||||
tty_message("\tcurrentInvoke.available: %d", node->currentInvoke.available);
|
||||
|
@ -27,5 +27,6 @@
|
||||
#
|
||||
#############################################################################
|
||||
|
||||
java/lang/invoke/MethodHandles/CatchExceptionTest.java 8146623 generic-all
|
||||
java/lang/invoke/MethodHandles/CatchExceptionTest.java 8146623 generic-all
|
||||
java/lang/management/MemoryMXBean/CollectionUsageThreshold.java 8318668 generic-all
|
||||
com/sun/jdi/InterruptHangTest.java 8306679,8043571 generic-all
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2006, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -28,76 +28,175 @@ import com.sun.jdi.request.*;
|
||||
/**
|
||||
* @test
|
||||
* @bug 6459476
|
||||
* @summary Debuggee is blocked, looks like running
|
||||
* @summary Test interrupting debuggee with single stepping enable
|
||||
* @author jjh
|
||||
*
|
||||
* @run build TestScaffold VMConnection TargetListener TargetAdapter
|
||||
* @run compile -g InterruptHangTest.java
|
||||
* @run driver InterruptHangTest
|
||||
* @run driver InterruptHangTest precise
|
||||
* @run driver InterruptHangTest aggressive
|
||||
* @run driver InterruptHangTest remote
|
||||
*/
|
||||
|
||||
/**
|
||||
* Debuggee has two threads. Debugger keeps stepping in
|
||||
* the first thread. The second thread keeps interrupting the first
|
||||
* thread. If a long time goes by with the debugger not getting
|
||||
* a step event, the test fails.
|
||||
* The debuggee loops in the main thread while the debugger has single stepping
|
||||
* enabled for the debuggee's main thread. The main thread is repeatedly
|
||||
* interrupted while it is looping. If a long time goes by with the debugger
|
||||
* not getting a single step event, the test fails.
|
||||
*
|
||||
* Interrupts are generated in 3 difference modes:
|
||||
* precise - The debuggee creates a 2nd thread that repeatedly calls
|
||||
* Thread.interrupt() on the main thread, but does so in a controlled
|
||||
* fashion that allows to test to verify that every interrupt is
|
||||
* received and handled.
|
||||
* aggressive - The debuggee creates a 2nd thread that repeatedly calls
|
||||
* Thread.interrupt() on the main thread, but does so at a high pace
|
||||
* and without any coordination with the main thread. Because of
|
||||
* this it is not possible to account for all the interrupts since some
|
||||
* might be done before the previous interrupt is handled.
|
||||
* remote - Much like the "aggressive" mode, except interrupts are remotely
|
||||
* generated by the debugger using ThreadReference.interrupt(). There
|
||||
* is no "precise" mode for remotely generated interrupts since it is
|
||||
* difficult to coordinate between the debugger and debuggee in a
|
||||
* reasonable way.
|
||||
*/
|
||||
class InterruptHangTarg {
|
||||
public static String sync = "sync";
|
||||
public static void main(String[] args){
|
||||
int answer = 0;
|
||||
System.out.println("Howdy!");
|
||||
Thread interruptorThread = DebuggeeWrapper.newThread(new Interruptor(Thread.currentThread()));
|
||||
|
||||
synchronized(sync) {
|
||||
class InterruptHangTarg {
|
||||
static String sync = "sync";
|
||||
static int interruptsSent;
|
||||
static boolean remoteMode;
|
||||
static boolean preciseMode;
|
||||
static boolean aggressiveMode;
|
||||
|
||||
private final static int INTERRUPTS_EXPECTED = 200;
|
||||
|
||||
public static void main(String[] args){
|
||||
int answer = 0; // number of interrupts answered
|
||||
|
||||
System.out.println("Howdy!");
|
||||
|
||||
remoteMode = "remote".equals(args[0]);
|
||||
preciseMode = "precise".equals(args[0]);
|
||||
aggressiveMode = "aggressive".equals(args[0]);
|
||||
|
||||
if (!remoteMode && !preciseMode && !aggressiveMode) {
|
||||
throw new RuntimeException("Invalid first arg for debuggee: " + args[0]);
|
||||
}
|
||||
|
||||
// Create the debuggee interruptor thread if needed.
|
||||
Thread interruptorThread;
|
||||
if (preciseMode) {
|
||||
interruptorThread =
|
||||
DebuggeeWrapper.newThread(new PreciseInterruptor(Thread.currentThread()));
|
||||
interruptorThread.start();
|
||||
try {
|
||||
sync.wait();
|
||||
} catch (InterruptedException ee) {
|
||||
System.out.println("Debuggee interruptee: interrupted before starting loop");
|
||||
}
|
||||
} else if (aggressiveMode) {
|
||||
interruptorThread =
|
||||
DebuggeeWrapper.newThread(new AggressiveInterruptor(Thread.currentThread()));
|
||||
interruptorThread.start();
|
||||
} else {
|
||||
interruptorThread = null; // we are in "remote" interrupt mode
|
||||
}
|
||||
|
||||
// Debugger will keep stepping thru this loop
|
||||
for (int ii = 0; ii < 200; ii++) {
|
||||
answer++;
|
||||
for (int ii = 0; ii < INTERRUPTS_EXPECTED; ii++) {
|
||||
boolean wasInterrupted = false;
|
||||
try {
|
||||
// Give other thread a chance to run
|
||||
// Give other thread a chance to interrupt
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException ee) {
|
||||
System.out.println("Debuggee interruptee: interrupted at iteration: "
|
||||
+ ii);
|
||||
answer++;
|
||||
wasInterrupted = true;
|
||||
boolean isInterrupted = Thread.currentThread().isInterrupted();
|
||||
System.out.println("Debuggee interruptee(" + ii + "): isInterrupted(" + isInterrupted + ")");
|
||||
if (preciseMode) {
|
||||
// When Thread.sleep() is interrupted, the interrupted status of the thread
|
||||
// should remain cleared (since InterruptedException was thrown), and the
|
||||
// the next interrupt won't come in until after the sync.notify() below.
|
||||
// Note this is not true for the aggressive and remote modes since
|
||||
// interrupts can come in at any time, even while we are handling
|
||||
// an intrrupt.
|
||||
if (isInterrupted) {
|
||||
throw new RuntimeException("Thread should not have interrupted status set.");
|
||||
}
|
||||
synchronized(InterruptHangTarg.sync) {
|
||||
// Let the interruptor thread know it can start interrupting again
|
||||
sync.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every Thread.sleep() call should get interrupted
|
||||
if (!wasInterrupted) {
|
||||
throw new RuntimeException("Thread was never interrupted during sleep: " + ii);
|
||||
}
|
||||
}
|
||||
// Kill the interrupter thread
|
||||
interruptorThread.interrupt();
|
||||
|
||||
if (interruptorThread != null) {
|
||||
synchronized(InterruptHangTarg.sync) {
|
||||
// Kill the interrupter thread
|
||||
interruptorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
// Print how many times an interrupt was sent. When in remote mode, the RemoteInterruptor
|
||||
// is in a different process, so interruptsSent is not updated, therefore we don't
|
||||
// print it here. The RemoteInterruptor thread keeps its own count and prints
|
||||
// it before it exits.
|
||||
if (!remoteMode) {
|
||||
System.out.println("interrupts sent: " + interruptsSent);
|
||||
}
|
||||
|
||||
// When in precise mode, make sure that every interrupt sent resulted in
|
||||
// an InterruptedException. Note the interruptor always ends up sending
|
||||
// one extra interrupt at the end.
|
||||
if (preciseMode && interruptsSent != answer + 1) {
|
||||
throw new RuntimeException("Too many interrupts sent. Sent: " + interruptsSent +
|
||||
". Expected to send: " + answer + 1);
|
||||
}
|
||||
System.out.println("Goodbye from InterruptHangTarg!");
|
||||
}
|
||||
}
|
||||
|
||||
class Interruptor implements Runnable {
|
||||
Thread interruptee;
|
||||
Interruptor(Thread interruptee) {
|
||||
this.interruptee = interruptee;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
synchronized(InterruptHangTarg.sync) {
|
||||
InterruptHangTarg.sync.notify();
|
||||
static class AggressiveInterruptor implements Runnable {
|
||||
Thread interruptee;
|
||||
AggressiveInterruptor(Thread interruptee) {
|
||||
this.interruptee = interruptee;
|
||||
}
|
||||
|
||||
int ii = 0;
|
||||
while(true) {
|
||||
ii++;
|
||||
interruptee.interrupt();
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException ee) {
|
||||
System.out.println("Debuggee Interruptor: finished after " +
|
||||
ii + " iterrupts");
|
||||
break;
|
||||
public void run() {
|
||||
while (true) {
|
||||
InterruptHangTarg.interruptsSent++;
|
||||
interruptee.interrupt();
|
||||
try {
|
||||
Thread.sleep(5);
|
||||
} catch (InterruptedException ee) {
|
||||
System.out.println("Debuggee Interruptor: finished after " +
|
||||
InterruptHangTarg.interruptsSent + " iterrupts");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class PreciseInterruptor implements Runnable {
|
||||
Thread interruptee;
|
||||
PreciseInterruptor(Thread interruptee) {
|
||||
this.interruptee = interruptee;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
synchronized(InterruptHangTarg.sync) {
|
||||
while (true) {
|
||||
InterruptHangTarg.interruptsSent++;
|
||||
interruptee.interrupt();
|
||||
try {
|
||||
// Wait until the interruptee has handled the interrupt
|
||||
InterruptHangTarg.sync.wait();
|
||||
} catch (InterruptedException ee) {
|
||||
System.out.println("Debuggee Interruptor: finished after " +
|
||||
InterruptHangTarg.interruptsSent + " iterrupts");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,16 +204,48 @@ class Interruptor implements Runnable {
|
||||
/********** test program **********/
|
||||
|
||||
public class InterruptHangTest extends TestScaffold {
|
||||
class RemoteInterruptor extends Thread {
|
||||
static int interruptsSent;
|
||||
ThreadReference interruptee;
|
||||
|
||||
RemoteInterruptor(ThreadReference interruptee) {
|
||||
this.interruptee = interruptee;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
try {
|
||||
while (true) {
|
||||
interruptee.interrupt();
|
||||
interruptsSent++;
|
||||
Thread.sleep(5);
|
||||
}
|
||||
} catch (InterruptedException ee) {
|
||||
println("RemoteInterruptor thread: Unexpected Interrupt");
|
||||
throw new RuntimeException(ee);
|
||||
} catch (IllegalThreadStateException | VMDisconnectedException ee) {
|
||||
println("RemoteInterruptor thread: Got expected " + ee.getClass().getSimpleName()
|
||||
+ " after " + interruptsSent + " interrupts sent. Exiting.");
|
||||
} catch (Throwable ee) {
|
||||
println("RemoteInterruptor thread: Got unexpected exception after "
|
||||
+ interruptsSent + " interrupts sent. Exiting with exception.");
|
||||
ee.printStackTrace(System.out);
|
||||
throw new RuntimeException(ee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThreadReference mainThread;
|
||||
Thread timerThread;
|
||||
String sync = "sync";
|
||||
static int nSteps = 0;
|
||||
static boolean remoteMode;
|
||||
|
||||
InterruptHangTest (String args[]) {
|
||||
super(args);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
remoteMode = "remote".equals(args[0]);
|
||||
new InterruptHangTest(args).startTests();
|
||||
}
|
||||
|
||||
@ -174,11 +305,23 @@ public class InterruptHangTest extends TestScaffold {
|
||||
}
|
||||
};
|
||||
|
||||
Thread remoteInterruptorThread = null;
|
||||
if (remoteMode) {
|
||||
// Create a thread to call ThreadReference.interrupt() on the
|
||||
// debuggee main thread.
|
||||
remoteInterruptorThread = new RemoteInterruptor(mainThread);
|
||||
remoteInterruptorThread.setName("RemoteInterruptor");
|
||||
remoteInterruptorThread.setDaemon(true);
|
||||
remoteInterruptorThread.start();
|
||||
}
|
||||
|
||||
/*
|
||||
* resume the target listening for events
|
||||
*/
|
||||
|
||||
listenUntilVMDisconnect();
|
||||
if (remoteInterruptorThread != null) {
|
||||
remoteInterruptorThread.join();
|
||||
}
|
||||
timerThread.interrupt();
|
||||
|
||||
/*
|
||||
@ -186,9 +329,9 @@ public class InterruptHangTest extends TestScaffold {
|
||||
* if anything has called failure("foo") testFailed will be true
|
||||
*/
|
||||
if (!testFailed) {
|
||||
println("InterruptHangTest: passed");
|
||||
println("InterruptHangTest("+ args[0] + "): passed");
|
||||
} else {
|
||||
throw new Exception("InterruptHangTest: failed");
|
||||
throw new Exception("InterruptHangTest("+ args[0] + "): failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user