What do abortOnFail and delayAbortEval do to a k6 threshold, and when exactly does the abort fire?
answer
- object form only, never the string
- stops the run, not the verdict
- evaluated on a repeating tick
- delay arms the abort
- empty sinks are skipped in-run
basics
~20 sabortOnFail makes k6 stop a running test once that threshold fails; delayAbortEval holds the abort back until the run has lasted longer than the given duration. k6 re-evaluates thresholds every two seconds, so the abort lands on the next tick.
solid answer
~40 sBoth live only in the object form of a k6 threshold entry. With `abortOnFail: true`, a failing expression stops the test early instead of letting the remaining duration play out. k6 re-runs every threshold on a **two-second tick** while the test is live, so "immediately" means "at the next tick". `delayAbortEval: '30s'` arms that abort only once elapsed run time has passed 30 seconds, which is how you keep a cold-start spike from killing a run before the warm-up is over. Two details bite: `delayAbortEval` does nothing unless `abortOnFail` is also true, and the in-run pass skips any metric with no samples yet, so the abort cannot fire before that metric has recorded something.
code
javascript · 12 linesexport const options = {
vus: 50,
duration: '10m',
thresholds: {
// arm the abort only after the 30s ramp, so cold-start
// latency cannot kill the run two seconds in
http_req_duration: [
{ threshold: 'p(95)<500', abortOnFail: true, delayAbortEval: '30s' },
],
http_req_failed: ['rate<0.01'],
},
};go deeper
Recall that these two keys exist only in the object form of a threshold entry, and that abortOnFail is what makes a failing rule end a k6 run early rather than letting it finish.
Explain the evaluation tick and the arming rule: delayAbortEval is compared against elapsed run time, and it does nothing at all unless abortOnFail is true on the same entry.
Show how the warm-up window interacts with it - empty sinks are skipped in-run, and the first evaluation sees very few samples - and choose a delay that covers the ramp for the run you are actually gating.
Decide whether abortOnFail belongs in your k6 scripts at all: it buys a faster signal by throwing away the rest of the run's data, so settle which scripts get it and which are always left to complete.
## What the two keys are `abortOnFail` and `delayAbortEval` are the two optional keys of a threshold's **object form**. They cannot be expressed in the short string form at all, which is the reason the object form exists. ```javascript export const options = { thresholds: { http_req_duration: [ { threshold: 'p(95)<500', abortOnFail: true, delayAbortEval: '30s' }, ], http_req_failed: ['rate<0.01'], }, }; ``` - **`abortOnFail: true`** - if this expression evaluates false *while the test is still running*, k6 stops the run early instead of letting the remaining duration play out. - **`delayAbortEval: '30s'`** - a duration that arms the abort. Until the run has lasted longer than it, a failing expression is recorded as failing but is not allowed to stop anything. ## The two-second tick Neither key makes k6 watch a metric continuously. While a test is live, k6 re-evaluates every declared threshold **on a ticker that fires every two seconds**. So the practical meaning of "abort as soon as it fails" is "abort at the first two-second evaluation on which it is false". A rule that goes bad half a second after the run starts still costs you the rest of that two-second window, plus whatever the running iterations take to unwind. That tick is also the only place an abort can happen. When the run reaches its natural end, k6 evaluates the thresholds one last time to decide the verdict - and that final pass **discards the abort signal entirely**. So `abortOnFail` can only ever shorten a run; it never changes the pass or fail result you would have got anyway. ## How delayAbortEval is actually applied The rule is a strict comparison against elapsed run time, evaluated at each tick: 1. The expression is evaluated. If it passes, nothing happens. 2. If it fails, k6 marks the threshold failed and the metric tainted. 3. If `abortOnFail` is false, it stops there - **a `delayAbortEval` on a non-aborting threshold is inert**. 4. If `abortOnFail` is true, k6 asks whether `delayAbortEval` is set. If it is not, the abort fires now. 5. If it is set, the abort fires only when the configured duration is **strictly less than** the elapsed run time. At exactly the boundary the abort does not fire; it fires on the following tick. | `abortOnFail` | `delayAbortEval` | behaviour on a failing expression | |---|---|---| | absent or false | absent | recorded as failed, run continues to its end | | absent or false | `'30s'` | identical - the delay is ignored entirely | | true | absent | run stops at the first tick on which it fails | | true | `'30s'` | run stops at the first tick after 30 seconds of elapsed run time | The duration is written the way every other k6 duration is: `'10s'`, `'1m'`, `'1m30s'`. A bare number is also accepted and read as milliseconds, so `delayAbortEval: 30000` and `delayAbortEval: '30s'` are the same thing - but the string form is what the documentation shows and what a reviewer will recognise. ## The warm-up interaction nobody expects There is a second, quieter delay built into the in-run pass. When k6 evaluates thresholds during the run, it **skips any metric whose sink is still empty** - no samples, no evaluation. The consequences are worth stating plainly: - An `abortOnFail` threshold cannot fire before its metric has recorded at least one sample, whatever `delayAbortEval` says. A rule on a metric that never gets data will never abort anything. - The first evaluation therefore runs against however few samples have arrived in the first couple of seconds. On a `p(95)`, that can be a handful of requests - and a cold connection pool, a TLS handshake and a cold cache are all in them. That is the case `delayAbortEval` exists for. Setting it to cover your ramp-up means the abort is armed only once the numbers are made of steady-state traffic, rather than of the first few unlucky requests. Without it, an aggressive `p(95)` rule with `abortOnFail: true` will routinely kill a run two seconds in. ## What to check when it does not work - **Nothing aborted.** Confirm `abortOnFail` really is `true` on the entry, not on a sibling entry, and that the key is spelled exactly - k6 ignores unrecognised keys inside a threshold object without any warning, so `abortOnFailure: true` looks right and does nothing. - **It aborted too early.** Add or lengthen `delayAbortEval` to cover the ramp. - **It aborted with an odd delay.** Remember the two-second tick, and that k6 running in the cloud evaluates thresholds far less often - once a minute - so an abort there can lag by up to that long.
- Does abortOnFail change whether a k6 test is reported as failed?No. The threshold was already breached, and the end-of-run evaluation records that either way; the abort signal from that final pass is discarded. `abortOnFail` only decides whether you spend the remaining duration finding out.
- Why might an abortOnFail threshold on a custom k6 metric never fire at all?The in-run evaluation skips metrics whose sink is still empty. If the script never records a sample into that metric - a code path that is not exercised, say - the threshold is never evaluated during the run and there is nothing to abort on.
- What is the shortest a k6 run with abortOnFail can be cut to?Roughly two seconds plus unwinding, because thresholds are re-evaluated on a two-second tick and the first tick is the earliest opportunity. There is no evaluation at time zero, and the metric must already have at least one sample.
delayAbortEval is the hush button on a smoke alarm: the sensor is live from the moment you start cooking, but it is not allowed to empty the building until the set time has passed.
saying these in an interview costs you the question
- expects the abort the instant a request breaches the rule
- sets delayAbortEval without setting abortOnFail
- thinks abortOnFail changes the run's pass or fail verdict
- puts abortOnFail in the short string form of a threshold
- assumes an abortOnFail rule fires before its metric has any data