Skip to content

Commit 44ab197

Browse files
committed
Handle failed process launches at the call sites
With direct argv, a missing or non-executable command now throws from runProcess(), as its javadoc always documented ('If there is any problem with the process, throws an exception') - the removed shell wrapper used to mask launch failures as exit code 127. Handle the launch failure where graceful failure is the intent: - SpecsGraphviz: dot being absent is a normal condition, so checkDot() returns false and renderDot() logs instead of crashing. - ProcessExecution: the jobs framework is built on return codes with no exception handling, so run() reports the failed launch as exit code 127 instead of propagating. This also restores SpecsSystem.isCommandAvailable()'s intended behavior: it detects missing commands by catching the launch exception, which the wrapper's masking previously defeated.
1 parent b54f698 commit 44ab197

3 files changed

Lines changed: 55 additions & 6 deletions

File tree

SpecsUtils/src/pt/up/fe/specs/util/SpecsGraphviz.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,13 @@ public class SpecsGraphviz {
2929
private static final Lazy<Boolean> IS_DOT_AVAILABLE = Lazy.newInstance(SpecsGraphviz::checkDot);
3030

3131
private static boolean checkDot() {
32-
var result = SpecsSystem.runProcess(Arrays.asList("dot", "-?"), false, false);
33-
return result.getReturnValue() == 0;
32+
try {
33+
var result = SpecsSystem.runProcess(Arrays.asList("dot", "-?"), false, false);
34+
return result.getReturnValue() == 0;
35+
} catch (RuntimeException e) {
36+
// The launch itself failed, e.g. Graphviz is not installed
37+
return false;
38+
}
3439
}
3540

3641
public static boolean isDotAvailable() {
@@ -47,9 +52,19 @@ public static void renderDot(File dotFile, DotRenderFormat format, File outputFi
4752
var command = Arrays.asList("dot", format.getFlag(), dotFile.getAbsolutePath(), "-o",
4853
outputFile.getAbsolutePath());
4954

50-
var result = SpecsSystem.runProcess(command, false, false);
51-
if (result.getReturnValue() == 0) {
52-
SpecsLogs.debug(() -> "Rendered dot file '" + dotFile.getAbsolutePath() + "' as " + format);
55+
try {
56+
var result = SpecsSystem.runProcess(command, false, false);
57+
if (result.getReturnValue() == 0) {
58+
SpecsLogs.debug(() -> "Rendered dot file '" + dotFile.getAbsolutePath() + "' as " + format);
59+
}
60+
} catch (RuntimeException e) {
61+
// Only a missing Graphviz installation is skipped; any other
62+
// failure is rethrown
63+
if (!SpecsSystem.isLaunchFailure(e)) {
64+
throw e;
65+
}
66+
67+
SpecsLogs.msgInfo("Graphviz not available, could not render dot file '" + dotFile.getAbsolutePath() + "'");
5368
}
5469
}
5570

SpecsUtils/src/pt/up/fe/specs/util/SpecsSystem.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,26 @@ public static boolean isCommandAvailable(List<String> command, File workingdir)
578578

579579
}
580580

581+
/**
582+
* Checks if the throwable was caused by a failure to launch the process
583+
* itself (e.g., command not found or not executable), as opposed to a
584+
* failure after a successful launch.
585+
*
586+
* @param throwable
587+
* @return
588+
*/
589+
public static boolean isLaunchFailure(Throwable throwable) {
590+
for (Throwable currentThrowable = throwable; currentThrowable != null; currentThrowable = currentThrowable
591+
.getCause()) {
592+
if (currentThrowable instanceof IOException) {
593+
return true;
594+
}
595+
}
596+
597+
return false;
598+
}
599+
600+
581601
/**
582602
* Adds a path to the java.library.path property, and flushes the path cache so
583603
* that subsequent System.load calls

SpecsUtils/src/pt/up/fe/specs/util/jobs/execution/ProcessExecution.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.io.File;
1717
import java.util.List;
1818

19+
import pt.up.fe.specs.util.SpecsLogs;
1920
import pt.up.fe.specs.util.SpecsSystem;
2021

2122
/**
@@ -45,7 +46,20 @@ public ProcessExecution(List<String> commandArgs, String workingFoldername) {
4546
*/
4647
@Override
4748
public int run() {
48-
return SpecsSystem.run(this.commandArgs, new File(this.workingFoldername));
49+
try {
50+
return SpecsSystem.run(this.commandArgs, new File(this.workingFoldername));
51+
} catch (RuntimeException e) {
52+
// Only a launch failure due to a missing command maps to a return
53+
// code (the shell convention of 127, 'command not found'). Any
54+
// other failure is not representable as an exit code and is
55+
// rethrown, as the process may even have been running.
56+
if (!SpecsSystem.isLaunchFailure(e)) {
57+
throw e;
58+
}
59+
60+
SpecsLogs.msgInfo("Could not run command '" + getCommandString() + "': " + e.getMessage());
61+
return 127;
62+
}
4963
}
5064

5165
@Override

0 commit comments

Comments
 (0)