Repository navigation
Lock the round when activating it, so a double click can't create every task twice - #626
Merged
Merged
Conversation
…e twice Two activation requests a few seconds apart (a double click while the first is still creating tasks) both read the round as paused and each created a full set of juror tasks. On production this left 22 rounds with every task doubled (one with every task tripled), and jurors voted on both copies, so their vote counted twice in the round's averages (#334). activate_round now re-reads the round with SELECT ... FOR UPDATE (and populate_existing, so no stale copy from the session) before checking its status. A second activation waits for the first to commit, then sees the round active and is refused. The Activate button is disabled while its request runs. The new test commits the round as active from another connection after the request has loaded it; it fails without the lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 2, 2026
This was
linked to
issues
Oct 4, 2026
Resolves the conflict with #632 (#619) in montage/tests/test_web_basic.py: both PRs added a test at the same spot; both are kept unchanged (the concurrent-activation test for #334, then the batched task insert test). rdb.py merged cleanly: activate_round takes the round lock before create_initial_tasks, which now inserts in batches. Co-Authored-By: Claude Opus 5.5 <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.
Fixes #334 ("Photos appear in the voting sequence twice"). The main cause turns out to be a double click on
Activate, which gives every juror a second copy of every task.
What goes wrong
Activating a round creates all juror tasks inside the request, which can take seconds for a large round. If
the coordinator clicks Activate again while the first request is still running, the second request also reads
the round as
paused, and creates a second full set of tasks. Nothing stops this:activate_roundchecks thestatus without a lock, and the button stays clickable.
On production this has happened in at least 22 rounds since April 2025: every task doubled (one round tripled,
after three clicks within 18 seconds). The audit log shows two or three
open_roundentries a few seconds apartfor each of them. Jurors voted on both copies of almost every duplicated task, often differently, and the
round average used to decide which entries advance counts both votes.
The fix
activate_roundre-reads the round withSELECT ... FOR UPDATEbefore checking its status. A secondactivation waits for the first to commit, then sees the round active and gets the normal "can only activate
round in a paused state" error.
Tests
test_concurrent_activation_creates_no_duplicate_tasks: another connection commits the round asactive after the request has loaded it. It fails on master (the activation creates a second set of tasks)
and passes with the fix. SQLite ignores
FOR UPDATE, so the test covers the re-read; the lock itself wastested on MariaDB, below.
created exactly one task per entry; two activation requests sent at the same moment gave 200 and 400 (the
second waited for the first's lock), again one task per entry.
Not in this PR
gets a 500 instead of the clean error. Still no duplicates. The upcoming import-worker PR turns this into a 400.
When the import-worker PR is rebased on this,
activate_roundconflicts: keep the import-worker version,which has the same lock.
Written with Claude (Claude Code) on behalf of @lgelauff.
🤖 Generated with Claude Code