Skip to content

feat: add user locking to isolation reservation table#290

Open
porcellus wants to merge 21 commits intofeat/isolation-reservation-tablefrom
feat/isolation-reservation-table-locking
Open

feat: add user locking to isolation reservation table#290
porcellus wants to merge 21 commits intofeat/isolation-reservation-tablefrom
feat/isolation-reservation-table-locking

Conversation

@porcellus
Copy link
Copy Markdown
Collaborator

Summary of change

(A few sentences about this PR)

Related issues

  • Link to issue1 here
  • Link to issue1 here

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your
changes work. Bonus points for screenshots and videos!)

Documentation changes

(If relevant, please create a PR in our docs repo, or create a checklist here
highlighting the necessary changes)

Checklist for important updates

  • Changelog has been updated
  • pluginInterfaceSupported.json file has been updated (if needed)
  • Changes to the version if needed
    • In build.gradle
  • Had installed and ran the pre-commit hook
  • If there are new dependencies that have been added in build.gradle, please make sure to add them
    in implementationDependencies.json.
  • Issue this PR against the latest non released version branch.
    • To know which one it is, run find the latest released tag (git tag) in the format vX.Y.Z, and then find the
      latest branch (git branch --all) whose X.Y is greater than the latest released tag.
    • If no such branch exists, then create one from the latest released branch.
  • When adding new recipes, ensure that its performance is being measured in the OneMillionUsersTest

Remaining TODOs for this PR

  • Item1
  • Item2

porcellus and others added 13 commits January 25, 2026 01:21
- Add LockedUserImpl with connection validation
- Add UserLockingQueries with FOR UPDATE locking
- Implement UserLockingStorage and AccountInfoStorage in Start.java
- Use consistent ordering (TreeSet) to prevent deadlocks
- Re-verify primary user mapping under lock

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…oForLinking

- Add new reserveAccountInfoForLinking_Transaction overload that accepts LockedUser
- Update linkAccounts_Transaction to acquire locks via lockUsersForLinking
- Validate primary/recipe user status using LockedUser methods
- Skip advisory lock since row-level locks handle concurrency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…_Transaction

- Add new updateAccountInfo_Transaction method with LockedUser parameter
  to AccountInfoQueries.java
- Update storage methods to acquire locks before calling account info update:
  - updateUsersEmail_Transaction (EmailPassword)
  - updateUserEmail_Transaction (ThirdParty)
  - updateUserEmailAndPhone_Transaction (Passwordless)
  - updateUserEmail_Transaction (WebAuthN)
- Remove old updateAccountInfo_Transaction method that took String userId
- Convert UserNotFoundForLockingException to UnknownUserIdException

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ountInfo

- Add LockedUser overload of addPrimaryUserAccountInfo_Transaction
- Update makePrimaryUser_Transaction to acquire lock before calling method
- Validate via LockedUser state (isLinked/isPrimary) before DB operations
- Remove advisory lock since row-level locking is now used via LockedUser

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…nForPrimaryUserForUnlinking

Implement the AccountInfoStorage.removeAccountInfoReservationForPrimaryUserForUnlinking_Transaction
method that requires LockedUser parameters. The implementation:
- Validates the recipe user is part of the primary user group (linked or is the primary itself)
- Delegates to the existing string-based implementation after validation

Also updates Start.java to implement the AccountInfoStorage interface method.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ransaction

- Add LockedUser.isValidForConnection() validation at method start
- Remove redundant AppIdentifier parameter (derive from TenantIdentifier)
- Update callers to use simplified signature

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix UserLockingQueries to use app_id_to_user_id table instead of
  all_auth_recipe_users, allowing users removed from all tenants to be locked
- Fix readPrimaryUserId to check is_linked_or_is_a_primary_user flag,
  distinguishing standalone users from primary/linked users
- Fix lockUsers to handle empty string (standalone user) vs null (not found)
- Fix reserveAccountInfoForLinking_Transaction validation:
  - Allow linked users as "primary" parameter (getPrimaryUserId returns their
    actual primary)
  - Add check for recipe user being a primary user (cannot link a primary
    as recipe user)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…aryUser_Transaction

