Skip to content

feat(stabilization)!: thread_aware 1.0 - #643

Draft
martintmk wants to merge 23 commits into
mainfrom
user/martintomka/20260806-stabilize-thread-aware
Draft

feat(stabilization)!: thread_aware 1.0#643
martintmk wants to merge 23 commits into
mainfrom
user/martintomka/20260806-stabilize-thread-aware

Conversation

@martintmk

@martintmk martintmk commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds thread_aware_core 1.0.0 with the stable ThreadAware trait, Affinity type, and pinned_affinities topology helper.
  • Keeps the core crate dependency-free and always no_std; an opt-in std feature adds standard-library implementations for HashMap, Path, and PathBuf.
  • Re-exports the stable APIs directly from thread_aware, preserving thread_aware::ThreadAware and thread_aware::affinity::{Affinity, pinned_affinities} for downstream users.
  • Removes external-crate integration features and documents the stable boundary and affinity invariants.

Validation

  • thread_aware_core and thread_aware tests and doctests pass with all applicable features.
  • Both crates compile without default features.
  • thread_aware_core packages and verifies successfully with zero dependencies.
  • Formatting, generated README, spelling, Clippy, mutation, coverage, and external-type checks pass.
  • Independent Claude and Gemini reviews completed; their documentation findings were addressed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3a761577-6422-4fd1-a0f7-88807d7f6134
@martintmk martintmk added the agency-rocket Touched by a rocket skill label Aug 6, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3a761577-6422-4fd1-a0f7-88807d7f6134
@martintmk martintmk changed the title docs(thread_aware): add stabilization notes feat(stabilization)!: stabilize thread_aware 1.0 Aug 6, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3a761577-6422-4fd1-a0f7-88807d7f6134
@martintmk martintmk changed the title feat(stabilization)!: stabilize thread_aware 1.0 feat(stabilization)!: thread_aware 1.0 Aug 6, 2026
@codecov

codecov Bot commented Aug 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (af4bdaa) to head (6244971).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##             main     #643     +/-   ##
=========================================
  Coverage   100.0%   100.0%             
=========================================
  Files         543      550      +7     
  Lines       60394    61487   +1093     
=========================================
+ Hits        60394    61487   +1093     
Flag Coverage Δ
linux 94.1% <100.0%> (?)
linux-arm 94.2% <100.0%> (?)
windows 94.2% <100.0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

✅ Version increments look sufficient

cargo semver-checks compared the 1 crate(s) this PR publishes against their previous version-bump commit in git history. Every version increment is sufficient for the detected API changes.

Crate Baseline Baseline commit This PR Minimum required Status
thread_aware_core new crate 1.0.0 1.0.0 ✅ ok

This check is informational and does not block the merge.

View the check run

Comment thread crates/thread_aware_core/Cargo.toml
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Make the stable core crate dependency- and feature-free, and remove external-type implementations and their benchmarks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Comment thread crates/thread_aware_core/src/impls.rs
Keep the core crate dependency-free while providing Path, PathBuf, and HashMap implementations behind an opt-in std feature.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Comment thread crates/seatbelt/Cargo.toml
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 392f99fd-c9b0-40c7-aad7-3e87a132b16e
Comment thread crates/thread_aware/src/__private.rs Outdated

#[doc(inline)]
pub use crate::core::ThreadAware;
pub use thread_aware_core::ThreadAware;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the _private module should not exist, thread_aware just re-exports the types from thread_aware_core directly

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agreed - I’ll remove the private wrapper and re-export the core types directly.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed: removed thread_aware::__private; the trait is now re-exported directly from thread_aware_core at the crate root.


AI response generated by rocket

Comment thread crates/thread_aware/src/affinity.rs Outdated

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho, this could me moved to thread_aware_core, check if we can hide the constructor on Affinity again if this is moved

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agreed - I’ll move this into thread_aware_core and check whether Affinity can hide its constructor again.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed: moved pinned_affinities into thread_aware_core and made thread_aware::affinity a direct re-export. Affinity::new still needs public visibility because thread_aware::registry constructs arbitrary hardware mappings from a sibling crate; hiding it would require moving the registry or adding another public construction backdoor.


