package jetbrains.mps.debugger.java.runtime.engine;
/*Generated by MPS */
import jetbrains.mps.debug.api.AbstractDebugSessionCreator;
import org.apache.log4j.Logger;
import org.apache.log4j.LogManager;
import java.util.Map;
import com.sun.jdi.connect.Connector;
import jetbrains.mps.debugger.java.api.settings.DebugConnectionSettings;
import jetbrains.mps.debugger.java.runtime.engine.events.EventsProcessor;
import java.util.List;
import com.intellij.execution.process.ProcessListener;
import java.util.ArrayList;
import com.intellij.execution.ExecutionResult;
import jetbrains.mps.debugger.java.runtime.state.DebugSession;
import com.intellij.openapi.project.Project;
import jetbrains.mps.debug.api.BreakpointManagerComponent;
import jetbrains.mps.debugger.java.runtime.evaluation.EvaluationProvider;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.ExecutionException;
import jetbrains.mps.debug.api.run.DebuggerRunProfileState;
import jetbrains.mps.debug.api.IDebuggerSettings;
import org.jetbrains.annotations.Nullable;
import com.intellij.execution.Executor;
import com.intellij.execution.runners.ProgramRunner;
import jetbrains.mps.ide.ThreadUtils;
import org.jetbrains.annotations.NotNull;
import com.intellij.execution.process.ProcessHandler;
import jetbrains.mps.debugger.java.runtime.configurations.remote.RemoteProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.util.concurrency.Semaphore;
import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes;
import jetbrains.mps.baseLanguage.closures.runtime.Wrappers;
import com.sun.jdi.VirtualMachine;
import java.io.IOException;
import com.sun.jdi.connect.ListeningConnector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.Bootstrap;
import java.util.Iterator;
public class VmCreator extends AbstractDebugSessionCreator {
private static Logger LOG = LogManager.getLogger(VmCreator.class);
private static final int LOCAL_START_TIMEOUT = 15000;
/*package*/ static final String SOCKET_ATTACHING_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
/*package*/ static final String SHMEM_ATTACHING_CONNECTOR_NAME = "com.sun.jdi.SharedMemoryAttach";
/*package*/ static final String SOCKET_LISTENING_CONNECTOR_NAME = "com.sun.jdi.SocketListen";
/*package*/ static final String SHMEM_LISTENING_CONNECTOR_NAME = "com.sun.jdi.SharedMemoryListen";
private Map<String, Connector.Argument> myArguments;
private DebugConnectionSettings myConnectionSettings;
private final EventsProcessor myEventsProcessor;
private boolean myIsFailed = false;
/**
* holds listeners before process is executed; then adds them to process handler.
*/
private final List<ProcessListener> myProcessListeners = new ArrayList<ProcessListener>();
private ExecutionResult myExecutionResult;
private final DebugSession myDebuggerSession;
public VmCreator(Project project) {
myEventsProcessor = new EventsProcessor(project, BreakpointManagerComponent.getInstance(project));
myDebuggerSession = new DebugSession(myEventsProcessor, project);
myDebuggerSession.setEvaluationProvider(new EvaluationProvider(myDebuggerSession));
}
private DebugConnectionSettings createLocalConnectionSettings(RunProfileState state) throws ExecutionException {
if (state instanceof DebuggerRunProfileState) {
IDebuggerSettings debuggerSettings = ((DebuggerRunProfileState) state).getDebuggerSettings();
if (debuggerSettings instanceof DebugConnectionSettings) {
return (DebugConnectionSettings) debuggerSettings;
}
throw new ExecutionException("Unknown Debugger Settings " + debuggerSettings);
} else {
throw new ExecutionException("Unknown Run Profile State");
}
}
@Nullable
@Override
public ExecutionResult startSession(final Executor executor, final ProgramRunner runner, final RunProfileState state, Project project) throws ExecutionException {
ThreadUtils.assertEDT();
myConnectionSettings = createLocalConnectionSettings(state);
myEventsProcessor.getSystemMessagesReporter().setProcessName(getConnectionSettings().getPresentation());
createVirtualMachine();
try {
synchronized (myProcessListeners) {
myExecutionResult = execute(executor, runner, state);
if (myExecutionResult == null) {
createVmFailed("Execution result created by " + runner + " is null.");
return null;
}
for (ProcessListener processListener : myProcessListeners) {
myExecutionResult.getProcessHandler().addProcessListener(processListener);
}
myProcessListeners.clear();
@NotNull ProcessHandler processHandler = myExecutionResult.getProcessHandler();
myDebuggerSession.setProcessHandler(processHandler);
myEventsProcessor.getSystemMessagesReporter().setProcessHandler(processHandler);
fixStopBugUnderLinux(processHandler, myDebuggerSession);
}
} catch (ExecutionException e) {
createVmFailed(e);
throw e;
}
return myExecutionResult;
}
private void createVmFailed(Throwable t) {
createVmFailed(t.getMessage());
LOG.warn("Create VM failed", t);
}
private void createVmFailed(String message) {
myEventsProcessor.getSystemMessagesReporter().reportError(message);
fail();
}
private void fixStopBugUnderLinux(final ProcessHandler processHandler, final DebugSession session) {
if (!((processHandler instanceof RemoteProcessHandler))) {
// add listener only to non-remote process handler:
// on Unix systems destroying process does not cause VMDeathEvent to be generated,
// so we need to call debugProcess.stop() explicitly for graceful termination.
// RemoteProcessHandler on the other hand will call debugProcess.stop() as a part of destroyProcess() and detachProcess() implementation,
// so we shouldn't add the listener to avoid calling stop() twice
processHandler.addProcessListener(new ProcessAdapter() {
@Override
public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
if (event.getProcessHandler() != processHandler) {
return;
}
// if current thread is a "debugger manager thread", stop will execute synchronously
session.getEventsProcessor().stop(willBeDestroyed);
// wait at most 10 seconds: the problem is that debugProcess.stop() can hang if there are troubles in the debuggee
// if processWillTerminate() is called from AWT thread debugProcess.waitFor() will block it and the whole app will hang
// if (!DebuggerManagerThread.isManagerThread()) {
// session.getEventsProcessor().waitFor(10000);
// }
// TODO we do not have waitFor(int) method
}
});
}
}
private void fail() {
synchronized (this) {
if (myIsFailed) {
return;
}
myIsFailed = true;
}
myEventsProcessor.stop(false);
}
private void createVirtualMachine() {
final Semaphore semaphore = new Semaphore();
// semaphore - maybe not to call this method multiple times when a VM is not ready
semaphore.down();
final DebugProcessMulticaster processMulticaster = myEventsProcessor.getMulticaster();
processMulticaster.addListener(new DebugProcessAdapter() {
@Override
public void connectorIsReady() {
VmCreator.LOG.debug("Connector is ready.");
semaphore.up();
processMulticaster.removeListener(this);
}
});
myEventsProcessor.schedule(new _FunctionTypes._void_P0_E0() {
public void invoke() {
final Wrappers._T<VirtualMachine> vm = new Wrappers._T<VirtualMachine>(null);
try {
final long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < LOCAL_START_TIMEOUT) {
try {
vm.value = doCreateVirtualMachine();
LOG.debug("Created VM " + vm.value);
break;
} catch (Throwable t) {
createVmFailed(t);
break;
}
}
} finally {
semaphore.up();
}
if (vm.value != null) {
executeAfterProcessStarted(new Runnable() {
@Override
public void run() {
VmCreator.LOG.debug("Schedule commit command.");
myEventsProcessor.schedule(new _FunctionTypes._void_P0_E0() {
public void invoke() {
myEventsProcessor.commitVm(vm.value);
}
});
}
});
} else {
LOG.debug("VM is null.");
}
}
}, new _FunctionTypes._void_P0_E0() {
public void invoke() {
semaphore.up();
}
});
semaphore.waitFor();
}
private VirtualMachine doCreateVirtualMachine() throws RunFailedException {
try {
if (myArguments != null) {
throw new IOException("debugger already listening");
}
if (myConnectionSettings.isServerMode()) {
LOG.debug("Virtual Machine creation: server mode.");
ListeningConnector connector = (ListeningConnector) findConnector((myConnectionSettings.isUseSockets() ? SOCKET_LISTENING_CONNECTOR_NAME : SHMEM_LISTENING_CONNECTOR_NAME));
fillConnectorArguments(connector);
LOG.debug("Start listening");
connector.startListening(myArguments);
myEventsProcessor.getMulticaster().connectorIsReady();
try {
LOG.debug("Start accepting.");
return connector.accept(myArguments);
} catch (IOException ex) {
throw new RunFailedException(ex);
} finally {
if (myArguments != null) {
try {
connector.stopListening(myArguments);
} catch (IllegalArgumentException e) {
// ignored
} catch (IllegalConnectorArgumentsException e) {
// ignored
}
}
}
} else {
AttachingConnector connector = (AttachingConnector) findConnector((myConnectionSettings.isUseSockets() ? SOCKET_ATTACHING_CONNECTOR_NAME : SHMEM_ATTACHING_CONNECTOR_NAME));
fillConnectorArguments(connector);
try {
return connector.attach(myArguments);
} catch (IOException ex) {
throw new RunFailedException(ex);
}
}
} catch (IOException e) {
throw new RunFailedException(e);
} catch (IllegalConnectorArgumentsException e) {
throw new RunFailedException(e);
} finally {
myArguments = null;
}
}
private void fillConnectorArguments(Connector connector) throws RunFailedException {
if (connector == null) {
throw new RunFailedException("debug connector not found");
}
myArguments = connector.defaultArguments();
if (myArguments == null) {
throw new RunFailedException("no debug listen port");
}
// negative port number means the caller leaves to debugger to decide at which hport to listen
Connector.Argument portArg = (myConnectionSettings.isUseSockets() ? myArguments.get("port") : myArguments.get("name"));
if (portArg != null) {
portArg.setValue(Integer.toString(myConnectionSettings.getPort()));
}
Connector.Argument timeoutArg = myArguments.get("timeout");
if (timeoutArg != null) {
timeoutArg.setValue("0");
// wait forever
}
Connector.Argument hostArgument = myArguments.get("hostname");
if (hostArgument != null) {
hostArgument.setValue(myConnectionSettings.getHostName());
}
}
private Connector findConnector(String connectorName) throws RunFailedException {
VirtualMachineManager virtualMachineManager = null;
try {
virtualMachineManager = Bootstrap.virtualMachineManager();
} catch (Error e) {
throw new RunFailedException("jdi bootstrap error", e);
}
List connectors;
if (SOCKET_ATTACHING_CONNECTOR_NAME.equals(connectorName) || SHMEM_ATTACHING_CONNECTOR_NAME.equals(connectorName)) {
connectors = virtualMachineManager.attachingConnectors();
} else
if (SOCKET_LISTENING_CONNECTOR_NAME.equals(connectorName) || SHMEM_LISTENING_CONNECTOR_NAME.equals(connectorName)) {
connectors = virtualMachineManager.listeningConnectors();
} else {
return null;
}
for (Iterator it = connectors.iterator(); it.hasNext();) {
Connector connector = (Connector) it.next();
if (connectorName.equals(connector.name())) {
return connector;
}
}
return null;
}
public void addProcessListener(ProcessListener processListener) {
synchronized (myProcessListeners) {
if (myExecutionResult != null) {
myExecutionResult.getProcessHandler().addProcessListener(processListener);
} else {
myProcessListeners.add(processListener);
}
}
}
public void removeProcessListener(ProcessListener processListener) {
synchronized (myProcessListeners) {
if (myExecutionResult != null) {
myExecutionResult.getProcessHandler().removeProcessListener(processListener);
} else {
myProcessListeners.remove(processListener);
}
}
}
private void executeAfterProcessStarted(final Runnable run) {
VmCreator.RunsAfterProcessStarted processListener = new VmCreator.RunsAfterProcessStarted(run);
addProcessListener(processListener);
if (myExecutionResult != null) {
if (myExecutionResult.getProcessHandler().isStartNotified()) {
processListener.run();
}
}
}
@Override
public DebugSession getDebugSession() {
return myDebuggerSession;
}
public DebugConnectionSettings getConnectionSettings() {
return myConnectionSettings;
}
private class RunsAfterProcessStarted extends ProcessAdapter {
private Runnable myRunnable;
private boolean alreadyRun = false;
public RunsAfterProcessStarted(Runnable runnable) {
myRunnable = runnable;
}
public synchronized void run() {
if (!(alreadyRun)) {
alreadyRun = true;
myRunnable.run();
}
removeProcessListener(this);
}
@Override
public void startNotified(ProcessEvent event) {
run();
}
}
}