diff --git a/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c b/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c index 5628f8b44db..00c34844c98 100644 --- a/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c +++ b/src/jdk.jdwp.agent/share/native/libjdwp/threadControl.c @@ -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); diff --git a/test/jdk/ProblemList-Xcomp.txt b/test/jdk/ProblemList-Xcomp.txt index 5f7f4a912aa..6e8953c521e 100644 --- a/test/jdk/ProblemList-Xcomp.txt +++ b/test/jdk/ProblemList-Xcomp.txt @@ -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 diff --git a/test/jdk/com/sun/jdi/InterruptHangTest.java b/test/jdk/com/sun/jdi/InterruptHangTest.java index e2c238eee49..a8062586d27 100644 --- a/test/jdk/com/sun/jdi/InterruptHangTest.java +++ b/test/jdk/com/sun/jdi/InterruptHangTest.java @@ -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"); } } }