Repository navigation
Harden LightEpoch: make the epoch announce part of the slot-claim CAS - #2015
Merged
Tiago Nápoli (tiagonapoli) merged 17 commits intoAug 6, 2026
Conversation
A thread entering a protected region announced its epoch with a plain store, which is not ordered against the reclaimer's later load of the same slot. A reclaimer could scan a live reader's slot, see it as free, raise SafeToReclaimEpoch past the reader's epoch, and free a page the reader was about to dereference. The claim CAS now writes localCurrentEpoch directly, so claiming the slot and announcing the epoch are one locked RMW and the announce is globally visible before any load in the protected region can issue. No barrier and no atomic is added; the lock cmpxchg was already there. localCurrentEpoch doubles as the ownership word, sound because a protected thread never announces epoch 0. Release() correspondingly clears threadId before freeing the slot. LightEpoch moves to its own Garnet.LightEpoch project so it can be tested and disassembled in isolation, with unit tests and a quarantine litmus harness under playground/LightEpochLitmus. Co-authored-by: Copilot <[email protected]> Copilot-Session: 38cd2f3a-d460-407c-8a96-a7330974ce99
Tiago Nápoli (tiagonapoli)
force-pushed
the
workstream/lightepoch-x86-minimal-v2
branch
from
August 4, 2026 01:23
30d3992 to
216b7b3
Compare
CodeQL builds the whole solution with 'dotnet build -f net8.0', which failed because the litmus project only targeted net10.0. Inherit the repo default net8.0;net10.0 and pin the Dockerfile/README commands to net10.0. Co-authored-by: Copilot <[email protected]> Copilot-Session: b5f062b9-2d72-4cf0-b6f3-4c9beb98d068
Drop the Garnet prefix from the epoch library and its unit test project so they match the Tsavorite.core / Tsavorite.test.* naming of the rest of the storage engine. Co-authored-by: Copilot <[email protected]> Copilot-Session: d306b783-4a33-4673-9e29-790995df8179
Undo the split of LightEpoch into a standalone project: the sources return to src/core/Epochs/ and the duplicated Murmur3 helper is dropped in favor of the existing Utility.Murmur3. LightEpochLitmus now references Tsavorite.core. Co-authored-by: Copilot <[email protected]> Copilot-Session: d306b783-4a33-4673-9e29-790995df8179
LightEpoch already exposed a public Dispose(); declaring the interface adds nothing and was not part of the fix. Co-authored-by: Copilot <[email protected]> Copilot-Session: d306b783-4a33-4673-9e29-790995df8179
Co-authored-by: Copilot <[email protected]> Copilot-Session: d306b783-4a33-4673-9e29-790995df8179
Collaborator
Author
Epoch BDN results — no regressionRan
For Bottom lineNo regression on any of the three epoch benchmarks. |
Badrish Chandramouli (badrishc)
approved these changes
Aug 5, 2026
Rewrite the ProtectAndDrain docs to state that it refreshes an already-held slot rather than entering the protected region, that SafeToReclaimEpoch is gated on the minimum announced epoch so a holder that never refreshes stalls reclamation process-wide, and that a refresh relinquishes protection for the previously announced epoch. Read CurrentEpoch volatile when announcing and inline ReserveEntry into ReserveEntryForThread. Co-authored-by: Copilot <[email protected]> Copilot-Session: b6f65ef0-7c7f-40d9-a95f-3ecb62de9d5a
Tiago Nápoli (tiagonapoli)
force-pushed
the
workstream/lightepoch-x86-minimal-v2
branch
from
August 5, 2026 18:19
6d43835 to
c567ead
Compare
Co-authored-by: Copilot <[email protected]> Copilot-Session: b6f65ef0-7c7f-40d9-a95f-3ecb62de9d5a
Co-authored-by: Copilot <[email protected]> Copilot-Session: b6f65ef0-7c7f-40d9-a95f-3ecb62de9d5a
Co-authored-by: Copilot <[email protected]> Copilot-Session: b6f65ef0-7c7f-40d9-a95f-3ecb62de9d5a
Co-authored-by: Copilot <[email protected]> Copilot-Session: b6f65ef0-7c7f-40d9-a95f-3ecb62de9d5a
Make the ProtectAndDrain announce a release store and the reclaimer's ComputeNewSafeToReclaimEpoch scan an acquire load, so a slot's announced epoch cannot be observed out of order with the work it guards. Hoist the announced epoch into a local so Drain reuses it instead of re-reading the slot. Co-authored-by: Copilot <[email protected]> Copilot-Session: 8b507896-826f-444c-b958-51c19863f429
Ted Hart (TedHartMS)
approved these changes
Aug 5, 2026
Tiago Nápoli (tiagonapoli)
merged commit Aug 6, 2026
8b329e3
into
microsoft:main
416 of 417 checks passed
This was referenced Aug 6, 2026
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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Note: this does not affect production use of Garnet. Tsavorite's CRUD paths (Read/Upsert/RMW/Delete) already issue interlocked operations and memory barriers around epoch protection, so the required ordering is established independently and the scenario below cannot occur there. This change hardens
LightEpochitself for non-Tsavorite-core paths that use it directly, without relying on the caller's barriers.Summary
A thread entering a protected region announces its epoch with a plain store, which is not ordered against the reclaimer's later load of the same slot. A reclaimer can scan a live reader's slot, see it as free, raise
SafeToReclaimEpochpast the reader's epoch, and free a page the reader is about to dereference.The fix changes which word the claim CAS operates on - localCurrentEpoch instead of threadId. No barrier and no atomic is added — the
lock cmpxchgwas already there, it just lands on a different word.Scoped to x86-64. ARM64 additionally need more fixes; that ordering work, along with herd7 models and TLA+ specs, is deliberately left out of this change.
The bug
Store-then-load-of-another-address is the one reordering TSO permits:
threadId= tidlocalCurrentEpoch= 5 (buffered)SafeToReclaimEpoch= 5, free the pageThe fix
The CAS writes
localCurrentEpochdirectly, so claiming the slot and announcing the epoch are one locked RMWViolation proof
8-hour hardware run
Two Azure VMs, quarantine litmus, baseline vs fixed (2026-07-30, westus2, x86-64):
Local repro
--buggypoints the harness atBuggyLightEpoch, a frozen copy ofmain's version, so both arms run back to back on one machine (20 logical processors, x86-64, 15 s runs):Benchmarks
BDN
RawStringOperations, 3 interleaved runs per arm on a dedicated x86 VM (Xeon 8272CL, 16 vCPU): mean |Δ| 1.46%, 5 faster / 5 slower, 0 B allocated in both arms.GetNotFoundhad the widest spread, so it was re-run alone with 8 interleaved runs per arm:The fix is 1.65% faster, but the baseline's own run-to-run spread is far larger than that — it's host noise, not a real effect. No measurable cost.