Update addTenantIdToPrimaryUser_Transaction to accept LockedUser instead of
String userId:
- Change method signature in AccountInfoQueries and Start
- Add isPrimary() validation to ensure the user is actually a primary user
- Extract supertokensUserId from LockedUser for query execution

This ensures proper row-level locking is acquired before adding account info
reservations when associating a primary user with a tenant.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
UserLockingQueries now fetches recipe_id from app_id_to_user_id during
lock acquisition and passes it to LockedUserImpl. This ensures recipe ID
is available even for users removed from all tenants.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update getRecipeIdForUser_Transaction to take LockedUser instead of userId
string. The method now simply returns lockedUser.getRecipeId() since the
recipe ID is fetched during lock acquisition from app_id_to_user_id.

This removes the need for a separate query and ensures the recipe ID is
available even for users not in any tenant (users removed from all tenants).

Callers now:
1. First acquire lock via UserLockingQueries.lockUser()
2. Get recipe ID from LockedUser instead of separate query

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…level locks to core

- Delete lockThirdPartyInfoAndTenant_Transaction methods (unused)
- Delete String-based addPrimaryUserAccountInfo_Transaction (LockedUser version exists)
- Delete String-based reserveAccountInfoForLinking_Transaction (LockedUser version exists)
- Rename String-based removeAccountInfoReservationForPrimaryUserForUnlinking_Transaction
  to doRemoveAccountInfoReservationForUnlinking (internal helper)
- Remove lock call from listPrimaryUsersByThirdPartyInfo_Transaction (locking now in core)
- Keep getAllPasswordResetTokenInfoForUser_Transaction (used for token-level locking)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@porcellus porcellus changed the title featisolation reservation table locking feat: add user locking to isolation reservation table Jan 27, 2026
porcellus and others added 5 commits January 29, 2026 22:19
Migrate plugin-level locks to core pattern by removing FOR UPDATE from:
- TOTPQueries.getDevices_Transaction: caller should obtain lock via UserLockingStorage
- UserMetadataQueries.getUserMetadata_Transaction: advisory lock already provides protection
- UserMetadataQueries.getMultipleUsersMetadatas_Transaction: caller should obtain locks

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert removeAccountInfoForRecipeUserWhileRemovingTenant_Transaction
and removeAccountInfoReservationForPrimaryUserWhileRemovingTenant_Transaction
to accept LockedUser instead of String userId.

This provides compile-time safety and eliminates redundant subqueries
for primary_user_id lookup since it's already available from LockedUser.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread jar/postgresql-plugin-9.3.1.jar Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually don't commit these jars. It's done by release process.

String QUERY = "SELECT user_id FROM " + Config.getConfig(start).getAppIdToUserIdTable()
+ " WHERE app_id = ? AND user_id = ? FOR UPDATE";

Boolean found = execute(con, QUERY, pst -> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Boolean found = execute(con, QUERY, pst -> {
boolean found = execute(con, QUERY, pst -> {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boolean is actually an object, with 3 possible states (true, false, null). rs.next() returns boolean, so no possibility of it being null. On top of that, the boolean -> Boolean uses auto-boxing behind the scenes, making it costlier than it should be.

Comment thread startDb.sh Outdated
-d -p 5432:5432 \
-v ~/Desktop/db/pstgres:/var/lib/postgresql/18/docker \
postgres \
docker container run --name postgres $RUNPARAMS -p 5432:5432 -e 'POSTGRES_USER=root' -e 'POSTGRES_PASSWORD=root' --rm -d percona/percona-distribution-postgresql:13-arm64 postgres \
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to commit this too? startDb.sh is still used in CI test runs.

porcellus and others added 3 commits February 19, 2026 11:31
…' into feat/isolation-reservation-table-locking
- Remove redundant primaryUser param from
  removeAccountInfoReservationForPrimaryUserForUnlinking_Transaction
- Use primitive boolean instead of Boolean in UserLockingQueries
- Revert startDb.sh to upstream version
- Remove incorrectly committed postgresql-plugin-9.4.0.jar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants