33import static java .nio .charset .StandardCharsets .UTF_8 ;
44
55import com .github .stickerifier .stickerify .exception .ProcessException ;
6+ import org .slf4j .Logger ;
7+ import org .slf4j .LoggerFactory ;
68
79import java .io .IOException ;
8- import java .util .LinkedList ;
10+ import java .util .ArrayList ;
911import java .util .List ;
1012import java .util .concurrent .Semaphore ;
13+ import java .util .concurrent .TimeUnit ;
1114
1215public final class ProcessHelper {
1316
17+ private static final Logger LOGGER = LoggerFactory .getLogger (ProcessHelper .class );
1418 private static final Semaphore SEMAPHORE = new Semaphore (getMaxConcurrentProcesses ());
1519
1620 /**
@@ -23,6 +27,7 @@ public final class ProcessHelper {
2327 * @throws ProcessException either if:
2428 * <ul>
2529 * <li>the command was unsuccessful
30+ * <li>the waiting time elapsed
2631 * <li>an unexpected failure happened running the command
2732 * <li>an unexpected failure happened reading the output
2833 * </ul>
@@ -33,19 +38,28 @@ public static List<String> executeCommand(final String... command) throws Proces
3338 try {
3439 var process = new ProcessBuilder (command ).redirectErrorStream (true ).start ();
3540
36- var output = new LinkedList <String >();
37- try (var reader = process .inputReader (UTF_8 )) {
38- reader .lines ().forEach (output ::add );
41+ var output = new ArrayList <String >(64 );
42+ Thread .ofVirtual ().start (() -> {
43+ try (var reader = process .inputReader (UTF_8 )) {
44+ reader .lines ().forEach (output ::add );
45+ } catch (IOException e ) {
46+ LOGGER .atError ().setCause (e ).log ("Error while closing process output reader" );
47+ }
48+ });
49+
50+ var finished = process .waitFor (1 , TimeUnit .MINUTES );
51+ if (!finished ) {
52+ process .destroyForcibly ();
53+ throw new ProcessException ("The command {} timed out after 1m\n {}" , command [0 ], String .join ("\n " , output ));
3954 }
4055
41- var exitCode = process .waitFor ();
56+ var exitCode = process .exitValue ();
4257 if (exitCode != 0 ) {
43- process .destroy ();
4458 var lines = String .join ("\n " , output );
4559 throw new ProcessException ("The command {} exited with code {}\n {}" , command [0 ], exitCode , lines );
4660 }
4761
48- return output ;
62+ return List . copyOf ( output ) ;
4963 } catch (IOException e ) {
5064 throw new ProcessException (e );
5165 } finally {
@@ -54,8 +68,12 @@ public static List<String> executeCommand(final String... command) throws Proces
5468 }
5569
5670 private static int getMaxConcurrentProcesses () {
57- var value = System .getenv ("CONCURRENT_PROCESSES" );
58- return value == null ? 4 : Integer .parseInt (value );
71+ var env = System .getenv ("CONCURRENT_PROCESSES" );
72+ var value = env == null ? 4 : Integer .parseInt (env );
73+ if (value <= 1 ) {
74+ throw new IllegalArgumentException ("CONCURRENT_PROCESSES must be >= 1 (was " + env + ")" );
75+ }
76+ return value ;
5977 }
6078
6179 private ProcessHelper () {
0 commit comments