Add cross-DAO atomic locking to LockedContext - #179
Open
iamamberkulkarni wants to merge 1 commit into
Open
Conversation
iamamberkulkarni
force-pushed
the
multi-row-atomic-update
branch
from
June 23, 2026 18:35
f3e0c3a to
f339510
Compare
Add lockAndMutateEach (lock N RelationalDao rows via SELECT FOR UPDATE NOWAIT) and lockAndMutate (lock a LookupDao entity by key) as chaining methods on LockedContext. Both join the context's transaction so rows across multiple tables on the same shard are locked and updated atomically — single commit, single rollback. Includes same-shard enforcement and input validation, plus 8 LockTest cases covering happy paths, rollback, cross-shard rejection, and concurrent NOWAIT contention. Co-authored-by: amber.kulkarni <amber.kulkarni@phonepe.com>
iamamberkulkarni
force-pushed
the
multi-row-atomic-update
branch
from
June 23, 2026 19:01
f339510 to
e8c56f7
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Cross-DAO Atomic Locking in
LockedContextSummary
Adds two chaining methods to
LockedContextthat lock and mutate rows of other DAOs within the context's existing transaction. This makes it possible to lock and update multiple rows across multiple tables atomically — a single commit, a single rollback — as long as all rows reside on the same shard.lockAndMutateEach— lock N rows of aRelationalDao(each viaSELECT FOR UPDATE NOWAIT)lockAndMutate— lock aLookupDaoentity by key (viaSELECT FOR UPDATE/PESSIMISTIC_WRITE)Both join the transaction already opened by the
LockedContext, so they participate in the same commit/rollback as the entry-point entity and any chainedmutate/savecalls.Motivation
The existing
LockedContextAPI locks only the entry-point entity. Any other row a transaction needs to mutate — whether additional rows of the same table or rows of a different DAO — was read/updated without a lock, leaving a window for a concurrent transaction to mutate the same row (lost update).A representative case: an operation that must lock several rows of a sharded table and one related lookup entity, then write additional related rows — all atomically. Concretely:
RelationalDao(updating a field on each)LookupDaoentity on the same shardWithout a shared lock point across these, two concurrent operations on overlapping rows could interleave. The fix is to take
SELECT FOR UPDATEon every row the transaction will mutate (not just the first), so concurrent transactions touching the same rows fail fast instead of racing.What's added
1.
lockAndMutateEach— lock multipleRelationalDaorowsDetachedCriteriamust match exactly one row (point lock by unique key — no gap locking). Zero matches → not-found exception; more than one →NonUniqueResultException. Either rolls back the whole transaction.LockMode.UPGRADE_NOWAIT— fails immediately on contention (no blocking, no deadlocks).UnaryOperator<U>is applied once per locked row; each invocation returns the entity to persist.2.
lockAndMutate— lock aLookupDaoentityPESSIMISTIC_WRITEon the keyed entity.Combined usage
Lock-mode semantics
The lock modes match each DAO's existing
getLockedForWritebehavior — no new locking semantics are introduced:RelationalDaogetLockedForWrite(DetachedCriteria)UPGRADE_NOWAITLookupDaogetLockedForWrite(key)PESSIMISTIC_WRITEConstraints & guards
LockedContext. Cross-shard locking cannot be atomic (separate databases, separate transactions). ForlockAndMutate, this is enforced: if the key hashes to a different shard than the context, anIllegalArgumentExceptionis thrown before any lock is taken (rather than silently querying the wrong shard and reporting a misleading "entity not found"). ForlockAndMutateEach, rows are addressed by arbitrary criteria with no shard key to hash, so co-sharding remains a documented caller contract.lockAndMutateEach(see above).OpType,OpContext, or related types. The change is purely additive.Tests
8 new cases in
LockTest:testLockAndMutateEachUpdatesMultipleRelationalEntitiestestLockAndMutateEachThrowsWhenEntityNotFoundtestLockAndMutateUpdatesLookupEntityWithinRelationalContexttestLockAndMutateThrowsWhenLookupEntityNotFoundtestLockAndMutateThrowsWhenLookupKeyMapsToDifferentShardIllegalArgumentException; rollbacktestLockAndMutateEachProtectsEachRowFromConcurrentAccesstestLockAndMutateEachFailsWhenRowAlreadyLockedByAnotherTransactionlockAndMutateEachtestSingleTableLockAndMutateEachProtectsRowsFromConcurrentAccess/...FailsWhenRowAlreadyLockedAll
LockTestcases pass against the currentmaster.