-
-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathProcessHelper.java
More file actions
82 lines (69 loc) · 2.79 KB
/
ProcessHelper.java
File metadata and controls
82 lines (69 loc) · 2.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package com.github.stickerifier.stickerify.process;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.github.stickerifier.stickerify.exception.ProcessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.StringJoiner;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public final class ProcessHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessHelper.class);
private static final Semaphore SEMAPHORE = new Semaphore(getMaxConcurrentProcesses());
/**
* Executes passed-in command and ensures it completed successfully.
* Concurrency is limited by a process-wide semaphore sized by the {@code CONCURRENT_PROCESSES}
* environment variable (defaults to 4).
*
* @param command the command to be executed
* @return the merged stdout/stderr of the command
* @throws ProcessException either if:
* <ul>
* <li>the command was unsuccessful
* <li>the waiting time elapsed
* <li>an unexpected failure happened running the command
* <li>an unexpected failure happened reading the output
* </ul>
* @throws InterruptedException if the current thread is interrupted while waiting for the command to finish
*/
public static String executeCommand(final String... command) throws ProcessException, InterruptedException {
SEMAPHORE.acquire();
try (var process = new ProcessBuilder(command).redirectErrorStream(true).start()) {
var output = new StringJoiner("\n");
var readerThread = Thread.ofVirtual().start(() -> {
try (var reader = process.inputReader(UTF_8)) {
reader.lines().forEach(output::add);
} catch (IOException e) {
LOGGER.atError().setCause(e).log("Error while closing process output reader");
}
});
var finished = process.waitFor(1, TimeUnit.MINUTES);
if (!finished) {
process.destroyForcibly();
readerThread.join();
throw new ProcessException("The command {} timed out after 1m\n{}", command[0], output.toString());
}
readerThread.join();
var exitCode = process.exitValue();
if (exitCode != 0) {
throw new ProcessException("The command {} exited with code {}\n{}", command[0], exitCode, output.toString());
}
return output.toString();
} catch (IOException e) {
throw new ProcessException(e);
} finally {
SEMAPHORE.release();
}
}
private static int getMaxConcurrentProcesses() {
var concurrentProcesses = System.getenv("CONCURRENT_PROCESSES");
var value = concurrentProcesses == null ? 4 : Integer.parseInt(concurrentProcesses);
if (value < 1) {
throw new IllegalArgumentException("The environment variable CONCURRENT_PROCESSES must be >= 1 (was " + concurrentProcesses + ")");
}
return value;
}
private ProcessHelper() {
throw new UnsupportedOperationException();
}
}