Skip to content

Commit

Permalink
Merge pull request #1420 from cescoffier/issues/add-exit-codes
Browse files Browse the repository at this point in the history
add exit codes in the launcher commands
  • Loading branch information
vietj committed May 21, 2016
2 parents 8952de7 + 287ebcc commit e365d9c
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 31 deletions.
14 changes: 13 additions & 1 deletion src/main/asciidoc/java/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1586,4 +1586,16 @@ overriding `link:../../apidocs/io/vertx/core/Launcher.html#afterStartingVertx-io
`link:../../apidocs/io/vertx/core/impl/launcher/VertxCommandLauncher.html#getMainVerticle--[getMainVerticle]` and
`link:../../apidocs/io/vertx/core/impl/launcher/VertxCommandLauncher.html#getDefaultCommand--[getDefaultCommand]`
* add / remove commands using `link:../../apidocs/io/vertx/core/impl/launcher/VertxCommandLauncher.html#register-java.lang.Class-[register]`
and `link:../../apidocs/io/vertx/core/impl/launcher/VertxCommandLauncher.html#unregister-java.lang.String-[unregister]`
and `link:../../apidocs/io/vertx/core/impl/launcher/VertxCommandLauncher.html#unregister-java.lang.String-[unregister]`

=== Launcher and exit code

When you use the `link:../../apidocs/io/vertx/core/Launcher.html[Launcher]` class as main class, it uses the following exit code:

