skip to content

Pausing and Pacing

Gatling's two waiting steps and the run-wide policy over them: a plain pause, and a pace that absorbs the server's own latency. Interviewers ask which one holds an iteration rate steady.

on this pageshow

explore

questions

5

In a Gatling scenario, how long does pause(10) wait, and what does pause(1, 4) do differently?

level: juniorimportance: must knowfreq 68%

answer

  1. the virtual user's own waiting step
  2. a bare number means seconds
  3. two arguments give a random range
  4. redrawn per user, per visit

basics

~20 s

pause(10) waits a fixed ten seconds: a bare number means seconds in every Gatling SDK. pause(1, 4) draws a fresh duration uniformly at random between one and four seconds each time a virtual user reaches that step.

solid answer

~50 s

`pause` is Gatling's plain wait step, placed in a chain like any other action. With one argument it waits exactly that long, and a bare number is read as **seconds** — `pause(10)` is ten seconds, not ten milliseconds. With two arguments it becomes a uniform random wait: Gatling converts both bounds to milliseconds and draws a new value in that range for every virtual user on every visit, so two users reaching `pause(1, 4)` together almost certainly resume at different moments. Either form also takes a typed duration (`Duration.ofMillis(100)` in Java and Kotlin, `100.millis` in Scala, `{ amount: 100, unit: "milliseconds" }` in JavaScript), a Gatling EL string such as `"#{thinkTime}"`, or a function of the session. The wait is scheduled on the event loop, so it holds no thread and never appears as a row in the report.

code

java · 5 lines
java
ScenarioBuilder scn = scenario("Browse")
  .exec(http("Home").get("/"))
  .pause(10)
  .exec(http("Product").get("/product/42"))
  .pause(Duration.ofSeconds(1), Duration.ofSeconds(4));

go deeper

for a junior

Be ready to put a pause between two requests and to say what unit a bare number carries.

for a middle

Be ready to explain that the two-argument form redraws a uniform value per user per visit, and to name the four ways a duration can be spelled.

for a senior

Be ready to say what a pause costs the load generator and why time spent in one never shows up in the per-request statistics.

for a principal

Be ready to argue where wait durations should come from across a suite — literals in the chain, session data, or a policy declared once on setUp.

