Skip to content

Cross-participant race: force-close updateMany not covered by per-participant advisory lock #254

Description

@thpr

Context

Flagged during review of #250 (per-participant advisory lock for concurrent same-participant scans). #250 fixes same-participant races; this is the remaining cross-participant facility-state hole. Pre-existing — not introduced by #250.

Problem

In scan-service.ts (the last-keyholder force-close path, ~line 118), an updateMany closes everyone's open visits when the keyholder checks out. But the scan route's advisory lock — pg_advisory_xact_lock(participant.id) — is keyed per participant.

A non-keyholder checking in concurrently holds a different lock key, so the two transactions don't contend. Sequence:

  1. Keyholder scan begins force-close (updateMany closing all open visits).
  2. Non-keyholder scan (different lock key) creates a fresh open visit.
  3. Depending on interleave, the new visit can survive the force-close — the participant ends up checked into a just-closed facility.

Why it's not fixed by #250

#250's lock serializes only scans for the same participant. The force-close touches global facility state under a per-participant key, so cross-participant invariants remain unprotected.

Possible directions

  • A facility-level advisory lock (fixed key) taken by the force-close path and by any check-in that depends on facility-open state, or
  • A partial unique index / state check that rejects a check-in created against a facility being closed, or
  • Re-validate facility-open state inside the check-in transaction after acquiring the lock.

Needs design — noting so the gap is tracked rather than assumed closed by #250.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions