Skip to content

fix(config): concurrent deletes of a plan's accounts still leave it enabled with zero accounts (#1065 follow-up) #1072

Closed

Description

@IAmJSD

Follow-up to #264 / #1065. #1065 merged at 077fdc2 with the BLOCKING finding from its independent review still open:

two accounts of the same plan deleted concurrently leave the plan enabled with zero accounts. The lock is per account row (FOR UPDATE ... WHERE id = $1), so deleting A and B take different locks and nothing serializes them. Under READ COMMITTED each transaction's NOT EXISTS (other account) still sees the other account's uncommitted plan_accounts row, so neither disables the plan.

The reviewer reproduced it in 39 of 40 iterations on real Postgres. The same review noted a related unserialized interaction: SetPlanAccounts removing B from {A, B} while A is deleted.

Fix (written, not yet pushed)

  • Lock order: lock every plan that targets the account in id order (SELECT … FROM purchase_plans … ORDER BY p.id FOR UPDATE OF p) before the account row, then lock again for plans that gained the account while waiting.
  • Concurrent deletes: a second delete that shares a plan waits on the plan lock, re-reads plan_accounts after the first commits, and disables the plan.
  • SetPlanAccounts: it reads the plan FOR SHARE, so it is serialized against the delete too. Taking the plan lock before the account lock also closes the deadlock window documented in fix(config): disable plans left with no accounts when their account is deleted #1065's body.

Tests

New real-Postgres tests:

  • ConcurrentDeletesOfOnePlansAccounts uses a lock-wait gate to force the race on every run.
  • ConcurrentSetPlanAccounts runs 20 iterations of a delete against SetPlanAccounts.
  • DisablesManagedRampPlanKeepingSchedule checks that a managed ramp plan keeps its schedule columns.

On the current code, the first two fail: the plan is left enabled with zero accounts, and SQLSTATE 40P01 (deadlock) respectively.

The PR will follow once the full internal/config integration suite can be re-run locally.

Activity

  1. self-assigned this
    on Oct 18, 2026
  2. IAmJSD commented on Oct 18, 2026

    @IAmJSD
    CollaboratorAuthor

    Claiming: working on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions