-
Notifications
You must be signed in to change notification settings - Fork 28
perf(thread_aware)!: partition relocation storage per affinity #681
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Sander Saares (sandersaares)
merged 54 commits into
main
from
u/sasaares/fix-thread-aware
Aug 25, 2026
Merged
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
9d72f79
fix(thread_aware): probe relocation destination under a shared lock
sandersaares 31b7b45
test(thread_aware): report the storm benchmark as a median over workers
sandersaares a609a80
test(thread_aware): separate contention from oversubscription in the …
sandersaares ba9fe5a
test(thread_aware): make the relocation storm benchmark measure conte…
sandersaares 8465e67
test(thread_aware): bracket the storm benchmark's timed region with load
sandersaares 5fb8616
test(thread_aware): measure concurrent relocation as batched throughput
sandersaares 735f8f3
perf(thread_aware): partition relocation storage by affinity
sandersaares fcbc74c
refactor(thread_aware): hide storage behind an opaque SharedStorage h…
sandersaares 1b6ff35
test(thread_aware): drop the vestigial unused_results allow in the cg…
sandersaares a0c8aaa
test(thread_aware): drop unnecessary lint allows from the relocation …
sandersaares 9ca3a69
docs(thread_aware): tighten the relocation-locking and benchmark prose
sandersaares bb4e350
refactor(thread_aware): guarantee slot locks never poison, and rename…
sandersaares ebb5d30
refactor(thread_aware): clarify relocation docs and skip redundant fa…
sandersaares 891c3f0
test(thread_aware): cover the source-slot restore against mutation
sandersaares ce84329
chore(thread_aware): bump to 0.10.0 for the breaking storage API change
sandersaares 9395c8c
feat(thread_aware): make Storage publicly constructible and fillable
sandersaares fdb8131
test(thread_aware): use unwrap over expect in concurrent relocation test
sandersaares 5a404a0
test(thread_aware): cover Storage::default and the slot-count guard
sandersaares a9de8a8
fix(thread_aware): assert slot invariant after releasing the write lock
sandersaares 2ad7cf3
fix(thread_aware): saturate strong_count against a concurrent-relocat…
sandersaares 34bda02
fix(thread_aware): gate derive-using bench and test targets on the de…
sandersaares fe35f4e
docs(thread_aware): add design.md and align claims with real guarantees
sandersaares df08d3b
fix(thread_aware)!: keep the carried value on a same-slot relocation
sandersaares da67ae5
test(thread_aware): isolate the cross-slot miss in the miss benchmark
sandersaares 665d5db
fix(thread_aware): soft-pass out-of-range slot index in release
sandersaares 5d17d48
perf(thread_aware): back the growable slot table with a lock-free vector
sandersaares 2674518
refactor(thread_aware): replace the boxcar slot vector with a OnceLoc…
sandersaares da9acf0
feat(thread_aware): observe out-of-range affinity via a metric
sandersaares 90239aa
Merge remote-tracking branch 'origin/main' into u/sasaares/fix-thread…
sandersaares a39b063
test(thread_aware): catch the out-of-range affinity metric mutation
sandersaares 44f1d03
refactor(thread_aware): make out-of-range relocation a no-op
sandersaares 2495b45
test(thread_aware): cover out-of-range relocation branches
sandersaares a1c7630
docs(thread_aware): clarify relocation holds one slot lock at a time
sandersaares c86f3ff
refactor(thread_aware): NonZero strategy count, Arc submodule, storag…
sandersaares 12968d3
refactor(thread_aware): share bench object model; refine bench prose
sandersaares 8fb43ba
docs(thread_aware): document NonZero count and direct-access panic co…
sandersaares 18ed517
Merge remote-tracking branch 'origin/main' into u/sasaares/fix-thread…
sandersaares 1948325
fix(thread_aware): repair private intra-doc link and cover probe hit …
sandersaares ed7b4c1
refactor(thread_aware): adopt write-once OnceLock slot cells
sandersaares a514ff1
test(thread_aware): pin the sourceless same-slot relocation arm
sandersaares 275ef36
docs(thread_aware): align benchmark comments with lock-free cells
sandersaares b8385c6
docs(thread_aware): correct hit-path prose and slot-table wording
sandersaares 13651e5
fix(thread_aware): run relocation factory once per affinity under a race
sandersaares 14b1eb9
refactor(thread_aware): return Result from Storage::insert
sandersaares 5a30d97
fix(thread_aware): document slot initialization constraints
sandersaares 94e555a
fix(thread_aware): make adopter race test mutation-safe
sandersaares a34ad18
docs(thread_aware): clarify affinitized constructor behavior
sandersaares f2b23fc
fix(thread_aware): record source before relocation fast paths
sandersaares cc5be07
fix(thread_aware): address relocation review findings
sandersaares ca2286d
docs(thread_aware): clarify Strategy Arc contract
sandersaares 838e267
perf(thread_aware): avoid recounting initialized slots
sandersaares 1224e78
refactor(thread_aware): isolate relocation benchmarks
sandersaares bd358df
fix(thread_aware): correct hit-path benchmark handoffs
sandersaares 3427d57
ci(thread_aware): exclude benchmark support from test metrics
sandersaares File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| # `thread_aware` design | ||
|
|
||
| This document describes the user-visible behavior and design tenets of | ||
| `thread_aware::Arc` and its companion `storage::Storage` handle. The | ||
| `ThreadAware` trait and the relocation model these build on are introduced in | ||
| the crate root documentation; this document focuses on the strategy-partitioned | ||
| `Arc`. The internal mechanism — the write-once partition slots and the | ||
| relocation protocol built on them — is documented separately in | ||
| [implementation.md](implementation.md). | ||
|
|
||
| ## 1. Purpose | ||
|
|
||
| `thread_aware::Arc<T, S>` is a shared pointer, like `std::sync::Arc`, that | ||
| additionally keeps a distinct value per strategy partition. It lets a type that | ||
| is not itself `ThreadAware` — a third-party client, a connection pool, any value | ||
| that benefits from being local to the core or NUMA node using it — be shared | ||
| through a handle that reacts to relocation by adopting, or lazily creating, the | ||
| value belonging to the destination affinity's partition. | ||
|
|
||
| Cloning an `Arc<T, S>` is cheap and shares state. Dereferencing it yields the | ||
| value carried by that holder. The `Strategy` type parameter `S` decides how | ||
| affinities map to partitions. | ||
|
|
||
| ## 2. Affinities and strategies | ||
|
|
||
| An affinity identifies the placement of the code that holds the `Arc`. The | ||
| strategy maps that affinity to the partition whose value the holder sees: | ||
|
|
||
| - `PerProcess` maps every affinity to one partition, so all clones share a single value | ||
| process-wide and relocation keeps that shared value: an `Arc<T, PerProcess>` | ||
| behaves like a plain `sync::Arc<T>`. | ||
| - `PerCore` defines one partition per processor. A holder that relocates to another | ||
| processor observes that processor partition's value. | ||
| - `PerNuma` defines one partition per memory region, so holders on cores of the | ||
| same NUMA node share a value and holders on different nodes do not. | ||
|
|
||
| Custom strategies are possible; they are expected to report a partition count — | ||
| always at least one — that is consistent across the affinities that share one | ||
| `Arc` (see [implementation.md](implementation.md)). | ||
|
|
||
| ## 3. Strategy-partitioned values | ||
|
|
||
| An `Arc<T, S>` always carries a current value in hand and derefs to it directly, | ||
| without synchronization. It additionally keeps the value materialized for each | ||
| strategy partition; a holder that relocates into a partition adopts that | ||
| partition's value, and clones in the same partition share one underlying | ||
| `sync::Arc<T>`. | ||
|
|
||
| Constructors create the initial carried value eagerly. Additional partition | ||
| values are then produced lazily — a partition is materialized the first time a | ||
| holder relocates into it across a partition boundary while it is still empty — | ||
| and how a value is produced depends on the constructor used: | ||
|
|
||
| - `new` / `new_boxed` run a constructor function once per partition, giving each | ||
| partition a freshly built, independent value. Neither requires `T: Clone` or | ||
| `T: ThreadAware`. | ||
| - `new_with` runs a closure that may capture other `ThreadAware` state, which is | ||
| itself relocated for the destination before the value is built. | ||
| - `from_unaware` takes one value and clones it for each partition. | ||
| - `with_clone_fn` takes a concrete value plus a clone function, so trait-object | ||
| values can be reproduced per partition without an object-safe `Clone`. | ||
|
|
||
| Materialization runs while the destination partition is being initialized. | ||
| Constructor functions, clone functions, and captured `ThreadAware` state must not | ||
| relocate an `Arc` backed by the same storage into that partition or form a cycle | ||
| among partition initializations. Write-once initialization is non-reentrant, so | ||
| such dependencies can deadlock. | ||
|
|
||
| A relocation whose source and destination resolve to the same partition is not a | ||
| cross-partition move: the holder keeps the value it is already carrying rather than | ||
| producing a new one. This is why every relocation under `PerProcess` — where all | ||
| affinities share one partition — preserves the shared value. | ||
|
|
||
| Relocation is a cooperative performance optimization, not a guarantee: consistent | ||
| with the crate-wide contract for `ThreadAware`, a holder that reaches a new | ||
| affinity without a relocation call still functions correctly, keeping the value it | ||
| already carries rather than switching. Dereferencing never blocks. | ||
|
|
||
| `Arc::strong_count` estimates how many strong references to the holder's current | ||
| value are held outside the shared storage: it is that value's raw `sync::Arc` | ||
| strong count minus the references the storage's partitions hold. It samples those two | ||
| counts separately, so under concurrent relocation the result is approximate, and | ||
| it saturates rather than underflowing. | ||
|
|
||
| ## 4. Unsized values | ||
|
|
||
| `T` may be unsized, so `Arc<dyn Trait, S>` and other trait-object or slice values | ||
| are supported. Because an unsized value cannot be passed or held by value, the | ||
| unsized-capable entry points work through a pointer: `new_boxed` and | ||
| `with_clone_fn` produce `Box<T>`, and `from_storage` and `Storage::insert` accept | ||
| a ready `sync::Arc<T>`. This support is a deliberate, retained capability of the | ||
| type; see [implementation.md](implementation.md) for how it shapes the slot | ||
| representation. | ||
|
|
||
| ## 5. Prepared storage | ||
|
|
||
| `storage::Storage<T, S>` is the strategy-partitioned table an `Arc` shares across | ||
| its clones, exposed as a handle a caller can build directly. This serves the case | ||
| where the partition values are known in advance rather than materialized lazily | ||
| on relocation: | ||
|
|
||
| 1. Build an empty table with `Storage::new`. | ||
| 2. Publish a `sync::Arc<T>` for each strategy partition by passing a representative | ||
| affinity to `Storage::insert`; read one back with `Storage::get`. | ||
| 3. Hand the table to `Arc::from_storage` together with the current affinity to | ||
| obtain an `Arc` backed by those values. | ||
|
|
||
| `from_storage` requires that the table already hold a value for the current | ||
| affinity's partition. An `Arc` built this way that later relocates into an | ||
| affinity whose partition the table left empty behaves like a plain `sync::Arc`, | ||
| keeping the value it carries. The handle exposes only the affinity-keyed | ||
| insert/get surface; the partition layout behind it is not part of the contract. | ||
|
|
||
| `Storage::insert` and `Storage::get` require an affinity that maps into the table's | ||
| coordinate space — one within the partition count the strategy reports. A caller | ||
| building storage by hand controls its own affinities, so these accessors reject | ||
| an out-of-range affinity rather than tolerate it. | ||
|
|
||
| ## 6. Design tenets | ||
|
|
||
| - **Performance first, correctness always.** The type exists to reduce | ||
| cross-affinity contention, but relocation is advisory: skipping it degrades | ||
| performance, never correctness. | ||
| - **Cheap, lock-free reads.** Dereferencing yields the carried value directly and | ||
| does not synchronize; synchronization is confined to relocation. | ||
| - **Per-partition values, in-partition sharing.** Lazily materialized values are | ||
| kept per partition and are not shared across partitions, while holders in one | ||
| partition share its value. Prepared storage is under the caller's control and | ||
| can deliberately place the same value in several partitions. | ||
| - **Unsized support is a feature.** Trait-object and slice values are a retained | ||
| capability, weighed against representations that would trade `?Sized` away. | ||
| - **Storage is usable, its shape is not exposed.** Callers can construct and fill | ||
| a `Storage`, but only through an affinity-keyed contract that leaves the | ||
| representation free to change. |
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.