Problem/Motivation

SubscriptionEntity::preSave() enforces one active subscription per user by loading the user's other active subscriptions and superseding them. That is check-then-act with no lock and no database constraint. A webhook insert and a RemoteSyncWorker or user-insert sync for the same user running concurrently both see zero others and both save active; the duplicates resolver sorts it out later, but in between the user holds two active rows and both can be billed. The drupal_static guards are per-request only, which is right for their purpose and not a cross-request guard.

Proposed resolution

  • Acquire lock subscription_manager:single_active:{uid} around the check and the supersede, release after save (postSave or a finally block in the storage handler), with a short wait rather than a drop since both writers are legitimate.
  • Test: two concurrent saves (simulated by holding the lock from the test) leave one active row.

Remaining tasks

  • Implementation and test; no change record.

User interface changes

None.

API changes

None.

Data model changes

None.

Comments

colan created an issue.