Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions src/main/java/org/buildobjects/process/Proc.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,28 +77,35 @@ public Proc(String command,
throw new StartupException("Could not startup process '" + toString() + "'.", e);
}

try {
startControlThread();

do {
ExecutionEvent nextEvent = timeout == null ? eventQueue.poll(MAX_VALUE, HOURS) : eventQueue.poll(timeout, MILLISECONDS);
try {
ExecutionEvent nextEvent = timeout == null ? eventQueue.poll(MAX_VALUE, HOURS) : eventQueue.poll(timeout, MILLISECONDS);

if (nextEvent == null) {
killCleanUpAndThrowTimeoutException();
}
if (nextEvent == null) {
killCleanUpAndThrowTimeoutException();
}

if (nextEvent == PROCESS_EXITED) {
break;
}
if (nextEvent == PROCESS_EXITED) {
break;
}

if (nextEvent == EXCEPTION_IN_STREAM_HANDLING) {
if (nextEvent == EXCEPTION_IN_STREAM_HANDLING) {
killProcessCleanup();
break;
}
} catch (InterruptedException e) {
// if this thread is interrupted, the process is destroyed
// the process gets an exit code, so the next event is PROCESS_EXITED
killProcessCleanup();
break;
continue;
}

throw new RuntimeException("Felix reckons we should never reach this point");
} while (true);

try {
List<Throwable> exceptions = ioHandler.joinConsumption();
if (!exceptions.isEmpty()) {
throw new IllegalStateException("Exception in stream consumption", exceptions.get(0));
Expand Down Expand Up @@ -150,14 +157,31 @@ public void run() {
}

private void killCleanUpAndThrowTimeoutException() {
process.destroy();
ioHandler.cancelConsumption();
killProcessCleanup();
throw new TimeoutException(toString(), timeout);
}

private void killProcessCleanup() {
process.destroy();
List<Throwable> throwables = new ArrayList<>();

// destroy this process and all sub processes
ProcessHandle.of(process.pid())
.ifPresentOrElse(rootHandle -> {
rootHandle.descendants()
.forEach(subHandle -> {
try {
subHandle.destroy();
}
catch (Throwable t) {
throwables.add(new IllegalStateException("Could not destroy process " + subHandle.pid(), t));
}
});
rootHandle.destroy();
}, process::destroy);
ioHandler.cancelConsumption();

if(!throwables.isEmpty())
throw new RuntimeException(throwables.get(0));
}

public void dispatch(ExecutionEvent event) {
Expand Down
69 changes: 69 additions & 0 deletions src/test/java/org/buildobjects/process/ProcBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.Assert.*;

Expand Down Expand Up @@ -262,6 +265,41 @@ public void testDisablingTimeout() {
assertEquals(result.getExecutionTime(), 7000, 500);
}

/**
* Tests if the process stops the execution when the thread is interrupted
*/
@Test
public void testInterruptingProcess() throws InterruptedException {
AtomicBoolean finished = new AtomicBoolean(false);
AtomicReference<ProcResult> resultRef = new AtomicReference<>(null);

Thread executorThread = new Thread(new Runnable() {
@Override
public void run() {
resultRef.set(new ProcBuilder("sleep")
.withArg("10")
.ignoreExitStatus()
.withNoTimeout()
.run());
finished.set(true);
}
});
executorThread.start();

// wait some time
Thread.sleep(2000);

// stop the thread
executorThread.interrupt();

// wait shortly
Thread.sleep(500);

assertTrue(finished.get());
assertNotNull(resultRef.get());
assertEquals(2000, resultRef.get().getExecutionTime(), 300);
}

/**
* Exit Status
* -----------
Expand Down Expand Up @@ -384,6 +422,37 @@ public void testHonorsOnlyDefinedExitStatuses() {
}
}

/**
* If the process is interrupted, the exit code should be 1
*/
@Test
public void testExitStatusWhenProcessIsInterrupted() throws InterruptedException {
AtomicReference<ProcResult> resultRef = new AtomicReference<>(null);
Thread executorThread = new Thread(new Runnable() {
@Override
public void run() {
resultRef.set(new ProcBuilder("sleep")
.withArg("10")
.withExpectedExitStatuses(1)
.withNoTimeout()
.run());
}
});
executorThread.start();

// wait some time
Thread.sleep(2000);

// stop the thread
executorThread.interrupt();

// wait shortly
Thread.sleep(500);

assertNotNull(resultRef.get());
assertEquals(1, resultRef.get().getExitValue());
}

/**
*
* Good to Know
Expand Down