feat: add concurrency limit to Open Model Thread Group (#5966)#6727
Open
vlsi wants to merge 2 commits into
Open
feat: add concurrency limit to Open Model Thread Group (#5966)#6727vlsi wants to merge 2 commits into
vlsi wants to merge 2 commits into
Conversation
OMTG created a new JMeterThread and a fresh test-plan clone for every arrival with no upper bound, so a server that responds slower than arrivals are scheduled made JMeter pile up threads until it ran out of memory. Users also asked to model a system with a hard concurrency limit, such as a fixed-size connection pool. Add a positional concurrency(N) schedule step, alongside rate(...), that caps the number of threads running at the same time. The cap may vary over time: concurrency(100) ... concurrency(200) applies 100 for the first window and 200 for the next. ConcurrencyLimit reads the cap by the time elapsed since the schedule start rather than by the arrivals generator position, so the cap still rises while the producer is blocked waiting for a free thread. ConcurrencyGate reserves a slot before cloning the test plan, so a capped arrival waits instead of allocating a clone, which bounds memory by the limit. An arrival that still has no free thread when the schedule ends is dropped rather than stretching the test past its nominal end. Add openmodel.max_concurrency as an installation-wide safety net for schedules that set no concurrency(...) of their own; it defaults to unlimited to preserve existing behavior. Closes apache#5966 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The schedule preview only drew the target arrival rate. Show the concurrency limit too, without overloading a single axis: rate and thread count are different units, so a shared or secondary Y axis would invite a false visual correlation. A single limit that applies from the start is shown as a "Max threads: N" subtitle. A limit that changes over time is drawn as a second panel stacked below the rate, sharing the time axis (gggrid sharex + align). The limit uses geomStep, not geomLine, because it is piecewise-constant and jumps between windows rather than ramping. Extract TargetRateChart.buildSpec so the lets-plot specification can be tested without the Swing/Batik rendering. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Open Model Thread Group creates a new
JMeterThreadand a fresh test-plan clone for every arrival, with no upper bound. When the server responds slower than arrivals are scheduled, threads pile up until JMeter runs out of memory — several users report thousands to tens of thousands of threads for a load a handful of threads could serve (#5966). A second, deliberate use case is modeling a system with a hard concurrency limit, such as a fixed-size connection pool.What
Add a positional
concurrency(N)schedule step, alongsiderate(...), that caps the number of threads running at the same time. Likerate, it is positional and may vary over time:caps the load at 100 threads for the first ten minutes and 200 for the next.
ConcurrencyLimitreads the cap by the time elapsed since the schedule start, not by the arrivals-generator position, so the cap still rises while the producer is blocked waiting for a free thread.ConcurrencyGatereserves a slot before cloning the test plan, so a capped arrival waits for a free thread instead of allocating a clone. This bounds memory by the limit rather than by the arrival rate.openmodel.max_concurrencyis an installation-wide safety net for schedules that set noconcurrency(...)of their own. It defaults to unlimited to preserve existing behavior.The design leaves room for the
thread_lifetime/ thread-reuse ideas discussed in the issue:concurrencycounts active threads, so a future clone pool can sit under the gate without changing the meaning of existing schedules.How to verify
New tests in
src/core/.../threads/openmodel/:ConcurrencyLimitTest— the cap changes on the arrival boundary regardless of generator position.ConcurrencyGateTest— blocking, deadline drop, release-unblocks-waiter, and interruptibility of a blocked producer.ConcurrencyParseErrorTest—concurrency(0), non-integer, and malformed input are rejected.ConcurrencyLimitBehaviorTest— end-to-end: at most N threads run at once and delayed arrivals are counted.ThreadScheduleTest— parser cases forconcurrency(...).Open question
openmodel.max_concurrencydefaults to unlimited, so an unconfigured plan can still hit OOM — the graceful-by-default behavior asked for in the issue would need a non-zero default (e.g. 10000), which changes behavior for existing plans. Left as unlimited here; happy to switch the default if that is preferred.🤖 Generated with Claude Code