Repository navigation
Conversation
A poll locked its request FOR UPDATE and, for an approved one, asked the pool for a second connection to read the user and the scope's repositories, while every other poll of the same code held a connection waiting for that lock. A handful of concurrent polls took the whole pool, and every request of the instance waited for the acquire timeout and failed, until the request expired. The poll now locks the request FOR UPDATE NOWAIT and answers slow_down at once when another poll holds it, and collect reads the user and the repositories on the transaction's own connection, so a poll never waits for a connection or a lock while it holds one. find_repository_by_name takes any executor for that. The test polls one approved code six times at once on a pool of two connections: one poll gets the token, every poll answers within seconds, and a request beside them is served (before: 10 s, the acquire timeout). Fixes #794 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012e6DJackg4JuAo1ir7BJjS
schmitch
added this pull request to the merge queue
Nov 3, 2026
Any commits made after this event will not be merged.
This branch has not been deployed
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 #794
Problem
polllocked the request rowFOR UPDATE. For an approved request,collectthen asked the pool for a second connection (user_dao::find_user_by_id(&state.pool, …), andrepository_dao::find_repository_by_name(&state.pool, …)for a scope that names repositories). Meanwhile every other poll of the samedevice_codeheld a connection while it waited in PostgreSQL for the row lock. With about as many concurrent polls as the pool has connections, the lock holder could not get its second connection. After the acquire timeout it answered 503, and the next poll took the lock and needed a second connection too. Every other database-backed request on the instance waited for the acquire timeout and failed the same way, until the request expired.Change
device_auth_dao::lock_for_pollusesFOR UPDATE NOWAITand returnsPollLock::{Locked, Busy, Unknown}. When another transaction holds the row (SQLSTATE55P03) the answer isBusy, which comes back at once.pollanswersBusywithslow_down(RFC 8628 §3.5) and drops the aborted transaction, so a concurrent poll never waits for the lock while it holds a connection. The interval is not raised in this case, because the row is locked.collectreads the user and resolves the scope's repositories on the transaction's own connection, so a poll never waits for a second connection while it holds the lock.repository_dao::find_repository_by_namenow takes anyPgExecutor(same query, so existing callers are unchanged)..sqlxis regenerated for theNOWAITquery.slow_downcase.Test
crates/server/tests/device_auth.rs,concurrent_polls_of_an_approved_code_leave_the_pool_alone: on a server whose pool has 2 connections (database.max_connections = 2, the default 10 s acquire timeout), six concurrent polls of one approved code, with a scope naming a repository so the repository lookup runs. Alongside them, a signed-in request lists the user's tokens. The test checks that exactly one poll gets the token, that the others answerslow_downorexpired_token, and that every poll and the request beside them answer within 5 s. The harness getscommon::sized_pool(max_connections, acquire_timeout), which builds the pool the waymaindoes.Without the fix the test fails:
a request beside the polls took 10.051845858s, which is the acquire timeout. With it, all 9device_authtests pass.cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo sqlx prepare --checkandcargo xtask changelog --checkpass too.Changelog
No fragment. The device login is new in 2.2.0 (#649) and has not been released, so 2.2.0's notes have no bug to list as fixed. Its feature entry (
docs/changes/2.2.0/649.feature.md) needs no change. The issue moved from 2.3.0 to the 2.2.0 milestone as a release blocker.Generated by Claude Code