skip to content

A Gatling run exits 1 on a build runner. Does that prove the command line was wrong?

level: seniorimportance: should knowfreq 38%

answer

  1. One means no verdict, not failed
  2. Bad arguments and crashes look identical
  3. Gatling rethrows instead of coding a crash
  4. Unknown options are dropped, not rejected
  5. Run crashed goes to the console

basics

~10 s

No. Gatling maps 1 to invalid arguments, but a crashed run also exits 1: Gatling logs the crash and rethrows instead of returning a status code, so the exit call is never reached.

solid answer

~40 s

`1` is what Gatling's `StatusCode` calls `InvalidArguments`, and the argument parser does return it. But it is not the only way a process ends at `1`. When a run crashes, Gatling logs the failure and **rethrows** the cause rather than converting it to a status code, so the call that would have exited with a code is never reached and the JVM terminates on an uncaught exception — which is also `1`. A fatal error inside an action terminates the JVM directly with `1` as well. Compounding it, Gatling's parser deliberately does not error on an unknown argument, so a mistyped option is silently dropped rather than rejected; the run then fails later, through the crash path, for a reason that looks like bad arguments but never went through the argument code.

code

bash · 8 lines
bash
# Both crash lines are SLF4J logger calls and Gatling ships a console appender only,
# so they land on stdout/stderr - never in a file under the results directory.
java -cp "$CP" io.gatling.app.Gatling -s com.example.CheckoutSimulation -rf target/gatling 2>&1 | tee run.out
status=${PIPESTATUS[0]}
if [ "$status" -eq 1 ]; then
  echo "No verdict was produced - read the run output before blaming the service"
  grep -E 'Run crashed|Gatling crashed' run.out || echo "no crash logged: the command line was rejected"
fi

go deeper

for a junior

Know that 1 is the invalid-arguments code, and that a non-zero result is not automatically a performance failure. Check the log before drawing a conclusion from the number.

for a middle

Be ready to explain that a crashed run is rethrown rather than mapped to a status code, so the JVM exits 1 on an uncaught exception and the two cases become indistinguishable.

for a senior

Show how you would triage a 1 in practice: look for the crash line on the run's console output rather than in the results directory, and know that an unknown option is silently dropped rather than rejected, which makes a typo look like bad arguments.

for a principal

Argue for treating no verdict differently from a bad verdict in how results are surfaced, since one is a broken job and the other is a real signal about the system.