AI response generated by rocket

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
Comment thread crates/anyspawn/src/spawner.rs Outdated
/// create per-core state: they ignore relocation and behave identically
/// regardless of which core they run on.
#[derive(Clone, ThreadAware)]
#[derive(Clone, thread_aware::ThreadAware)]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[derive(Clone, thread_aware::ThreadAware)]
#[derive(Clone, ThreadAware)]

(and rever similar changes)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Agreed - I’ll restore the derive here and revert the similar manual implementations introduced by this PR.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed: restored unqualified ThreadAware derives and the existing thread_aware re-export imports across downstream crates. The redundant direct thread_aware_core dependencies were removed as well.


AI response generated by rocket

Comment thread crates/tick/Cargo.toml
"thread_aware::core::ThreadAware",
"thread_aware::affinity::MemoryAffinity",
"thread_aware::affinity::PinnedAffinity",
"thread_aware_core::ThreadAware",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about thread_aware_core::Affinity

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 I don’t think thread_aware_core::Affinity should be added here. The external-type check reports only thread_aware_core::ThreadAware for tick’s public API; adding Affinity would create an unused approval rather than cover an exposed type.


AI response generated by rocket

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
Comment thread crates/thread_aware_core/src/lib.rs Outdated
Comment thread crates/thread_aware_core/src/lib.rs Outdated
/// Panics if either count is zero or an index is outside its corresponding
/// count.
#[must_use]
pub const fn new(processor_index: u16, memory_region_index: u16, processor_count: u16, memory_region_count: u16) -> Self {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure that these are the four properties we can stabilize? What if we need to further enrich Affinity in the future? Should we have a builder here perhaps?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 The private fields let us add derived or optional state later without breaking struct construction, and we can add new constructors or a builder additively. The main commitment here is the four-argument Affinity::new contract plus its Copy/equality semantics. I’d keep the current constructor rather than stabilize a builder preemptively, but leave this thread open for the API-design decision.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AI got little bit trigger happy, I have disabled automatic responses for humans. Let's discuss this (as humans :D)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is also inconsistency in what types are accepted in the construct and how these are exposed through properties (u16 vs usize).

Also another alternative if we are not sure about the constructor:

Leverage _private module and expose a pub-internal new_affinity API that could be consumed in thread_aware.

Full sem-ver guarantees but it's not visible to users anymore.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we agree that _private modules are acceptable? I thought they would generally break with cargo doing updates.

I see two options:

  • Make affinities more opaque - don't expose the indices and counts at all, and just have the pinned_affinities function that produces a bunch of opaque affinities.
  • Agree that this list of affinity properties makes sense.

I'd lean towards the former in the first iteration, but we'll have to figure out the list of affinity properties at some point.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we agree that _private modules are acceptable? I thought they would generally break with cargo doing updates.

There is nothing special about _private module. Full SemVer guarantees need to be preserved between updates. It is just that we have lower API quality gate for items in _private module. Used as a last resort.

Make affinities more opaque

Making Affinity opaque makes sense to me. Let's see if this is feasible.

AI: Can you checked whether it is safe to make Affinity type opaque?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[AI Agent]

I checked all workspace consumers. Making construction opaque is safe and straightforward: Affinity::new is only needed by thread_aware_core::pinned_affinities, tests, and ThreadRegistry::affinities; the registry can instead derive per-region counts and consume pinned_affinities, so new can become private.

Making the properties fully opaque is feasible, but not a surgical visibility change. ThreadRegistry::pin_to, PerCore, PerNuma, Storage, and the anyspawn example currently consume the index/count accessors. Rust has no friend-crate visibility, so moving those accessors to a #[doc(hidden)]/_private public API would still leave a SemVer surface. A genuinely opaque design should remove the accessors and refactor consumers to operate on opaque identity/grouping instead—for example, registry-owned affinity-to-processor lookup plus storage keyed/grouped by affinity (potentially with an opaque memory-region identity). That also gives us a place to reject affinities not produced for the registry, rather than trusting a caller-provided processor index.

Recommendation: make Affinity::new private now and expose only pinned_affinities; treat removal of all four accessors as a small coordinated redesign rather than hiding them behind a private-looking public module. No code changes made pending agreement on the grouping API needed by PerNuma.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommendation: make Affinity::new private now and expose only pinned_affinities;

Proceed with this change

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Fixed in d48b4e88: Affinity::new is now private, and ThreadRegistry constructs its values exclusively through pinned_affinities. Registry processor ordering is grouped by dense NUMA region so affinity indices continue to map correctly when pinning.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also hide the public accessors

[package]
name = "thread_aware_core"
description = "Stable core traits and types for thread-aware state."
version = "1.0.0"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we sure we can go straight to 1.0? Should we be a bit more conservative and give ourselves a week or two of 0.1?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 A short 0.1 soak would reduce release risk, but it would also postpone the stable boundary this PR is explicitly creating. My recommendation is to keep 1.0 only if we consider the trait contract and Affinity semantics settled after this review; otherwise, publish 0.1 first and promote after the soak. I’m leaving this open for the release decision.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say we can go with alpha, i.e. 1.0.0-alpha.1 (or something in that sense)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember pre-releases causing a bunch of challenges for interoptopus. I'd probably rather just go with 0.1 in the first few weeks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
/// runtime that created the value. An `Affinity` is a logical identifier; it does
/// not pin the current thread or change operating-system scheduling.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Affinity {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review this type and fields it uses and ensure this model aligns well with how NUMA nodes are used/modelled in real processors. Try to find any gaps or potential blockers/breaking changes we would have to make if we stick with this model.

use multi models to research this area thoroughly

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Understood - I’m starting a multi-model review focused on whether the Affinity representation matches real NUMA and processor topologies, and on API choices that could force future breaking changes.


AI response generated by rocket

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Multi-model review complete (Opus, Sonnet, and GPT). The reviewers independently converged on these risks:

High-confidence correctness issue

  • thread_aware/src/registry.rs:98-140 uses numa_nodes.len() as memory_region_count, but that vector is indexed by the raw OS memory-region ID and may contain holes. For sparse IDs such as {2, 7}, affinities get dense region indices {0, 1} but report a count of 8. Track the distinct dense-region count separately, or use a raw-ID-to-dense-index map.

High API and correctness risks

  • Affinity::new permits callers to fabricate topology metadata, while ThreadRegistry::pin_to trusts only processor_index. An affinity from another registry can therefore pin the wrong physical processor or cause current_affinity() to report topology the registry never created. Construction should be opaque/fallible, or pin_to must validate registry/topology identity.
  • processor_index is a dense ordinal over the registry's selected logical processors, not a physical core index or OS processor ID. Two registries can produce equal affinities for different hardware, while the same hardware can compare unequal when topology counts differ. Equality/hash semantics need an explicit topology identity or a clearly enforced registry-local contract.
  • The public four-u16 constructor freezes argument order, widths, and count semantics. The u16 inputs versus usize accessors are also inconsistent. Hiding raw construction behind a documented factory/builder and accepting domain-appropriate typed values would preserve more room before stabilization.

Model scope to decide explicitly

  • Real systems also expose socket/package, logical processor versus physical core/SMT sibling, Windows processor group, sparse OS IDs, and heterogeneous-core efficiency class. Private fields and additive accessors can be added later without breaking callers, but changing constructor, equality/hash, or the meaning of processor_index cannot. The type should explicitly be either a registry-local locality coordinate or a fuller hardware-topology identity.

Lower-priority fixes

  • Use checked addition in pinned_affinities before narrowing the processor count.
  • The registry's < u16::MAX assertion rejects a count that u16 can represent; use <= if the narrow representation remains.

Recommendation before stabilizing: fix sparse NUMA counting, make construction opaque or topology-validated, define equality/identity semantics, rename/document the logical-processor ordinal precisely, and use the proposed alpha release to validate that contract.

No code changes were made from this review.


AI response generated by rocket

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

⚠️ Potential breaking changes detected

cargo semver-checks flagged the following on this PR. This is informational -- breaking changes between commits are expected; the major-version bump happens at release time, not on every PR.

thread_aware

     Cloning origin/main
    Building thread_aware v0.9.0 (current)
       Built [   5.205s] (current)
     Parsing thread_aware v0.9.0 (current)
      Parsed [   0.004s] (current)
    Building thread_aware v0.9.0 (baseline)
       Built [   5.974s] (baseline)
     Parsing thread_aware v0.9.0 (baseline)
      Parsed [   0.005s] (baseline)
    Checking thread_aware v0.9.0 -> v0.9.0 (no change; assume minor)
     Checked [   0.019s] 196 checks: 191 pass, 5 fail, 0 warn, 49 skip

--- failure feature_missing: package feature removed or renamed ---

Description:
A feature has been removed from this package's Cargo.toml. This will break downstream crates which enable that feature.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#cargo-feature-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/feature_missing.ron

Failed in:
  feature bytes in the package's Cargo.toml
  feature http in the package's Cargo.toml
  feature uuid in the package's Cargo.toml
  feature jiff02 in the package's Cargo.toml

--- failure function_missing: pub fn removed or renamed ---

Description:
A publicly-visible function cannot be imported by its prior path. A `pub use` may have been removed, or the function itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/function_missing.ron

Failed in:
  function thread_aware::affinity::pinned_affinities, previously in file /home/runner/work/oxidizer/oxidizer/target/semver-checks/git-origin_main/b88820a54d7bddbaad5a0ee32332b9a2f6e0d949/crates/thread_aware/src/affinity.rs:69

--- failure struct_missing: pub struct removed or renamed ---

Description:
A publicly-visible struct cannot be imported by its prior path. A `pub use` may have been removed, or the struct itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/struct_missing.ron

Failed in:
  struct thread_aware::affinity::Affinity, previously in file /home/runner/work/oxidizer/oxidizer/target/semver-checks/git-origin_main/b88820a54d7bddbaad5a0ee32332b9a2f6e0d949/crates/thread_aware/src/affinity.rs:13

--- failure trait_missing: pub trait removed or renamed ---

Description:
A publicly-visible trait cannot be imported by its prior path. A `pub use` may have been removed, or the trait itself may have been renamed or removed entirely.
        ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/trait_missing.ron

Failed in:
  trait thread_aware::ThreadAware, previously in file /home/runner/work/oxidizer/oxidizer/target/semver-checks/git-origin_main/b88820a54d7bddbaad5a0ee32332b9a2f6e0d949/crates/thread_aware/src/core.rs:73

--- failure trait_removed_supertrait: supertrait removed or renamed ---

Description:
A supertrait was removed from a trait. Users of the trait can no longer assume it can also be used like its supertrait.
        ref: https://doc.rust-lang.org/reference/items/traits.html#supertraits
       impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.46.0/src/lints/trait_removed_supertrait.ron

Failed in:

     Summary semver requires new major version: 5 major and 0 minor checks failed
  supertrait thread_aware::ThreadAware of trait ThreadAwareAsyncFnMut in file /home/runner/work/oxidizer/oxidizer/crates/thread_aware/src/closure/mod.rs:66
  supertrait thread_aware::ThreadAware of trait ThreadAwareAsyncFn in file /home/runner/work/oxidizer/oxidizer/crates/thread_aware/src/closure/mod.rs:58
  supertrait thread_aware::ThreadAware of trait ThreadAwareFnMut in file /home/runner/work/oxidizer/oxidizer/crates/thread_aware/src/closure/mod.rs:42
  supertrait thread_aware::ThreadAware of trait ThreadAwareFn in file /home/runner/work/oxidizer/oxidizer/crates/thread_aware/src/closure/mod.rs:34
  supertrait thread_aware::ThreadAware of trait ThreadAwareFnOnce in file /home/runner/work/oxidizer/oxidizer/crates/thread_aware/src/closure/mod.rs:26
  supertrait thread_aware::ThreadAware of trait ThreadAwareAsyncFnOnce in file /home/runner/work/oxidizer/oxidizer/crates/thread_aware/src/closure/mod.rs:50
    Finished [  11.997s] thread_aware

/// runtime that created the value. An `Affinity` is a logical identifier; it does
/// not pin the current thread or change operating-system scheduling.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Affinity {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's split the type definitions out into separate files, please - I went looking for affinity.rs and was confused about where the code was. lib.rs and mod.rs should be reserved for structural content to avoid surprises.

memory_region_count: u16,
}

