feat: [GH-140] Add *AndEdit atomic read-modify-write methods#143
Open
javierlores wants to merge 2 commits into
Open
feat: [GH-140] Add *AndEdit atomic read-modify-write methods#143javierlores wants to merge 2 commits into
javierlores wants to merge 2 commits into
Conversation
Add findAndEdit, findUniqueAndEdit, and findFirstAndEdit to Runway as true single-transaction read-modify-write primitives: each binds a Reader and Saver to one staged connection so the find's read and the save's write share one transaction (and one set of locks), giving concurrent callers mutual exclusion. On write conflict the cycle is retried with jittered backoff up to the bounded attempt limit; on exhaustion a RetryExhaustedException is thrown so a contended claim is never mistaken for 'nothing matched'. The incremental path is used unconditionally because BatchSaver defers the server-side STAGE until commit time, which would place the read outside the transaction. Stacked on feature/GH-139 (findFirst). Tests written (not run) per repo policy; spotlessApply clean.
Apply Order/Page client-side in editWithinTransaction when the connected server cannot sort or paginate natively, mirroring the read-path fallback. The in-transaction find still reads (and therefore locks) every match, so atomicity and mutual exclusion are preserved; only the ordering/limit are applied client-side afterward via a new sortAndPage helper. This keeps findFirstAndEdit and the paginated guards correct on legacy servers, not just native-capable ones. Also: - Drop the unused `any` (hierarchy) parameter from editWithinTransaction; every call site passed false. - Honor interrupts during the retry backoff: restore the interrupt flag and abandon the retry loop instead of silently continuing to contend. - Add NOTE comments documenting the retry-on-every-TransactionException semantics and the connection-held-across-backoff choice. Tests: legacy-path coverage (hasNativeSortingAndPagination=false) for findFirstAndEdit client-side ordering and findUniqueAndEdit duplicate detection without native pagination.
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.
Closes #140. Stacked on #141 (GH-139
findFirst) — base isfeature/GH-139so this diff shows only the*AndEditdelta. Retarget todeveloponce #141 merges.What this does
Adds the
*AndEditfamily of atomic read-modify-write primitives to Runway:findAndEdit(Class<T>, Criteria, Consumer<T>)— edits every match; all edits commit in one transaction (all-or-nothing). Empty set when no match (consumer never runs).findUniqueAndEdit(Class<T>, Criteria, Consumer<T>)— edits the single match;nullwhen none; throwsDuplicateEntryExceptionon >1 (checked inside the txn, before the consumer runs).findFirstAndEdit(Class<T>, Criteria, Order, Consumer<T>)— edits the first match under the requiredOrder;nullwhen none.All three route through a private
editWithinTransaction(...)helper.How it works (design A from the ticket)
True single-transaction read-modify-write: an
IncrementalReaderand anIncrementalSaverare bound to the same stagedConcourseconnection, so the find's read and the save's write share one transaction and one set of locks. That shared lock set is what gives concurrent callers real mutual exclusion (the connector-claiming use case in cinchapi-server).IncrementalSaverunconditionally (even whensupportsBulkCommands), becauseBatchSaverdefers the server-sideSTAGEto commit time, which would place the find's read outside the transaction. Documented inline.TransactionException: abort and retry the whole cycle (re-find → re-apply → re-save) up toMAX_SPURIOUS_SAVE_RETRIES, with jittered exponential backoff (backoffWithJitter). Each retry re-finds fresh instances.RetryExhaustedException(carries the attempt count) rather than returning a non-committed result — distinct fromnull/empty (= no match).Files
Runway.java— three public methods +editWithinTransaction+backoffWithJitter(+RETRY_BACKOFF_BASE_MILLIS).RetryExhaustedException.java— new, extendsRunwayException.FindAndEditTest,FindUniqueAndEditTest,FindFirstAndEditTest— parameterized overBatchSaver/IncrementalSaver, including concurrency tests.Testing
Tests written but not run, per repo conventions (live Concourse server required).
spotlessApplyrun for formatting.