From 9656d7086db0c886be3b58ea9218606c0e3d4e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Tue, 8 Sep 2026 10:14:34 -0300 Subject: [PATCH 01/17] docs(thread_aware): add a thread-aware authoring guide Adds a `_documentation` module with a task-oriented guide for authors of thread-aware types, complementing the existing (reference-style) API docs. Covers what thread-awareness is and why it exists, how to author a type (derive, `#[thread_aware(skip)]`, hand-written impls, `Unaware`, strategy `Arc`), how to choose among them, how to test that relocation reaches the right fields, how to debug and read relocation telemetry, and how to validate correctness. The anti-patterns section folds in the migration experience of moving a large production service onto an Oxidizer runtime - `Clone` copying stored affinity rather than relocating, `#[thread_aware(skip)]` on a sole field silently no-op'ing, not trusting inherited markings, and relocating the whole dependency graph once at a boundary. Follows the `recoverable::_documentation` pattern. All examples are doctested under both default and all-feature configurations. Refs AB#7552151, AB#7722787. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 254 ++++++++++++++++++ crates/thread_aware/src/lib.rs | 4 + 2 files changed, 258 insertions(+) create mode 100644 crates/thread_aware/src/_documentation/mod.rs diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs new file mode 100644 index 000000000..a62c886fe --- /dev/null +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -0,0 +1,254 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! A guide to authoring thread-aware types. +//! +//! The crate-level docs explain *what* [`ThreadAware`](crate::ThreadAware) is and the relocation +//! contract it expresses. This guide is the companion *how-to*: how to make your own types +//! thread-aware correctly, which implementation to reach for, how to test and debug the result, +//! and the mistakes that compile cleanly yet quietly do nothing. +//! +//! It is written for authors who see `T: ThreadAware` in an API and need to satisfy it, and for +//! reviewers deciding whether a `#[derive(ThreadAware)]` or a `#[thread_aware(skip)]` is the right +//! call. The lessons in [Anti-patterns](#anti-patterns) are drawn from migrating a large +//! production service onto an Oxidizer-backed runtime. +//! +//! # Why thread-awareness exists +//! +//! Oxidizer runtimes are thread-per-core: each worker owns its slice of the machine, and shared +//! state that silently spans cores turns into cross-NUMA traffic and lock contention. A +//! thread-aware type is told, through [`relocate`](crate::ThreadAware::relocate), that it has just +//! moved from one worker to another, and is given the chance to *rebind* its affinity-bearing +//! state - reconnect to the destination's I/O scheduler, re-home an allocation in the local NUMA +//! node, or detach from memory it was sharing with the previous worker. +//! +//! Relocation is a **performance cooperation**, never a correctness guarantee. A type must remain +//! correct if `relocate` is called at the wrong moment, called with the wrong threads, or never +//! called at all - see [Performance vs. Correctness](crate#performance-vs-correctness). That +//! single fact drives most of the guidance below: because nothing enforces relocation, a type that +//! *silently* fails to relocate is the failure mode to design against. +//! +//! # Authoring a thread-aware type +//! +//! ## Prefer the derive +//! +//! In almost all cases, implement [`ThreadAware`](crate::ThreadAware) with the derive macro. It +//! generates a [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every +//! field, which is exactly what a compound type owes its parts: +//! +//! ```rust +//! use thread_aware::{Thread, ThreadAware}; +//! +//! #[derive(ThreadAware)] +//! struct Connection { +//! pool: Vec, +//! scratch: String, +//! } +//! +//! // A runtime hands `relocate` the worker the value came from and the one it is moving to. +//! fn on_move(mut c: Connection, from: Option<&Thread>, to: &Thread) { +//! c.relocate(from, to); +//! } +//! ``` +//! +//! The `std` library types you are most likely to hold - `Vec`, `Box`, `Option`, `Result`, tuples, +//! arrays, maps - already implement the trait, so the derive "just works" on compounds of them. +//! +//! ## Skipping a field +//! +//! Annotate a field with `#[thread_aware(skip)]` when it carries no affinity and should be moved +//! as-is: a plain identifier, a length, a foreign handle that does no thread-local work. A skipped +//! field is never relocated, and the derive adds a `where Self: Send` bound to keep the +//! `ThreadAware: Send` supertrait satisfied. +//! +//! ```rust +//! use thread_aware::ThreadAware; +//! +//! #[derive(ThreadAware)] +//! struct Request { +//! body: Vec, +//! // A request id has no thread affinity; moving it verbatim is correct. +//! #[thread_aware(skip)] +//! id: u64, +//! } +//! ``` +//! +//! `skip` is a claim that a field genuinely has nothing to rebind. It is not an escape hatch for +//! "this field does not implement `ThreadAware` yet" - reach for [`Unaware`](crate::Unaware) or +//! [`Arc`](crate::Arc) for that, so the intent is visible in the type. +//! +//! ## What the generated bounds mean +//! +//! The derive bounds the **field type**, not the parameters inside it. For every relocated field +//! whose type mentions a generic parameter, it emits `where : ThreadAware` - the exact +//! obligation the generated body discharges when it relocates that field. So a `Vec` field +//! yields `where Vec: ThreadAware`, and a `Wrapper` field yields +//! `where Wrapper: ThreadAware`, governed by that wrapper's own impl rather than by a bound on +//! `T`. A field whose +//! type reaches no parameter, and a marker payload behind a function pointer +//! (`PhantomData`), owe no bound at all. See +//! [the derive's reference](crate::ThreadAware#generic-bounds) for the full rules. +//! +//! ## Implementing the trait by hand +//! +//! Write the impl yourself when relocation means something specific - re-homing an allocation, +//! swapping a per-core cache, reconnecting to a scheduler. The method receives the source worker +//! (`None` if unknown) and the destination: +//! +//! ```rust +//! use thread_aware::{Thread, ThreadAware}; +//! +//! struct PerCoreScratch { +//! buffer: Vec, +//! } +//! +//! impl ThreadAware for PerCoreScratch { +//! fn relocate(&mut self, _source: Option<&Thread>, _destination: &Thread) { +//! // The scratch buffer belonged to the previous worker; drop it so the next use +//! // re-allocates in the destination's NUMA node instead of reaching across. +//! self.buffer = Vec::new(); +//! } +//! } +//! ``` +//! +//! # Choosing an implementation +//! +//! | You have… | Reach for | Because | +//! |---|---|---| +//! | A compound of thread-aware fields | `#[derive(ThreadAware)]` | Forwards relocation to each field. | +//! | A field with genuine per-core behavior | a hand-written impl | Only you know what "rebind" means. | +//! | A foreign type that carries no affinity | [`Unaware`](crate::Unaware) | A `MoveAsIs`: implements the trait as a no-op. | +//! | Shared state that should differ per worker | [`Arc`](crate::Arc) | Materializes a separate `T` per destination. | +//! | Shared state that is the same everywhere | [`Arc`](crate::Arc) | Behaves as a vanilla `Arc`. | +//! +//! [`Unaware`](crate::Unaware) wraps a value and satisfies `ThreadAware` without reacting to +//! relocation - use it for inert, foreign, or allocation-free values that legitimately do not care +//! which worker they are on. Wrapping a type that *does* implement the trait is discouraged: it +//! silences that type's own relocation (a performance loss, not a correctness bug). +//! +//! The strategy-partitioned [`Arc`](crate::Arc) is the usual bridge to a type that does not +//! implement the trait itself: an `Arc` gives each worker its own `Foo`, while an +//! `Arc` shares one - the same `Arc` API, differing only in what relocation does. +//! +//! # Anti-patterns +//! +//! These are the shapes that compile, satisfy `T: ThreadAware`, and still leave state stranded on +//! the wrong worker. None of them produces a compile error, and most produce no runtime warning +//! either, so they are worth recognizing by sight. +//! +//! ## `Clone` does not relocate +//! +//! This is the one to internalize first. A thread-aware type stores its affinity in a field that +//! only [`relocate`](crate::ThreadAware::relocate) mutates. **`Clone` copies that stored affinity +//! verbatim.** Cloning a value that was built on worker A and using the clone on worker B does not +//! move it to B - it is still bound to A, quietly, until something calls `relocate`. +//! +//! ```text +//! let services = build_on_startup_worker(); // affinity = startup worker +//! let per_request = services.clone(); // affinity = startup worker (copied!) +//! // `per_request` now funnels every task back onto the startup worker. +//! ``` +//! +//! In one migration this single clone routed an entire process's work onto one core while the +//! others idled. If you clone a long-lived, affinity-bearing graph, relocate the clone at the point +//! it enters its new worker. +//! +//! ## `skip` on the only field is a silent no-op +//! +//! `#[thread_aware(skip)]` on the *sole* field of a type makes `relocate` do nothing, yet the type +//! still satisfies `T: ThreadAware`. Downstream code compiles, runtimes accept it, and no affinity +//! ever moves - with no error and no warning. A type whose every field is skipped is +//! indistinguishable from one that is genuinely inert; make sure that is what you meant. +//! +//! ## Do not trust inherited markings +//! +//! An existing `#[derive(ThreadAware)]` or `#[thread_aware(skip)]` is a decision someone made under +//! their constraints, and at least one such marking per audit tends to be a compile-shortcut that +//! reduces to a silent no-op. When you take a dependency on a type being thread-aware, verify that +//! its relocation actually reaches the state you care about rather than inheriting the annotation as +//! fact. +//! +//! ## Relocate the whole graph once, at the boundary +//! +//! When work crosses into a worker from the outside - an FFI entry, a hand-off from a foreign +//! thread - relocate the entire long-lived dependency graph **once**, at that boundary, rather than +//! special-casing each affinity-bearing dependency downstream. Make the graph's root `ThreadAware` +//! (by +//! derive) so a single `relocate` at the entry rebinds every affinity-bearing resource beneath it. +//! Relocating a subtree while its parent was built from a stale clone (see above) is how affinity +//! goes stale in practice. +//! +//! # Testing +//! +//! Because relocation is silent when it is wrong, test it by observation, not by trusting that the +//! derive did the right thing. The reliable pattern is a leaf type whose `relocate` records that it +//! was called, composed into the type under test; after one relocation, assert that every +//! non-skipped field was reached and every skipped field was not. +//! +//! ```rust +//! use thread_aware::{Thread, ThreadAware}; +//! +//! /// Counts relocations so a test can prove which fields the derive reaches. +//! #[derive(Default)] +//! struct Tracker { +//! relocations: usize, +//! } +//! +//! impl ThreadAware for Tracker { +//! fn relocate(&mut self, _source: Option<&Thread>, _destination: &Thread) { +//! self.relocations += 1; +//! } +//! } +//! +//! #[derive(ThreadAware)] +//! struct UnderTest { +//! tracked: Tracker, +//! #[thread_aware(skip)] +//! skipped: Tracker, +//! } +//! +//! fn assert_reaches_the_right_fields(from: Option<&Thread>, to: &Thread) { +//! let mut value = UnderTest { +//! tracked: Tracker::default(), +//! skipped: Tracker::default(), +//! }; +//! value.relocate(from, to); +//! assert_eq!(value.tracked.relocations, 1, "non-skipped fields must be relocated"); +//! assert_eq!(value.skipped.relocations, 0, "skipped fields must not be relocated"); +//! } +//! ``` +//! +//! Construct the [`Thread`](crate::Thread) values a real test needs with +//! [`ThreadBuilder`](crate::ThreadBuilder) (available with the default `std` feature). The +//! `test-utils` feature additionally offers a [`Relocator`](crate::Relocator) helper for driving +//! relocations in tests. +//! +//! # Debugging and telemetry +//! +//! When a value seems bound to the wrong worker, the question is almost always *was `relocate` +//! called, and did it reach this field?* The `Tracker` pattern above answers it in a test; in a +//! running system, a runtime that detects affinity-bearing state being touched from the wrong +//! worker is the signal to watch - for example, a debug-build warning such as a `*.thread_mismatch` +//! event with a backtrace at the offending access. Treat such a warning as a missing or too-late +//! `relocate`, most often a [stale clone](#clone-does-not-relocate). +//! +//! Remember that the *absence* of a warning does not prove correctness: relocation is best-effort, +//! so a value can be on the wrong worker with no diagnostic at all. Coverage of the relocation path +//! belongs in your tests, not in production telemetry. +//! +//! # Validating correctness +//! +//! What the toolchain checks for you, and what it cannot: +//! +//! * **The compiler** enforces the `ThreadAware: Send` supertrait and, through the derive's +//! field-type bounds, that every relocated field is itself thread-aware. It cannot tell whether a +//! `#[thread_aware(skip)]` is *justified* - only that the resulting type is still `Send`. +//! * **The derive** emits each field-type predicate once and suppresses a bound the author already +//! wrote, so a correct `#[derive(ThreadAware)]` does not trip +//! `clippy::trait_duplication_in_bounds`. +//! * **Your tests** are the only thing that checks the property that actually matters: that +//! relocation reaches the state it is supposed to. Nothing else does. +//! +//! The through-line of this guide: a thread-aware type that does the wrong thing usually does it +//! silently. Author for that - prefer the derive, justify every `skip`, relocate clones and graphs +//! at their boundaries, and prove it with a test that observes relocation happening. diff --git a/crates/thread_aware/src/lib.rs b/crates/thread_aware/src/lib.rs index 682a33183..214167a77 100644 --- a/crates/thread_aware/src/lib.rs +++ b/crates/thread_aware/src/lib.rs @@ -133,6 +133,10 @@ extern crate std; mod wrappers; +/// A guide to authoring thread-aware types: how to implement, test, and debug them, and the +/// anti-patterns to avoid. See [the guide](_documentation). +pub mod _documentation; + pub mod closure; #[cfg(feature = "test-utils")] From e6f95d3d10b91b7998c9b96795a6ef61844f712a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Tue, 8 Sep 2026 10:48:46 -0300 Subject: [PATCH 02/17] style(thread_aware): wrap guide doctest asserts for rustfmt `format_code_in_doc_comments` measures doc-comment code at the reduced width left by the `//! ` prefix, so the two `assert_eq!` calls in the testing example must wrap. Matches `cargo +nightly fmt --config-path ./unstable-rustfmt.toml`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index a62c886fe..55392c835 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -213,8 +213,14 @@ //! skipped: Tracker::default(), //! }; //! value.relocate(from, to); -//! assert_eq!(value.tracked.relocations, 1, "non-skipped fields must be relocated"); -//! assert_eq!(value.skipped.relocations, 0, "skipped fields must not be relocated"); +//! assert_eq!( +//! value.tracked.relocations, 1, +//! "non-skipped fields must be relocated" +//! ); +//! assert_eq!( +//! value.skipped.relocations, 0, +//! "skipped fields must not be relocated" +//! ); //! } //! ``` //! From 643ff25aa99cfc4e799a0c3ffe2878b5424b56b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:55:08 -0300 Subject: [PATCH 03/17] docs(thread_aware): gate the authoring guide with cfg(any(doc, test)) Addresses review feedback on PR #742: export the `_documentation` module only for rustdoc and tests, matching the established pattern in `recoverable` and `fetch`, so it does not become part of the crate's public API in normal builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/thread_aware/src/lib.rs b/crates/thread_aware/src/lib.rs index 214167a77..b2b1788b9 100644 --- a/crates/thread_aware/src/lib.rs +++ b/crates/thread_aware/src/lib.rs @@ -135,6 +135,7 @@ mod wrappers; /// A guide to authoring thread-aware types: how to implement, test, and debug them, and the /// anti-patterns to avoid. See [the guide](_documentation). +#[cfg(any(doc, test))] pub mod _documentation; pub mod closure; From 53482de5a541729f70986f8c4b06d2ba08225f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:51:08 -0300 Subject: [PATCH 04/17] docs(thread_aware): address review feedback on the authoring guide Per @martintmk's review of PR #742: - Trim "Why thread-awareness exists" to a pointer at the crate-level Theory of Operation instead of duplicating it. - Link "the derive macro" to the actual macro (`macro@crate::ThreadAware`). - Simplify "What the generated bounds mean" - defer the detail to the derive's Generic Bounds reference rather than restating it. - Add a "Per-worker state with `Arc`" section explaining when to reach for `Arc` (separate per-worker instances) vs `PerProcess`/`PerNumaNode`. - Make "Testing" more concise and lead with the `test-utils` `Relocator` helper. - Drop "Debugging and telemetry" - the telemetry story is not defined yet. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 81 +++++++------------ 1 file changed, 30 insertions(+), 51 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index 55392c835..f3b7af3bf 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -15,26 +15,20 @@ //! //! # Why thread-awareness exists //! -//! Oxidizer runtimes are thread-per-core: each worker owns its slice of the machine, and shared -//! state that silently spans cores turns into cross-NUMA traffic and lock contention. A -//! thread-aware type is told, through [`relocate`](crate::ThreadAware::relocate), that it has just -//! moved from one worker to another, and is given the chance to *rebind* its affinity-bearing -//! state - reconnect to the destination's I/O scheduler, re-home an allocation in the local NUMA -//! node, or detach from memory it was sharing with the previous worker. -//! -//! Relocation is a **performance cooperation**, never a correctness guarantee. A type must remain -//! correct if `relocate` is called at the wrong moment, called with the wrong threads, or never -//! called at all - see [Performance vs. Correctness](crate#performance-vs-correctness). That -//! single fact drives most of the guidance below: because nothing enforces relocation, a type that -//! *silently* fails to relocate is the failure mode to design against. +//! The crate-level [Theory of Operation](crate#theory-of-operation) covers what relocation is and +//! why thread-per-core runtimes need it. The one idea this guide leans on: relocation is a +//! **performance cooperation, never a correctness guarantee** (see +//! [Performance vs. Correctness](crate#performance-vs-correctness)). Nothing enforces it, so the +//! failure mode to design against is a type that *silently* fails to relocate. //! //! # Authoring a thread-aware type //! //! ## Prefer the derive //! -//! In almost all cases, implement [`ThreadAware`](crate::ThreadAware) with the derive macro. It -//! generates a [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every -//! field, which is exactly what a compound type owes its parts: +//! In almost all cases, implement [`ThreadAware`](crate::ThreadAware) with +//! [the derive macro](macro@crate::ThreadAware). It generates a +//! [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every field, which +//! is exactly what a compound type owes its parts: //! //! ```rust //! use thread_aware::{Thread, ThreadAware}; @@ -79,15 +73,10 @@ //! //! ## What the generated bounds mean //! -//! The derive bounds the **field type**, not the parameters inside it. For every relocated field -//! whose type mentions a generic parameter, it emits `where : ThreadAware` - the exact -//! obligation the generated body discharges when it relocates that field. So a `Vec` field -//! yields `where Vec: ThreadAware`, and a `Wrapper` field yields -//! `where Wrapper: ThreadAware`, governed by that wrapper's own impl rather than by a bound on -//! `T`. A field whose -//! type reaches no parameter, and a marker payload behind a function pointer -//! (`PhantomData`), owe no bound at all. See -//! [the derive's reference](crate::ThreadAware#generic-bounds) for the full rules. +//! You rarely need to reason about this: the derive adds exactly the `ThreadAware` bounds its +//! generated body needs and no more, so a correct type "just derives". When it matters - a generic +//! wrapper, or a marker field that should stay bound-free - the derive's +//! [Generic Bounds](macro@crate::ThreadAware#generic-bounds) reference has the rules. //! //! ## Implementing the trait by hand //! @@ -111,6 +100,16 @@ //! } //! ``` //! +//! ## Per-worker state with `Arc` +//! +//! When several workers share a value but each should keep its *own* instance - a per-core cache, a +//! pool you do not want contended across cores - wrap it in the strategy-partitioned +//! [`Arc`](crate::Arc). Relocation materializes a separate `T` for the destination +//! worker (lazily, on first use there), so the sharing is per-worker instead of process-wide. Reach +//! for [`Arc`](crate::Arc), which behaves as a vanilla `Arc`, when one shared +//! instance is what you want, and [`Arc`](crate::Arc) for one instance per NUMA +//! node. This is also the usual bridge to a type that does not implement `ThreadAware` itself. +//! //! # Choosing an implementation //! //! | You have… | Reach for | Because | @@ -126,10 +125,6 @@ //! which worker they are on. Wrapping a type that *does* implement the trait is discouraged: it //! silences that type's own relocation (a performance loss, not a correctness bug). //! -//! The strategy-partitioned [`Arc`](crate::Arc) is the usual bridge to a type that does not -//! implement the trait itself: an `Arc` gives each worker its own `Foo`, while an -//! `Arc` shares one - the same `Arc` API, differing only in what relocation does. -//! //! # Anti-patterns //! //! These are the shapes that compile, satisfy `T: ThreadAware`, and still leave state stranded on @@ -173,17 +168,16 @@ //! When work crosses into a worker from the outside - an FFI entry, a hand-off from a foreign //! thread - relocate the entire long-lived dependency graph **once**, at that boundary, rather than //! special-casing each affinity-bearing dependency downstream. Make the graph's root `ThreadAware` -//! (by -//! derive) so a single `relocate` at the entry rebinds every affinity-bearing resource beneath it. +//! (by derive) so a single `relocate` at the entry rebinds every affinity-bearing resource beneath +//! it. //! Relocating a subtree while its parent was built from a stale clone (see above) is how affinity //! goes stale in practice. //! //! # Testing //! -//! Because relocation is silent when it is wrong, test it by observation, not by trusting that the -//! derive did the right thing. The reliable pattern is a leaf type whose `relocate` records that it -//! was called, composed into the type under test; after one relocation, assert that every -//! non-skipped field was reached and every skipped field was not. +//! Relocation is silent when it is wrong, so test it by observation. Compose a leaf type whose +//! `relocate` records that it ran, relocate the type under test once, and assert that every +//! non-skipped field was reached and every skipped one was not: //! //! ```rust //! use thread_aware::{Thread, ThreadAware}; @@ -224,23 +218,8 @@ //! } //! ``` //! -//! Construct the [`Thread`](crate::Thread) values a real test needs with -//! [`ThreadBuilder`](crate::ThreadBuilder) (available with the default `std` feature). The -//! `test-utils` feature additionally offers a [`Relocator`](crate::Relocator) helper for driving -//! relocations in tests. -//! -//! # Debugging and telemetry -//! -//! When a value seems bound to the wrong worker, the question is almost always *was `relocate` -//! called, and did it reach this field?* The `Tracker` pattern above answers it in a test; in a -//! running system, a runtime that detects affinity-bearing state being touched from the wrong -//! worker is the signal to watch - for example, a debug-build warning such as a `*.thread_mismatch` -//! event with a backtrace at the offending access. Treat such a warning as a missing or too-late -//! `relocate`, most often a [stale clone](#clone-does-not-relocate). -//! -//! Remember that the *absence* of a warning does not prove correctness: relocation is best-effort, -//! so a value can be on the wrong worker with no diagnostic at all. Coverage of the relocation path -//! belongs in your tests, not in production telemetry. +//! The `test-utils` feature's [`Relocator`](crate::Relocator) drives relocations without hand-built +//! [`Thread`](crate::Thread) values, which is usually what a real test wants. //! //! # Validating correctness //! From 75221ab35708f57f8f2bccbc1b5761a23ddd5a94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:10:35 -0300 Subject: [PATCH 05/17] docs(thread_aware): keep guide intra-doc links accurate across feature configs Addresses Copilot review comments on PR #742: - Use the `derive@` disambiguator for the derive-macro links (matches the repo convention, e.g. `internity`), replacing `macro@`. - The `Arc` strategy section pointed at `crate::Arc` / `crate::PerThread` / `crate::PerNumaNode`, which are `std`-gated, so the links broke under `--no-default-features`. Name the `std` feature and drop the feature-gated intra-doc links in favour of plain code spans. - `Relocator` is `test-utils`-gated; its intra-doc link broke in doc builds without that feature. Reword to a plain code span. Doctests still pass under default and all-feature builds; the guide no longer contributes any broken-intra-doc-link warnings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index f3b7af3bf..49dc77e1d 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -26,7 +26,7 @@ //! ## Prefer the derive //! //! In almost all cases, implement [`ThreadAware`](crate::ThreadAware) with -//! [the derive macro](macro@crate::ThreadAware). It generates a +//! [the derive macro](derive@crate::ThreadAware). It generates a //! [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every field, which //! is exactly what a compound type owes its parts: //! @@ -76,7 +76,7 @@ //! You rarely need to reason about this: the derive adds exactly the `ThreadAware` bounds its //! generated body needs and no more, so a correct type "just derives". When it matters - a generic //! wrapper, or a marker field that should stay bound-free - the derive's -//! [Generic Bounds](macro@crate::ThreadAware#generic-bounds) reference has the rules. +//! [Generic Bounds](derive@crate::ThreadAware#generic-bounds) reference has the rules. //! //! ## Implementing the trait by hand //! @@ -103,12 +103,12 @@ //! ## Per-worker state with `Arc` //! //! When several workers share a value but each should keep its *own* instance - a per-core cache, a -//! pool you do not want contended across cores - wrap it in the strategy-partitioned -//! [`Arc`](crate::Arc). Relocation materializes a separate `T` for the destination -//! worker (lazily, on first use there), so the sharing is per-worker instead of process-wide. Reach -//! for [`Arc`](crate::Arc), which behaves as a vanilla `Arc`, when one shared -//! instance is what you want, and [`Arc`](crate::Arc) for one instance per NUMA -//! node. This is also the usual bridge to a type that does not implement `ThreadAware` itself. +//! pool you do not want contended across cores - wrap it in the strategy-partitioned `Arc` +//! that the crate's `std` feature provides (`thread_aware::Arc`). With the `PerThread` strategy, +//! relocation materializes a separate `T` for the destination worker (lazily, on first use there), +//! so the sharing is per-worker instead of process-wide. Use `PerProcess`, which behaves as a +//! vanilla `Arc`, when one shared instance is what you want, and `PerNumaNode` for one instance per +//! NUMA node. This is also the usual bridge to a type that does not implement `ThreadAware` itself. //! //! # Choosing an implementation //! @@ -117,8 +117,8 @@ //! | A compound of thread-aware fields | `#[derive(ThreadAware)]` | Forwards relocation to each field. | //! | A field with genuine per-core behavior | a hand-written impl | Only you know what "rebind" means. | //! | A foreign type that carries no affinity | [`Unaware`](crate::Unaware) | A `MoveAsIs`: implements the trait as a no-op. | -//! | Shared state that should differ per worker | [`Arc`](crate::Arc) | Materializes a separate `T` per destination. | -//! | Shared state that is the same everywhere | [`Arc`](crate::Arc) | Behaves as a vanilla `Arc`. | +//! | Shared state that should differ per worker | `Arc` (`std`) | Materializes a separate `T` per destination. | +//! | Shared state that is the same everywhere | `Arc` (`std`) | Behaves as a vanilla `Arc`. | //! //! [`Unaware`](crate::Unaware) wraps a value and satisfies `ThreadAware` without reacting to //! relocation - use it for inert, foreign, or allocation-free values that legitimately do not care @@ -218,8 +218,8 @@ //! } //! ``` //! -//! The `test-utils` feature's [`Relocator`](crate::Relocator) drives relocations without hand-built -//! [`Thread`](crate::Thread) values, which is usually what a real test wants. +//! The `test-utils` feature adds a `Relocator` (`thread_aware::Relocator`) that drives relocations +//! without hand-built [`Thread`](crate::Thread) values, which is usually what a real test wants. //! //! # Validating correctness //! From c1e896e5052bebb4595466c9137959d10250471e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Thu, 10 Sep 2026 10:32:15 -0300 Subject: [PATCH 06/17] docs(thread_aware): drop the last std-gated Arc intra-doc link in the guide Follow-up to 2ce36aaa: the "Skipping a field" section still linked `[Arc](crate::Arc)`, which is `std`-gated and breaks under `--no-default-features`. Replace it with a plain code span that names the `std` feature, matching the treatment applied to the other `Arc` references. The guide now contributes no broken-intra-doc-link warnings in the `--no-default-features` doc build; doctests still pass under default and all-feature builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index 49dc77e1d..fdd231f4b 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -68,8 +68,9 @@ //! ``` //! //! `skip` is a claim that a field genuinely has nothing to rebind. It is not an escape hatch for -//! "this field does not implement `ThreadAware` yet" - reach for [`Unaware`](crate::Unaware) or -//! [`Arc`](crate::Arc) for that, so the intent is visible in the type. +//! "this field does not implement `ThreadAware` yet" - reach for [`Unaware`](crate::Unaware) or the +//! strategy-partitioned `Arc` (with the `std` feature) for that, so the intent is visible in the +//! type. //! //! ## What the generated bounds mean //! From 9bfd55b38385384ad437169b3a1b33b630aae8bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Fri, 11 Sep 2026 07:27:23 -0300 Subject: [PATCH 07/17] docs(thread_aware): link facade types to docs.rs in the authoring guide Reference the facade types the guide mentions - the ThreadAware derive, Unaware, Arc, and Relocator - by docs.rs URL rather than intra-doc links, matching the pattern thread_aware_core already uses for these same types. This avoids a dev-dependency on thread_aware (and the dependency cycle it would create if the guide ever moves to thread_aware_core), and keeps every link resolvable regardless of which features the doc build enables - Arc is std-gated and Relocator is test-utils-gated, so intra-doc links to them broke under --no-default-features. Core types (the ThreadAware trait, Thread, relocate) stay as intra-doc links since they resolve in either crate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index fdd231f4b..cf448a1fc 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -26,9 +26,9 @@ //! ## Prefer the derive //! //! In almost all cases, implement [`ThreadAware`](crate::ThreadAware) with -//! [the derive macro](derive@crate::ThreadAware). It generates a -//! [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every field, which -//! is exactly what a compound type owes its parts: +//! [the derive macro](https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html). It +//! generates a [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every +//! field, which is exactly what a compound type owes its parts: //! //! ```rust //! use thread_aware::{Thread, ThreadAware}; @@ -68,7 +68,8 @@ //! ``` //! //! `skip` is a claim that a field genuinely has nothing to rebind. It is not an escape hatch for -//! "this field does not implement `ThreadAware` yet" - reach for [`Unaware`](crate::Unaware) or the +//! "this field does not implement `ThreadAware` yet" - reach for +//! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) or the //! strategy-partitioned `Arc` (with the `std` feature) for that, so the intent is visible in the //! type. //! @@ -77,7 +78,8 @@ //! You rarely need to reason about this: the derive adds exactly the `ThreadAware` bounds its //! generated body needs and no more, so a correct type "just derives". When it matters - a generic //! wrapper, or a marker field that should stay bound-free - the derive's -//! [Generic Bounds](derive@crate::ThreadAware#generic-bounds) reference has the rules. +//! [Generic Bounds](https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html#generic-bounds) +//! reference has the rules. //! //! ## Implementing the trait by hand //! @@ -105,7 +107,8 @@ //! //! When several workers share a value but each should keep its *own* instance - a per-core cache, a //! pool you do not want contended across cores - wrap it in the strategy-partitioned `Arc` -//! that the crate's `std` feature provides (`thread_aware::Arc`). With the `PerThread` strategy, +//! ([`thread_aware::Arc`](https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html), with +//! the crate's `std` feature). With the `PerThread` strategy, //! relocation materializes a separate `T` for the destination worker (lazily, on first use there), //! so the sharing is per-worker instead of process-wide. Use `PerProcess`, which behaves as a //! vanilla `Arc`, when one shared instance is what you want, and `PerNumaNode` for one instance per @@ -117,11 +120,12 @@ //! |---|---|---| //! | A compound of thread-aware fields | `#[derive(ThreadAware)]` | Forwards relocation to each field. | //! | A field with genuine per-core behavior | a hand-written impl | Only you know what "rebind" means. | -//! | A foreign type that carries no affinity | [`Unaware`](crate::Unaware) | A `MoveAsIs`: implements the trait as a no-op. | +//! | A foreign type that carries no affinity | [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) | A `MoveAsIs`: implements the trait as a no-op. | //! | Shared state that should differ per worker | `Arc` (`std`) | Materializes a separate `T` per destination. | //! | Shared state that is the same everywhere | `Arc` (`std`) | Behaves as a vanilla `Arc`. | //! -//! [`Unaware`](crate::Unaware) wraps a value and satisfies `ThreadAware` without reacting to +//! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) wraps a value +//! and satisfies `ThreadAware` without reacting to //! relocation - use it for inert, foreign, or allocation-free values that legitimately do not care //! which worker they are on. Wrapping a type that *does* implement the trait is discouraged: it //! silences that type's own relocation (a performance loss, not a correctness bug). @@ -219,8 +223,10 @@ //! } //! ``` //! -//! The `test-utils` feature adds a `Relocator` (`thread_aware::Relocator`) that drives relocations -//! without hand-built [`Thread`](crate::Thread) values, which is usually what a real test wants. +//! The `test-utils` feature adds a +//! [`Relocator`](https://docs.rs/thread_aware/latest/thread_aware/struct.Relocator.html) that drives +//! relocations without hand-built [`Thread`](crate::Thread) values, which is usually what a real +//! test wants. //! //! # Validating correctness //! From c3ab7fb964374657aee3276da81d119bed8f56aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:19:26 -0300 Subject: [PATCH 08/17] docs(thread_aware): gate the authoring guide on the derive feature Addresses a Copilot review comment on PR #742: the guide's examples all use `#[derive(ThreadAware)]`, which is only re-exported with the `derive` feature, but the module was included for `cfg(any(doc, test))` even when that feature is off - so the doctests would not compile in a build without the optional macro. Add `feature = "derive"` to the module's cfg. The guide (and its doctests) is present exactly when the derive it documents is available - in the default and all-feature builds - and simply absent otherwise, which is what `docs/feature-gated-doctests.md` requires. Verified: guide doctests run under default features and are absent (no failure) under --no-default-features. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/thread_aware/src/lib.rs b/crates/thread_aware/src/lib.rs index b2b1788b9..ffb54c545 100644 --- a/crates/thread_aware/src/lib.rs +++ b/crates/thread_aware/src/lib.rs @@ -135,7 +135,11 @@ mod wrappers; /// A guide to authoring thread-aware types: how to implement, test, and debug them, and the /// anti-patterns to avoid. See [the guide](_documentation). -#[cfg(any(doc, test))] +/// +/// Gated on `derive` because every example is built around `#[derive(ThreadAware)]`, which is only +/// available with that feature; this keeps the guide's doctests valid in a build without it (they +/// are simply absent) per `docs/feature-gated-doctests.md`. +#[cfg(all(any(doc, test), feature = "derive"))] pub mod _documentation; pub mod closure; From 7257c11ea4e755fd4dbe1732ab675f0ee27fff7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Fri, 11 Sep 2026 16:28:48 -0300 Subject: [PATCH 09/17] docs(thread_aware): reword to avoid the doctests spelling flag --- crates/thread_aware/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/thread_aware/src/lib.rs b/crates/thread_aware/src/lib.rs index ffb54c545..4ab6459cb 100644 --- a/crates/thread_aware/src/lib.rs +++ b/crates/thread_aware/src/lib.rs @@ -137,7 +137,7 @@ mod wrappers; /// anti-patterns to avoid. See [the guide](_documentation). /// /// Gated on `derive` because every example is built around `#[derive(ThreadAware)]`, which is only -/// available with that feature; this keeps the guide's doctests valid in a build without it (they +/// available with that feature; this keeps the guide's examples valid in a build without it (they /// are simply absent) per `docs/feature-gated-doctests.md`. #[cfg(all(any(doc, test), feature = "derive"))] pub mod _documentation; From c6ce0ad5eba74f3a06d48d7111ca008ce6fa7219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Tue, 29 Sep 2026 10:12:17 -0300 Subject: [PATCH 10/17] docs(thread_aware): fix stale Arc guidance and make the testing example execute Addresses review feedback from @geeknoid and Copilot on the authoring guide: - The "Per-worker state with Arc" section and the decision table named `thread_aware::Arc` / `PerNumaNode`, but `thread_aware` no longer exports an `Arc` or strategy types. The strategy-partitioned pointer now lives in the separate `performables` crate (`performables::arc::Arc`, strategies `PerProcess` / `PerThread` / `PerNuma`). Point the guide at those types via docs.rs links so every recommended path resolves, and note it is a separate dependency. - The testing example only *defined* `assert_reaches_the_right_fields` and never called it, so the doctest compiled without ever running `relocate` or the assertions. Rewrite it to build two coordinates with `ThreadBuilder`, relocate the value, and assert inline - so removing the `relocate` call or changing a count now fails the doctest. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- crates/thread_aware/src/_documentation/mod.rs | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index cf448a1fc..73c71a459 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -106,13 +106,18 @@ //! ## Per-worker state with `Arc` //! //! When several workers share a value but each should keep its *own* instance - a per-core cache, a -//! pool you do not want contended across cores - wrap it in the strategy-partitioned `Arc` -//! ([`thread_aware::Arc`](https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html), with -//! the crate's `std` feature). With the `PerThread` strategy, -//! relocation materializes a separate `T` for the destination worker (lazily, on first use there), -//! so the sharing is per-worker instead of process-wide. Use `PerProcess`, which behaves as a -//! vanilla `Arc`, when one shared instance is what you want, and `PerNumaNode` for one instance per -//! NUMA node. This is also the usual bridge to a type that does not implement `ThreadAware` itself. +//! pool you do not want contended across cores - reach for the strategy-partitioned `Arc` from +//! the separate [`performables`](https://docs.rs/performables) crate +//! ([`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html); +//! add it as a dependency). With the +//! [`PerThread`](https://docs.rs/performables/latest/performables/arc/struct.PerThread.html) +//! strategy, relocation materializes a separate `T` for the destination worker (lazily, on first use +//! there), so the sharing is per-worker instead of process-wide. Use +//! [`PerProcess`](https://docs.rs/performables/latest/performables/arc/struct.PerProcess.html), which +//! behaves as a vanilla `Arc`, when one shared instance is what you want, and +//! [`PerNuma`](https://docs.rs/performables/latest/performables/arc/struct.PerNuma.html) for one +//! instance per NUMA node. This is also the usual bridge to a type that does not implement +//! `ThreadAware` itself. //! //! # Choosing an implementation //! @@ -121,8 +126,8 @@ //! | A compound of thread-aware fields | `#[derive(ThreadAware)]` | Forwards relocation to each field. | //! | A field with genuine per-core behavior | a hand-written impl | Only you know what "rebind" means. | //! | A foreign type that carries no affinity | [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) | A `MoveAsIs`: implements the trait as a no-op. | -//! | Shared state that should differ per worker | `Arc` (`std`) | Materializes a separate `T` per destination. | -//! | Shared state that is the same everywhere | `Arc` (`std`) | Behaves as a vanilla `Arc`. | +//! | Shared state that should differ per worker | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Materializes a separate `T` per destination. | +//! | Shared state that is the same everywhere | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Behaves as a vanilla `Arc`. | //! //! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) wraps a value //! and satisfies `ThreadAware` without reacting to @@ -185,7 +190,9 @@ //! non-skipped field was reached and every skipped one was not: //! //! ```rust -//! use thread_aware::{Thread, ThreadAware}; +//! use std::thread; +//! +//! use thread_aware::{Thread, ThreadAware, ThreadBuilder}; //! //! /// Counts relocations so a test can prove which fields the derive reaches. //! #[derive(Default)] @@ -206,27 +213,25 @@ //! skipped: Tracker, //! } //! -//! fn assert_reaches_the_right_fields(from: Option<&Thread>, to: &Thread) { -//! let mut value = UnderTest { -//! tracked: Tracker::default(), -//! skipped: Tracker::default(), -//! }; -//! value.relocate(from, to); -//! assert_eq!( -//! value.tracked.relocations, 1, -//! "non-skipped fields must be relocated" -//! ); -//! assert_eq!( -//! value.skipped.relocations, 0, -//! "skipped fields must not be relocated" -//! ); -//! } +//! // Build two worker coordinates and relocate the value between them. +//! let builder = ThreadBuilder::default(); +//! let from = builder.build(thread::current().id()); +//! let to = builder.build(thread::spawn(|| thread::current().id()).join().unwrap()); +//! +//! let mut value = UnderTest { +//! tracked: Tracker::default(), +//! skipped: Tracker::default(), +//! }; +//! value.relocate(Some(&from), &to); +//! +//! assert_eq!(value.tracked.relocations, 1, "non-skipped fields must be relocated"); +//! assert_eq!(value.skipped.relocations, 0, "skipped fields must not be relocated"); //! ``` //! -//! The `test-utils` feature adds a -//! [`Relocator`](https://docs.rs/thread_aware/latest/thread_aware/struct.Relocator.html) that drives -//! relocations without hand-built [`Thread`](crate::Thread) values, which is usually what a real -//! test wants. +//! The example runs its own assertions, so removing the `relocate` call or changing either count +//! makes it fail. For a real test suite the `test-utils` feature's +//! [`Relocator`](https://docs.rs/thread_aware/latest/thread_aware/struct.Relocator.html) drives +//! relocations without hand-built [`Thread`](crate::Thread) values. //! //! # Validating correctness //! From adf43151ca458d355d06ae25c52bbe68090e6e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pato=20Sanda=C3=B1a?= <1194304+psandana@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:07:25 -0300 Subject: [PATCH 11/17] style(thread_aware): wrap testing-doctest asserts for nightly rustfmt --- crates/thread_aware/src/_documentation/mod.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index 73c71a459..2354cf887 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -216,7 +216,8 @@ //! // Build two worker coordinates and relocate the value between them. //! let builder = ThreadBuilder::default(); //! let from = builder.build(thread::current().id()); -//! let to = builder.build(thread::spawn(|| thread::current().id()).join().unwrap()); +//! let other = thread::spawn(|| thread::current().id()).join().unwrap(); +//! let to = builder.build(other); //! //! let mut value = UnderTest { //! tracked: Tracker::default(), @@ -224,8 +225,14 @@ //! }; //! value.relocate(Some(&from), &to); //! -//! assert_eq!(value.tracked.relocations, 1, "non-skipped fields must be relocated"); -//! assert_eq!(value.skipped.relocations, 0, "skipped fields must not be relocated"); +//! assert_eq!( +//! value.tracked.relocations, 1, +//! "non-skipped fields must be relocated" +//! ); +//! assert_eq!( +//! value.skipped.relocations, 0, +//! "skipped fields must not be relocated" +//! ); //! ``` //! //! The example runs its own assertions, so removing the `relocate` call or changing either count From 1ee99c08eb016790ea5fac368488e372739e29e6 Mon Sep 17 00:00:00 2001 From: psandana <1194304+psandana@users.noreply.github.com> Date: Wed, 30 Sep 2026 08:43:02 -0300 Subject: [PATCH 12/17] docs(thread_aware): address authoring-guide review feedback - Gate the testing doctest body on std (Pattern C hidden shims) so the derive-only rustdoc build no longer references the std-gated ThreadBuilder; note the std / test-utils boundary in prose. - Add doc(cfg(feature = "derive")) to the _documentation module. - Drop the unfulfilled "debug" claim from the guide scope and module summary. - Skip example now uses a non-ThreadAware foreign handle, not a u64; warn against skipping already-thread-aware fields. - State relocate's no-panic/no-error contract in the hand-impl section. - Describe PerThread Arc materialization as happening during relocate. - Replace the fictional MoveAsIs with a plain no-op description. - Narrow the Clone-copies-affinity wording to fieldwise clones. - Remove the unsupported per-audit frequency claim. - Distinguish the derive-mechanics demo from validating a real type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e0d5396-3a32-40ed-8870-241cd4929082 --- crates/thread_aware/src/_documentation/mod.rs | 79 ++++++++++++------- crates/thread_aware/src/lib.rs | 5 +- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index 2354cf887..b68053909 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -5,8 +5,8 @@ //! //! The crate-level docs explain *what* [`ThreadAware`](crate::ThreadAware) is and the relocation //! contract it expresses. This guide is the companion *how-to*: how to make your own types -//! thread-aware correctly, which implementation to reach for, how to test and debug the result, -//! and the mistakes that compile cleanly yet quietly do nothing. +//! thread-aware correctly, which implementation to reach for, how to test the result, and the +//! mistakes that compile cleanly yet quietly do nothing. //! //! It is written for authors who see `T: ThreadAware` in an API and need to satisfy it, and for //! reviewers deciding whether a `#[derive(ThreadAware)]` or a `#[thread_aware(skip)]` is the right @@ -50,28 +50,35 @@ //! //! ## Skipping a field //! -//! Annotate a field with `#[thread_aware(skip)]` when it carries no affinity and should be moved -//! as-is: a plain identifier, a length, a foreign handle that does no thread-local work. A skipped -//! field is never relocated, and the derive adds a `where Self: Send` bound to keep the -//! `ThreadAware: Send` supertrait satisfied. +//! Reach for `#[thread_aware(skip)]` when a field's type does not implement `ThreadAware` and has +//! no affinity to rebind - a foreign handle, an FFI resource that does no thread-local work. The +//! field is never relocated, and the derive drops the `ThreadAware` bound it would otherwise place +//! on it (adding `where Self: Send` to keep the supertrait satisfied). //! //! ```rust //! use thread_aware::ThreadAware; //! +//! // A handle from a C library: it does not implement `ThreadAware`, and it carries no +//! // thread affinity of its own. +//! struct ForeignHandle { +//! raw: usize, +//! } +//! //! #[derive(ThreadAware)] //! struct Request { //! body: Vec, -//! // A request id has no thread affinity; moving it verbatim is correct. //! #[thread_aware(skip)] -//! id: u64, +//! handle: ForeignHandle, //! } //! ``` //! -//! `skip` is a claim that a field genuinely has nothing to rebind. It is not an escape hatch for -//! "this field does not implement `ThreadAware` yet" - reach for -//! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) or the -//! strategy-partitioned `Arc` (with the `std` feature) for that, so the intent is visible in the -//! type. +//! Do not skip a field whose type already implements `ThreadAware` - even a primitive like `u64`, +//! whose impl is a harmless no-op. Forwarding is free and stays correct if the field later gains +//! affinity-bearing state; `skip` removes that safety net, so revisit each `skip` whenever the +//! field type changes. When you instead want a non-`ThreadAware` value to read as explicitly inert +//! in the type, wrap it in +//! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) rather than +//! skipping. //! //! ## What the generated bounds mean //! @@ -103,6 +110,12 @@ //! } //! ``` //! +//! [`relocate`](crate::ThreadAware::relocate) has no error channel and must not panic or block - it +//! runs on the runtime's placement path. If the ideal adaptation is unavailable (a reconnect +//! fails, a resource can't be rebuilt), keep the existing usable state, defer the work, or fall +//! back to a slower path rather than unwinding; see the +//! [trait contract](crate::ThreadAware::relocate) for the full requirements. +//! //! ## Per-worker state with `Arc` //! //! When several workers share a value but each should keep its *own* instance - a per-core cache, a @@ -111,8 +124,9 @@ //! ([`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html); //! add it as a dependency). With the //! [`PerThread`](https://docs.rs/performables/latest/performables/arc/struct.PerThread.html) -//! strategy, relocation materializes a separate `T` for the destination worker (lazily, on first use -//! there), so the sharing is per-worker instead of process-wide. Use +//! strategy, relocating to a destination worker that has no instance yet materializes a separate +//! `T` for it *during* the `relocate` call; relocating back to a worker that already has one reuses +//! it. Either way the sharing is per-worker instead of process-wide. Use //! [`PerProcess`](https://docs.rs/performables/latest/performables/arc/struct.PerProcess.html), which //! behaves as a vanilla `Arc`, when one shared instance is what you want, and //! [`PerNuma`](https://docs.rs/performables/latest/performables/arc/struct.PerNuma.html) for one @@ -125,7 +139,7 @@ //! |---|---|---| //! | A compound of thread-aware fields | `#[derive(ThreadAware)]` | Forwards relocation to each field. | //! | A field with genuine per-core behavior | a hand-written impl | Only you know what "rebind" means. | -//! | A foreign type that carries no affinity | [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) | A `MoveAsIs`: implements the trait as a no-op. | +//! | A foreign type that carries no affinity | [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) | Implements relocation as a no-op; moves the wrapped value unchanged. | //! | Shared state that should differ per worker | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Materializes a separate `T` per destination. | //! | Shared state that is the same everywhere | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Behaves as a vanilla `Arc`. | //! @@ -143,10 +157,12 @@ //! //! ## `Clone` does not relocate //! -//! This is the one to internalize first. A thread-aware type stores its affinity in a field that -//! only [`relocate`](crate::ThreadAware::relocate) mutates. **`Clone` copies that stored affinity -//! verbatim.** Cloning a value that was built on worker A and using the clone on worker B does not -//! move it to B - it is still bound to A, quietly, until something calls `relocate`. +//! This is the one to internalize first. A thread-aware type typically stores its affinity in a +//! field that only [`relocate`](crate::ThreadAware::relocate) mutates - and a derived (or otherwise +//! fieldwise) `Clone` then **copies that stored affinity verbatim**. A hand-written `Clone` could +//! rebind instead, but the trait neither requires nor guarantees that. Cloning such a value built +//! on worker A and using the clone on worker B does not move it to B - it stays bound to A, +//! quietly, until something calls `relocate`. //! //! ```text //! let services = build_on_startup_worker(); // affinity = startup worker @@ -168,10 +184,9 @@ //! ## Do not trust inherited markings //! //! An existing `#[derive(ThreadAware)]` or `#[thread_aware(skip)]` is a decision someone made under -//! their constraints, and at least one such marking per audit tends to be a compile-shortcut that -//! reduces to a silent no-op. When you take a dependency on a type being thread-aware, verify that -//! its relocation actually reaches the state you care about rather than inheriting the annotation as -//! fact. +//! their constraints, and a marking can reduce to a silent no-op. When you take a dependency on a +//! type being thread-aware, verify that its relocation actually reaches the state you care about +//! rather than inheriting the annotation as fact. //! //! ## Relocate the whole graph once, at the boundary //! @@ -190,6 +205,8 @@ //! non-skipped field was reached and every skipped one was not: //! //! ```rust +//! # fn main() { +//! # #[cfg(feature = "std")] { //! use std::thread; //! //! use thread_aware::{Thread, ThreadAware, ThreadBuilder}; @@ -233,12 +250,20 @@ //! value.skipped.relocations, 0, //! "skipped fields must not be relocated" //! ); +//! # } +//! # } //! ``` //! //! The example runs its own assertions, so removing the `relocate` call or changing either count -//! makes it fail. For a real test suite the `test-utils` feature's -//! [`Relocator`](https://docs.rs/thread_aware/latest/thread_aware/struct.Relocator.html) drives -//! relocations without hand-built [`Thread`](crate::Thread) values. +//! makes it fail. Building [`Thread`](crate::Thread) coordinates this way needs the `std` feature; +//! for a real test suite the `test-utils` feature's +//! [`Relocator`](https://docs.rs/thread_aware/latest/thread_aware/struct.Relocator.html) (which +//! implies `std`) drives relocations without hand-built coordinates. +//! +//! `UnderTest` here only exercises the derive's field-forwarding mechanics. Point the same +//! observe-relocation technique at your *real* type: instantiate it with a recording leaf where it +//! is generic or injectable, otherwise capture its actual affinity-bearing state before and after +//! `relocate`. A green test on a stand-in proxy does not prove your production graph relocates. //! //! # Validating correctness //! diff --git a/crates/thread_aware/src/lib.rs b/crates/thread_aware/src/lib.rs index 4ab6459cb..d61a55ed2 100644 --- a/crates/thread_aware/src/lib.rs +++ b/crates/thread_aware/src/lib.rs @@ -133,13 +133,14 @@ extern crate std; mod wrappers; -/// A guide to authoring thread-aware types: how to implement, test, and debug them, and the -/// anti-patterns to avoid. See [the guide](_documentation). +/// A guide to authoring thread-aware types: how to implement and test them, and the anti-patterns +/// to avoid. See [the guide](_documentation). /// /// Gated on `derive` because every example is built around `#[derive(ThreadAware)]`, which is only /// available with that feature; this keeps the guide's examples valid in a build without it (they /// are simply absent) per `docs/feature-gated-doctests.md`. #[cfg(all(any(doc, test), feature = "derive"))] +#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub mod _documentation; pub mod closure; From 6dd1a20186ee414022221e638e67cf0a6524a252 Mon Sep 17 00:00:00 2001 From: psandana <1194304+psandana@users.noreply.github.com> Date: Wed, 30 Sep 2026 09:50:21 -0300 Subject: [PATCH 13/17] docs(thread_aware): reword to satisfy spellcheck Replace "fieldwise" and "injectable" (not in the crate dictionary) with plain-English equivalents; no content change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e0d5396-3a32-40ed-8870-241cd4929082 --- crates/thread_aware/src/_documentation/mod.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index b68053909..c59997a1c 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -158,11 +158,11 @@ //! ## `Clone` does not relocate //! //! This is the one to internalize first. A thread-aware type typically stores its affinity in a -//! field that only [`relocate`](crate::ThreadAware::relocate) mutates - and a derived (or otherwise -//! fieldwise) `Clone` then **copies that stored affinity verbatim**. A hand-written `Clone` could -//! rebind instead, but the trait neither requires nor guarantees that. Cloning such a value built -//! on worker A and using the clone on worker B does not move it to B - it stays bound to A, -//! quietly, until something calls `relocate`. +//! field that only [`relocate`](crate::ThreadAware::relocate) mutates - and a derived `Clone` then +//! **copies that stored affinity verbatim**. A hand-written `Clone` could rebind instead, but the +//! trait neither requires nor guarantees that. Cloning such a value built on worker A and using the +//! clone on worker B does not move it to B - it stays bound to A, quietly, until something calls +//! `relocate`. //! //! ```text //! let services = build_on_startup_worker(); // affinity = startup worker @@ -262,8 +262,9 @@ //! //! `UnderTest` here only exercises the derive's field-forwarding mechanics. Point the same //! observe-relocation technique at your *real* type: instantiate it with a recording leaf where it -//! is generic or injectable, otherwise capture its actual affinity-bearing state before and after -//! `relocate`. A green test on a stand-in proxy does not prove your production graph relocates. +//! is generic or dependency-injected, otherwise capture its real affinity-bearing state before and +//! after `relocate`. A green test on a stand-in proxy does not prove your production graph +//! relocates. //! //! # Validating correctness //! From c32b749d353589e68ff93465d94ee50b02ba8eb4 Mon Sep 17 00:00:00 2001 From: psandana <1194304+psandana@users.noreply.github.com> Date: Wed, 30 Sep 2026 13:34:06 -0300 Subject: [PATCH 14/17] docs(thread_aware_core): link to the thread_aware authoring guide The derive-first authoring guide can't live in `thread_aware_core`: the `#[derive(ThreadAware)]` macro hardcodes `::thread_aware` paths and its testing recipe needs `thread_aware`-only `ThreadBuilder` (`Thread::new` is `pub(crate)`), so hosting it here would require a `thread_aware_core -> thread_aware` dev-dependency cycle. Point core readers at the guide via docs.rs instead, matching how core already references the facade. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e0d5396-3a32-40ed-8870-241cd4929082 --- crates/thread_aware_core/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/thread_aware_core/src/lib.rs b/crates/thread_aware_core/src/lib.rs index 755285b59..56419a0b2 100644 --- a/crates/thread_aware_core/src/lib.rs +++ b/crates/thread_aware_core/src/lib.rs @@ -40,11 +40,13 @@ //! - **[`thread_aware`]** — the utilities that make relocation convenient: a //! [`#[derive(ThreadAware)]`][derive] macro, closure adapters, wrappers for foreign types, //! runtime coordinate construction, and strategy-partitioned [`Arc`][arc] storage. Free to -//! evolve, and not meant to appear in a public API. +//! evolve, and not meant to appear in a public API. Its [authoring guide] is the how-to for +//! making your own types thread-aware. //! //! [`thread_aware`]: https://docs.rs/thread_aware //! [derive]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html //! [arc]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html +//! [authoring guide]: https://docs.rs/thread_aware/latest/thread_aware/_documentation/index.html //! //! Depend on this crate directly when all you need is the trait. It has no normal dependencies //! and works without `std`: with default features turned off, [`Thread`] loses its thread id From 08c3c1f74ef4180195632b93761eab6d40acf873 Mon Sep 17 00:00:00 2001 From: psandana <1194304+psandana@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:02:59 -0300 Subject: [PATCH 15/17] docs(thread_aware_core): regenerate README for the authoring-guide link Auto-generated by cargo-doc2readme (pinned 0.7.3) after the crate-doc change in the previous commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e0d5396-3a32-40ed-8870-241cd4929082 --- crates/thread_aware_core/README.md | 102 +++++++++++++++-------------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/crates/thread_aware_core/README.md b/crates/thread_aware_core/README.md index 18eb9d522..3c01f5c32 100644 --- a/crates/thread_aware_core/README.md +++ b/crates/thread_aware_core/README.md @@ -40,11 +40,12 @@ the larger utility surface evolves independently. * **[`thread_aware`][__link5]** — the utilities that make relocation convenient: a [`#[derive(ThreadAware)]`][__link6] macro, closure adapters, wrappers for foreign types, runtime coordinate construction, and strategy-partitioned [`Arc`][__link7] storage. Free to - evolve, and not meant to appear in a public API. + evolve, and not meant to appear in a public API. Its [authoring guide][__link8] is the how-to for + making your own types thread-aware. Depend on this crate directly when all you need is the trait. It has no normal dependencies -and works without `std`: with default features turned off, [`Thread`][__link8] loses its thread id -component and keeps [`Owner`][__link9] and [`NumaNode`][__link10]. +and works without `std`: with default features turned off, [`Thread`][__link9] loses its thread id +component and keeps [`Owner`][__link10] and [`NumaNode`][__link11]. ## Why relocation exists @@ -54,19 +55,19 @@ driver, and does not synchronize with other workers. When a value moves to anoth worker, what used to be close by is now in the wrong place: a cache line shared between threads, memory in a distant region, a handle to another thread’s driver. -[`ThreadAware`][__link11] lets that state repair itself. The runtime moves the value, then calls -[`relocate`][__link12] to report where it now lives. Relocation has two +[`ThreadAware`][__link12] lets that state repair itself. The runtime moves the value, then calls +[`relocate`][__link13] to report where it now lives. Relocation has two sides, and most code sits on only one of them. ## Library authors: implementing the trait -**Library and application authors** implement [`ThreadAware`][__link13], usually through the -[`#[derive(ThreadAware)]`][__link14] macro. They never call -[`relocate`][__link15] and never construct a [`Thread`][__link16]; the runtime does -both and then invokes the implementation. It is a callback, like [`Drop::drop`][__link17]. +**Library and application authors** implement [`ThreadAware`][__link14], usually through the +[`#[derive(ThreadAware)]`][__link15] macro. They never call +[`relocate`][__link16] and never construct a [`Thread`][__link17]; the runtime does +both and then invokes the implementation. It is a callback, like [`Drop::drop`][__link18]. -The derive lives in [`thread_aware`][__link18], so a library that wants it depends on that crate. -Only the trait, [`Thread`][__link19], and its component identifiers cross the public boundary, and all +The derive lives in [`thread_aware`][__link19], so a library that wants it depends on that crate. +Only the trait, [`Thread`][__link20], and its component identifiers cross the public boundary, and all come from here, so the dependency stays an implementation detail: ```rust @@ -83,15 +84,15 @@ pub struct Encoder { The derive writes the forwarding implementation, calling `relocate` on `scratch` and `dictionary` in turn. Because a composed type forwards to its fields, one call at the top -reaches everything below it. Callers of `Encoder` never name [`thread_aware`][__link20]. +reaches everything below it. Callers of `Encoder` never name [`thread_aware`][__link21]. ## Runtime authors: driving relocation -**Runtime authors** create a list of [`Thread`][__link21] values describing their workers. How that +**Runtime authors** create a list of [`Thread`][__link22] values describing their workers. How that list is constructed is a runtime implementation detail and is not relevant to runtime consumers. -After moving a value, the runtime calls [`relocate`][__link22], passing where +After moving a value, the runtime calls [`relocate`][__link23], passing where the value came from and where it now runs. ## Performance, not correctness @@ -109,18 +110,18 @@ call. ## What the ids mean * **Thread id** identifies a live OS thread. -* **[`NumaNode`][__link23]** identifies nearby memory and is shared by threads in the same region. +* **[`NumaNode`][__link24]** identifies nearby memory and is shared by threads in the same region. It is meaningful across runtimes only when they number regions identically. -* **[`Owner`][__link24]** uniquely identifies the runtime a [`Thread`][__link25] belongs to. +* **[`Owner`][__link25]** uniquely identifies the runtime a [`Thread`][__link26] belongs to. These ids are opaque and need not be consecutive. Use only the coordinate your state depends on, and store keyed state in a map rather than an indexed array. ## Relation to `Send` -[`ThreadAware`][__link26] requires [`Send`][__link27], and in that order: a value is sent to another thread -first, then told where it landed. [`Send`][__link28] is what makes the move safe, and -[`ThreadAware`][__link29] adds nothing to it. +[`ThreadAware`][__link27] requires [`Send`][__link28], and in that order: a value is sent to another thread +first, then told where it landed. [`Send`][__link29] is what makes the move safe, and +[`ThreadAware`][__link30] adds nothing to it. ## Provided implementations @@ -130,13 +131,13 @@ relocation to their values, while map keys remain unchanged. General references, sets, `Cow`, and `Arc` have no implementation because relocation would be ambiguous or could violate their invariants. The narrow reference exception is `&'static str`: immutable process-lifetime labels cannot dangle and carry no referent state to relocate. -[`thread_aware`][__link30] provides wrappers for cases that need an explicit policy, including its -strategy-partitioned [`Arc`][__link31]. +[`thread_aware`][__link31] provides wrappers for cases that need an explicit policy, including its +strategy-partitioned [`Arc`][__link32]. ## Features -* **`std`** *(default)* - Adds runtime construction support and [`Thread::id`][__link32], which need - [`ThreadId`][__link33], and implements [`ThreadAware`][__link34] for standard library +* **`std`** *(default)* - Adds runtime construction support and [`Thread::id`][__link33], which need + [`ThreadId`][__link34], and implements [`ThreadAware`][__link35] for standard library types such as `HashMap`, `Path` and `PathBuf`. Turn it off for `no_std`, which needs only `alloc` and pointer-width atomics. @@ -146,39 +147,40 @@ strategy-partitioned [`Arc`][__link31]. This crate was developed as part of The Oxidizer Project. Browse this crate's source code. - [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQborR2_k_xJd4bTcf2krrNPIcbP72Pw1UdRjkbim_eMDe2BBthYvRhcoQbaud81CVbfjgbWXnplkiWVocb2M0ryv2Vh08bO8ENADRtsdlhZIGCcXRocmVhZF9hd2FyZV9jb3JlZTAuMS4x + [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQborR2_k_xJd4bTcf2krrNPIcbP72Pw1UdRjkbim_eMDe2BBthYvRhcoQbSZnONyJOJwIb2BxHmwWF9lob46XCiei0380bPr8T996eEh1hZIGCcXRocmVhZF9hd2FyZV9jb3JlZTAuMS4x [__link0]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware [__link1]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link10]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode - [__link11]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware - [__link12]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link13]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware - [__link14]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html - [__link15]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link16]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link17]: https://doc.rust-lang.org/stable/std/?search=ops::Drop::drop - [__link18]: https://docs.rs/thread_aware - [__link19]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link10]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner + [__link11]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode + [__link12]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link13]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate + [__link14]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link15]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html + [__link16]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate + [__link17]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link18]: https://doc.rust-lang.org/stable/std/?search=ops::Drop::drop + [__link19]: https://docs.rs/thread_aware [__link2]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link20]: https://docs.rs/thread_aware - [__link21]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link22]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link23]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode - [__link24]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner - [__link25]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link26]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware - [__link27]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html + [__link20]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link21]: https://docs.rs/thread_aware + [__link22]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link23]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate + [__link24]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode + [__link25]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner + [__link26]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link27]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware [__link28]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html - [__link29]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link29]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html [__link3]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link30]: https://docs.rs/thread_aware - [__link31]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html - [__link32]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread::id - [__link33]: https://doc.rust-lang.org/stable/std/?search=thread::ThreadId - [__link34]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link30]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link31]: https://docs.rs/thread_aware + [__link32]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html + [__link33]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread::id + [__link34]: https://doc.rust-lang.org/stable/std/?search=thread::ThreadId + [__link35]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware [__link4]: https://doc.rust-lang.org/stable/std/?search=thread::Thread [__link5]: https://docs.rs/thread_aware [__link6]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html [__link7]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html - [__link8]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link9]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner + [__link8]: https://docs.rs/thread_aware/latest/thread_aware/_documentation/index.html + [__link9]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread From 41c29e428b907f1bd7687c2ff5e267321cd5f7dc Mon Sep 17 00:00:00 2001 From: psandana <1194304+psandana@users.noreply.github.com> Date: Thu, 1 Oct 2026 09:47:01 -0300 Subject: [PATCH 16/17] docs(thread_aware): fix relocate blocking wording and stale core Arc refs - Guide: "must not block" was too absolute. Match the trait contract (thread_aware_core/src/thread_aware.rs) - relocate forbids long/external blocking (contended lock, network/disk I/O, waiting on external progress) while brief in-memory coordination is fine. This also aligns with the guide's own Arc example, whose impl holds a lock. - thread_aware_core docs: the strategy-partitioned Arc moved to performables::arc::Arc, so retarget the dead thread_aware::Arc link and stop listing Arc among thread_aware's utilities. Regenerate README (cargo-doc2readme 0.7.3). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e0d5396-3a32-40ed-8870-241cd4929082 --- crates/thread_aware/src/_documentation/mod.rs | 12 +- crates/thread_aware_core/README.md | 108 +++++++++--------- crates/thread_aware_core/src/lib.rs | 13 +-- 3 files changed, 66 insertions(+), 67 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index c59997a1c..3a07050b9 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -110,11 +110,13 @@ //! } //! ``` //! -//! [`relocate`](crate::ThreadAware::relocate) has no error channel and must not panic or block - it -//! runs on the runtime's placement path. If the ideal adaptation is unavailable (a reconnect -//! fails, a resource can't be rebuilt), keep the existing usable state, defer the work, or fall -//! back to a slower path rather than unwinding; see the -//! [trait contract](crate::ThreadAware::relocate) for the full requirements. +//! [`relocate`](crate::ThreadAware::relocate) has no error channel and must not panic. Because it +//! runs on the runtime's placement path, it also must not perform long or external blocking work - +//! no contended lock, network or disk I/O, or waiting on external progress (brief in-memory +//! coordination is fine). If the ideal adaptation is unavailable (a reconnect fails, a resource +//! can't be rebuilt), keep the existing usable state, defer the work, or fall back to a slower path +//! rather than unwinding; see the [trait contract](crate::ThreadAware::relocate) for the full +//! requirements. //! //! ## Per-worker state with `Arc` //! diff --git a/crates/thread_aware_core/README.md b/crates/thread_aware_core/README.md index 3c01f5c32..911c8103b 100644 --- a/crates/thread_aware_core/README.md +++ b/crates/thread_aware_core/README.md @@ -38,14 +38,13 @@ the larger utility surface evolves independently. agree on before either can relocate a value defined by the other. It evolves conservatively, reducing how much public APIs couple to changes in the utility crate. * **[`thread_aware`][__link5]** — the utilities that make relocation convenient: a - [`#[derive(ThreadAware)]`][__link6] macro, closure adapters, wrappers for foreign types, - runtime coordinate construction, and strategy-partitioned [`Arc`][__link7] storage. Free to - evolve, and not meant to appear in a public API. Its [authoring guide][__link8] is the how-to for - making your own types thread-aware. + [`#[derive(ThreadAware)]`][__link6] macro, closure adapters, wrappers for foreign types, and + runtime coordinate construction. Free to evolve, and not meant to appear in a public API. Its + [authoring guide][__link7] is the how-to for making your own types thread-aware. Depend on this crate directly when all you need is the trait. It has no normal dependencies -and works without `std`: with default features turned off, [`Thread`][__link9] loses its thread id -component and keeps [`Owner`][__link10] and [`NumaNode`][__link11]. +and works without `std`: with default features turned off, [`Thread`][__link8] loses its thread id +component and keeps [`Owner`][__link9] and [`NumaNode`][__link10]. ## Why relocation exists @@ -55,19 +54,19 @@ driver, and does not synchronize with other workers. When a value moves to anoth worker, what used to be close by is now in the wrong place: a cache line shared between threads, memory in a distant region, a handle to another thread’s driver. -[`ThreadAware`][__link12] lets that state repair itself. The runtime moves the value, then calls -[`relocate`][__link13] to report where it now lives. Relocation has two +[`ThreadAware`][__link11] lets that state repair itself. The runtime moves the value, then calls +[`relocate`][__link12] to report where it now lives. Relocation has two sides, and most code sits on only one of them. ## Library authors: implementing the trait -**Library and application authors** implement [`ThreadAware`][__link14], usually through the -[`#[derive(ThreadAware)]`][__link15] macro. They never call -[`relocate`][__link16] and never construct a [`Thread`][__link17]; the runtime does -both and then invokes the implementation. It is a callback, like [`Drop::drop`][__link18]. +**Library and application authors** implement [`ThreadAware`][__link13], usually through the +[`#[derive(ThreadAware)]`][__link14] macro. They never call +[`relocate`][__link15] and never construct a [`Thread`][__link16]; the runtime does +both and then invokes the implementation. It is a callback, like [`Drop::drop`][__link17]. -The derive lives in [`thread_aware`][__link19], so a library that wants it depends on that crate. -Only the trait, [`Thread`][__link20], and its component identifiers cross the public boundary, and all +The derive lives in [`thread_aware`][__link18], so a library that wants it depends on that crate. +Only the trait, [`Thread`][__link19], and its component identifiers cross the public boundary, and all come from here, so the dependency stays an implementation detail: ```rust @@ -84,15 +83,15 @@ pub struct Encoder { The derive writes the forwarding implementation, calling `relocate` on `scratch` and `dictionary` in turn. Because a composed type forwards to its fields, one call at the top -reaches everything below it. Callers of `Encoder` never name [`thread_aware`][__link21]. +reaches everything below it. Callers of `Encoder` never name [`thread_aware`][__link20]. ## Runtime authors: driving relocation -**Runtime authors** create a list of [`Thread`][__link22] values describing their workers. How that +**Runtime authors** create a list of [`Thread`][__link21] values describing their workers. How that list is constructed is a runtime implementation detail and is not relevant to runtime consumers. -After moving a value, the runtime calls [`relocate`][__link23], passing where +After moving a value, the runtime calls [`relocate`][__link22], passing where the value came from and where it now runs. ## Performance, not correctness @@ -110,18 +109,18 @@ call. ## What the ids mean * **Thread id** identifies a live OS thread. -* **[`NumaNode`][__link24]** identifies nearby memory and is shared by threads in the same region. +* **[`NumaNode`][__link23]** identifies nearby memory and is shared by threads in the same region. It is meaningful across runtimes only when they number regions identically. -* **[`Owner`][__link25]** uniquely identifies the runtime a [`Thread`][__link26] belongs to. +* **[`Owner`][__link24]** uniquely identifies the runtime a [`Thread`][__link25] belongs to. These ids are opaque and need not be consecutive. Use only the coordinate your state depends on, and store keyed state in a map rather than an indexed array. ## Relation to `Send` -[`ThreadAware`][__link27] requires [`Send`][__link28], and in that order: a value is sent to another thread -first, then told where it landed. [`Send`][__link29] is what makes the move safe, and -[`ThreadAware`][__link30] adds nothing to it. +[`ThreadAware`][__link26] requires [`Send`][__link27], and in that order: a value is sent to another thread +first, then told where it landed. [`Send`][__link28] is what makes the move safe, and +[`ThreadAware`][__link29] adds nothing to it. ## Provided implementations @@ -131,13 +130,13 @@ relocation to their values, while map keys remain unchanged. General references, sets, `Cow`, and `Arc` have no implementation because relocation would be ambiguous or could violate their invariants. The narrow reference exception is `&'static str`: immutable process-lifetime labels cannot dangle and carry no referent state to relocate. -[`thread_aware`][__link31] provides wrappers for cases that need an explicit policy, including its -strategy-partitioned [`Arc`][__link32]. +[`thread_aware`][__link30] provides wrappers for cases that need an explicit policy, and the companion +`performables` crate adds a strategy-partitioned [`Arc`][__link31]. ## Features -* **`std`** *(default)* - Adds runtime construction support and [`Thread::id`][__link33], which need - [`ThreadId`][__link34], and implements [`ThreadAware`][__link35] for standard library +* **`std`** *(default)* - Adds runtime construction support and [`Thread::id`][__link32], which need + [`ThreadId`][__link33], and implements [`ThreadAware`][__link34] for standard library types such as `HashMap`, `Path` and `PathBuf`. Turn it off for `no_std`, which needs only `alloc` and pointer-width atomics. @@ -147,40 +146,39 @@ strategy-partitioned [`Arc`][__link32]. This crate was developed as part of The Oxidizer Project. Browse this crate's source code. - [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQborR2_k_xJd4bTcf2krrNPIcbP72Pw1UdRjkbim_eMDe2BBthYvRhcoQbSZnONyJOJwIb2BxHmwWF9lob46XCiei0380bPr8T996eEh1hZIGCcXRocmVhZF9hd2FyZV9jb3JlZTAuMS4x + [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQborR2_k_xJd4bTcf2krrNPIcbP72Pw1UdRjkbim_eMDe2BBthYvRhcoQbmZMSr1CdOtQbji0VWctOlw0bg5-BcXSNHZQbrgPvE0UF_8FhZIGCcXRocmVhZF9hd2FyZV9jb3JlZTAuMS4x [__link0]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware [__link1]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link10]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner - [__link11]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode - [__link12]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware - [__link13]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link14]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware - [__link15]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html - [__link16]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link17]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link18]: https://doc.rust-lang.org/stable/std/?search=ops::Drop::drop - [__link19]: https://docs.rs/thread_aware + [__link10]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode + [__link11]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link12]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate + [__link13]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link14]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html + [__link15]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate + [__link16]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link17]: https://doc.rust-lang.org/stable/std/?search=ops::Drop::drop + [__link18]: https://docs.rs/thread_aware + [__link19]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread [__link2]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link20]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link21]: https://docs.rs/thread_aware - [__link22]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link23]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate - [__link24]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode - [__link25]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner - [__link26]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link27]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link20]: https://docs.rs/thread_aware + [__link21]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link22]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware::relocate + [__link23]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=NumaNode + [__link24]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner + [__link25]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link26]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link27]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html [__link28]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html - [__link29]: https://doc.rust-lang.org/stable/std/marker/trait.Send.html + [__link29]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware [__link3]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread - [__link30]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware - [__link31]: https://docs.rs/thread_aware - [__link32]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html - [__link33]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread::id - [__link34]: https://doc.rust-lang.org/stable/std/?search=thread::ThreadId - [__link35]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware + [__link30]: https://docs.rs/thread_aware + [__link31]: https://docs.rs/performables/latest/performables/arc/struct.Arc.html + [__link32]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread::id + [__link33]: https://doc.rust-lang.org/stable/std/?search=thread::ThreadId + [__link34]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=ThreadAware [__link4]: https://doc.rust-lang.org/stable/std/?search=thread::Thread [__link5]: https://docs.rs/thread_aware [__link6]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html - [__link7]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html - [__link8]: https://docs.rs/thread_aware/latest/thread_aware/_documentation/index.html - [__link9]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link7]: https://docs.rs/thread_aware/latest/thread_aware/_documentation/index.html + [__link8]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Thread + [__link9]: https://docs.rs/thread_aware_core/0.1.1/thread_aware_core/?search=Owner diff --git a/crates/thread_aware_core/src/lib.rs b/crates/thread_aware_core/src/lib.rs index 56419a0b2..48a22babd 100644 --- a/crates/thread_aware_core/src/lib.rs +++ b/crates/thread_aware_core/src/lib.rs @@ -38,14 +38,13 @@ //! agree on before either can relocate a value defined by the other. It evolves //! conservatively, reducing how much public APIs couple to changes in the utility crate. //! - **[`thread_aware`]** — the utilities that make relocation convenient: a -//! [`#[derive(ThreadAware)]`][derive] macro, closure adapters, wrappers for foreign types, -//! runtime coordinate construction, and strategy-partitioned [`Arc`][arc] storage. Free to -//! evolve, and not meant to appear in a public API. Its [authoring guide] is the how-to for -//! making your own types thread-aware. +//! [`#[derive(ThreadAware)]`][derive] macro, closure adapters, wrappers for foreign types, and +//! runtime coordinate construction. Free to evolve, and not meant to appear in a public API. Its +//! [authoring guide] is the how-to for making your own types thread-aware. //! //! [`thread_aware`]: https://docs.rs/thread_aware //! [derive]: https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html -//! [arc]: https://docs.rs/thread_aware/latest/thread_aware/struct.Arc.html +//! [arc]: https://docs.rs/performables/latest/performables/arc/struct.Arc.html //! [authoring guide]: https://docs.rs/thread_aware/latest/thread_aware/_documentation/index.html //! //! Depend on this crate directly when all you need is the trait. It has no normal dependencies @@ -136,8 +135,8 @@ //! General references, sets, `Cow`, and `Arc` have no implementation because relocation would be //! ambiguous or could violate their invariants. The narrow reference exception is `&'static str`: //! immutable process-lifetime labels cannot dangle and carry no referent state to relocate. -//! [`thread_aware`] provides wrappers for cases that need an explicit policy, including its -//! strategy-partitioned [`Arc`][arc]. +//! [`thread_aware`] provides wrappers for cases that need an explicit policy, and the companion +//! `performables` crate adds a strategy-partitioned [`Arc`][arc]. //! //! # Features //! From a993154e367f3c0075061c57fa2ad33ef255f41a Mon Sep 17 00:00:00 2001 From: psandana <1194304+psandana@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:32:59 -0300 Subject: [PATCH 17/17] docs(thread_aware): intra-doc links for in-crate refs; refine PerThread/NUMA wording Address authoring-guide review feedback: - Convert in-crate references (derive macro, Unaware, Generic Bounds anchor) from hardcoded docs.rs URLs to intra-doc links, so they resolve to the version being built and are validated at doc time. Cross-crate performables (Arc/PerThread/PerProcess/PerNuma) and test-utils-gated Relocator links stay external by necessity. - Soften the hand-impl comment to describe local-allocator placement rather than over-promising NUMA-node behavior. - Qualify the PerThread paragraph: each worker keeps its own value, built from the pointer's factory when relocated there, not lazily on first use. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5e0d5396-3a32-40ed-8870-241cd4929082 --- crates/thread_aware/src/_documentation/mod.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/thread_aware/src/_documentation/mod.rs b/crates/thread_aware/src/_documentation/mod.rs index 3a07050b9..e94ea703f 100644 --- a/crates/thread_aware/src/_documentation/mod.rs +++ b/crates/thread_aware/src/_documentation/mod.rs @@ -26,7 +26,7 @@ //! ## Prefer the derive //! //! In almost all cases, implement [`ThreadAware`](crate::ThreadAware) with -//! [the derive macro](https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html). It +//! [the derive macro](derive@crate::ThreadAware). It //! generates a [`relocate`](crate::ThreadAware::relocate) that forwards the notification to every //! field, which is exactly what a compound type owes its parts: //! @@ -76,16 +76,14 @@ //! whose impl is a harmless no-op. Forwarding is free and stays correct if the field later gains //! affinity-bearing state; `skip` removes that safety net, so revisit each `skip` whenever the //! field type changes. When you instead want a non-`ThreadAware` value to read as explicitly inert -//! in the type, wrap it in -//! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) rather than -//! skipping. +//! in the type, wrap it in [`Unaware`](crate::Unaware) rather than skipping. //! //! ## What the generated bounds mean //! //! You rarely need to reason about this: the derive adds exactly the `ThreadAware` bounds its //! generated body needs and no more, so a correct type "just derives". When it matters - a generic //! wrapper, or a marker field that should stay bound-free - the derive's -//! [Generic Bounds](https://docs.rs/thread_aware/latest/thread_aware/derive.ThreadAware.html#generic-bounds) +//! [Generic Bounds](derive@crate::ThreadAware#generic-bounds) //! reference has the rules. //! //! ## Implementing the trait by hand @@ -104,7 +102,8 @@ //! impl ThreadAware for PerCoreScratch { //! fn relocate(&mut self, _source: Option<&Thread>, _destination: &Thread) { //! // The scratch buffer belonged to the previous worker; drop it so the next use -//! // re-allocates in the destination's NUMA node instead of reaching across. +//! // re-allocates fresh, letting the destination's allocator place it in local +//! // memory instead of carrying the old worker's buffer across. //! self.buffer = Vec::new(); //! } //! } @@ -126,9 +125,10 @@ //! ([`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html); //! add it as a dependency). With the //! [`PerThread`](https://docs.rs/performables/latest/performables/arc/struct.PerThread.html) -//! strategy, relocating to a destination worker that has no instance yet materializes a separate -//! `T` for it *during* the `relocate` call; relocating back to a worker that already has one reuses -//! it. Either way the sharing is per-worker instead of process-wide. Use +//! strategy, each worker keeps its own value instead of sharing one process-wide: a worker that +//! already has one reuses it, and a worker that does not is given one when it is relocated there +//! (built from the pointer's factory, if it was constructed with one) *during* the `relocate` call, +//! not lazily on first use. Use //! [`PerProcess`](https://docs.rs/performables/latest/performables/arc/struct.PerProcess.html), which //! behaves as a vanilla `Arc`, when one shared instance is what you want, and //! [`PerNuma`](https://docs.rs/performables/latest/performables/arc/struct.PerNuma.html) for one @@ -141,11 +141,11 @@ //! |---|---|---| //! | A compound of thread-aware fields | `#[derive(ThreadAware)]` | Forwards relocation to each field. | //! | A field with genuine per-core behavior | a hand-written impl | Only you know what "rebind" means. | -//! | A foreign type that carries no affinity | [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) | Implements relocation as a no-op; moves the wrapped value unchanged. | -//! | Shared state that should differ per worker | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Materializes a separate `T` per destination. | +//! | A foreign type that carries no affinity | [`Unaware`](crate::Unaware) | Implements relocation as a no-op; moves the wrapped value unchanged. | +//! | Shared state that should differ per worker | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Gives each worker its own `T`. | //! | Shared state that is the same everywhere | [`performables::arc::Arc`](https://docs.rs/performables/latest/performables/arc/struct.Arc.html) | Behaves as a vanilla `Arc`. | //! -//! [`Unaware`](https://docs.rs/thread_aware/latest/thread_aware/struct.Unaware.html) wraps a value +//! [`Unaware`](crate::Unaware) wraps a value //! and satisfies `ThreadAware` without reacting to //! relocation - use it for inert, foreign, or allocation-free values that legitimately do not care //! which worker they are on. Wrapping a type that *does* implement the trait is discouraged: it