fix: clean up abandoned keyed once cells - #142
Merged
Merged
Conversation
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.
Summary
OnceMapandsingleflight::Groupentries after the last active call fails, panics, or is cancelledhashbrown::HashTableto make exact abandoned-entry cleanup expected O(1), without an active-call counter or an extra atomic RMW on every callClonebounds to keys and custom hashersOnceCellaccess behind the internalOnceTableandOnceTableEntryAPIsOnceCell::initialized_mutquery withinitializedDesign
This revisits #133 with one ownership invariant:
Arc<OnceTableEntry<K, V>>The entry stores its precomputed hash, key, and
OnceCell. Moving the key into the shared entry lets a call retain entry identity without cloningK. When an unsuccessful call exits, its guard locks the table while retaining itsArc. If the entry is still uninitialized and the strong count is two, the table and the current call are its only owners. The table then removes that exact allocation using its cached hash andArc::ptr_eq.Allocation identity is required because
forget,discard, orremovecan detach an old entry and a later call can insert a replacement for the same key. A stale guard must not remove the replacement.HashTableis used instead ofHashMap::retainorHashMap::raw_entry_mut:retainscans every entry and made failed cleanup O(n)HashMaplookup can match only on the key, while exact cleanup must match the entry allocation after the guard has transferred ownership ofKHashTablecan hash into the relevant bucket and compare the complete entry, giving expected O(1) exact removalThe hash-table details stay in one private
OnceTable. Its lookup and insertion methods return logical entries rather than exposinghashbrownentry handles.OnceTableEntryowns value inspection and initialization, soOnceMapandGroupdo not reach through it to the underlyingOnceCell. Cleanup guards keep the lifecycle rule—the last caller removes an uninitialized entry—at the call site.Groupremoves a completed entry inside the initializer beforeOnceCellpublishes the result. The cachedOnceMappath clones the value directly while holding the table lock, so it does not add anArcRMW.hashbrown0.17.1 has the same Rust 1.85 MSRV as mea. Default features are disabled and onlyinline-moreis enabled, so this adds no transitive normal dependency.Benchmarks
These are Divan medians from an arm64 Linux OrbStack VM using Rust 1.85.1, comparing the previous O(n) implementation at
5899bbfwith this update. The successful paths are unchanged in behavior; the failure rows include cleanup.HashTablecleanupThe cached and vacant
OnceMappaths remain within measurement noise. Failed cleanup no longer scales linearly with the number of cached entries. macOS measurements show the same shape, including a reduction from about 2.10 µs to 208 ns for the 1024-entry failure case. A focused before/after run for the final structural simplification measured the extended vacant-success case at the same 114.2 ns median on both revisions.Verification
cargo x lintcargo test --workspace --all-featurescargo +1.85.0 check --workspace --all-targetscargo +1.85.0 test --workspace --no-default-featurescargo x semver --release-version 0.6.6cargo bench --bench primitives -- --test