`1` is the most misleading value Gatling can hand a build runner, because two completely different situations produce it and only one of them is the one the name suggests. ## What `1` is declared to mean Gatling's `StatusCode` names `1` `InvalidArguments`. The argument parser returns it when it cannot make sense of the command line, and the main entry point exits with that value. That much matches the name. ## The other way to reach `1` Gatling's run wrapper catches failures in two arms: - a lifecycle exception — configuration loading, instantiating the simulation, a `before` or `after` hook, building the scenarios, or the injection itself failing — is logged and then its **cause is rethrown**; - any other `Throwable` is logged as `Run crashed` and **rethrown** as well. Neither arm maps to a `StatusCode`. The rethrown exception propagates out of `main`, so the line that would have called the exit function with a status code is never reached. The JVM ends on an uncaught exception in the main thread, and that terminates the process with **`1`**. A build runner cannot tell the two apart from the integer. It sees `1` for a rejected command line and `1` for a simulation that blew up halfway through injection. The difference is only in the run's own output: a rethrown crash leaves a `Run crashed` line and a stack trace there; a rejected command line leaves a parser message and no run at all. Look for them on the console, not in the results directory — both are SLF4J calls and Gatling's shipped logback configuration declares a single console appender and no file appender, so nothing routes them to a file. The only `.log` written under a results directory is `simulation.log`, and that is a binary record stream: it carries the run's statistics and the messages of its failed requests, never a log line such as `Run crashed`. On the mistyped-option route there is no results directory to search in the first place, because the run dies before one is created. ## And a third: a fatal error inside an action Separately from the status-code scheme, a handful of sites call the exit function with `1` directly. The most important is Gatling's action base: when an action throws something fatal — as opposed to a normal failure, which just marks the virtual user's session failed and moves on — Gatling logs `Gatling crashed` and terminates the JVM on the spot. No reports, no assertions, no verdict. So three distinct routes end at the same integer: - **the parser** returning the invalid-arguments code, the only one the name describes; - **a rethrown crash**, where the status-code path was never reached at all; - **a direct termination** from a fatal error inside an action, outside the status-code scheme entirely. ## The trap that ties them together Gatling's command-line parser deliberately **turns off erroring on unknown arguments**. An option the parser does not recognise is not rejected; parsing succeeds and the option is dropped. So: 1. You mistype `--simulation` as `--simulaton`. 2. The parser does not recognise it, does not error, and does not set the simulation class. 3. Gatling later goes to resolve which simulation to run, finds nothing, and throws. 4. That throw goes through the rethrow path, and the process exits `1`. The outcome — exit `1` after a typo in an argument — looks exactly like `InvalidArguments`, and it never touched that code. This is worth internalising, because the fix ("read the run's output, not the integer") is not obvious from the symptom. ## A related pair in the DSL Two stop constructs differ precisely in the signal they leave behind. `stopLoadGenerator` ends the run gracefully — the run finishes, assertions are evaluated, and the exit code is the usual `0` or `2`. `crashLoadGenerator`, added when the older injector-named options were renamed in **3.12**, is the same stop except that it is treated as a crash: the run's completion fails, the wrapper rethrows, and the process ends non-zero through the crash path rather than returning a status code. If you want a run to end early *and* be unmistakably a failure, that difference is the one that matters. ## What to do with it | observation | what it actually tells you | |---|---| | exit `0` | nothing failed that was asserted — possibly nothing was asserted | | exit `1` | Gatling produced **no verdict**: bad arguments, or the run died | | exit `2` | a verdict was produced and at least one assertion did not hold | | anything else | no `StatusCode` was returned: the process was killed, the JVM died, or simulation code exited it | The practical rule: **treat `1` as "no result", not as "failed"**. A run that never produced a verdict should be surfaced differently from a run that produced a bad one, because the responses are different — one is investigated, the other is retried or fixed in the job definition. One last wrinkle for anyone running simulations through sbt's test framework rather than the JVM launcher: that path catches the exception itself and reports it using the assertions-failed code. The same crash therefore surfaces as `1` under one launcher and as a failed test under the other, which is worth knowing before you conclude that two runners disagree about what happened.

  • Why does a mistyped Gatling command-line option not simply produce the invalid-arguments code?
    Because Gatling's parser switches off erroring on unknown arguments. An unrecognised option is dropped and parsing still succeeds, so the value it should have supplied is simply missing. The run then fails later for that missing value, through the crash path, and the process ends at `1` without the argument code ever being returned.
  • In Gatling, how do stopLoadGenerator and crashLoadGenerator differ in what the process returns?
    `stopLoadGenerator` ends the run gracefully: assertions are still evaluated and the exit code is the normal `0` or `2`. `crashLoadGenerator` marks the run as crashed instead, so the failure is rethrown and the process ends non-zero through the crash path rather than by returning a status code.
  • What does a fatal error inside a Gatling action do to the run?
    It terminates the JVM immediately with `1`. Ordinary failures in an action are logged and the virtual user's session is marked failed so the run continues, but a fatal `Throwable` is logged as `Gatling crashed` and the process exits on the spot — no reports, no assertion evaluation, no verdict.

saying these in an interview costs you the question

  • Reading exit 1 as proof that the command line was wrong
  • Treating exit 1 as a performance failure in a build gate
  • Assuming Gatling maps every crash onto a status code
  • Expecting a mistyped option to be rejected by the parser
  • Thinking stopLoadGenerator and crashLoadGenerator exit the same way