* `0` if the process ends smoothly, or if an uncaught error is thrown
* `1` for general purpose error
* `11` if Vert.x cannot be initialized
* `12` if a spawn process cannot be started, found or stopped. This error code is used by the `start` and
`stop` command
* `14` if the system configuration is not meeting the system requirement (shc as java not found)
* `15` if the main verticle cannot be deployed
73 changes: 71 additions & 2 deletions src/main/java/io/vertx/core/impl/launcher/commands/ExecUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,37 @@ public class ExecUtils {
private static final String SINGLE_QUOTE = "\'";
private static final String DOUBLE_QUOTE = "\"";


// A note about exit code
// * 0 for success
// * 1 for general error
// * exit codes 1-2, 126-165, and 255 have special meanings, and should therefore be avoided for user-specified
// exit parameters
// * Ending with exit 127 would certainly cause confusion when troubleshooting as it's the command not found code
// * 130, 134, 133, 135, 137, 140, 129, 142, 143, 145, 146, 149, 150, 152, 154, 155, 158 are used by the JVM, and
// so have special meanings
// * it's better to avoid 3-9

/**
* Error code used when vert.x cannot be initialized.
*/
public static final int VERTX_INITIALIZATION_EXIT_CODE = 11;

/**
* Error code used when a deployment failed.
*/
public static final int VERTX_DEPLOYMENT_EXIT_CODE = 15;

/**
* Error code used when a spawn process cannot be found, created, or stopped smoothly.
*/
public static final int PROCESS_ERROR_EXIT_CODE = 12;

/**
* Error code used when the system configuration does not satisfy the requirements (java not found for example).
*/
public static final int SYSTEM_CONFIGURATION_EXIT_CODE = 14;

/**
* The {@code os.name} property is mandatory (from the Java Virtual Machine specification).
*/
Expand All @@ -50,7 +81,7 @@ public static String quoteArgument(final String argument) {

// strip the quotes from both ends
while (cleanedArgument.startsWith(SINGLE_QUOTE) && cleanedArgument.endsWith(SINGLE_QUOTE)
|| cleanedArgument.startsWith(DOUBLE_QUOTE) && cleanedArgument.endsWith(DOUBLE_QUOTE)) {
|| cleanedArgument.startsWith(DOUBLE_QUOTE) && cleanedArgument.endsWith(DOUBLE_QUOTE)) {
cleanedArgument = cleanedArgument.substring(1, cleanedArgument.length() - 1);
}

Expand All @@ -74,7 +105,7 @@ public static String quoteArgument(final String argument) {
/**
* Adds an argument to the given list. It automatically adds quotes to the argument if necessary.
*
* @param args the list of arguments
* @param args the list of arguments
* @param argument the argument to add
*/
public static void addArgument(List<String> args, String argument) {
Expand All @@ -88,5 +119,43 @@ public static boolean isWindows() {
return osName.contains("windows");
}

/**
* Exits the JVM with the given exit code.
*
* @param code the code, {@code 0} for success. By convention a non zero value if return to denotes an
* error.
*/
public static void exit(int code) {
System.exit(code);
}

/**
* Exits the JVM and indicate an issue during the Vert.x initialization.
*/
public static void exitBecauseOfVertxInitializationIssue() {
exit(VERTX_INITIALIZATION_EXIT_CODE);
}

/**
* Exits the JVM and indicate an issue during the deployment of the main verticle.
*/
public static void exitBecauseOfVertxDeploymentIssue() {
exit(VERTX_DEPLOYMENT_EXIT_CODE);
}

/**
* Exits the JVM and indicate an issue with a process creation or termination.
*/
public static void exitBecauseOfProcessIssue() {
exit(PROCESS_ERROR_EXIT_CODE);
}

/**
* Exits the JVM and indicate an issue with the system configuration.
*/
public static void exitBecauseOfSystemConfigurationIssue() {
exit(SYSTEM_CONFIGURATION_EXIT_CODE);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ public void run() {

super.run();
if (vertx == null) {
return; // Already logged.
// Already logged.
ExecUtils.exitBecauseOfVertxInitializationIssue();
}
deploymentOptions = new DeploymentOptions();
configureFromSystemProperties(deploymentOptions, DEPLOYMENT_OPTIONS_PROP_PREFIX);
Expand Down Expand Up @@ -298,8 +299,7 @@ protected synchronized void shutdownRedeployment() {
* @param onCompletion an optional on-completion handler. If set it must be invoked at the end of this method.
*/
protected synchronized void stopBackgroundApplication(Handler<Void> onCompletion) {
executionContext.execute("stop", vertxApplicationBackgroundId);

executionContext.execute("stop", vertxApplicationBackgroundId, "--redeploy");
if (redeployTerminationPeriod > 0) {
try {
Thread.sleep(redeployTerminationPeriod);
Expand Down Expand Up @@ -383,6 +383,8 @@ protected void deploy() {
private void handleDeployFailed(Throwable cause) {
if (executionContext.main() instanceof VertxLifecycleHooks) {
((VertxLifecycleHooks) executionContext.main()).handleDeployFailed(vertx, mainVerticle, deploymentOptions, cause);
} else {
ExecUtils.exitBecauseOfVertxDeploymentIssue();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import io.vertx.core.spi.launcher.DefaultCommand;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -124,9 +123,10 @@ public void run() {
}
builder.start();
out.println(id);
} catch (IOException e) {
} catch (Exception e) {
out.println("Cannot create vert.x application process");
e.printStackTrace(out);
ExecUtils.exitBecauseOfProcessIssue();
}

}
Expand Down Expand Up @@ -162,8 +162,8 @@ private File getJava() {
}

if (!java.isFile()) {
throw new IllegalStateException("Cannot find java executable - " + java.getAbsolutePath()
+ " does not exist");
out.println("Cannot find java executable - " + java.getAbsolutePath() + " does not exist");
ExecUtils.exitBecauseOfSystemConfigurationIssue();
}
return java;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

package io.vertx.core.impl.launcher.commands;

import io.vertx.core.cli.annotations.Argument;
import io.vertx.core.cli.annotations.Description;
import io.vertx.core.cli.annotations.Name;
import io.vertx.core.cli.annotations.Summary;
import io.vertx.core.cli.annotations.*;
import io.vertx.core.spi.launcher.DefaultCommand;

import java.io.BufferedReader;
Expand All @@ -44,6 +41,12 @@ public class StopCommand extends DefaultCommand {

private String id;

/**
* Whether or not we are in redeploy mode. In redeploy mode, do not exit the VM.
*/
private boolean redeploy;


private static final Pattern PS = Pattern.compile("([0-9]+)\\s.*-Dvertx.id=.*");

/**
Expand All @@ -57,6 +60,12 @@ public void setApplicationId(String id) {
this.id = id;
}

@Option(longName = "redeploy", flag = true)
@Hidden
public void setRedeploy(boolean redeploy) {
this.redeploy = redeploy;
}

/**
* Stops a running vert.x application launched with the `start` command.
*/
Expand All @@ -80,18 +89,28 @@ private void terminateLinuxApplication() {
String pid = pid();
if (pid == null) {
out.println("Cannot find process for application using the id '" + id + "'.");
if (!redeploy) {
ExecUtils.exitBecauseOfProcessIssue();
}
return;
}

List<String> cmd = new ArrayList<>();
cmd.add("kill");
cmd.add(pid);
try {
new ProcessBuilder(cmd).start().waitFor();
out.println("Application '" + id + "' stopped.");
int result = new ProcessBuilder(cmd).start().waitFor();
out.println("Application '" + id + "' terminated with status " + result);
if (!redeploy) {
// We leave the application using the same exit code.
ExecUtils.exit(result);
}
} catch (Exception e) {
out.println("Failed to stop application '" + id + "'");
e.printStackTrace(out);
if (!redeploy) {
ExecUtils.exitBecauseOfProcessIssue();
}
}
}

Expand All @@ -108,13 +127,19 @@ private void terminateWindowsApplication() {

try {
final Process process = new ProcessBuilder(cmd).start();
out.println("Application '" + id + "' stopped");
process.waitFor();
int result = process.waitFor();
out.println("Application '" + id + "' terminated with status " + result);
if (!redeploy) {
// We leave the application using the same exit code.
ExecUtils.exit(result);
}
} catch (Exception e) {
out.println("Failed to stop application '" + id + "'");
e.printStackTrace(out);
if (!redeploy) {
ExecUtils.exitBecauseOfProcessIssue();
}
}

}

private String pid() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ private void executeUserCommand(Handler<Void> onCompletion) {
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.start();

process.waitFor();
int status = process.waitFor();
System.out.println("User command terminated with status " + status);
} catch (Throwable e) {
System.err.println("Error while executing the on-redeploy command : '" + cmd + "'");
e.printStackTrace();
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/io/vertx/core/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -1483,6 +1483,19 @@
* * add / remove commands using {@link io.vertx.core.impl.launcher.VertxCommandLauncher#register(java.lang.Class)}
* and {@link io.vertx.core.impl.launcher.VertxCommandLauncher#unregister(java.lang.String)}
*
* === Launcher and exit code
*
* When you use the {@link io.vertx.core.Launcher} class as main class, it uses the following exit code:
*
* * {@code 0} if the process ends smoothly, or if an uncaught error is thrown
* * {@code 1} for general purpose error
* * {@code 11} if Vert.x cannot be initialized
* * {@code 12} if a spawn process cannot be started, found or stopped. This error code is used by the `start` and
* `stop` command
* * {@code 14} if the system configuration is not meeting the system requirement (shc as java not found)
* * {@code 15} if the main verticle cannot be deployed
*
*
*/
@Document(fileName = "index.adoc")
@io.vertx.codegen.annotations.ModuleGen(name = "vertx", groupPackage = "io.vertx")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ public void testStartListStop() throws InterruptedException {
String[] lines = output.toString().split(System.lineSeparator());
String id = lines[1].trim().substring(0, lines[1].trim().indexOf("\t"));
output.reset();
cli.dispatch(new String[]{"stop", id});
// pass --redeploy to not call system.exit
cli.dispatch(new String[]{"stop", id, "--redeploy"});
assertThat(output.toString())
.contains("Stopping vert.x application '" + id + "'")
.contains("Application '" + id + "' stopped");
.contains("Application '" + id + "' terminated with status 0");


waitForShutdown();

Expand Down Expand Up @@ -102,10 +104,11 @@ public void testStartListStopWithJVMOptions() throws InterruptedException, IOExc
String[] lines = output.toString().split(System.lineSeparator());
String id = lines[1].trim().substring(0, lines[1].trim().indexOf("\t"));
output.reset();
cli.dispatch(new String[]{"stop", id});
// pass --redeploy to not call system.exit
cli.dispatch(new String[]{"stop", id, "--redeploy"});
assertThat(output.toString())
.contains("Stopping vert.x application '" + id + "'")
.contains("Application '" + id + "' stopped");
.contains("Application '" + id + "' terminated with status 0");

waitForShutdown();

Expand Down Expand Up @@ -158,10 +161,11 @@ public void testStartListStopWithId() throws InterruptedException, IOException {
String id = lines[1].trim().substring(0, lines[1].trim().indexOf("\t"));
assertThat(id).isEqualToIgnoringCase("hello");
output.reset();
cli.dispatch(new String[]{"stop", id});
// pass --redeploy to not call system.exit
cli.dispatch(new String[]{"stop", id, "--redeploy"});
assertThat(output.toString())
.contains("Stopping vert.x application '" + id + "'")
.contains("Application '" + id + "' stopped");
.contains("Application '" + id + "' terminated with status 0");

waitForShutdown();

Expand Down Expand Up @@ -194,10 +198,11 @@ public void testStartListStopWithIdAndAnotherArgument() throws InterruptedExcept
String id = lines[1].trim().substring(0, lines[1].trim().indexOf("\t"));
assertThat(id).isEqualToIgnoringCase("hello");
output.reset();
cli.dispatch(new String[]{"stop", id});
// pass --redeploy to not call system.exit
cli.dispatch(new String[]{"stop", id, "--redeploy"});
assertThat(output.toString())
.contains("Stopping vert.x application '" + id + "'")
.contains("Application '" + id + "' stopped");
.contains("Application '" + id + "' terminated with status 0");

waitForShutdown();

Expand Down Expand Up @@ -230,10 +235,11 @@ public void testStartListStopWithIdAndAnotherArgumentBeforeId() throws Interrupt
String id = lines[1].trim().substring(0, lines[1].trim().indexOf("\t"));
assertThat(id).isEqualToIgnoringCase("hello");
output.reset();
cli.dispatch(new String[]{"stop", id});
// pass --redeploy to not call system.exit
cli.dispatch(new String[]{"stop", id, "--redeploy"});
assertThat(output.toString())
.contains("Stopping vert.x application '" + id + "'")
.contains("Application '" + id + "' stopped");
.contains("Application '" + id + "' terminated with status 0");

waitForShutdown();

Expand Down Expand Up @@ -263,6 +269,22 @@ public void testFatJarExtraction() {
assertThat(ListCommand.extractApplicationDetails(command)).isEqualTo("");
}

@Test
public void testStoppingAnUnknownProcess() {
if (ExecUtils.isWindows()) {
// Test skipped on windows, because on windows we do not check whether or not the pid exists.
return;
}
record();
String id = "this-process-does-not-exist";
output.reset();
// pass --redeploy to not call system.exit
cli.dispatch(new String[]{"stop", id, "--redeploy"});
assertThat(output.toString())
.contains("Stopping vert.x application '" + id + "'")
.contains("Cannot find process for application using the id");
}

@Test
public void testVerticleExtraction() {
String command = "vertx run verticle test1 -Dvertx.id=xxx --cluster";
Expand Down

0 comments on commit e365d9c

Please sign in to comment.