Skip to content

fix: fall back when Redis has no BLMOVE instead of crash-looping - #21

Merged
adhikjoshi merged 2 commits into
mainfrom
fix/blmove-fallback-for-redis-6.0
Aug 21, 2026
Merged

fix: fall back when Redis has no BLMOVE instead of crash-looping#21
adhikjoshi merged 2 commits into
mainfrom
fix/blmove-fallback-for-redis-6.0

Conversation

@adhikjoshi

@adhikjoshi adhikjoshi commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

The failure

BLMOVE needs Redis ≥ 6.2. Every GPU worker container in production is still on Redis 6.0.16, and 1.0.17 — which introduced the BLMOVE-based claim — calls it unconditionally.

Reproduced on a live flux-klein box while upgrading it:

ERROR - Worker 0 crashed with error: unknown command `BLMOVE`, with args
        beginning with: `ml_tasks`, `inflight:18187a468ab3:0`, `LEFT`, `RIGHT`, `15`,

…repeating roughly thirty times a second, forever. The worker never registered, ml_tasks never drained, and nothing looked wrong from outside — ps showed modelq running and burning CPU, while every request came back "status": "queued" with an ETA climbing 30s at a time.

Nothing in production has hit this yet only because every deployed container still runs an older ModelQ (0.1.21 / 1.0.9 / 1.0.10 / 1.0.14 across the boxes I checked). Worker requirements.txt files already pin modelq==1.0.17, so the next image rebuild would have.

The fix

Probe BLMOVE once, cache the answer, and on unknown command claim tasks with an atomic Lua LPOP+RPUSH polled on a short interval.

Both properties the BLMOVE claim exists for are preserved:

  • Atomic — the task is never absent from both the queue and the in-flight list.
  • FIFO — claims come off the head, matching the rpush producer.

Only the blocking is emulated.

BRPOPLPUSH is deliberately not the fallback. It is the obvious Redis 6.0 substitute and it is atomic — but it pops the tail, which against an rpush producer silently turns the queue LIFO and starves the oldest request.

Only unknown command disables BLMOVE. Any other ResponseError (LOADING, etc.) leaves a capable server on the fast path.

Tests

tests/test_blmove_fallback.py — claim, atomicity, FIFO order, empty-queue timeout, probe-once (the crash loop was 30 exceptions/second), the modern-Redis control case, and the unrelated-error case.

Mutation-tested: forcing _blmove_supported() to return True turns five of them red with the exact production ResponseError.

Test extra bumped to fakeredis[lua] so the Lua path is actually exercised.

Full suite: 114 passed.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

BLMOVE needs Redis >= 6.2. Every GPU worker container in production is still on
Redis 6.0.16, and 1.0.17 (which introduced the BLMOVE claim) calls it
unconditionally. Upgrading a worker onto that Redis produces:

  ERROR - Worker 0 crashed with error: unknown command `BLMOVE`, with args
          beginning with: `ml_tasks`, `inflight:18187a468ab3:0`, `LEFT`, ...

about thirty times a second, forever. Reproduced on a live flux-klein box: the
worker never registered, the queue never drained, and nothing looked wrong from
outside -- `ps` showed modelq running and burning CPU while every request came
back "queued" with an ever-growing ETA. Nothing in production has hit this yet
only because every deployed container still runs an older ModelQ; requirements
files already pin 1.0.17, so the next rebuild would have.

Probe BLMOVE once, cache the answer, and on "unknown command" claim tasks with
an atomic Lua LPOP+RPUSH polled on a short interval instead. Both properties the
BLMOVE claim exists for are preserved: the task is never absent from both lists,
and the queue stays FIFO. Only the blocking is emulated.

BRPOPLPUSH is deliberately not used as the fallback. It is the obvious Redis 6.0
substitute and it is atomic, but it pops the tail -- against an rpush producer
that silently turns the queue LIFO and starves the oldest request.

Only "unknown command" disables BLMOVE; any other ResponseError (LOADING, etc.)
leaves a capable server on the fast path.

tests/test_blmove_fallback.py covers claim, atomicity, FIFO order, empty-queue
timeout, probe-once, the modern-Redis control case, and the unrelated-error
case. Mutation-tested: forcing _blmove_supported() to True turns five of them
red with the exact production error. Test extra bumped to fakeredis[lua].
The Redis 6.0 fallback claims tasks with an atomic Lua LPOP+RPUSH. Without the
lua extra, fakeredis answers EVALSHA with 'unknown command' and the fallback
tests fail for a reason that has nothing to do with the code under test.
@adhikjoshi
adhikjoshi merged commit a2a7e23 into main Aug 21, 2026
3 checks passed
@adhikjoshi
adhikjoshi deleted the fix/blmove-fallback-for-redis-6.0 branch August 21, 2026 08:23
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.

1 participant