fix: atomic usage recording to prevent race conditions #53

Open
Charon wants to merge 1 commit from feat/fix-usage-race-condition into dev

1 commit

Author SHA1 Message Date
Claude
ef7ed4ca77
fix: atomic usage recording and provisioning to prevent race conditions
Addresses #42 — race condition in EntitlementService usage recording.

Changes:

1. New atomic check-and-record methods:
   - checkAndRecordUsage() — workspace-scoped
   - checkAndRecordNamespaceUsage() — namespace-scoped
   These use DB::transaction() with lockForUpdate() on usage records
   to prevent concurrent requests from reading the same count and
   both passing the limit check (TOCTOU race).

2. New locked usage query methods:
   - getLockedCurrentUsage() — bypasses cache, uses SELECT FOR UPDATE
   - getLockedNamespaceCurrentUsage() — namespace equivalent
   These are used within the atomic transactions to get a consistent
   usage count under pessimistic locking.

3. provisionPackage() and provisionNamespacePackage():
   Wrapped base-package check-and-cancel in DB::transaction() with
   lockForUpdate() to prevent duplicate active base packages from
   concurrent provisioning requests.

4. provisionBoost():
   Wrapped in DB::transaction() with lockForUpdate() on existing
   boosts. Added duplicate detection for blesta_addon_id to prevent
   the same external addon from being provisioned twice.

5. provisionNamespaceBoost():
   Wrapped in DB::transaction() with lockForUpdate() on existing
   namespace boosts to serialise concurrent provisioning.

The existing can() + recordUsage() pattern is preserved for backward
compatibility. Callers should migrate to checkAndRecordUsage() for
race-safe usage tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 13:09:49 +00:00