| /* | |
| * 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"); | |
| } | |
| } | |
| } |