blob: c68dffcddcdbaf1e7b67452d3f2802f0491ff83a [file] [log] [blame]
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed 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 com.intellij.execution.rmi;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.Executor;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.configurations.RunProfileState;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.process.*;
import com.intellij.execution.runners.DefaultProgramRunner;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.rmi.PortableRemoteObject;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
/**
* @author Gregory.Shrago
*/
public abstract class RemoteProcessSupport<Target, EntryPoint, Parameters> {
public static final Logger LOG = Logger.getInstance("#" + RemoteProcessSupport.class);
private final Class<EntryPoint> myValueClass;
private final HashMap<Pair<Target, Parameters>, Info> myProcMap = new HashMap<Pair<Target, Parameters>, Info>();
static {
RemoteServer.setupRMI();
}
public RemoteProcessSupport(Class<EntryPoint> valueClass) {
myValueClass = valueClass;
}
protected abstract void fireModificationCountChanged();
protected abstract String getName(Target target);
protected void logText(Parameters configuration, ProcessEvent event, Key outputType, Object info) {
}
public void stopAll() {
stopAll(false);
}
public void stopAll(boolean wait) {
ArrayList<ProcessHandler> allHandlers = new ArrayList<ProcessHandler>();
synchronized (myProcMap) {
for (Info o : myProcMap.values()) {
ContainerUtil.addIfNotNull(o.handler, allHandlers);
}
}
for (ProcessHandler handler : allHandlers) {
handler.destroyProcess();
}
if (wait) {
for (ProcessHandler handler : allHandlers) {
handler.waitFor();
}
}
}
public List<Parameters> getActiveConfigurations(@NotNull Target target) {
ArrayList<Parameters> result = new ArrayList<Parameters>();
synchronized (myProcMap) {
for (Pair<Target, Parameters> pair : myProcMap.keySet()) {
if (pair.first == target) {
result.add(pair.second);
}
}
}
return result;
}
public Set<Pair<Target, Parameters>> getActiveConfigurations() {
synchronized (myProcMap) {
return new HashSet<Pair<Target, Parameters>>(myProcMap.keySet());
}
}
public EntryPoint acquire(@NotNull Target target, @NotNull Parameters configuration) throws Exception {
ApplicationManagerEx.getApplicationEx().assertTimeConsuming();
Ref<RunningInfo> ref = Ref.create(null);
Pair<Target, Parameters> key = Pair.create(target, configuration);
if (!getExistingInfo(ref, key)) {
startProcess(target, configuration, key);
if (ref.isNull()) {
try {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (ref) {
while (ref.isNull()) {
ref.wait(1000);
ProgressManager.checkCanceled();
}
}
}
catch (InterruptedException e) {
ProgressManager.checkCanceled();
}
}
}
if (ref.isNull()) throw new RuntimeException("Unable to acquire remote proxy for: " + getName(target));
RunningInfo info = ref.get();
if (info.handler == null) {
String message = info.name;
if (message != null && message.startsWith("ERROR: transport error 202:")) {
message = "Unable to start java process in debug mode: -Xdebug parameters are already in use.";
}
throw new ExecutionException(message);
}
return acquire(info);
}
public void release(@NotNull Target target, @Nullable Parameters configuration) {
ArrayList<ProcessHandler> handlers = new ArrayList<ProcessHandler>();
synchronized (myProcMap) {
for (Pair<Target, Parameters> pair : myProcMap.keySet()) {
if (pair.first == target && (configuration == null || pair.second == configuration)) {
ContainerUtil.addIfNotNull(myProcMap.get(pair).handler, handlers);
}
}
}
if (handlers.isEmpty()) return;
for (ProcessHandler handler : handlers) {
handler.destroyProcess();
}
fireModificationCountChanged();
}
private void startProcess(Target target, Parameters configuration, Pair<Target, Parameters> key) {
ProgramRunner runner = new DefaultProgramRunner() {
@Override
@NotNull
public String getRunnerId() {
return "MyRunner";
}
@Override
public boolean canRun(@NotNull String executorId, @NotNull RunProfile profile) {
return true;
}
};
Executor executor = DefaultRunExecutor.getRunExecutorInstance();
ProcessHandler processHandler = null;
try {
RunProfileState state = getRunProfileState(target, configuration, executor);
ExecutionResult result = state.execute(executor, runner);
//noinspection ConstantConditions
processHandler = result.getProcessHandler();
}
catch (Exception e) {
dropProcessInfo(key, e instanceof ExecutionException? e.getMessage() : ExceptionUtil.getUserStackTrace(e, LOG), processHandler);
return;
}
processHandler.addProcessListener(getProcessListener(key));
processHandler.startNotify();
}
protected abstract RunProfileState getRunProfileState(Target target, Parameters configuration, Executor executor)
throws ExecutionException;
private boolean getExistingInfo(Ref<RunningInfo> ref, Pair<Target, Parameters> key) {
Info info;
synchronized (myProcMap) {
info = myProcMap.get(key);
try {
while (info != null && (!(info instanceof RunningInfo) ||
info.handler.isProcessTerminating() ||
info.handler.isProcessTerminated())) {
myProcMap.wait(1000);
ProgressManager.checkCanceled();
info = myProcMap.get(key);
}
}
catch (InterruptedException e) {
ProgressManager.checkCanceled();
}
if (info == null) {
myProcMap.put(key, new PendingInfo(ref, null));
}
}
if (info instanceof RunningInfo) {
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (ref) {
ref.set((RunningInfo)info);
ref.notifyAll();
}
}
return info != null;
}
private EntryPoint acquire(final RunningInfo port) throws Exception {
EntryPoint result = RemoteUtil.executeWithClassLoader(new ThrowableComputable<EntryPoint, Exception>() {
@Override
public EntryPoint compute() throws Exception {
Registry registry = LocateRegistry.getRegistry("localhost", port.port);
Remote remote = registry.lookup(port.name);
if (Remote.class.isAssignableFrom(myValueClass)) {
return RemoteUtil.substituteClassLoader(narrowImpl(remote, myValueClass), myValueClass.getClassLoader());
}
else {
return RemoteUtil.castToLocal(remote, myValueClass);
}
}
}, getClass().getClassLoader()); // should be the loader of client plugin
// init hard ref that will keep it from DGC and thus preventing from System.exit
port.entryPointHardRef = result;
return result;
}
private static <T> T narrowImpl(Remote remote, Class<T> to) {
//noinspection unchecked
return (T)(to.isInstance(remote) ? remote : PortableRemoteObject.narrow(remote, to));
}
private ProcessListener getProcessListener(final Pair<Target, Parameters> key) {
return new ProcessListener() {
@Override
public void startNotified(ProcessEvent event) {
ProcessHandler processHandler = event.getProcessHandler();
processHandler.putUserData(ProcessHandler.SILENTLY_DESTROY_ON_CLOSE, Boolean.TRUE);
Info o;
synchronized (myProcMap) {
o = myProcMap.get(key);
if (o instanceof PendingInfo) {
myProcMap.put(key, new PendingInfo(((PendingInfo)o).ref, processHandler));
}
}
}
@Override
public void processTerminated(ProcessEvent event) {
if (dropProcessInfo(key, null, event.getProcessHandler())) {
fireModificationCountChanged();
}
}
@Override
public void processWillTerminate(ProcessEvent event, boolean willBeDestroyed) {
if (dropProcessInfo(key, null, event.getProcessHandler())) {
fireModificationCountChanged();
}
}
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
String text = StringUtil.notNullize(event.getText());
if (outputType == ProcessOutputTypes.STDERR) {
LOG.warn(text.trim());
}
else {
LOG.info(text.trim());
}
RunningInfo result = null;
PendingInfo info;
synchronized (myProcMap) {
Info o = myProcMap.get(key);
logText(key.second, event, outputType, o);
if (o instanceof PendingInfo) {
info = (PendingInfo)o;
if (outputType == ProcessOutputTypes.STDOUT) {
String prefix = "Port/ID:";
if (text.startsWith(prefix)) {
String pair = text.substring(prefix.length()).trim();
int idx = pair.indexOf("/");
result = new RunningInfo(info.handler, Integer.parseInt(pair.substring(0, idx)), pair.substring(idx + 1));
myProcMap.put(key, result);
myProcMap.notifyAll();
}
}
else if (outputType == ProcessOutputTypes.STDERR) {
info.stderr.append(text);
}
}
else {
info = null;
}
}
if (result != null) {
synchronized (info.ref) {
info.ref.set(result);
info.ref.notifyAll();
}
fireModificationCountChanged();
try {
RemoteDeadHand.TwoMinutesTurkish.startCooking("localhost", result.port);
}
catch (Exception e) {
LOG.warn("The cook failed to start due to " + ExceptionUtil.getRootCause(e));
}
}
}
};
}
private boolean dropProcessInfo(Pair<Target, Parameters> key, @Nullable String errorMessage, @Nullable ProcessHandler handler) {
Info info;
synchronized (myProcMap) {
info = myProcMap.get(key);
if (info != null && (handler == null || info.handler == handler)) {
myProcMap.remove(key);
myProcMap.notifyAll();
}
else {
// different processHandler
info = null;
}
}
if (info instanceof PendingInfo) {
PendingInfo pendingInfo = (PendingInfo)info;
if (pendingInfo.stderr.length() > 0 || pendingInfo.ref.isNull()) {
if (errorMessage != null) pendingInfo.stderr.append(errorMessage);
pendingInfo.ref.set(new RunningInfo(null, -1, pendingInfo.stderr.toString()));
}
synchronized (pendingInfo.ref) {
pendingInfo.ref.notifyAll();
}
}
return info != null;
}
private static class Info {
final ProcessHandler handler;
Info(ProcessHandler handler) {
this.handler = handler;
}
}
private static class PendingInfo extends Info {
final Ref<RunningInfo> ref;
final StringBuilder stderr = new StringBuilder();
PendingInfo(Ref<RunningInfo> ref, ProcessHandler handler) {
super(handler);
this.ref = ref;
}
}
private static class RunningInfo extends Info {
final int port;
final String name;
Object entryPointHardRef;
RunningInfo(ProcessHandler handler, int port, String name) {
super(handler);
this.port = port;
this.name = name;
}
}
}