blob: 54423ac407138c29bf3764f5e5da03056147c758 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdi.internal.connect;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.ListIterator;
import org.eclipse.jdi.TimeoutException;
import org.eclipse.jdi.internal.VirtualMachineImpl;
import org.eclipse.jdi.internal.jdwp.JdwpCommandPacket;
import org.eclipse.jdi.internal.jdwp.JdwpPacket;
import org.eclipse.jdi.internal.jdwp.JdwpReplyPacket;
import org.eclipse.jdt.internal.debug.core.JDIDebugOptions;
import org.eclipse.osgi.util.NLS;
import com.sun.jdi.VMDisconnectedException;
import com.sun.jdi.connect.spi.Connection;
/**
* This class implements a thread that receives packets from the Virtual
* Machine.
*
*/
public class PacketReceiveManager extends PacketManager {
/** Generic timeout value for not blocking. */
public static final int TIMEOUT_NOT_BLOCKING = 0;
/** Generic timeout value for infinite timeout. */
public static final int TIMEOUT_INFINITE = -1;
/** List of Command packets received from Virtual Machine. */
private LinkedList<JdwpCommandPacket> fCommandPackets;
/** List of Reply packets received from Virtual Machine. */
private LinkedList<JdwpReplyPacket> fReplyPackets;
/**
* List of Packets that have timed out already. Maintained so that responses
* can be discarded if/when they are received.
*/
private ArrayList<Integer> fTimedOutPackets;
private VirtualMachineImpl fVM;
/**
* Create a new thread that receives packets from the Virtual Machine.
*/
public PacketReceiveManager(Connection connection, VirtualMachineImpl vmImpl) {
super(connection);
fVM = vmImpl;
fCommandPackets = new LinkedList<JdwpCommandPacket>();
fReplyPackets = new LinkedList<JdwpReplyPacket>();
fTimedOutPackets = new ArrayList<Integer>();
}
@Override
public void disconnectVM() {
super.disconnectVM();
synchronized (fCommandPackets) {
fCommandPackets.notifyAll();
}
synchronized (fReplyPackets) {
fReplyPackets.notifyAll();
}
}
/**
* Thread's run method.
*/
@Override
public void run() {
try {
while (!VMIsDisconnected()) {
// Read a packet from the input stream.
readAvailablePacket();
}
}
// if the remote VM is interrupted, drop the connection and clean up,
// don't wait for it to happen on its own
catch (InterruptedIOException e) {
disconnectVM(e);
} catch (IOException e) {
disconnectVM(e);
}
}
/**
* @return Returns a specified Command Packet from the Virtual Machine.
*/
public JdwpCommandPacket getCommand(int command, long timeToWait)
throws InterruptedException {
JdwpCommandPacket packet = null;
synchronized (fCommandPackets) {
long remainingTime = timeToWait;
long timeBeforeWait;
long waitedTime;
// Wait until command is available.
while (!VMIsDisconnected()
&& (packet = removeCommandPacket(command)) == null
&& (timeToWait < 0 || remainingTime > 0)) {
timeBeforeWait = System.currentTimeMillis();
waitForPacketAvailable(remainingTime, fCommandPackets);
waitedTime = System.currentTimeMillis() - timeBeforeWait;
remainingTime -= waitedTime;
}
}
// Check for an IO Exception.
if (VMIsDisconnected()) {
String message;
if (getDisconnectException() == null) {
message = ConnectMessages.PacketReceiveManager_Got_IOException_from_Virtual_Machine_1;
} else {
String exMessage = getDisconnectException().getMessage();
if (exMessage == null) {
message = NLS.bind(ConnectMessages.PacketReceiveManager_Got__0__from_Virtual_Machine_1,
new String[] { getDisconnectException()
.getClass().getName() });
} else {
message = NLS.bind(ConnectMessages.PacketReceiveManager_Got__0__from_Virtual_Machine___1__1,
new String[] {
getDisconnectException().getClass()
.getName(), exMessage });
}
}
throw new VMDisconnectedException(message);
}
// Check for a timeout.
if (packet == null) {
throw new TimeoutException();
}
return packet;
}
/**
* @return Returns a specified Reply Packet from the Virtual Machine.
*/
public JdwpReplyPacket getReply(int id, long timeToWait) {
JdwpReplyPacket packet = null;
long remainingTime = timeToWait;
synchronized (fReplyPackets) {
final long timeBeforeWait = System.currentTimeMillis();
// Wait until reply is available.
while (!VMIsDisconnected() && remainingTime > 0) {
packet = removeReplyPacket(id);
if (packet != null) {
break;
}
try {
waitForPacketAvailable(remainingTime, fReplyPackets);
}
// if the remote VM is interrupted DO NOT drop the connection -
// see bug 171075
// just stop waiting for the reply and treat it as a timeout
catch (InterruptedException e) {
if (JDIDebugOptions.DEBUG) {
JDIDebugOptions.trace(null, "Interrupt observed while waiting for packet: " + id, e); //$NON-NLS-1$
}
// Do not stop waiting on interrupt, this causes
// sporadic TimeoutException's without timeout
// break;
}
long waitedTime = System.currentTimeMillis() - timeBeforeWait;
remainingTime = timeToWait - waitedTime;
}
}
if (packet == null) {
synchronized (fReplyPackets) {
packet = removeReplyPacket(id);
}
}
// Check for an IO Exception.
if (VMIsDisconnected())
throw new VMDisconnectedException(
ConnectMessages.PacketReceiveManager_Got_IOException_from_Virtual_Machine_2);
// Check for a timeout.
if (packet == null) {
synchronized (fTimedOutPackets) {
fTimedOutPackets.add(new Integer(id));
}
throw new TimeoutException(NLS.bind(
ConnectMessages.PacketReceiveManager_0, new String[] { id
+ "" })); //$NON-NLS-1$
}
return packet;
}
/**
* @return Returns a specified Reply Packet from the Virtual Machine.
*/
public JdwpReplyPacket getReply(JdwpCommandPacket commandPacket) {
return getReply(commandPacket.getId(), fVM.getRequestTimeout());
}
/**
* Wait for an available packet from the Virtual Machine.
*/
private void waitForPacketAvailable(long timeToWait, Object lock)
throws InterruptedException {
if (timeToWait == 0)
return;
else if (timeToWait < 0)
lock.wait();
else
lock.wait(timeToWait);
}
/**
* @return Returns and removes a specified command packet from the command
* packet list.
*/
private JdwpCommandPacket removeCommandPacket(int command) {
ListIterator<JdwpCommandPacket> iter = fCommandPackets.listIterator();
while (iter.hasNext()) {
JdwpCommandPacket packet = iter.next();
if (packet.getCommand() == command) {
iter.remove();
return packet;
}
}
return null;
}
/**
* @return Returns a specified reply packet from the reply packet list.
*/
private JdwpReplyPacket removeReplyPacket(int id) {
ListIterator<JdwpReplyPacket> iter = fReplyPackets.listIterator();
while (iter.hasNext()) {
JdwpReplyPacket packet = iter.next();
if (packet.getId() == id) {
iter.remove();
return packet;
}
}
return null;
}
/**
* Add a command packet to the command packet list.
*/
private void addCommandPacket(JdwpCommandPacket packet) {
if (isTimedOut(packet)) {
return; // already timed out. No need to keep this one
}
synchronized (fCommandPackets) {
fCommandPackets.add(packet);
fCommandPackets.notifyAll();
}
}
/**
* Returns whether the request for the given packet has already timed out.
*
* @param packet
* response packet
* @return whether the request for the given packet has already timed out
*/
private boolean isTimedOut(JdwpPacket packet) {
synchronized (fTimedOutPackets) {
if (fTimedOutPackets.isEmpty()) {
return false;
}
Integer id = new Integer(packet.getId());
return fTimedOutPackets.remove(id);
}
}
/**
* Add a reply packet to the reply packet list.
*/
private void addReplyPacket(JdwpReplyPacket packet) {
if (isTimedOut(packet)) {
return; // already timed out. No need to keep this one
}
synchronized (fReplyPackets) {
fReplyPackets.add(packet);
fReplyPackets.notifyAll();
}
}
/**
* Read a packet from the input stream and add it to the appropriate packet
* list.
*/
private void readAvailablePacket() throws IOException {
// Read a packet from the Input Stream.
byte[] bytes = getConnection().readPacket();
JdwpPacket packet = JdwpPacket.build(bytes);
// Add packet to command or reply queue.
if (packet instanceof JdwpCommandPacket)
addCommandPacket((JdwpCommandPacket) packet);
else
addReplyPacket((JdwpReplyPacket) packet);
}
}