- Further simplification by using Futures :)

This commit is contained in:
Sebastian Stenzel 2014-12-16 16:56:42 +01:00
parent dbadf54893
commit b7f3f00ce2
5 changed files with 147 additions and 150 deletions

View File

@ -22,7 +22,6 @@ import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;

View File

@ -1,69 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Markus Kreusch
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Markus Kreusch
******************************************************************************/
package org.cryptomator.ui.util.command;
import static org.apache.commons.io.IOUtils.copy;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
final class AsyncStreamCopier implements Runnable {
private final Lock lock = new ReentrantLock();
private final Condition doneCondition = lock.newCondition();
private final InputStream input;
private final OutputStream output;
private IOException exception;
private boolean done;
public AsyncStreamCopier(InputStream input, OutputStream output) {
this.input = input;
this.output = output;
}
@Override
public void run() {
lock.lock();
try (InputStream inputToBeClosed = input; OutputStream outputToBeClosed = output) {
copy(input, output);
done = true;
doneCondition.signal();
} catch (IOException e) {
exception = e;
} finally {
lock.unlock();
}
}
private void waitUntilDone() {
lock.lock();
try {
while (!done) {
doneCondition.await();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void assertOk() throws IOException {
this.waitUntilDone();
if (exception != null) {
throw exception;
}
}
}

View File

@ -5,18 +5,16 @@
*
* Contributors:
* Markus Kreusch
* Sebastian Stenzel - using Futures, lazy loading for out/err.
******************************************************************************/
package org.cryptomator.ui.util.command;
import static java.lang.String.format;
import static org.apache.commons.io.IOUtils.copy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.io.InputStream;
import org.cryptomator.ui.util.mount.CommandFailedException;
import org.slf4j.Logger;
@ -26,99 +24,48 @@ public class CommandResult {
private static final Logger LOG = LoggerFactory.getLogger(CommandResult.class);
private final ByteArrayOutputStream output = new ByteArrayOutputStream();
private final ByteArrayOutputStream error = new ByteArrayOutputStream();
private final Lock lock = new ReentrantLock();
private final Condition finishedCondition = lock.newCondition();
private final Process process;
private final AsyncStreamCopier processOutputCopier;
private final AsyncStreamCopier processErrorCopier;
private boolean finished;
private CommandFailedException exception;
public CommandResult(Process process, String[] lines, Executor cmdExecutor, long timeout, TimeUnit unit) {
this.process = process;
processOutputCopier = new AsyncStreamCopier(process.getInputStream(), output);
processErrorCopier = new AsyncStreamCopier(process.getErrorStream(), error);
cmdExecutor.execute(processOutputCopier);
cmdExecutor.execute(processErrorCopier);
cmdExecutor.execute(new CommandTask(timeout, unit));
public CommandResult(Process process) {
this.process = process;
}
public String getOutput() throws CommandFailedException {
this.waitUntilFinished();
return new String(output.toByteArray());
try (InputStream in = process.getInputStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
copy(in, out);
return new String(out.toByteArray());
} catch (IOException e) {
throw new CommandFailedException(e);
}
}
public String getError() throws CommandFailedException {
this.waitUntilFinished();
return new String(error.toByteArray());
try (InputStream in = process.getErrorStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
copy(in, out);
return new String(out.toByteArray());
} catch (IOException e) {
throw new CommandFailedException(e);
}
}
public int getExitValue() throws CommandFailedException {
this.waitUntilFinished();
return process.exitValue();
}
private void waitUntilFinished() throws CommandFailedException {
lock.lock();
try {
while (!finished) {
finishedCondition.await();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
if (exception != null) {
throw exception;
}
}
private void logDebugInfo() {
public void logDebugInfo() {
if (LOG.isDebugEnabled()) {
LOG.debug("Command execution finished. Exit code: {}\n" + "Output:\n" + "{}\n" + "Error:\n" + "{}\n", process.exitValue(), new String(output.toByteArray()), new String(error.toByteArray()));
try {
LOG.debug("Command execution finished. Exit code: {}\n" + "Output:\n" + "{}\n" + "Error:\n" + "{}\n", process.exitValue(), getOutput(), getError());
} catch (CommandFailedException e) {
LOG.debug("Command execution finished. Exit code: {}\n", process.exitValue());
}
}
}
void assertOk() throws CommandFailedException {
int exitValue = getExitValue();
if (exitValue != 0) {
throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, new String(output.toByteArray()),
new String(error.toByteArray())));
}
}
private class CommandTask implements Runnable {
private final long timeout;
private final TimeUnit unit;
private CommandTask(long timeout, TimeUnit unit) {
this.timeout = timeout;
this.unit = unit;
}
@Override
public void run() {
try {
if (!process.waitFor(timeout, unit)) {
exception = new CommandFailedException("Waiting time elapsed before command execution finished");
}
processOutputCopier.assertOk();
processErrorCopier.assertOk();
logDebugInfo();
} catch (IOException | InterruptedException e) {
exception = new CommandFailedException(e);
} finally {
lock.lock();
finished = true;
finishedCondition.signal();
lock.unlock();
}
throw new CommandFailedException(format("Command execution failed. Exit code: %d\n" + "# Output:\n" + "%s\n" + "# Error:\n" + "%s", exitValue, getOutput(), getError()));
}
}

View File

@ -14,9 +14,11 @@ import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
@ -62,7 +64,8 @@ final class CommandRunner {
for (final String line : script.getLines()) {
final String[] cmds = ArrayUtils.add(determineCli(), line);
final Process proc = Runtime.getRuntime().exec(cmds, env.toArray(new String[0]));
result = run(proc, script.getLines(), timeout, unit);
result = run(proc, timeout, unit);
result.logDebugInfo();
result.assertOk();
}
return result;
@ -71,8 +74,14 @@ final class CommandRunner {
}
}
private static CommandResult run(Process process, String[] lines, long timeout, TimeUnit unit) {
return new CommandResult(process, lines, CMD_EXECUTOR, timeout, unit);
private static CommandResult run(Process process, long timeout, TimeUnit unit) throws CommandFailedException {
try {
final FutureCommandResult futureCommandResult = new FutureCommandResult(process);
CMD_EXECUTOR.execute(futureCommandResult);
return futureCommandResult.get(timeout, unit);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
throw new CommandFailedException("Waiting time elapsed before command execution finished");
}
}
private static String[] determineCli() {

View File

@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2014 Markus Kreusch
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel
******************************************************************************/
package org.cryptomator.ui.util.command;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.cryptomator.ui.util.mount.CommandFailedException;
class FutureCommandResult implements Future<CommandResult>, Runnable {
private final Process process;
private final AtomicBoolean canceled = new AtomicBoolean();
private final AtomicBoolean done = new AtomicBoolean();
private final Lock lock = new ReentrantLock();
private final Condition doneCondition = lock.newCondition();
private CommandFailedException exception;
public FutureCommandResult(Process process) {
this.process = process;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (done.get()) {
return false;
} else if (canceled.compareAndSet(false, true)) {
if (mayInterruptIfRunning) {
process.destroyForcibly();
}
}
return true;
}
@Override
public boolean isCancelled() {
return canceled.get();
}
private void setDone() {
lock.lock();
try {
done.set(true);
doneCondition.signalAll();
} finally {
lock.unlock();
}
}
@Override
public boolean isDone() {
return done.get();
}
@Override
public CommandResult get() throws InterruptedException, ExecutionException {
lock.lock();
try {
while(!done.get()) {
doneCondition.await();
}
} finally {
lock.unlock();
}
if (exception != null) {
throw new ExecutionException(exception);
}
return new CommandResult(process);
}
@Override
public CommandResult get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
lock.lock();
try {
while(!done.get()) {
doneCondition.await(timeout, unit);
}
} finally {
lock.unlock();
}
if (exception != null) {
throw new ExecutionException(exception);
}
return new CommandResult(process);
}
@Override
public void run() {
try {
process.waitFor();
} catch (InterruptedException e) {
exception = new CommandFailedException(e);
} finally {
setDone();
}
}
}