Overlapping operations for the same key in one MutableStore instance can leave a local write unsynchronized and remove the state needed to retry it.
Queue replacement can discard a newer write
Cleanup builds a replacement queue under the per-key mutex, then releases that mutex before installing the replacement. A newer write can enter the existing queue between those steps. See queue cleanup.
Possible sequence:
- Write A reaches the server successfully.
- A’s cleanup computes an empty replacement queue and releases the per-key mutex.
- Write B enters the existing queue and persists locally.
- A installs its replacement queue, discarding B.
- B’s synchronization attempt fails with
No writes found for key=….
B receives an error, but this path bypasses failed-sync bookkeeping. The server can hold A while local storage holds B, with no pending queue entry or failure marker for B.
B may still exist locally. The failure is that Store has lost the state needed to synchronize it through eager recovery.
An older retry can acknowledge a newer failed write
An eager retry triggered by a read captures a local value before posting it. When the request succeeds, cleanup uses the completion time as its cutoff. See eager conflict resolution.
Possible sequence:
- An eager retry captures A and starts a slow server update.
- A newer write B persists locally, fails its server update, and records a failure marker.
- The older update of A succeeds.
- Its cleanup removes B, invokes B’s success callback using A’s result, and clears the failure marker.
B’s original update failed. Its callback can still report success even though B never reached the server.
Existing coverage
The two existing JVM concurrency tests passed. The concurrent test explicitly tolerates No writes found errors and checks for queue corruption. It does not establish synchronization correctness. See the tests.
#735 fixed the queue’s lock-polarity issue and left the logical same-key race outside its scope.
Acceptance criteria
I want the fix to preserve pending writes while keeping local writes responsive during slow network requests.
Overlapping operations for the same key in one
MutableStoreinstance can leave a local write unsynchronized and remove the state needed to retry it.Queue replacement can discard a newer write
Cleanup builds a replacement queue under the per-key mutex, then releases that mutex before installing the replacement. A newer write can enter the existing queue between those steps. See queue cleanup.
Possible sequence:
No writes found for key=….B receives an error, but this path bypasses failed-sync bookkeeping. The server can hold A while local storage holds B, with no pending queue entry or failure marker for B.
B may still exist locally. The failure is that Store has lost the state needed to synchronize it through eager recovery.
An older retry can acknowledge a newer failed write
An eager retry triggered by a read captures a local value before posting it. When the request succeeds, cleanup uses the completion time as its cutoff. See eager conflict resolution.
Possible sequence:
B’s original update failed. Its callback can still report success even though B never reached the server.
Existing coverage
The two existing JVM concurrency tests passed. The concurrent test explicitly tolerates
No writes founderrors and checks for queue corruption. It does not establish synchronization correctness. See the tests.#735 fixed the queue’s lock-polarity issue and left the logical same-key race outside its scope.
Acceptance criteria
I want the fix to preserve pending writes while keeping local writes responsive during slow network requests.