Copyright (c) 2007, 2016 IBM Corporation and others. This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which accompanies this distribution, and is available at https://www.eclipse.org/legal/epl-2.0/ SPDX-License-Identifier: EPL-2.0 Contributors: IBM Corporation - initial API and implementation Google Inc - add support for accepting multiple connections
/******************************************************************************* * Copyright (c) 2007, 2016 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 * * Contributors: * IBM Corporation - initial API and implementation * Google Inc - add support for accepting multiple connections *******************************************************************************/
package org.eclipse.jdt.internal.launching; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Map; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.debug.core.DebugEvent; import org.eclipse.debug.core.DebugException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchConfiguration; import org.eclipse.debug.core.model.IDebugTarget; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStreamsProxy; import org.eclipse.jdi.TimeoutException; import org.eclipse.jdt.debug.core.JDIDebugModel; import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; import org.eclipse.osgi.util.NLS; import com.sun.jdi.VMDisconnectedException; import com.sun.jdi.VirtualMachine; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.IllegalConnectorArgumentsException; import com.sun.jdi.connect.ListeningConnector; import com.sun.jdi.connect.TransportTimeoutException;
A process that represents a VM listening connector that is waiting for some VM(s) to remotely connect. Allows the user to see the status of the connection and terminate it. If a successful connection occurs, the debug target is added to the launch and, if a configured number of connections have been reached, then this process is removed.
See Also:
Since:3.4
/** * A process that represents a VM listening connector that is waiting for some VM(s) to remotely connect. Allows the user to see the status of the * connection and terminate it. If a successful connection occurs, the debug target is added to the launch and, if a configured number of connections * have been reached, then this process is removed. * * @since 3.4 * @see SocketListenConnector */
public class SocketListenConnectorProcess implements IProcess {
Whether this process has been terminated.
/** * Whether this process has been terminated. */
private boolean fTerminated = false;
The launch this process belongs to
/** * The launch this process belongs to */
private ILaunch fLaunch;
The port this connector will listen on.
/** * The port this connector will listen on. */
private String fPort;
The number of incoming connections to accept (0 = unlimited). Setting to 1 mimics previous behaviour.
/** * The number of incoming connections to accept (0 = unlimited). Setting to 1 mimics previous behaviour. */
private int fConnectionLimit;
The number of connections accepted so far.
/** The number of connections accepted so far. */
private int fAccepted = 0;
The system job that will wait for incoming VM connections.
/** * The system job that will wait for incoming VM connections. */
private WaitForConnectionJob fWaitForConnectionJob;
Time when this instance was created (milliseconds)
/** Time when this instance was created (milliseconds) */
private long fStartTime;
Creates this process. The label for this process will state the port the connector is listening at.
Params:
  • launch – the launch this process belongs to
  • port – the port the connector will wait on
  • connectionLimit – the number of incoming connections to accept (0 = unlimited)
/** * Creates this process. The label for this process will state * the port the connector is listening at. * @param launch the launch this process belongs to * @param port the port the connector will wait on * @param connectionLimit the number of incoming connections to accept (0 = unlimited) */
public SocketListenConnectorProcess(ILaunch launch, String port, int connectionLimit){ fLaunch = launch; fPort = port; fConnectionLimit = connectionLimit; }
Starts a job that will accept a VM remotely connecting to the given connector. The #startListening() method must have been called on the connector with the same arguments before calling this method. The 'port' argument in the map should have the same value as the port specified in this process' constructor.
Params:
  • connector – the connector that will accept incoming connections
  • arguments – map of arguments that are used by the connector
Throws:
  • CoreException – if a problem occurs trying to accept a connection
See Also:
/** * Starts a job that will accept a VM remotely connecting to the * given connector. The #startListening() method must have been * called on the connector with the same arguments before calling * this method. The 'port' argument in the map should have the same * value as the port specified in this process' constructor. * * @param connector the connector that will accept incoming connections * @param arguments map of arguments that are used by the connector * @throws CoreException if a problem occurs trying to accept a connection * @see SocketListenConnector */
public void waitForConnection(ListeningConnector connector, Map<String, Connector.Argument> arguments) throws CoreException{ if (isTerminated()){ throw new CoreException(getStatus(LaunchingMessages.SocketListenConnectorProcess_0, null, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED)); } fStartTime = System.currentTimeMillis(); fAccepted = 0; // If the connector does not support multiple connections, accept a single connection try { if (!connector.supportsMultipleConnections()) { fConnectionLimit = 1; } } catch (IOException | IllegalConnectorArgumentsException ex) { fConnectionLimit = 1; } fLaunch.addProcess(this); fWaitForConnectionJob = new WaitForConnectionJob(connector,arguments); fWaitForConnectionJob.setPriority(Job.SHORT); fWaitForConnectionJob.setSystem(true); fWaitForConnectionJob.addJobChangeListener(new JobChangeAdapter(){ @Override public void running(IJobChangeEvent event) { fireReadyToAcceptEvent(); } @Override public void done(IJobChangeEvent event) { if (event.getResult().isOK() && continueListening()) { fWaitForConnectionJob.schedule(); } else { try{ terminate(); } catch (DebugException e){} } } }); fWaitForConnectionJob.schedule(); }
Return true if this connector should continue listening for further connections.
/** * Return true if this connector should continue listening for further connections. */
protected boolean continueListening() { return !isTerminated() && (fWaitForConnectionJob != null && !fWaitForConnectionJob.fListeningStopped) && (fConnectionLimit <= 0 || fConnectionLimit - fAccepted > 0); }
Returns an error status using the passed parameters.
Params:
  • message – the status message
  • exception – lower level exception associated with the error, or null if none
  • code – error code
Returns:the new IStatus
/** * Returns an error status using the passed parameters. * * @param message the status message * @param exception lower level exception associated with the * error, or <code>null</code> if none * @param code error code * @return the new {@link IStatus} */
protected static IStatus getStatus(String message, Throwable exception, int code) { return new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), code, message, exception); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IProcess#getExitValue() */ @Override public int getExitValue() throws DebugException { return 0; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IProcess#getLabel() */ @Override public String getLabel() { return NLS.bind(LaunchingMessages.SocketListenConnectorProcess_1, new String[]{fPort}); } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IProcess#getLaunch() */ @Override public ILaunch getLaunch() { return fLaunch; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.ITerminate#canTerminate() */ @Override public boolean canTerminate() { return !fTerminated; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.ITerminate#isTerminated() */ @Override public boolean isTerminated() { return fTerminated; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.ITerminate#terminate() */ @Override public void terminate() throws DebugException { if (!fTerminated){ fTerminated = true; fLaunch.removeProcess(this); if (fWaitForConnectionJob != null){ fWaitForConnectionJob.cancel(); fWaitForConnectionJob.stopListening(); fWaitForConnectionJob = null; } fireTerminateEvent(); } }
Fires a terminate event.
/** * Fires a terminate event. */
protected void fireTerminateEvent() { DebugPlugin manager= DebugPlugin.getDefault(); if (manager != null) { manager.fireDebugEventSet(new DebugEvent[]{new DebugEvent(this, DebugEvent.TERMINATE)}); } }
Fires a custom model specific event when this connector is ready to accept incoming connections from a remote VM.
/** * Fires a custom model specific event when this connector is ready to accept incoming * connections from a remote VM. */
protected void fireReadyToAcceptEvent(){ DebugPlugin manager= DebugPlugin.getDefault(); if (manager != null) { manager.fireDebugEventSet(new DebugEvent[]{new DebugEvent(this, DebugEvent.MODEL_SPECIFIC, IJavaLaunchConfigurationConstants.DETAIL_CONFIG_READY_TO_ACCEPT_REMOTE_VM_CONNECTION)}); } } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IProcess#getStreamsProxy() */ @Override public IStreamsProxy getStreamsProxy() { return null; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IProcess#getAttribute(java.lang.String) */ @Override public String getAttribute(String key) { return null; } /* (non-Javadoc) * @see org.eclipse.debug.core.model.IProcess#setAttribute(java.lang.String, java.lang.String) */ @Override public void setAttribute(String key, String value) { } /* (non-Javadoc) * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ @Override public <T> T getAdapter(Class<T> adapter) { return null; }
Return the time since this connector was started.
/** * Return the time since this connector was started. */
private String getRunningTime() { long total = System.currentTimeMillis() - fStartTime; StringWriter result = new StringWriter(); PrintWriter writer = new PrintWriter(result); int minutes = (int) (total / 60 / 1000); int seconds = (int) (total / 1000) % 60; int milliseconds = (int) (total / 1000) % 1000; writer.printf("%02d:%02d.%03d", minutes, seconds, milliseconds).close(); //$NON-NLS-1$ return result.toString(); }
Job that waits for incoming VM connections. When a remote VM connection is accepted, a debug target is created.
/** * Job that waits for incoming VM connections. When a remote VM connection is accepted, a debug target is created. */
class WaitForConnectionJob extends Job{ private ListeningConnector fConnector; private Map<String, Connector.Argument> fArguments;
Flag that can be set to tell this job that waiting for incoming connections has been cancelled. If true, IOExceptions will be ignored, allowing other threads to close the socket without generating an error.
/** * Flag that can be set to tell this job that waiting * for incoming connections has been cancelled. If true, * IOExceptions will be ignored, allowing other threads * to close the socket without generating an error. */
private boolean fListeningStopped = false; public WaitForConnectionJob(ListeningConnector connector, Map<String, Connector.Argument> arguments) { super(getLabel()); fConnector = connector; fArguments = arguments; } @Override protected IStatus run(IProgressMonitor monitor) { try{ // The following code sets a timeout (not officially supported in Sun's spec). // Allows polling for job cancellation. If the implementation does not support timeout // the job cannot be cancelled (but the launch can still be terminated). Connector.Argument timeout = fArguments.get("timeout"); //$NON-NLS-1$ if (timeout != null){ timeout.setValue("3000"); //$NON-NLS-1$ } VirtualMachine vm = null; while (vm == null && !monitor.isCanceled()){ try { vm = fConnector.accept(fArguments); } catch (TransportTimeoutException e){ } } if (monitor.isCanceled()){ fConnector.stopListening(fArguments); return Status.CANCEL_STATUS; } ILaunchConfiguration configuration = fLaunch.getLaunchConfiguration(); boolean allowTerminate = false; if (configuration != null) { try{ allowTerminate = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_ALLOW_TERMINATE, false); } catch (CoreException e) { LaunchingPlugin.log(e); } } Connector.Argument portArg= fArguments.get("port"); //$NON-NLS-1$ String vmLabel = constructVMLabel(vm, portArg.value(), fLaunch.getLaunchConfiguration()); IDebugTarget debugTarget= JDIDebugModel.newDebugTarget(fLaunch, vm, vmLabel, null, allowTerminate, true); fLaunch.addDebugTarget(debugTarget); fAccepted++; return Status.OK_STATUS; } catch (IOException e) { if (fListeningStopped){ return Status.CANCEL_STATUS; } return getStatus(LaunchingMessages.SocketListenConnectorProcess_4, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED); } catch (IllegalConnectorArgumentsException e) { return getStatus(LaunchingMessages.SocketListenConnectorProcess_4, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED); } } /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#canceling() */ @Override protected void canceling() { stopListening(); }
Tells the listening connector to stop listening. Ensures that the socket is closed and the port released. Sets a flag so that the IOException thrown by the connector's accept method will be ignored.
/** * Tells the listening connector to stop listening. Ensures * that the socket is closed and the port released. Sets a flag * so that the IOException thrown by the connector's accept method * will be ignored. */
protected void stopListening() { if (!fListeningStopped){ try{ fListeningStopped = true; fConnector.stopListening(fArguments); } catch (IOException e) { done(getStatus(LaunchingMessages.SocketListenConnectorProcess_5, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED)); } catch (IllegalConnectorArgumentsException e) { done(getStatus(LaunchingMessages.SocketListenConnectorProcess_5, e, IJavaLaunchConfigurationConstants.ERR_REMOTE_VM_CONNECTION_FAILED)); } } }
Helper method that constructs a human-readable label for a remote VM.
Params:
  • vm – the VM
  • port – the port
  • configuration – the configuration
Returns:the new VM label
/** * Helper method that constructs a human-readable label for a remote VM. * @param vm the VM * @param port the port * @param configuration the configuration * @return the new VM label */
protected String constructVMLabel(VirtualMachine vm, String port, ILaunchConfiguration configuration) { String name = null; try { name = vm.name(); } catch (TimeoutException e) { // do nothing } catch (VMDisconnectedException e) { // do nothing } if (name == null) { if (configuration == null) { name = ""; //$NON-NLS-1$ } else { name = configuration.getName(); } } StringBuilder buffer = new StringBuilder(name); if (fConnectionLimit != 1) { // if we're accepting multiple incoming connections, // append the time when each connection was accepted buffer.append('<').append(getRunningTime()).append('>'); } buffer.append('['); buffer.append(port); buffer.append(']'); return buffer.toString(); } } }