impl Affinity {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We run into some API design issues here that are perhaps already embodied in ThreadAware and make it cumbersome to stabilize. Perhaps we need to actually do a proper API evolution and cleanup of ThreadAware APIs themselves before we stabilize anything?

I personally would like to see a design document that lays out the design tenets of ThreadAware and how it is meant to function - it has sort of accumulated stuff and transformed 4 different times over the years and I do not really have a feel for it, it has always been something I stumble through. Perhaps a design document + discussion would be fruitful on this topic.

Anyway, looking at some specifics here.

For one, calling this type Affinity is misleading - the name suggests it implies some data is actually affine to the Affinity but that's not really the case. If you have a thread_aware::Arc<T, PerProcess> then whatever value Affinity has is meaningless. And if you have a thread_aware::Arc<T, PerNuma> then half of it (the core number) is meaningless. The actual decision of "what am I affine to" is made by the thread-aware type itself. Affinity therefore does not signify affinity. This implies, at minimum, that the thing is misnamed. I suggest something like ThreadContext or just Context here - these are descriptors of a specific thread in context of which a relocation is happening.

For another, as a robot in a neighboring thread already noted, the coordinate space of these dimensions is unspecified. Processor index in what range of values? Memory region index in what range of values? Nothing defines that and there is no universal definition we can assume. Any index/count we provide must have a defined coordinate space - what are we counting/indexing exactly? How do we map to sparse "true" coordinate spaces (e.g. a machine processors 1 and 399 only)? pinned_affinities() de-facto partially defines the coordinate space as a dense one without ever actually saying it out loud, creating a lot of confusion here. Today, consumers of pinned_affinities() and Affinities just have to magically align with the undocumented assumptions in this function. Let's document this whole topic.

/// not pin the current thread or change operating-system scheduling.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Affinity {
processor_index: u16,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These fields all require values. How can we represent relocation to/from threads that are not pinned to a processor or memory region?

processor_index: u16,
memory_region_index: u16,
processor_count: u16,
memory_region_count: u16,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not obvious to me why these are u16 if they are only ever accessed as usize

/// Panics if there are more than `u16::MAX` processors or memory regions.
#[must_use]
#[expect(clippy::needless_range_loop, reason = "clearer in this case")]
pub fn pinned_affinities(counts: &[usize]) -> alloc::vec::Vec<Affinity> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The probability of needing it is somewhat low but we should have a non-breaking migration path to a universe where we add additional coordinates to threads, such as "cache domains" (e.g. to subdivide a memory region into multiple sub-regions - these already exist today, physically, but we are somewhat behind the curve in modeling hardware realities so we have not used/needed such concepts today).

This assumes that callers who are creating Affinity objects without knowledge of these extended concepts should have a way to continue creating them. A single free function with a fixed behavior feels slightly strange, as we would have to add a second function alongside to provide support for such concepts. A builder seems more generalizable.

/// remain correct when relocation is not reported, is reported more than once, or
/// uses equal source and destination affinities.
///
/// # Example

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example does not really seem to show off a meaningful implementation - it is an API dummy with no useful behavior. A more useful example would showcase how the function is used to achieve some realistic goal (i..e. specializing behavior for some purpose). We just made some claims about performance and contention-avoidance mechanisms above but only showcase how to set the value of a field.


/// Marks state that can adapt after being transferred between affinities.
///
/// Implementations commonly recreate affinity-local resources, select a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what this paragraph means.

pub trait ThreadAware: Send {
/// Adapts this value in place for the destination affinity.
///
/// `source` is `None` when the value's previous affinity is unknown. Callers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also seems worth mentioning whether implementations are expected to tolerate relocations across unrelated affinities (e.g. relocation 1 to "processor 5 of 10" and relocation 2 to "processor 19 of 2000"). I make no comment on what the expectation should be, merely that it is a question that implementors will have, so we should answer here.

pub struct Affinity {
processor_index: u16,
memory_region_index: u16,
processor_count: u16,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO counts should be NonZero<T> to avoid completely unnecessary zero-checking path.

Explore an opaque, extensible redesign of the affinity descriptor: a Dimension-keyed LocalityId identity built through Affinity::builder, replacing the frozen four-number processor/memory-region shape. Unset dimensions are absent, and LocalityId is an opaque Copy+Eq+Hash key created from a u16 index or a static name.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
…conflict)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8f9c8e6e-73c6-47c0-830e-2f674656eceb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agency-rocket Touched by a rocket skill

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants