Skip to content

feat: add a queue reset so a repeated push cannot enqueue work twice - #27

Merged
beagleknight merged 1 commit into
mainfrom
feat/reset-queue
Aug 12, 2026
Merged

feat: add a queue reset so a repeated push cannot enqueue work twice#27
beagleknight merged 1 commit into
mainfrom
feat/reset-queue

Conversation

@beagleknight

Copy link
Copy Markdown
Collaborator

Problem

A backend spec failed in factorial CI with a T::Struct prop redefinition error. It looked like a Sorbet/Tapioca problem. It was not: the same spec file was loaded twice in one process.

The queue key is scoped by CI run, not by CI attempt. That is deliberate. Re-running a single failed runner does not re-run the job that pushed, so the runner must still find the queue and the published marker that the first attempt created.

The cost is that the producer is not idempotent:

  • A producer that pushes and then fails leaves the work list orphaned, because its runners never start.
  • A producer that runs again (whole-workflow re-run, or a failed-jobs re-run that includes it) appends a second copy on top.

Every file is then enqueued twice. The suite runs twice, and any two copies that reach the same worker are loaded twice in one process. For test files that define constants at file scope, the second load is fatal.

Change

RedisQueue#clear removes the queue and its <key>:published marker in a single DEL. It is exposed two ways:

specbandit push --key KEY --reset    # empty the key, then push
specbandit reset --key KEY           # standalone

Reset is opt-in. push keeps its current meaning and appending to a queue stays possible.

What reset does not touch

Per-runner rerun keys and failed keys. A runner's replay memory has to survive a re-run of that runner. A runner that finds data in both the shared queue and its rerun key is the full-rerun case, already handled in v1.2.0: it resets its own memory and steals.

Nothing is cleared when there is nothing to push in its place. Dropping the marker on its own would make every worker on that key crash as "never published".

Tests

  • RedisQueue#clear issues one DEL with both key names.
  • Publisher: clear happens before the push, only with reset: true, never on an empty push, and the leftover count is reported.
  • Integration, against a real Redis: two plain pushes leave 14 entries, a third push with --reset leaves 7 and the marker survives.

Also fixes a leak in the existing failed-key integration test, which deleted <key>-failed but not its :published marker, so every run left a key behind.

Follow-up, not in this PR

The flaky-retry pass consumes <key>-failed-<n>, which is written by work, not push, so a re-run can still leave stale entries there. The blast radius is re-running an already-failed spec, not a double load.

Next

Needs a Release dispatch (bump: minor → v1.3.0) before factorial can adopt it. The npm package needs the same change; that PR is separate.

The queue key is scoped by CI run, not by CI attempt. That is deliberate:
re-running a single failed runner does not re-run the job that pushed, so
the runner must still find the queue and the published marker the first
attempt created.

The cost is that the producer is not idempotent. A producer that pushes
and then fails, or that is re-run with the whole workflow, appends a
second copy of the work list. Every file is then enqueued twice, the
suite runs twice, and any two copies that reach the same worker are
loaded twice in one process. That last part is fatal for test files that
define constants at file scope.

Add RedisQueue#clear, which removes the queue and its published marker in
one DEL, and expose it two ways:

- `specbandit push --reset` empties the key immediately before the RPUSH.
- `specbandit reset --key KEY` does the same as a standalone command.

Reset is opt-in, so `push` keeps its current meaning and appending to a
queue stays possible.

Per-runner rerun and failed keys are left alone, so a single-runner
re-run can still replay its own files. A runner that finds data in both
the shared queue and its rerun key is already handled by the full-rerun
path.

Nothing is cleared when there is nothing to push in its place: dropping
the marker on its own would make every worker on that key crash as
"never published".
@beagleknight
beagleknight merged commit d76721b into main Aug 12, 2026
3 checks passed
@beagleknight
beagleknight deleted the feat/reset-queue branch August 12, 2026 10:02
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