ed6499446d
Reviewed-by: lmesnik, amenkov
340 lines
13 KiB
Java
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");
|
|
}
|
|
}
|
|
}
|