/** * Copyright 2015 Palantir Technologies, Inc. * * 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.palantir.giraffe.internal; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.concurrent.Executor; import java.util.concurrent.RunnableFuture; import java.util.concurrent.atomic.AtomicReference; import com.google.common.util.concurrent.AbstractFuture; import com.palantir.giraffe.command.Command; import com.palantir.giraffe.command.CommandContext; import com.palantir.giraffe.command.CommandException; import com.palantir.giraffe.command.CommandFuture; import com.palantir.giraffe.command.CommandResult; import com.palantir.giraffe.command.TerminatedCommand; /** * An abstract runnable {@link CommandFuture}. Subclasses must implement * {@code startProcess()} to start executing a command. * * @author bkeyes */ public abstract class CommandFutureTask extends AbstractFuture<CommandResult> implements CommandFuture, RunnableFuture<CommandResult> { protected final Command command; protected final CommandContext context; private final Executor executor; private final ProcessStreamHandler handler; private final AtomicReference<HandlableProcess> processRef; protected CommandFutureTask(Command command, CommandContext context, Executor executor) { this.command = command; this.context = context; this.executor = executor; handler = new ProcessStreamHandler(context); processRef = new AtomicReference<>(); } @Override public InputStream getStdOut() { return handler.getOutput(); } @Override public InputStream getStdErr() { return handler.getError(); } @Override public OutputStream getStdIn() { return handler.getInput(); } @Override public final void run() { if (isCancelled()) { return; } try { HandlableProcess process = startProcess(); processRef.set(process); if (isCancelled()) { // future was cancelled while we started the process destroyProcess(); return; } handler.addListener(new ExceptionListener()); handler.startCopy(process, executor); int exitStatus = process.waitFor(); handler.finishCopy(); try { process.closeStreams(); } catch (IOException ignore) { // the process is terminated and the public facing streams are // closed, so a failure to close the process streams isn't bad } CommandResult result = handler.toResult(exitStatus, StandardCharsets.UTF_8); if (context.getExitStatusVerifier().apply(exitStatus)) { set(result); } else { TerminatedCommand failed = new TerminatedCommand(command, context, result); setException(new CommandException(failed)); } } catch (InterruptedException e) { destroyProcess(); Thread.currentThread().interrupt(); setException(new IllegalStateException("process thread unexpectedly interrupted", e)); } catch (Throwable e) { setException(e); destroyProcess(); } } protected abstract HandlableProcess startProcess() throws IOException; @Override protected void interruptTask() { destroyProcess(); } /** * Sets {@code process} to {@code null} and calls {@code destroy()} on the * previous value if it was non-null. At most one thread will succeed in * destroying the process. */ private void destroyProcess() { HandlableProcess toDestroy = processRef.getAndSet(null); if (toDestroy != null) { toDestroy.destroy(); } } private final class ExceptionListener implements ProcessStreamHandler.ExceptionListener { @Override public void onException(Throwable t) { // if process is null, it was destroyed and exceptions are expected if (processRef.get() != null) { setException(new IOException("exception while copying streams ", t)); destroyProcess(); } } } }