JDWP: test invocation with thread suspension

Tests the debuggee can still process new commands while a thread is
blocked invoking a command (because another event in another thread
suspended that thread). The tested commands are:
- ClassType.InvokeCommand
- ClassType.NewInstance
- ObjectReference.InvokeCommand

Also tests the case where the debugger detaches while a thread is
invoking a method on its behalf (with one of the command above). The
method invocation completes but no reply is sent and the thread does
not suspend itself or other threads now the debugger is no longer
attached.

Bug: 21515842

(cherry picked from commit 237ee3cf55ad8dbbab372f83f1988a3c7c7e3030)

Change-Id: I7ea452a9e5180730ed8e517d5d98b505ca676ffd
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/framework/jdwp/VmMirror.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/framework/jdwp/VmMirror.java
index 3ffa6df..67c0aa0 100644
--- a/jdwp/src/test/java/org/apache/harmony/jpda/tests/framework/jdwp/VmMirror.java
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/framework/jdwp/VmMirror.java
@@ -218,6 +218,19 @@
      * @return requestID id of request
      */
     public int setBreakpointAtMethodBegin(long classID, String methodName) {
+        return setBreakpointAtMethodBegin(classID, methodName, JDWPConstants.SuspendPolicy.ALL);
+    }
+
+    /**
+     * Sets breakpoint at the beginning of method with name <i>methodName</i>.
+     *
+     * @param classID
+     *            id of class with required method
+     * @param methodName
+     *            name of required method
+     * @return requestID id of request
+     */
+    public int setBreakpointAtMethodBegin(long classID, String methodName, byte suspendPolicy) {
         long methodID = getMethodID(classID, methodName);
 
         ReplyPacket lineTableReply = getLineTable(classID, methodID);
@@ -245,7 +258,7 @@
         Location breakpointLocation = new Location(JDWPConstants.TypeTag.CLASS,
                 classID, methodID, lineCodeIndex);
 
-        ReplyPacket reply = setBreakpoint(breakpointLocation);
+        ReplyPacket reply = setBreakpoint(breakpointLocation, suspendPolicy);
         checkReply(reply);
 
         return reply.getNextValueAsInt();
@@ -733,6 +746,22 @@
     }
 
     /**
+     * Returns suspend count for specified <code>threadID</code>.
+     *
+     * @param threadID
+     *            thread ID
+     * @return thread's suspend count
+     */
+    public int getThreadSuspendCount(long threadID) {
+        CommandPacket commandPacket = new CommandPacket(
+                JDWPCommands.ThreadReferenceCommandSet.CommandSetID,
+                JDWPCommands.ThreadReferenceCommandSet.SuspendCountCommand);
+        commandPacket.setNextValueAsThreadID(threadID);
+        ReplyPacket replyPacket = checkReply(performCommand(commandPacket));
+        return replyPacket.getNextValueAsInt();
+    }
+
+    /**
      * Returns name of thread group for specified <code>groupID</code>
      * 
      * @param groupID
@@ -2349,6 +2378,20 @@
     }
 
     /**
+     * Returns the value of one static field of the reference type
+     *
+     * @param refTypeID
+     *            The reference type ID.
+     * @param fieldID
+     *            ID of field to get
+     * @return A Value object representing the field's value
+     */
+    public final Value getReferenceTypeValue(long refTypeID, long fieldID) {
+        Value[] values = getReferenceTypeValues(refTypeID, new long[]{fieldID});
+        return values[0];
+    }
+
+    /**
      * Returns the value of the 'this' reference for this frame
      * 
      * @param threadID
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ClassType/InvokeMethodWithSuspensionTest.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ClassType/InvokeMethodWithSuspensionTest.java
new file mode 100644
index 0000000..d1d7efe
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ClassType/InvokeMethodWithSuspensionTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.ClassType;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.TaggedObject;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPInvokeMethodWithSuspensionTestCase;
+import org.apache.harmony.jpda.tests.jdwp.share.debuggee.InvokeMethodWithSuspensionDebuggee;
+
+/**
+ * JDWP unit test for ClassType.InvokeCommand command with a thread suspension.
+ */
+public class InvokeMethodWithSuspensionTest extends JDWPInvokeMethodWithSuspensionTestCase {
+    public void testInvokeWithMultipleEvents001() {
+        runInvokeMethodTest(InvokeMethodWithSuspensionDebuggee.STATIC_METHOD_NAME);
+    }
+
+    @Override
+    protected CommandPacket buildInvokeCommand(long threadId, long classID,
+            long methodId, int invoke_options) {
+        CommandPacket command = new CommandPacket(
+                JDWPCommands.ClassTypeCommandSet.CommandSetID,
+                JDWPCommands.ClassTypeCommandSet.InvokeMethodCommand);
+        command.setNextValueAsClassID(classID);
+        command.setNextValueAsThreadID(threadId);
+        command.setNextValueAsMethodID(methodId);
+        command.setNextValueAsInt(0);
+        command.setNextValueAsInt(invoke_options);
+        return command;
+    }
+
+    @Override
+    protected String getInvokeCommandName() {
+        return "ClassType.InvokeCommand";
+    }
+
+    @Override
+    protected void checkInvokeReply(ReplyPacket reply) {
+        // Check result is 'void'
+        Value invokeResult = reply.getNextValueAsValue();
+        assertNull("Expect null result value for 'void'", invokeResult);
+
+        // Check exception is null.
+        TaggedObject invokeException = reply.getNextValueAsTaggedObject();
+        assertEquals("Invalid exception object id", 0, invokeException.objectID);
+        assertAllDataRead(reply);
+
+    }
+
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ClassType/NewInstanceWithSuspensionTest.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ClassType/NewInstanceWithSuspensionTest.java
new file mode 100644
index 0000000..26509d3
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ClassType/NewInstanceWithSuspensionTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.ClassType;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.TaggedObject;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPInvokeMethodWithSuspensionTestCase;
+
+/**
+ * JDWP unit test for ClassType.NewInstance command with a thread suspension.
+ */
+public class NewInstanceWithSuspensionTest extends JDWPInvokeMethodWithSuspensionTestCase {
+    public void testInvokeWithMultipleEvents001() {
+        runInvokeMethodTest("<init>");
+    }
+
+    @Override
+    protected CommandPacket buildInvokeCommand(long threadId, long classID,
+            long methodId, int invoke_options) {
+        CommandPacket command = new CommandPacket(
+                JDWPCommands.ClassTypeCommandSet.CommandSetID,
+                JDWPCommands.ClassTypeCommandSet.NewInstanceCommand);
+        command.setNextValueAsClassID(classID);
+        command.setNextValueAsThreadID(threadId);
+        command.setNextValueAsMethodID(methodId);
+        command.setNextValueAsInt(0);
+        command.setNextValueAsInt(invoke_options);
+        return command;
+    }
+
+    @Override
+    protected String getInvokeCommandName() {
+        return "ClassType.NewInstance";
+    }
+
+    @Override
+    protected void checkInvokeReply(ReplyPacket reply) {
+        // Check result is 'void'
+        TaggedObject invokeNewObject = reply.getNextValueAsTaggedObject();
+        assertEquals(JDWPConstants.Tag.OBJECT_TAG, invokeNewObject.tag);
+        assertFalse("Invalid exception object id", invokeNewObject.objectID == 0);
+
+        // Check exception is null.
+        TaggedObject invokeException = reply.getNextValueAsTaggedObject();
+        assertEquals(JDWPConstants.Tag.OBJECT_TAG, invokeException.tag);
+        assertEquals("Invalid exception object id", 0, invokeException.objectID);
+
+        assertAllDataRead(reply);
+
+    }
+
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ObjectReference/InvokeMethodWithSuspensionTest.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ObjectReference/InvokeMethodWithSuspensionTest.java
new file mode 100644
index 0000000..578d446
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/ObjectReference/InvokeMethodWithSuspensionTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.ObjectReference;
+
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.TaggedObject;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPInvokeMethodWithSuspensionTestCase;
+import org.apache.harmony.jpda.tests.jdwp.share.debuggee.InvokeMethodWithSuspensionDebuggee;
+
+/**
+ * JDWP unit test for ObjectReference.InvokeCommand command with a thread suspension.
+ */
+public class InvokeMethodWithSuspensionTest extends JDWPInvokeMethodWithSuspensionTestCase {
+    public void testInvokeWithMultipleEvents001() {
+        runInvokeMethodTest(InvokeMethodWithSuspensionDebuggee.INSTANCE_METHOD_NAME);
+    }
+
+    @Override
+    protected CommandPacket buildInvokeCommand(long threadId, long classID,
+            long methodId, int invoke_options) {
+        // We must first find the 'this' object of the top frame.
+        ReplyPacket replyPacket = debuggeeWrapper.vmMirror.getThreadFrames(threadId, 0, 1);
+        int framesCount = replyPacket.getNextValueAsInt();
+        assertEquals("Invalid frame count:", 1, framesCount);
+        long topFrameId = replyPacket.getNextValueAsFrameID();
+        replyPacket.getNextValueAsLocation();  // consume 'location'
+        assertAllDataRead(replyPacket);
+
+        long receiverId = debuggeeWrapper.vmMirror.getThisObject(threadId, topFrameId);
+
+        CommandPacket command = new CommandPacket(
+                JDWPCommands.ObjectReferenceCommandSet.CommandSetID,
+                JDWPCommands.ObjectReferenceCommandSet.InvokeMethodCommand);
+        command.setNextValueAsThreadID(receiverId);
+        command.setNextValueAsThreadID(threadId);
+        command.setNextValueAsClassID(classID);
+        command.setNextValueAsMethodID(methodId);
+        command.setNextValueAsInt(0);
+        command.setNextValueAsInt(invoke_options);
+        return command;
+    }
+
+    @Override
+    protected String getInvokeCommandName() {
+        return "ObjectReference.InvokeCommand";
+    }
+
+    @Override
+    protected void checkInvokeReply(ReplyPacket reply) {
+        // Check result is 'void'
+        Value invokeResult = reply.getNextValueAsValue();
+        assertNull("Expect null result value for 'void'", invokeResult);
+
+        // Check exception is null.
+        TaggedObject invokeException = reply.getNextValueAsTaggedObject();
+        assertEquals("Invalid exception object id", 0, invokeException.objectID);
+        assertAllDataRead(reply);
+
+    }
+
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/VirtualMachine/DisposeDuringInvokeDebuggee.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/VirtualMachine/DisposeDuringInvokeDebuggee.java
new file mode 100644
index 0000000..7acc3a5
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/VirtualMachine/DisposeDuringInvokeDebuggee.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.VirtualMachine;
+
+import org.apache.harmony.jpda.tests.framework.TestErrorException;
+import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
+import org.apache.harmony.jpda.tests.share.SyncDebuggee;
+
+/**
+ * Debuggee for {@link DisposeDuringInvokeTest}.
+ */
+public class DisposeDuringInvokeDebuggee extends SyncDebuggee {
+    public static final String INVOKED_METHOD_NAME = "invokeMethodWithSynchronization";
+    public static final String BREAKPOINT_METHOD_NAME = "breakpointMethod";
+    public static final String THIS_FIELD_NAME = "thisObject";
+
+    // This field holds the receiver to invoke the invokeMethodWithSynchronization method.
+    public static DisposeDuringInvokeDebuggee thisObject;
+
+    private class DebuggeeThread extends Thread {
+        public DebuggeeThread() {
+            super("DebuggeeThread");
+        }
+
+        public void run() {
+            breakpointMethod();
+        }
+    }
+
+    /**
+     * The method used to suspend the DebuggeeThread on a breakpoint.
+     */
+    private void breakpointMethod() {
+    }
+
+    /**
+     * The method called by the DebuggeeThread through JDWP.
+     */
+    private void invokeMethodWithSynchronization() {
+        logWriter.println("#START invokeMethodWithSynchronization");
+
+        // Tell the test we are invoking the requested method.
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // Wait for the test to send a VirtualMachine.Dispose command and resume us.
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        logWriter.println("#END invokeMethodWithSynchronization");
+    }
+
+    @Override
+    public void run() {
+        thisObject = this;
+
+        DebuggeeThread thrd = new DebuggeeThread();
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        logWriter.println("Start DebuggeeThread");
+        thrd.start();
+
+        logWriter.println("Main thread waits for DebuggeeThread ...");
+        try {
+            thrd.join();
+        } catch (InterruptedException e) {
+           throw new TestErrorException(e);
+        }
+        logWriter.println("DebuggeeThread is finished");
+
+        // Tell the test we successfully waited for the end of DebuggeeThread.
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // Wait for the test to resume us.
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+    }
+
+    public static void main(String [] args) {
+        runDebuggee(DisposeDuringInvokeDebuggee.class);
+    }
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/VirtualMachine/DisposeDuringInvokeTest.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/VirtualMachine/DisposeDuringInvokeTest.java
new file mode 100644
index 0000000..7046d4e
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/VirtualMachine/DisposeDuringInvokeTest.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.VirtualMachine;
+
+import org.apache.harmony.jpda.tests.framework.TestErrorException;
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPSyncTestCase;
+import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
+
+import java.io.IOException;
+
+/**
+ * JDWP Unit test for VirtualMachine.Dispose command while a thread is invoking a method.
+ */
+public class DisposeDuringInvokeTest extends JDWPSyncTestCase {
+
+    @Override
+    protected String getDebuggeeClassName() {
+        return DisposeDuringInvokeDebuggee.class.getName();
+    }
+
+    /**
+     * This testcase exercises VirtualMachine.Dispose command when a thread, suspended by an
+     * event, is still invoking a method.
+     * <BR>At first the test starts DisposeDuringInvokeDebuggee debuggee.
+     * <BR>Then the test sets a breakpoint so that the tested thread (DebuggeeThread) gets suspended
+     * by an event. Once this thread is suspended, we send it an ObjectReference.InvokeMethod
+     * command to initiate a method invocation executing in that thread. The method will synchronize
+     * with the test, waiting for a signal to continue its execution.
+     * <BR>While the tested thread waits for the signal, we send a VirtualMachine.Dispose command to
+     * the debuggee and sends the expected signal so the tested thread completes the method
+     * invocation.
+     * <BR>Finally, we wait for the debuggee's main thread to signal us when the tested thread has
+     * normally terminated after we dispose the JDWP connection.
+     */
+    public void testDisposeDuringInvoke() {
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // Set breakpoint so the DebuggeeThread suspends itself only.
+        long classID = getClassIDBySignature(getDebuggeeClassSignature());
+        long invokedMethodId = getMethodID(classID,
+                DisposeDuringInvokeDebuggee.INVOKED_METHOD_NAME);
+        int breakpointID = debuggeeWrapper.vmMirror.setBreakpointAtMethodBegin(classID,
+                DisposeDuringInvokeDebuggee.BREAKPOINT_METHOD_NAME,
+                JDWPConstants.SuspendPolicy.EVENT_THREAD);
+        long thisObjectId = getReceiverObjectId(classID);
+
+        // Continue debuggee.
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        // Wait for the DebuggeeThread to suspend on the breakpoint.
+        long threadID = debuggeeWrapper.vmMirror.waitForBreakpoint(breakpointID);
+
+        // Send ObjectReference.InvokeMethod command.
+        CommandPacket command = new CommandPacket(
+                JDWPCommands.ObjectReferenceCommandSet.CommandSetID,
+                JDWPCommands.ObjectReferenceCommandSet.InvokeMethodCommand);
+        command.setNextValueAsThreadID(thisObjectId);
+        command.setNextValueAsThreadID(threadID);
+        command.setNextValueAsClassID(classID);
+        command.setNextValueAsMethodID(invokedMethodId);
+        command.setNextValueAsInt(0);
+        command.setNextValueAsInt(0);
+        try {
+            debuggeeWrapper.vmMirror.sendCommand(command);
+        } catch (IOException e) {
+            throw new TestErrorException("Failed to send ObjectReference.InvokeMethod command", e);
+        }
+
+        // Wait for the DebuggeeThread to start method invocation.
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // Detach from debuggee with a VirtualMachine.Dispose command.
+        debuggeeWrapper.vmMirror.dispose();
+
+        // Signal DebuggeeThread to continue so it completes method invocation.
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        // Wait for the DebuggeeThread to terminate. The debuggee's main thread waits for it
+        // (using Thread.join) before signaling us.
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // The test is a success: resume the debuggee to finish
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+    }
+
+    /**
+     * Returns the object ID of the debuggee object to issue the ObjectReference.InvokeMethod
+     * command.
+     *
+     * @param classID
+     *          the debuggee class ID.
+     * @return the object ID of the debuggee
+     */
+    private long getReceiverObjectId(long classID) {
+        long thisObjectFieldID = checkField(classID, DisposeDuringInvokeDebuggee.THIS_FIELD_NAME);
+        Value thisObjectValue =
+                debuggeeWrapper.vmMirror.getReferenceTypeValue(classID, thisObjectFieldID);
+        assertEquals("Invalid value tag:", JDWPConstants.Tag.OBJECT_TAG, thisObjectValue.getTag());
+        return thisObjectValue.getLongValue();
+    }
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/share/JDWPInvokeMethodWithSuspensionTestCase.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/share/JDWPInvokeMethodWithSuspensionTestCase.java
new file mode 100644
index 0000000..cbb0107
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/share/JDWPInvokeMethodWithSuspensionTestCase.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.share;
+
+import org.apache.harmony.jpda.tests.framework.TestErrorException;
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.jdwp.share.debuggee.InvokeMethodWithSuspensionDebuggee;
+import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
+
+import java.io.IOException;
+
+/**
+ * Base class for tests checking invoke command with thread suspension.
+ */
+public abstract class JDWPInvokeMethodWithSuspensionTestCase extends JDWPSyncTestCase {
+
+    @Override
+    protected final String getDebuggeeClassName() {
+        return InvokeMethodWithSuspensionDebuggee.class.getName();
+    }
+
+    /**
+     * This methods runs the {@link InvokeMethodWithSuspensionDebuggee} then sets up the
+     * following breakpoints:
+     * - breakpoint #1 to suspend the current thread only when the debuggee starts
+     * - breakpoint #2 to suspend all threads from a new thread started by a method called through
+     * JDWP.
+     * When we receive the event for breakpoint #1, we issue a request to invoke a method (or a
+     * constructor) in the event thread (suspended on the breakpoint). The event thread starts
+     * another child thread and loops as long as the child is running. However, we do not read the
+     * reply of the invoke yet.
+     * Next, we wait for the new thread to hit breakpoint #2. We resume all threads in the debuggee
+     * and read the reply of the invoke.
+     * Finally, we resume the thread that completed the invoke.
+     *
+     * @param invokedMethodName
+     *          the name of the method to invoke
+     */
+    protected void runInvokeMethodTest(String invokedMethodName) {
+        // Wait for debuggee to start.
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        long classID = getClassIDBySignature(getDebuggeeClassSignature());
+        long invokeMethodID = getMethodID(classID, invokedMethodName);
+
+        // Set breakpoint with EVENT_THREAD suspend policy so only the event thread is suspended.
+        // We will invoke the method in this thread.
+        int breakpointEventThread = debuggeeWrapper.vmMirror.setBreakpointAtMethodBegin(classID,
+                InvokeMethodWithSuspensionDebuggee.BREAKPOINT_EVENT_THREAD_METHOD_NAME,
+                JDWPConstants.SuspendPolicy.EVENT_THREAD);
+
+        // Set breakpoint with ALL suspend policy to suspend all threads. The thread started
+        // during the invoke will suspend all threads, including the thread executing the invoke.
+        int breakpointAllThreads = debuggeeWrapper.vmMirror.setBreakpointAtMethodBegin(classID,
+                InvokeMethodWithSuspensionDebuggee.BREAKPOINT_ALL_THREADS_METHOD_NAME,
+                JDWPConstants.SuspendPolicy.ALL);
+
+        // Tell the debuggee to continue.
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        // Wait for breakpoint and get id of suspended thread.
+        long eventThreadOne = debuggeeWrapper.vmMirror.waitForBreakpoint(breakpointEventThread);
+        int suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadOne);
+        assertEquals("Invalid suspend count:", 1, suspendCount);
+
+        // Send command but does not read reply now. That invoked method starts another thread
+        // that is going to hit a breakpoint and suspend all threads, including the thread invoking
+        // the method. The invoke can only complete when that new thread terminates, which requires
+        // we send a VirtualMachine.Resume command.
+        final int invoke_options = 0;  // resume/suspend all threads before/after the invoke.
+        CommandPacket invokeMethodCommand = buildInvokeCommand(eventThreadOne, classID,
+                invokeMethodID, invoke_options);
+
+        String commandName = getInvokeCommandName();
+        logWriter.println("Send " + commandName);
+        int invokeMethodCommandID = -1;
+        try {
+            invokeMethodCommandID = debuggeeWrapper.vmMirror.sendCommand(invokeMethodCommand);
+        } catch (IOException e) {
+            logWriter.printError("Failed to send " + commandName, e);
+            fail();
+        }
+
+        // Wait for 2nd breakpoint to hit.
+        long eventThreadTwo = debuggeeWrapper.vmMirror.waitForBreakpoint(breakpointAllThreads);
+        suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadTwo);
+        assertEquals("Invalid suspend count:", 1, suspendCount);
+
+        // At this point, the event thread #1 must have been suspended by event thread #2. Since
+        // the invoke has resumed it too, its suspend count must remain 1.
+        suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadOne);
+        assertEquals("Invalid suspend count:", 1, suspendCount);
+
+        // Test that sending another invoke command in the same thread returns an error.
+        CommandPacket anotherInvokeMethodCommand = buildInvokeCommand(eventThreadOne, classID,
+                invokeMethodID, invoke_options);
+        ReplyPacket anotherInvokeMethodReply =
+                debuggeeWrapper.vmMirror.performCommand(anotherInvokeMethodCommand);
+        // The RI returns INVALID_THREAD error while ART returns ALREADY_INVOKING error which is
+        // more accurate. We only test we get an error so we can run the test with both runtimes.
+        assertTrue("Expected an error",
+                anotherInvokeMethodReply.getErrorCode() != JDWPConstants.Error.NONE);
+
+        // Send a VirtualMachine.Resume to resume all threads. This will unblock the event thread
+        // with the invoke in-progress.
+        logWriter.println("Resume all threads");
+        resumeDebuggee();
+
+        // Now we can read the invoke reply.
+        ReplyPacket invokeMethodReply = null;
+        try {
+            logWriter.println("Receiving reply for command " + invokeMethodCommandID + " ...");
+            invokeMethodReply = debuggeeWrapper.vmMirror.receiveReply(invokeMethodCommandID);
+        } catch (Exception e) {
+            throw new TestErrorException("Did not receive invoke reply", e);
+        }
+        checkReplyPacket(invokeMethodReply, commandName + " command");
+        logWriter.println("Received reply for command " + invokeMethodCommandID + " OK");
+
+        checkInvokeReply(invokeMethodReply);
+
+        // The invoke is complete but the thread is still suspended: let's resume it now.
+        suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadOne);
+        assertEquals("Invalid suspend count:", 1, suspendCount);
+
+        logWriter.println("Resume event thread #1");
+        debuggeeWrapper.vmMirror.resumeThread(eventThreadOne);
+    }
+
+    /**
+     * Builds the packed for the tested JDWP command.
+     *
+     * @param threadId
+     *          the id of the thread that will invoke the method
+     * @param classID
+     *          the class ID of the invoked method
+     * @param methodId
+     *          the ID of the invoke method
+     * @param invokeOptions
+     *          options for the invoke
+     * @return a command
+     */
+    protected abstract CommandPacket buildInvokeCommand(long threadId, long classID,
+                                                        long methodId, int invokeOptions);
+
+    /**
+     * Returns the name of the command returned by {@link #buildInvokeCommand} for printing.
+     *
+     * @return the name of the invoke command sent to the debuggee
+     */
+    protected abstract String getInvokeCommandName();
+
+    /**
+     * Checks the reply for the tested JDWP command.
+     *
+     * @param reply the reply of the invoke
+     */
+    protected abstract void checkInvokeReply(ReplyPacket reply);
+
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/share/debuggee/InvokeMethodWithSuspensionDebuggee.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/share/debuggee/InvokeMethodWithSuspensionDebuggee.java
new file mode 100644
index 0000000..ce7327c
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/share/debuggee/InvokeMethodWithSuspensionDebuggee.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.harmony.jpda.tests.jdwp.share.debuggee;
+
+import org.apache.harmony.jpda.tests.jdwp.share.JDWPInvokeMethodWithSuspensionTestCase;
+import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
+import org.apache.harmony.jpda.tests.share.SyncDebuggee;
+
+/**
+ * Debuggee for subclasses of {@link JDWPInvokeMethodWithSuspensionTestCase}.
+ */
+public class InvokeMethodWithSuspensionDebuggee extends SyncDebuggee {
+    // Information for the test.
+    public static final String STATIC_METHOD_NAME = "invokedStaticMethod";
+    public static final String INSTANCE_METHOD_NAME = "invokedInstanceMethod";
+    public static final String BREAKPOINT_EVENT_THREAD_METHOD_NAME = "breakpointEventThread";
+    public static final String BREAKPOINT_ALL_THREADS_METHOD_NAME = "breakpointAllThreads";
+
+    private static volatile boolean testThreadFinished = false;
+    private static Thread testThread = null;
+    private static boolean enableThreadSuspensionForTesting = false;
+
+    private class TestThread extends Thread {
+        public TestThread() {
+            super("TestThread");
+        }
+
+        @Override
+        public void run() {
+            logWriter.println("TestThread starts");
+
+            // We're going to suspend all threads in the method below.
+            logWriter.println("Breakpoint for event thread #2");
+            breakpointAllThreads();
+
+            // The test needs to resume us so the invoke in progress in event thread #1 can
+            // complete.
+            testThreadFinished = true;
+
+            logWriter.println("TestThread ends");
+        }
+    }
+
+    // Invoked to suspend main thread (event thread #1) on a breakpoint.
+    public void breakpointEventThread() {
+    }
+
+    // Invoked to suspend test thread (event thread #2) and all others threads on a breakpoint.
+    public void breakpointAllThreads() {
+    }
+
+    // Invoked in event thread #1. This will unblock event thread #2 that will suspend all threads
+    // including event thread #1. This helps us check that the debugger is not blocked waiting for
+    // this invoke.
+    private static void causeEventThreadSuspension() {
+        if (enableThreadSuspensionForTesting) {
+            // Start event thread #2. It's going to hit a breakpoint and suspend us.
+            testThread.start();
+
+            // We don't use wait/notify pattern to be sure our loop is active.
+            while (!testThreadFinished) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    // Static method to test ClassType.InvokeMethod.
+    public static void invokedStaticMethod() {
+        causeEventThreadSuspension();
+    }
+
+    // Constructor to test ClassType.NewInstance.
+    public InvokeMethodWithSuspensionDebuggee() {
+        causeEventThreadSuspension();
+    }
+
+    // Instance method to test ObjectReference.InvokeMethod.
+    public void invokedInstanceMethod() {
+        causeEventThreadSuspension();
+    }
+
+    @Override
+    public void run() {
+        logWriter.println("InvokeMethodWithThreadSuspensionDebuggee starts");
+
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // Create test thread but do not start it now. It will be started by the invoke from
+        // the test through JDWP.
+        testThread = new TestThread();
+
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        enableThreadSuspensionForTesting = true;
+
+        // We want to suspend the main thread on a breakpoint.
+        logWriter.println("Breakpoint for event thread #1");
+        breakpointEventThread();
+
+        // Ensure tested thread is finished.
+        try {
+            testThread.join();
+        } catch (InterruptedException e) {
+            logWriter.printError("Failed to join tested thread", e);
+        }
+        testThread = null;
+
+        logWriter.println("InvokeMethodWithThreadSuspensionDebuggee ends");
+    }
+
+    public static void main(String[] args) {
+        runDebuggee(InvokeMethodWithSuspensionDebuggee.class);
+    }
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java
index 3936605..e38b3d0 100644
--- a/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java
@@ -62,9 +62,11 @@
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassObjectReference.ReflectedTypeTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.InvokeMethod002Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.InvokeMethod003Test.class);
+    suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.InvokeMethodWithSuspensionTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.InvokeMethodTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.NewInstance002Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.NewInstanceTest.class);
+    suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.NewInstanceWithSuspensionTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.SetValues002Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.SetValuesTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ClassType.SuperClassTest.class);
@@ -125,6 +127,7 @@
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.InvokeMethod002Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.InvokeMethod003Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.InvokeMethodTest.class);
+    suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.InvokeMethodWithSuspensionTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.IsCollectedTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.MonitorInfoTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.ObjectReference.ReferenceTypeTest.class);
@@ -198,6 +201,7 @@
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.ClassesBySignatureTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.ClassPathsTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.CreateStringTest.class);
+    suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.DisposeDuringInvokeTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.DisposeTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.DisposeObjectsTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.VirtualMachine.ExitTest.class);