6700889: Thread resume invalidates all stack frames, even from other threads
6701700: MonitorInfo objects aren't invalidated when the owning thread is resumed
Summary: Don't inform ThreadListeners for thread T1 when some other thread is resumed, and MonitoryIfoImpl must add itself as a ThreadListener
Reviewed-by: dcubed
--- a/jdk/src/share/classes/com/sun/tools/jdi/MonitorInfoImpl.java Fri Jul 04 18:55:37 2008 +0200
+++ b/jdk/src/share/classes/com/sun/tools/jdi/MonitorInfoImpl.java Wed Jul 09 13:43:26 2008 -0700
@@ -40,11 +40,12 @@
int stack_depth;
MonitorInfoImpl(VirtualMachine vm, ObjectReference mon,
- ThreadReference thread, int dpth) {
+ ThreadReferenceImpl thread, int dpth) {
super(vm);
this.monitor = mon;
this.thread = thread;
this.stack_depth = dpth;
+ thread.addListener(this);
}
--- a/jdk/src/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java Fri Jul 04 18:55:37 2008 +0200
+++ b/jdk/src/share/classes/com/sun/tools/jdi/ThreadReferenceImpl.java Wed Jul 09 13:43:26 2008 -0700
@@ -35,12 +35,34 @@
static final int SUSPEND_STATUS_SUSPENDED = 0x1;
static final int SUSPEND_STATUS_BREAK = 0x2;
- private ThreadGroupReference threadGroup;
private int suspendedZombieCount = 0;
- // This is cached only while the VM is suspended
- private static class Cache extends ObjectReferenceImpl.Cache {
- String name = null;
+ /*
+ * Some objects can only be created while a thread is suspended and are valid
+ * only while the thread remains suspended. Examples are StackFrameImpl
+ * and MonitorInfoImpl. When the thread resumes, these objects have to be
+ * marked as invalid so that their methods can throw
+ * InvalidStackFrameException if they are called. To do this, such objects
+ * register themselves as listeners of the associated thread. When the
+ * thread is resumed, its listeners are notified and mark themselves
+ * invalid.
+ * Also, note that ThreadReferenceImpl itself caches some info that
+ * is valid only as long as the thread is suspended. When the thread
+ * is resumed, that cache must be purged.
+ * Lastly, note that ThreadReferenceImpl and its super, ObjectReferenceImpl
+ * cache some info that is only valid as long as the entire VM is suspended.
+ * If _any_ thread is resumed, this cache must be purged. To handle this,
+ * both ThreadReferenceImpl and ObjectReferenceImpl register themselves as
+ * VMListeners so that they get notified when all threads are suspended and
+ * when any thread is resumed.
+ */
+
+ // This is cached for the life of the thread
+ private ThreadGroupReference threadGroup;
+
+ // This is cached only while this one thread is suspended. Each time
+ // the thread is resumed, we clear this and start with a fresh one.
+ private static class LocalCache {
JDWP.ThreadReference.Status status = null;
List<StackFrame> frames = null;
int framesStart = -1;
@@ -52,6 +74,17 @@
boolean triedCurrentContended = false;
}
+ private LocalCache localCache;
+
+ private void resetLocalCache() {
+ localCache = new LocalCache();
+ }
+
+ // This is cached only while all threads in the VM are suspended
+ // Yes, someone could change the name of a thread while it is suspended.
+ private static class Cache extends ObjectReferenceImpl.Cache {
+ String name = null;
+ }
protected ObjectReferenceImpl.Cache newCache() {
return new Cache();
}
@@ -59,8 +92,10 @@
// Listeners - synchronized on vm.state()
private List<WeakReference<ThreadListener>> listeners = new ArrayList<WeakReference<ThreadListener>>();
+
ThreadReferenceImpl(VirtualMachine aVm, long aRef) {
super(aVm,aRef);
+ resetLocalCache();
vm.state().addListener(this);
}
@@ -72,10 +107,24 @@
* VMListener implementation
*/
public boolean vmNotSuspended(VMAction action) {
- synchronized (vm.state()) {
- processThreadAction(new ThreadAction(this,
- ThreadAction.THREAD_RESUMABLE));
+ if (action.resumingThread() == null) {
+ // all threads are being resumed
+ synchronized (vm.state()) {
+ processThreadAction(new ThreadAction(this,
+ ThreadAction.THREAD_RESUMABLE));
+ }
+
}
+
+ /*
+ * Othewise, only one thread is being resumed:
+ * if it is us,
+ * we have already done our processThreadAction to notify our
+ * listeners when we processed the resume.
+ * if it is not us,
+ * we don't want to notify our listeners
+ * because we are not being resumed.
+ */
return super.vmNotSuspended(action);
}
@@ -191,23 +240,19 @@
}
private JDWP.ThreadReference.Status jdwpStatus() {
- JDWP.ThreadReference.Status status = null;
+ JDWP.ThreadReference.Status myStatus = localCache.status;
try {
- Cache local = (Cache)getCache();
-
- if (local != null) {
- status = local.status;
- }
- if (status == null) {
- status = JDWP.ThreadReference.Status.process(vm, this);
- if (local != null) {
- local.status = status;
+ if (myStatus == null) {
+ myStatus = JDWP.ThreadReference.Status.process(vm, this);
+ if ((myStatus.suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0) {
+ // thread is suspended, we can cache the status.
+ localCache.status = myStatus;
}
}
- } catch (JDWPException exc) {
+ } catch (JDWPException exc) {
throw exc.toJDIException();
}
- return status;
+ return myStatus;
}
public int status() {
@@ -245,8 +290,7 @@
public ThreadGroupReference threadGroup() {
/*
- * Thread group can't change, so it's cached more conventionally
- * than other things in this class.
+ * Thread group can't change, so it's cached once and for all.
*/
if (threadGroup == null) {
try {
@@ -260,19 +304,10 @@
}
public int frameCount() throws IncompatibleThreadStateException {
- int frameCount = -1;
try {
- Cache local = (Cache)getCache();
-
- if (local != null) {
- frameCount = local.frameCount;
- }
- if (frameCount == -1) {
- frameCount = JDWP.ThreadReference.FrameCount
+ if (localCache.frameCount == -1) {
+ localCache.frameCount = JDWP.ThreadReference.FrameCount
.process(vm, this).frameCount;
- if (local != null) {
- local.frameCount = frameCount;
- }
}
} catch (JDWPException exc) {
switch (exc.errorCode()) {
@@ -283,7 +318,7 @@
throw exc.toJDIException();
}
}
- return frameCount;
+ return localCache.frameCount;
}
public List<StackFrame> frames() throws IncompatibleThreadStateException {
@@ -297,23 +332,25 @@
/**
* Is the requested subrange within what has been retrieved?
- * local is known to be non-null
+ * local is known to be non-null. Should only be called from
+ * a sync method.
*/
- private boolean isSubrange(Cache local,
- int start, int length, List frames) {
- if (start < local.framesStart) {
+ private boolean isSubrange(LocalCache localCache,
+ int start, int length) {
+ if (start < localCache.framesStart) {
return false;
}
if (length == -1) {
- return (local.framesLength == -1);
+ return (localCache.framesLength == -1);
}
- if (local.framesLength == -1) {
- if ((start + length) > (local.framesStart + frames.size())) {
+ if (localCache.framesLength == -1) {
+ if ((start + length) > (localCache.framesStart +
+ localCache.frames.size())) {
throw new IndexOutOfBoundsException();
}
return true;
}
- return ((start + length) <= (local.framesStart + local.framesLength));
+ return ((start + length) <= (localCache.framesStart + localCache.framesLength));
}
public List<StackFrame> frames(int start, int length)
@@ -329,51 +366,42 @@
* Private version of frames() allows "-1" to specify all
* remaining frames.
*/
- private List<StackFrame> privateFrames(int start, int length)
+ synchronized private List<StackFrame> privateFrames(int start, int length)
throws IncompatibleThreadStateException {
- List<StackFrame> frames = null;
+
+ // Lock must be held while creating stack frames so if that two threads
+ // do this at the same time, one won't clobber the subset created by the other.
+
try {
- Cache local = (Cache)getCache();
-
- if (local != null) {
- frames = local.frames;
- }
- if (frames == null || !isSubrange(local, start, length, frames)) {
+ if (localCache.frames == null || !isSubrange(localCache, start, length)) {
JDWP.ThreadReference.Frames.Frame[] jdwpFrames
= JDWP.ThreadReference.Frames.
- process(vm, this, start, length).frames;
+ process(vm, this, start, length).frames;
int count = jdwpFrames.length;
- frames = new ArrayList<StackFrame>(count);
+ localCache.frames = new ArrayList<StackFrame>(count);
- // Lock must be held while creating stack frames.
- // so that a resume will not resume a partially
- // created stack.
- synchronized (vm.state()) {
- for (int i = 0; i<count; i++) {
- if (jdwpFrames[i].location == null) {
- throw new InternalException("Invalid frame location");
- }
- StackFrame frame = new StackFrameImpl(vm, this,
- jdwpFrames[i].frameID,
- jdwpFrames[i].location);
- // Add to the frame list
- frames.add(frame);
+ for (int i = 0; i<count; i++) {
+ if (jdwpFrames[i].location == null) {
+ throw new InternalException("Invalid frame location");
}
+ StackFrame frame = new StackFrameImpl(vm, this,
+ jdwpFrames[i].frameID,
+ jdwpFrames[i].location);
+ // Add to the frame list
+ localCache.frames.add(frame);
}
- if (local != null) {
- local.frames = frames;
- local.framesStart = start;
- local.framesLength = length;
- }
+ localCache.framesStart = start;
+ localCache.framesLength = length;
+ return Collections.unmodifiableList(localCache.frames);
} else {
- int fromIndex = start - local.framesStart;
+ int fromIndex = start - localCache.framesStart;
int toIndex;
if (length == -1) {
- toIndex = frames.size() - fromIndex;
+ toIndex = localCache.frames.size() - fromIndex;
} else {
toIndex = fromIndex + length;
}
- frames = frames.subList(fromIndex, toIndex);
+ return Collections.unmodifiableList(localCache.frames.subList(fromIndex, toIndex));
}
} catch (JDWPException exc) {
switch (exc.errorCode()) {
@@ -384,28 +412,18 @@
throw exc.toJDIException();
}
}
- return Collections.unmodifiableList(frames);
}
public List<ObjectReference> ownedMonitors() throws IncompatibleThreadStateException {
- List<ObjectReference> monitors = null;
try {
- Cache local = (Cache)getCache();
-
- if (local != null) {
- monitors = local.ownedMonitors;
- }
- if (monitors == null) {
- monitors = Arrays.asList(
+ if (localCache.ownedMonitors == null) {
+ localCache.ownedMonitors = Arrays.asList(
(ObjectReference[])JDWP.ThreadReference.OwnedMonitors.
process(vm, this).owned);
- if (local != null) {
- local.ownedMonitors = monitors;
- if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
- vm.printTrace(description() +
- " temporarily caching owned monitors"+
- " (count = " + monitors.size() + ")");
- }
+ if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
+ vm.printTrace(description() +
+ " temporarily caching owned monitors"+
+ " (count = " + localCache.ownedMonitors.size() + ")");
}
}
} catch (JDWPException exc) {
@@ -417,29 +435,22 @@
throw exc.toJDIException();
}
}
- return monitors;
+ return localCache.ownedMonitors;
}
public ObjectReference currentContendedMonitor()
throws IncompatibleThreadStateException {
- ObjectReference monitor = null;
try {
- Cache local = (Cache)getCache();
-
- if (local != null && local.triedCurrentContended) {
- monitor = local.contendedMonitor;
- } else {
- monitor = JDWP.ThreadReference.CurrentContendedMonitor.
+ if (localCache.contendedMonitor == null &&
+ !localCache.triedCurrentContended) {
+ localCache.contendedMonitor = JDWP.ThreadReference.CurrentContendedMonitor.
process(vm, this).monitor;
- if (local != null) {
- local.triedCurrentContended = true;
- local.contendedMonitor = monitor;
- if ((monitor != null) &&
- ((vm.traceFlags & vm.TRACE_OBJREFS) != 0)) {
- vm.printTrace(description() +
- " temporarily caching contended monitor"+
- " (id = " + monitor.uniqueID() + ")");
- }
+ localCache.triedCurrentContended = true;
+ if ((localCache.contendedMonitor != null) &&
+ ((vm.traceFlags & vm.TRACE_OBJREFS) != 0)) {
+ vm.printTrace(description() +
+ " temporarily caching contended monitor"+
+ " (id = " + localCache.contendedMonitor.uniqueID() + ")");
}
}
} catch (JDWPException exc) {
@@ -450,40 +461,31 @@
throw exc.toJDIException();
}
}
- return monitor;
+ return localCache.contendedMonitor;
}
public List<MonitorInfo> ownedMonitorsAndFrames() throws IncompatibleThreadStateException {
- List<MonitorInfo> monitors = null;
try {
- Cache local = (Cache)getCache();
-
- if (local != null) {
- monitors = local.ownedMonitorsInfo;
- }
- if (monitors == null) {
+ if (localCache.ownedMonitorsInfo == null) {
JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor[] minfo;
minfo = JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.process(vm, this).owned;
- monitors = new ArrayList<MonitorInfo>(minfo.length);
+ localCache.ownedMonitorsInfo = new ArrayList<MonitorInfo>(minfo.length);
for (int i=0; i < minfo.length; i++) {
JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor mi =
minfo[i];
MonitorInfo mon = new MonitorInfoImpl(vm, minfo[i].monitor, this, minfo[i].stack_depth);
- monitors.add(mon);
+ localCache.ownedMonitorsInfo.add(mon);
}
- if (local != null) {
- local.ownedMonitorsInfo = monitors;
- if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
- vm.printTrace(description() +
- " temporarily caching owned monitors"+
- " (count = " + monitors.size() + ")");
+ if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
+ vm.printTrace(description() +
+ " temporarily caching owned monitors"+
+ " (count = " + localCache.ownedMonitorsInfo.size() + ")");
}
}
- }
} catch (JDWPException exc) {
switch (exc.errorCode()) {
case JDWP.Error.THREAD_NOT_SUSPENDED:
@@ -493,7 +495,7 @@
throw exc.toJDIException();
}
}
- return monitors;
+ return localCache.ownedMonitorsInfo;
}
public void popFrames(StackFrame frame) throws IncompatibleThreadStateException {
@@ -511,7 +513,7 @@
}
public void forceEarlyReturn(Value returnValue) throws InvalidTypeException,
- ClassNotLoadedException,
+ ClassNotLoadedException,
IncompatibleThreadStateException {
if (!vm.canForceEarlyReturn()) {
throw new UnsupportedOperationException(
@@ -604,6 +606,9 @@
iter.remove();
}
}
+
+ // Discard our local cache
+ resetLocalCache();
}
}
}
--- a/jdk/src/share/classes/com/sun/tools/jdi/VMAction.java Fri Jul 04 18:55:37 2008 +0200
+++ b/jdk/src/share/classes/com/sun/tools/jdi/VMAction.java Wed Jul 09 13:43:26 2008 -0700
@@ -38,10 +38,18 @@
static final int VM_NOT_SUSPENDED = 2;
int id;
+ ThreadReference resumingThread;
VMAction(VirtualMachine vm, int id) {
+ this(vm, null, id);
+ }
+
+ // For id = VM_NOT_SUSPENDED, if resumingThread != null, then it is
+ // the only thread that is being resumed.
+ VMAction(VirtualMachine vm, ThreadReference resumingThread, int id) {
super(vm);
this.id = id;
+ this.resumingThread = resumingThread;
}
VirtualMachine vm() {
return (VirtualMachine)getSource();
@@ -49,4 +57,8 @@
int id() {
return id;
}
+
+ ThreadReference resumingThread() {
+ return resumingThread;
+ }
}
--- a/jdk/src/share/classes/com/sun/tools/jdi/VMState.java Fri Jul 04 18:55:37 2008 +0200
+++ b/jdk/src/share/classes/com/sun/tools/jdi/VMState.java Wed Jul 09 13:43:26 2008 -0700
@@ -116,16 +116,25 @@
}
/**
- * Tell listeners to invalidate suspend-sensitive caches.
+ * All threads are resuming
*/
- synchronized void thaw() {
+ void thaw() {
+ thaw(null);
+ }
+
+ /**
+ * Tell listeners to invalidate suspend-sensitive caches.
+ * If resumingThread != null, then only that thread is being
+ * resumed.
+ */
+ synchronized void thaw(ThreadReference resumingThread) {
if (cache != null) {
if ((vm.traceFlags & vm.TRACE_OBJREFS) != 0) {
vm.printTrace("Clearing VM suspended cache");
}
disableCache();
}
- processVMAction(new VMAction(vm, VMAction.VM_NOT_SUSPENDED));
+ processVMAction(new VMAction(vm, resumingThread, VMAction.VM_NOT_SUSPENDED));
}
private synchronized void processVMAction(VMAction action) {
--- a/jdk/src/share/classes/com/sun/tools/jdi/VirtualMachineImpl.java Fri Jul 04 18:55:37 2008 +0200
+++ b/jdk/src/share/classes/com/sun/tools/jdi/VirtualMachineImpl.java Wed Jul 09 13:43:26 2008 -0700
@@ -146,8 +146,9 @@
public boolean threadResumable(ThreadAction action) {
/*
* If any thread is resumed, the VM is considered not suspended.
+ * Just one thread is being resumed so pass it to thaw.
*/
- state.thaw();
+ state.thaw(action.thread());
return true;
}
--- a/jdk/test/com/sun/jdi/MonitorFrameInfo.java Fri Jul 04 18:55:37 2008 +0200
+++ b/jdk/test/com/sun/jdi/MonitorFrameInfo.java Wed Jul 09 13:43:26 2008 -0700
@@ -25,7 +25,8 @@
* @test
* @bug 6230699
* @summary Test ThreadReference.ownedMonitorsAndFrames()
- *
+ * @bug 6701700
+ * @summary MonitorInfo objects aren't invalidated when the owning thread is resumed
* @author Swamy Venkataramanappa
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
@@ -100,15 +101,15 @@
if (!mainThread.frame(0).location().method().name()
.equals("foo3")) {
- failure("frame failed");
+ failure("FAILED: frame failed");
}
if (mainThread.frames().size() != (initialSize + 3)) {
- failure("frames size failed");
+ failure("FAILED: frames size failed");
}
if (mainThread.frames().size() != mainThread.frameCount()) {
- failure("frames size not equal to frameCount");
+ failure("FAILED: frames size not equal to frameCount");
}
/* Test monitor frame info.
@@ -119,13 +120,32 @@
if (monitors.size() != expectedCount) {
failure("monitors count is not equal to expected count");
}
+ MonitorInfo mon = null;
for (int j=0; j < monitors.size(); j++) {
- MonitorInfo mon = (MonitorInfo)monitors.get(j);
+ mon = (MonitorInfo)monitors.get(j);
System.out.println("Monitor obj " + mon.monitor() + "depth =" +mon.stackDepth());
if (mon.stackDepth() != expectedDepth[j]) {
- failure("monitor stack depth is not equal to expected depth");
+ failure("FAILED: monitor stack depth is not equal to expected depth");
}
}
+
+ // The last gotten monInfo is in mon. When we resume the thread,
+ // it should become invalid. We will step out of the top frame
+ // so that the frame depth in this mon object will no longer be correct.
+ // That is why the monInfo's have to become invalid when the thread is
+ // resumed.
+ stepOut(mainThread);
+ boolean ok = false;
+ try {
+ System.out.println("*** Saved Monitor obj " + mon.monitor() + "depth =" +mon.stackDepth());
+ } catch(InvalidStackFrameException ee) {
+ // ok
+ ok = true;
+ System.out.println("Got expected InvalidStackFrameException after a resume");
+ }
+ if (!ok) {
+ failure("FAILED: MonitorInfo object was not invalidated by a resume");
+ }
} else {
System.out.println("can not get monitors frame info");
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/com/sun/jdi/ResumeOneThreadTest.java Wed Jul 09 13:43:26 2008 -0700
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2008 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/**
+ * @test
+ * @bug 6700889
+ * @summary Thread resume invalidates all stack frames, even from other threads
+ *
+ * @author jjh
+ *
+ * @run build TestScaffold VMConnection TargetListener TargetAdapter
+ * @run compile -g ResumeOneThreadTest.java
+ * @run main ResumeOneThreadTest
+ */
+import com.sun.jdi.*;
+import com.sun.jdi.event.*;
+import com.sun.jdi.request.*;
+
+import java.util.*;
+
+class ResumeOneThreadTarg extends Thread {
+ static String name1 = "Thread 1";
+ static String name2 = "Thread 2";
+
+ public ResumeOneThreadTarg(String name) {
+ super(name);
+ }
+
+ public static void main(String[] args) {
+ System.out.println(" Debuggee: Howdy!");
+ ResumeOneThreadTarg t1 = new ResumeOneThreadTarg(name1);
+ ResumeOneThreadTarg t2 = new ResumeOneThreadTarg(name2);
+
+ t1.start();
+ t2.start();
+ }
+
+ // This just starts two threads. Each runs to a bkpt.
+ public void run() {
+ if (getName().equals(name1)) {
+ run1();
+ } else {
+ run2();
+ }
+ }
+
+ public void bkpt1(String p1) {
+ System.out.println(" Debuggee: bkpt 1");
+ }
+
+ public void run1() {
+ bkpt1("Hello Alviso!");
+ }
+
+
+
+ public void bkpt2() {
+ System.out.println(" Debuggee: bkpt 2");
+ }
+
+ public void run2() {
+ bkpt2();
+ }
+}
+
+/********** test program **********/
+
+public class ResumeOneThreadTest extends TestScaffold {
+ ReferenceType targetClass;
+ ThreadReference mainThread;
+
+ BreakpointRequest request1;
+ BreakpointRequest request2;
+
+ ThreadReference thread1 = null;
+ ThreadReference thread2 = null;;
+ boolean theVMisDead = false;
+
+ ResumeOneThreadTest (String args[]) {
+ super(args);
+ }
+
+ public static void main(String[] args) throws Exception {
+ new ResumeOneThreadTest(args).startTests();
+ }
+
+
+ synchronized public void breakpointReached(BreakpointEvent event) {
+ println("-- Got bkpt at: " + event.location());
+ ThreadReference eventThread = event.thread();
+
+ if (eventThread.name().equals(ResumeOneThreadTarg.name1)) {
+ thread1 = eventThread;
+ }
+
+ if (eventThread.name().equals(ResumeOneThreadTarg.name2)) {
+ thread2 = eventThread;
+ }
+ }
+
+ public void vmDied(VMDeathEvent event) {
+ theVMisDead = true;
+ }
+
+ synchronized public void eventSetComplete(EventSet set) {
+ if (theVMisDead) {
+ return;
+ }
+ if (thread1 == null || thread2 == null) {
+ // Don't do a set.resume(), just let the other thread
+ // keep running until it hits its bkpt.
+ return;
+ }
+
+ // Both threads are stopped at their bkpts. Get a StackFrame from
+ // Thread 1 then resume Thread 2 and verify that the saved StackFrame is
+ // still valid.
+
+ // suspend everything.
+ println("-- All threads suspended");
+ vm().suspend();
+
+ StackFrame t1sf0 = null;
+ try {
+ t1sf0 = thread1.frame(0);
+ } catch (IncompatibleThreadStateException ee) {
+ failure("FAILED: Exception: " + ee);
+ }
+
+ println("-- t1sf0 args: " + t1sf0.getArgumentValues());
+
+ // Ok, we have a StackFrame for thread 1. Resume just thread 2
+ // Note that thread 2 has been suspended twice - by the SUSPEND_ALL
+ // bkpt, and by the above vm().suspend(), so we have to resume
+ // it twice.
+ request2.disable();
+
+ thread2.resume();
+ thread2.resume();
+ println("-- Did Resume on thread 2");
+
+ // Can we get frames for thread1?
+ try {
+ StackFrame t1sf0_1 = thread1.frame(0);
+ if (!t1sf0.equals(t1sf0_1)) {
+ failure("FAILED: Got a different frame 0 for thread 1 after resuming thread 2");
+ }
+ } catch (IncompatibleThreadStateException ee) {
+ failure("FAILED: Could not get frames for thread 1: Exception: " + ee);
+ } catch (Exception ee) {
+ failure("FAILED: Could not get frames for thread 1: Exception: " + ee);
+ }
+
+
+ try {
+ println("-- t1sf0 args: " + t1sf0.getArgumentValues());
+ } catch (InvalidStackFrameException ee) {
+ // This is the failure.
+ failure("FAILED Got InvalidStackFrameException");
+ vm().dispose();
+ throw(ee);
+ }
+
+ // Let the debuggee finish
+ request1.disable();
+ thread1.resume();
+ vm().resume();
+ println("--------------");
+ }
+
+ /********** test core **********/
+
+ protected void runTests() throws Exception {
+
+ /*
+ * Get to the top of main()
+ * to determine targetClass and mainThread
+ */
+ BreakpointEvent bpe = startToMain("ResumeOneThreadTarg");
+ targetClass = bpe.location().declaringType();
+ mainThread = bpe.thread();
+ EventRequestManager erm = vm().eventRequestManager();
+ final Thread mainThread = Thread.currentThread();
+
+ /*
+ * Set event requests
+ */
+
+ Location loc1 = findMethod(targetClass, "bkpt1", "(Ljava/lang/String;)V").location();
+ request1 = erm.createBreakpointRequest(loc1);
+ request1.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
+ request1.enable();
+
+ Location loc2 = findMethod(targetClass, "bkpt2", "()V").location();
+ request2 = erm.createBreakpointRequest(loc2);
+ request2.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
+ request2.enable();
+
+ /*
+ * resume the target, listening for events
+ */
+ listenUntilVMDisconnect();
+ /*
+ * deal with results of test
+ * if anything has called failure("foo") testFailed will be true
+ */
+ if (!testFailed) {
+ println("ResumeOneThreadTest: passed");
+ } else {
+ throw new Exception("ResumeOneThreadTest: failed");
+ }
+ }
+}