/******************************************************************************* * 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<>(); fReplyPackets = new LinkedList<>(); fTimedOutPackets = new ArrayList<>(); } @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); } }