In JMeter's Asynch sending mode, what happens when the asynch.batch.queue.size queue fills?
answer
- A bounded buffer, so it can run out
- The producer is a sampler thread
- The fallback call is the blocking one
- One conditional log line proves it happened
basics
~20 sThe sampler thread blocks. AsynchSampleSender first tries a non-blocking offer; when that fails it falls back to a blocking put and waits for the worker to drain space, counting the wait in QueueWaits and QueueWaitTime.
solid answer
~40 s`Asynch` mode gives each engine a bounded `ArrayBlockingQueue`, sized by `asynch.batch.queue.size` (default `100`), plus a daemon worker thread that drains it and forwards events with `processBatch`. The point is to keep the sampler thread off the network: normally it enqueues and moves on. When production outruns the worker the queue fills, `queue.offer(e)` returns false, and the sender falls back to `queue.put(e)`, which blocks the sampler thread until the worker frees a slot. That back-pressure happens after the sample has been recorded, so the sample's own elapsed time is not inflated, but the thread's next iteration is delayed. The engine's `jmeter-server.log` reports `QueueWaits` and `QueueWaitTime` in nanoseconds at test end, and only when at least one wait occurred — so the absence of that line is itself the all-clear.
code
text · 5 lines# jmeter-server.log on the engine, as the run starts
2026-09-09 10:14:02,118 INFO o.a.j.s.AsynchSampleSender: Using batch queue size (asynch.batch.queue.size): 100
# same file, written by testEnded - and ONLY when at least one thread had to wait
2026-09-09 10:31:47,904 INFO o.a.j.s.AsynchSampleSender: QueueWaits: 4821; QueueWaitTime: 913442000 (nanoseconds)go deeper
Recall that Asynch is one of the mode values and that asynch.batch.queue.size, default 100, bounds its queue. The detailed back-pressure behaviour is not expected at this level.
Explain the producer-consumer shape: a bounded queue on the engine, a daemon worker draining it into processBatch, and a blocking put once the non-blocking offer fails.
Show that you would look for the QueueWaits line in the engine log before theorising, and that you know a larger queue delays back-pressure rather than removing it.
Weigh whether decoupling sampler threads from the network is worth the engine heap a large queue holds, and whether the fleet is better served by more engines than by deeper buffers on each one.
## What Asynch changes Under `Standard` and `Batch`, `sampleOccurred` runs on the sampler thread and, when a send is due, that thread makes the RMI call itself. `AsynchSampleSender` breaks that coupling. Its `readResolve` — which runs on the **engine**, as the sender is deserialised — creates an `ArrayBlockingQueue<SampleEvent>` of `asynch.batch.queue.size` entries (default `100`) and starts a daemon `Worker` thread. From then on: 1. the sampler thread calls `queue.offer(e)` and returns immediately when there is room; 2. the worker blocks on `queue.take()`, then drains everything else with `queue.poll()`; 3. the worker forwards the drained list in one `processBatch` call; 4. at `testEnded`, a sentinel event is queued to stop the worker cleanly. Note that `Asynch` is *also* a batching mode — the worker groups whatever it can drain — but it does **not** read `num_sample_threshold` or `time_threshold`. Its batch size is whatever the queue happened to hold. That is why the manual describes it as smoothing peaks in sample generation. ## When the queue fills The queue is bounded, so it can only absorb a burst; it cannot absorb a sustained excess. When the worker cannot forward events as fast as the samplers create them: - `queue.offer(e)` returns `false`; - the sender increments `queueWaits`, takes a `System.nanoTime()` reading, and calls `queue.put(e)`; - `put` blocks the **sampler thread** until the worker frees a slot; - the elapsed nanoseconds are added to `queueWaitTime`. The block happens in the listener notification that follows the sample, not during it, so the sample's own `elapsed` value is untouched. What is affected is the thread's next iteration: it starts later than it otherwise would. Naming that effect precisely matters — attributing an observed throughput shortfall to the generator rather than to the system under test is performance-testing fundamentals material, and this leaf only tells you where JMeter records the evidence. ## How you know it happened `AsynchSampleSender` logs twice, both into the engine's log file: - at start-up, the resolved capacity: `Using batch queue size (asynch.batch.queue.size): 100`; - at `testEnded`, and **only if `queueWaits > 0`**: `QueueWaits: 4821; QueueWaitTime: 913442000 (nanoseconds)`. Because the second line is conditional, its absence means no sampler thread ever waited. That is an unusually clean signal for a JMeter internal, and it is the reason `Asynch` is worth reaching for when you want the question answered rather than assumed. ## Sizing it, and what it does not fix | Symptom | Does Asynch help? | |---|---| | sampler threads stalling on the RMI call | yes — that is exactly the coupling it removes | | bursty sample generation, adequate average rate | yes — the queue absorbs the peak | | controller cannot keep up with the sample volume | no — every event still arrives | | engine short of heap | no, and a large queue makes it worse | `asynch.batch.queue.size`, like the batch thresholds, is read into an instance field on the controller and into a static field on the engine, with `sample_sender_client_configured` (default `true`) choosing between them at `readResolve`. So by default you size the engines' queues from the controller's properties file, despite the manual saying the property is set on the server node. `StrippedAsynch` is the same sender with a `DataStrippingSampleSender` in front of it, and is the variant to prefer if you do not need response bodies — smaller events drain faster, so the queue fills less often.
- Does a full Asynch queue inflate the response times JMeter records?No. The block happens in `sampleOccurred`, which runs after the sampler has finished and the result's `elapsed` value is already fixed. What it delays is the thread's next iteration, so the achieved rate falls while the recorded per-sample timings stay clean.
- Does Asynch mode honour num_sample_threshold and time_threshold?No. `AsynchSampleSender` reads only `asynch.batch.queue.size`. Its worker takes one event and then drains whatever else is available, so batch size is whatever the queue held at that moment rather than a configured count or interval.
- Would simply making the queue much larger fix sustained back-pressure?No — a bigger bound only buys a longer burst. If the worker cannot forward events as fast as the samplers create them, the queue reaches any size you pick and then blocks, while the extra retained events cost engine heap in the meantime.
saying these in an interview costs you the question
- Says surplus samples are dropped when the queue is full
- Thinks the queue lives on the controller rather than the engine
- Claims blocking inflates the recorded elapsed time of samples
- Believes Asynch mode reduces what the controller receives
- Expects Asynch to honour num_sample_threshold and time_threshold
- Treats a much larger queue as a fix for sustained overload