Skip to content

DB.advisory_lock and LockTimeoutError (Postgres, transaction-scoped) - #342

Open
tmgbedu wants to merge 2 commits into
mainfrom
feat/advisory-lock
Open

tmgbedu wants to merge 2 commits into
mainfrom
feat/advisory-lock

Conversation

@tmgbedu

@tmgbedu tmgbedu commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Task #3834. Adds transaction-scoped Postgres advisory locks so apps no longer run raw SET LOCAL lock_timeout / pg_advisory_xact_lock SQL or catch driver exceptions.

API

async with DB.connection().transaction():
    await DB.advisory_lock("oauth:refresh:42", timeout_ms=3000)
  • DB.advisory_lock(key, timeout_ms=None, connection=None) and Connection.advisory_lock(key, timeout_ms=None) run pg_advisory_xact_lock. Postgres releases the lock at commit or rollback. There is no session-scoped variant.
  • DB.lock_timeout(timeout_ms, connection=None) / Connection.lock_timeout(timeout_ms) set a transaction-local timeout on its own.
  • A str key is hashed with blake2b (8 bytes, signed big-endian), which is stable across processes and Python/Postgres versions. hashtext() is internal, 32-bit, and has changed before; hash() is randomised per process. An int key is used as-is and must fit bigint.
  • The timeout is validated as an int in 0..2147483647 and bound through set_config('lock_timeout', :ms, true). Nothing is interpolated.
  • SQLSTATE 55P03 raises LockTimeoutError(key, timeout_ms), exported from fastapi_startkit.masoniteorm and chained from the driver error.
  • Calling either method with no open transaction raises RuntimeError. Non-Postgres connections raise NotImplementedError.

ADR 060 is added and indexed. 058 is taken by #341 and 059 by the in-progress subquery task. The transaction modules are untouched. DB.py additions sit above the transaction methods to avoid conflicting with #340.

Validation

  • uv run pytest --ignore=tests/masoniteorm/postgres --cov: 3762 passed, coverage 92.77%.
  • New unit tests (tests/masoniteorm/connections/test_advisory_lock.py, 35) cover:
    • hashing with known values, bounds and type errors
    • timeout validation
    • the no-transaction errors
    • bound SQL
    • 55P03 mapping and pass-through of other driver errors
    • SQLite NotImplementedError
    • facade forwarding
  • Live Postgres tests (tests/masoniteorm/postgres/test_advisory_lock.py, 9) passed against a throwaway postgres:17 docker container (POSTGRES_PORT=55434). They cover:
    • the lock is held, and an int key shows in pg_locks
    • a second transaction blocks until the holder commits
    • a timeout raises LockTimeoutError and the connection is released
    • the lock is released on commit and on rollback
    • a lock taken in a savepoint is held until the outer transaction ends
    • lock_timeout returns to its previous value after the transaction (same backend pid)
    • the facade works with a named connection

🤖 Generated with Claude Code

tmgbedu and others added 2 commits October 31, 2026 14:29
Transaction-scoped pg_advisory_xact_lock with str keys hashed by blake2b
to a signed bigint, a transaction-local lock_timeout set through a bound
set_config value, and SQLSTATE 55P03 mapped to LockTimeoutError. Other
drivers raise NotImplementedError; calls outside a transaction raise
RuntimeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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