Gatling's `pause` is the step a virtual user uses to do nothing for a while. It sits in a chain exactly like a request does — an `exec(...)` before it, an `exec(...)` after it — and its whole job is to make that one user wait a declared amount of time before moving on. ## The single-argument form `pause(10)` waits ten seconds. The rule that catches people is the unit: **a bare number is a count of seconds**, not milliseconds, in every Gatling SDK. In the Java API the overload is `pause(long duration)`, documented as "the pause duration in seconds"; in Scala the same bare number works because `io.gatling.core.Predef` declares `implicit def intToFiniteDuration(i: Int): FiniteDuration = i.seconds`. So `pause(100)` parks the user for a minute and forty seconds, which is almost never what someone who meant a tenth of a second intended. When you want a different unit, hand the step a typed duration instead. Each SDK spells that in its own type, and this is one of the places where the SDKs genuinely diverge: | SDK | how you write 100 ms | |---|---| | Java, Kotlin | `pause(Duration.ofMillis(100))` — a `java.time.Duration` | | Scala | `pause(100.millis)` — a `scala.concurrent.duration.FiniteDuration` | | JavaScript, TypeScript | `pause({ amount: 100, unit: "milliseconds" })` — an object literal | | all of them | `pause(100)` — a bare number, meaning **seconds** | ## The two-argument form `pause(1, 4)` is a *uniform random* wait between one and four seconds. It is not four one-second pauses, and it is not a fixed wait with a fixed jitter. What Gatling does is convert both bounds to milliseconds and call `ThreadLocalRandom.current.nextLong(minMillis, maxMillis)`, whose upper bound is exclusive; when the two bounds are equal it short-circuits and returns the bound itself. Two properties follow from that, and both matter when you read a report: * **The draw is per virtual user.** Two users arriving at the same `pause(1, 4)` at the same instant get independent values. * **The draw is per visit.** A user that loops back to the same step gets a new value each lap; nothing is cached in the session. That is precisely why the two-argument form de-synchronises a population that would otherwise march in lockstep, and it is the reason a scenario built from fixed pauses can produce a lumpy request pattern that a random range smooths out. ## The other two ways to say how long Both forms also accept a Gatling Expression Language string and a function of the session, so the duration can come from data rather than from a literal: * `pause("#{thinkTime}")` resolves the session attribute `thinkTime`. One `FiniteDuration` caster does that job for every SDK, and it accepts five shapes: an `Int` or a `Long` (read as seconds), a `String` that parses as a whole number (also read as seconds — which is what makes a CSV-fed attribute work, since CSV feeders yield nothing but `String`s), a `scala.concurrent.duration.FiniteDuration`, or a `java.time.Duration`. So the duration type here is *not* SDK-specific the way the literal argument in the table above is: a `java.time.Duration` resolves in a Scala simulation and a `FiniteDuration` resolves under the Java API. Anything outside those five shapes fails the virtual user at run time, not at compile time. Gatling's EL has used `#{...}` exclusively since 3.11.0, which removed the older `${...}` form, so `"${thinkTime}"` is no longer an expression at all. * `pause(session -> Duration.ofMillis(100))` in Java, or `pause(session => 100.millis)` in Scala, computes the wait from whatever the session holds. The same four shapes — bare number, typed duration, EL string, function — are available for the minimum and the maximum of the two-argument form. ## What a pause is not 1. **It is not a request.** The pause action only schedules the continuation; it never calls the stats engine, so a pause produces no row in the statistics table and no point on any response-time chart. Time spent pausing is invisible in the per-request numbers. 2. **It does not hold a thread.** `Pause` calls `session.eventLoop.schedule(...)` and returns. A paused virtual user costs a scheduled task and its session, which is why a single load generator can hold a very large number of users in think time at once. 3. **It is not `pace`.** `pause` is unconditional: it adds its duration on top of whatever the preceding steps took. The step that measures elapsed time and waits only the remainder is `pace`. 4. **Its duration is not necessarily what you wrote.** The number you pass is the input to a run-wide pause *type*, and the built-in default — constant, meaning "exactly this" — is only the default. Change the type on `setUp` and the same `pause(2)` becomes the mean of a distribution instead of a fixed wait. ## Naming the step in each SDK `pause` is spelled identically in all five authoring languages, so nothing about the name changes between Java, Kotlin, Scala, JavaScript and TypeScript. What changes is the duration argument in the table above, and — in Scala only — the fact that `pause(10)` is reaching an implicit conversion rather than a dedicated overload.

  • What must the session hold for pause("#{thinkTime}") to work?
    Any of five shapes, because a single FiniteDuration TypeCaster resolves the expression for every SDK: an Int or a Long, read as a count of seconds; a String that parses as a whole number, also read as seconds — which is why a CSV-fed attribute works, since CSV feeders yield nothing but Strings; a scala.concurrent.duration.FiniteDuration; or a java.time.Duration. The duration type is therefore not SDK-specific: a java.time.Duration resolves fine in a Scala simulation and a FiniteDuration resolves fine under the Java API. Only a value outside those five shapes — or a String that is not a whole number — fails, and it fails the virtual user at run time rather than at compile time, because the expression is only resolved when a user actually reaches the step.
  • Does a virtual user sitting in a pause hold a thread?
    No. The pause action schedules the continuation on the virtual user's event loop and returns, so a paused user costs a scheduled task and its session rather than an OS thread. That is what lets one load generator hold a very large number of users in think time at the same time.
  • Is the upper bound of pause(1, 4) inclusive?
    Not quite. Gatling converts both bounds to milliseconds and calls ThreadLocalRandom.nextLong(min, max), whose upper bound is exclusive, so a draw can be 3999 ms but never a full 4000 ms. It never matters in practice, but it is what the code does.

saying these in an interview costs you the question

  • Reading pause(100) as one hundred milliseconds.
  • Thinking pause(1, 4) waits one second four times.
  • Assuming the random value is drawn once and reused.
  • Expecting pauses to appear as rows in the report.
open as a page

In a Gatling scenario, what does pace(5) do that pause(5) does not, and which of the two holds a virtual user's request rate steady when the server slows down?

level: middleimportance: must knowfreq 60%

basics

~20 s

pace holds an iteration rate, pause does not. pause(5) always adds five seconds on top of the work; pace(5) waits only the remainder of a five-second window measured from the user's previous visit to that step.

open as a page

In a Gatling simulation, what does calling exponentialPauses() on setUp change about every pause(2) in the scenario, and how do you exempt a single one of them?

level: middleimportance: should knowfreq 38%

basics

~20 s

It turns the declared duration into a mean: every pause(2) becomes a draw from an exponential distribution averaging two seconds. To exempt one call, pass a pause type as its extra force argument, for example pause(2, constantPauses).

open as a page

In a Gatling scenario, what does rendezVous(100) do to the virtual users that reach it, and what happens when a user reaches it a second time?

level: seniorimportance: should knowfreq 30%

basics

~20 s

rendezVous(100) holds each arriving virtual user until a hundred are waiting, then releases all of them together. It then becomes a permanent pass-through, so every later arrival, including a user's second visit, goes straight through without waiting.

open as a page

Across a suite of Gatling simulations, would you declare the waiting policy on each pause call, on the injected population, or on setUp, and how would you decide?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Default to setUp, stated explicitly even when it matches the constant default, so one visible line governs the run. Drop to the population only when two user profiles differ; force one call only when that wait is a protocol requirement.

open as a page