Skip to content

Align queue concurrency override handling with River Pro - #704

Merged
bgentry merged 4 commits into
masterfrom
bg/concurrency-override-cleanup
Oct 12, 2026
Merged

bgentry merged 4 commits into
masterfrom
bg/concurrency-override-cleanup

Conversation

@bgentry

@bgentry bgentry commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

River Pro is standardizing how it reads queue concurrency overrides in river_queue.metadata.concurrency: a missing key, null, and {} all mean "no override", and any non-empty object is a full override where omitted fields are zero (no limit) and nothing is merged field by field. River UI is the main writer of these overrides, so this brings its reads and writes in line with those semantics.

The queue update endpoint now rejects negative global_limit or local_limit values with a 400 instead of storing them. Before, the form's min="0" was the only guard. When reading queues, {"concurrency": {}} is now treated as no override instead of an enabled 0/0 override. The producer list no longer reports a zero config for producers whose metadata has no concurrency config. Limiter state such as running doesn't count as config on its own.

The clear path still writes {}, and the frontend is unchanged because it already treats a null concurrency as no override. New tests cover the validation, every read case, and clearing via {"concurrency": null} and setting a full override from real JSON request bodies, including what ends up in the queue's metadata.

The queue update endpoint stores whatever `global_limit` and
`local_limit` it receives in `river_queue.metadata.concurrency`. The
form's `min="0"` attribute is the only guard, so a direct API call
(or a crafted request) can persist a negative limit that River Pro has
no meaningful interpretation for.

Validate the concurrency override before opening a transaction and
return a 400 via `apierror.NewBadRequestf` when either limit is
negative, matching the endpoint's other bad request responses. Zero
remains valid because it means "no limit". Rejecting the request up
front keeps invalid configuration out of the database entirely rather
than relying on every reader to cope with it.

Add queue update tests covering a negative global limit and a negative
local limit.
River Pro treats a missing `concurrency` key, `null`, and `{}` in
`river_queue.metadata` as "no override", and any non-empty object as a
full override. The queue serializer unmarshals the key into a
`*ConcurrencyConfig`, which yields nil for a missing key or `null` but a
non-nil zero config for `{}`. The queue detail page then shows the
override toggle as enabled with 0/0 limits even though River Pro is
using each client's own configuration.

Extract the override through `queueConcurrencyFromMetadata`, which
decodes the raw `concurrency` value into a map first and returns nil
when it is absent, `null`, empty, or not an object. Only a non-empty
object decodes into a `ConcurrencyConfig`, with omitted fields left at
zero rather than merged, mirroring River Pro's semantics. The frontend
already treats a `null` concurrency as "no override", so no UI change is
needed.

Add unit tests for empty metadata, a missing key, `null`, `{}`,
malformed values, a full override, and a partial override, plus a queue
get endpoint test showing `{"concurrency": {}}` is returned as no
override.
The producer list decodes `metadata.concurrency` into a non-pointer
`ConcurrencyConfig` and always returns its address when the metadata
parses. A producer whose metadata has no `concurrency` key, `null`, or
`{}` is therefore reported with a zero config instead of none. The
queue detail page uses producer configs to prefill the concurrency
form and to decide whether clients agree, so these phantom zero
configs show up as 0/0 limits and can make clients look inconsistent.

Extract the config through `producerConcurrencyFromMetadata`, which
returns nil unless the `concurrency` object contains at least one of
`global_limit`, `local_limit`, or `partition`. Producers also keep
limiter state like `running` in that object, so it is ignored when
deciding whether a config is present. The API type and the frontend's
`ConcurrencyConfig | null` producer type already allow a missing
config, and the comparison logic handles `null`, so no frontend change
is needed.

Add unit tests for empty metadata, a missing key, `null`, `{}`,
running state without config, malformed values, explicit zero limits,
and a full config.
The queue update endpoint distinguishes an omitted `concurrency` field,
an explicit `null`, and an object through `ExplicitNullable`, but only
the object case has coverage, and none of the tests decode the request
from JSON or check what lands in `river_queue.metadata`. A regression
in how `null` decodes or in what the clear path writes would go
unnoticed.

Add queue update tests that decode real JSON request bodies. Clearing
with `{"concurrency": null}` replaces an existing override with `{}`
metadata and returns no override, which River Pro reads as "no
override". Setting a full object stores it verbatim, including a zero
`local_limit` and sorted `partition.by_args`, and returns the same
config.
@bgentry
bgentry force-pushed the bg/concurrency-override-cleanup branch from 0458652 to 2cbda6b Compare October 12, 2026 03:17
@bgentry
bgentry marked this pull request as ready for review October 12, 2026 21:02
@bgentry
bgentry merged commit 012f377 into master Oct 12, 2026
22 checks passed
@bgentry
bgentry deleted the bg/concurrency-override-cleanup branch October 12, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants