jdk-24/test/jdk/com/sun/jdi/InterruptHangTest.java
2024-07-22 19:34:09 +00:00

340 lines
13 KiB
Java

/*
* 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
/**
* @test
* @bug 6459476
* @summary Test interrupting debuggee with single stepping enable
* @author jjh
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile -g InterruptHangTest.java
* @run driver InterruptHangTest precise
* @run driver InterruptHangTest aggressive
* @run driver InterruptHangTest remote
*/
/**
* 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 {
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();
} 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 < INTERRUPTS_EXPECTED; ii++) {
boolean wasInterrupted = false;
try {
// Give other thread a chance to interrupt. Normally only a very short
// sleep is needed, but we need to account for unexpected delays in
// the interrupt due to machine and network hiccups.
Thread.sleep(10*1000);
} catch (InterruptedException ee) {
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);
}
}
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!");
}
static class AggressiveInterruptor implements Runnable {
Thread interruptee;
AggressiveInterruptor(Thread interruptee) {
this.interruptee = interruptee;
}
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;
}
}
}
}
}
}
/********** 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();
}
/********** event handlers **********/
public void stepCompleted(StepEvent event) {
synchronized(sync) {
nSteps++;
}
println("Got StepEvent " + nSteps + " at line " +
event.location().method() + ":" +
event.location().lineNumber());
if (nSteps == 1) {
timerThread.start();
}
}
/********** test core **********/
protected void runTests() throws Exception {
BreakpointEvent bpe = startToMain("InterruptHangTarg");
mainThread = bpe.thread();
EventRequestManager erm = vm().eventRequestManager();
/*
* Set event requests
*/
StepRequest request = erm.createStepRequest(mainThread,
StepRequest.STEP_LINE,
StepRequest.STEP_OVER);
request.enable();
// Will be started by the step event handler
timerThread = new Thread("test timer") {
public void run() {
int mySteps = 0;
float timeoutFactor = Float.parseFloat(System.getProperty("test.timeout.factor", "1.0"));
long sleepSeconds = (long)(20 * timeoutFactor);
println("Timer watching for steps every " + sleepSeconds + " seconds");
while (true) {
try {
Thread.sleep(sleepSeconds * 1000);
synchronized(sync) {
println("steps = " + nSteps);
if (mySteps == nSteps) {
// no step for a long time
failure("failure: Debuggee appears to be hung (no steps for " + sleepSeconds + "s)");
vm().exit(-1);
break;
}
}
mySteps = nSteps;
} catch (InterruptedException ee) {
break;
}
}
}
};
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();
/*
* deal with results of test
* if anything has called failure("foo") testFailed will be true
*/
if (!testFailed) {
println("InterruptHangTest("+ args[0] + "): passed");
} else {
throw new Exception("InterruptHangTest("+ args[0] + "): failed");
}
}
}