Skip to content

Phase21 API Symmetry#114

Open
JustinKovacich wants to merge 9 commits into
feature/phase20_cleanupfrom
feature/phase21_api_symmetry
Open

Phase21 API Symmetry#114
JustinKovacich wants to merge 9 commits into
feature/phase20_cleanupfrom
feature/phase21_api_symmetry

Conversation

@JustinKovacich
Copy link
Copy Markdown
Contributor

@JustinKovacich JustinKovacich commented May 1, 2026

Summary

Phase 21 is the API ergonomics pass that closes out 0.8.0 (audit was run in late April against 9b4b4c8; six sub-tasks shipped over the back half of phase development). It does not introduce new functionality — every change is about making the existing Client / Server / *Deps / transport surface easier to read, harder to misuse, and consistent between the two sides.

The motivating audit found five concrete frictions: (1) Client and Server had divergent generic-parameter orders that punished anyone maintaining bounds against both; (2) Server::new was an async fn returning Self, so callers had to remember to also spawn server.run() and server.announcement_loop() separately, and forgetting either was silent; (3) mid-tier Deps customization required hand-assembling the full struct literal even to swap one field; (4) the channel types
define_static_channels! needs were documented only in source comments and example code; (5) ServerConfig assembly was struct-literal only. All five are fixed.

The work is bundled into 0.8.0 because no v0.8.0 tag was ever cut and nothing was published, so the "breaking" framing in any
internal phase 21 commit is breaking against transient in-tree state nobody saw — the only baseline that matters externally is 0.7.0.

Headline API changes (vs. 0.7.0)

Server::new returns a tuple now — same shape as Client::new:

// Before (transient in-tree shape, never published):
let mut server = Server::new(config).await?;
let publisher = server.publisher();
tokio::spawn(server.announcement_loop()?);
tokio::spawn(async move { server.run().await });

// After:
let (_server, handles, run) = Server::new(config).await?;
let publisher = handles.publisher;
tokio::spawn(run);

The single returned run-future drives both the receive loop and the SD OfferService announcement loop concurrently via select. The dispatcher topology where a co-located Client emits the offers (used in examples/client_server) opts in via ServerConfig::with_announce(false) instead of "just don't call announcement_loop."

SubscriptionHandle::subscribe / unsubscribe are now GATs — SubscribeFuture<'a> / UnsubscribeFuture<'a> named associated types instead of RPIT. Lets Server::run declare + Send + 'static explicitly with for<'a> Sub::SubscribeFuture<'a>: Send bounds, so tokio::spawn-vs-!Send-handle mismatches surface at the library boundary instead of inside tokio::spawn's bound check.

Generic-parameter order aligned — Client<MessageDefinitions, F, Tm, R, I, Sp, C> and Server<F, Tm, R, Sub, H, Hsd, Hep> now share F/Tm/R in the same positions. Letter-collisions between Spawner (S on Client) and SubscriptionHandle (S on Server) resolved with Sp and Sub respectively.

Tokio-defaulted Deps builders — ClientDeps::tokio() / ServerDeps::tokio() produce a fully-defaulted bundle; chain with_factory / with_timer / with_e2e_registry / with_subscriptions / with_spawner / with_local_spawner to override individual fields without spelling the rest out.

ServerConfig fluent builder —
ServerConfig::new(svc, inst).with_interface(…).with_local_port(…).with_ttl(…).with_event_group(…).with_announce(…). Existing struct-literal route stays open; builder is the recommended path in docs.

Discoverable channel types — new ClientChannelTypes trait alias + client::channels rustdoc surface for the types define_static_channels! populates.

Smaller surface deletions

  • Server::announcement_loop / announcement_loop_local removed (folded into the combined run-future).
  • Server::set_local_port removed — vestigial; the constructor's bind-time back-fill already records the kernel-assigned port into
    config.local_port before Server::new returns, so post-construction mutation would lie to peers.
  • announcement_loop_started: AtomicBool latch gone — single entry point makes the "started twice" failure mode structurally impossible.

Architectural cleanup

The receive loop, announcement loop, and SD-message handling are factored out of &self methods on Server into free async fns in src/server/runtime.rs. Server::run clones the cheap shared-handles into an async move so the returned future is independent of the &self borrow — that's what lets the constructor return (Server, ServerHandles, run-future) without aliasing the same struct.

Net diff for the run-loop work alone: −254 lines despite adding GATs, a new submodule, a behavioral test for with_announce(false) suppression, and the post-review fixes (cancel-safety SAFETY comment restored, ServerHandles re-exported, doctest examples switched from ignore to no_run).

Out of scope (parked)

  • define_client_channels! convenience macro wrapping define_static_channels! (depends on whether we ship a default channel-pool
    profile someday).
  • InterfaceHandle parallel on Server (server's offered endpoint is fixed at config per spec — symmetry-for-symmetry's-sake is wrong here).
  • client::Error::InvalidUsage to mirror server's variant (no reported pain).
  • Trait-surface architectural changes (TransportFactory, ChannelFactory, etc.).

Test plan

  • cargo check --workspace --all-targets — clean, zero warnings.
  • cargo test --features server-tokio,client-tokio,bare_metal --tests --lib -- --test-threads=1 — 518 lib + 5 embassy-net + 11
    client_server integration + 9 bare-metal/static-channels witnesses + 1 smoke, 543 passing, 3 ignored vsomeip docker-deps.
  • cargo test --doc — 13 passed; the 1 remaining failure (src/lib.rs - transport (line 309)) is pre-existing on main, verified via git stash.
  • Live-wire embassy-net loopback test (client_receives_server_sd_announcement) — passes, witnessing the new combined run-future drives announcements correctly through the embassy-net stack.
  • Pre-existing parallel-test flake in tests/client_server.rs unchanged (still passes serially via --test-threads=1).
  • examples/client_server adopts with_announce(false) for the dispatcher topology.
  • examples/embassy_net_client and simple-someip-embassy-net/tests/loopback.rs use run_with_buffers with Box::leak'd static buffers (the !Send-friendly path).
  • Behavioral negative test (with_announce_false_suppresses_offer_service) bound at 2.5s — listens on the SD multicast group and asserts no OfferService for the configured (SID, IID) is emitted.

JustinKovacich and others added 6 commits April 30, 2026 14:52
Generic letter `S` was overloaded — meant `Spawner` in `ClientDeps`
and `SubscriptionHandle` in `ServerDeps` / `Server` / `ServerHandles`
/ all server impl blocks. This made downstream wrappers maintaining
bounds against both sides duplicate work and couldn't read the two
type signatures as parallel.

Renames:
- `ClientDeps`: generic `S` → `Sp` (Spawner). Generic order
  `<F, S, Tm, R, I>` → `<F, Tm, R, I, Sp>`. Field order reordered
  to match (factory, timer, e2e_registry, interface, spawner) so
  shared infrastructure (F, Tm, R) is positionally aligned with
  ServerDeps.
- `ServerDeps`: generic `S` → `Sub` (SubscriptionHandle). Order
  already canonical (F, Tm, R, Sub).
- `ServerHandles`: rename `S` → `Sub`. Order unchanged.
- `Server` struct: generic `S` → `Sub`. Order
  `<R, S, F, Tm, H, Hsd, Hep>` → `<F, Tm, R, Sub, H, Hsd, Hep>`,
  matching ServerDeps. Default `Hep = Arc<EventPublisher<…>>`
  updated.
- All four `impl<…> Server<…>` blocks updated. Where-clauses
  reordered to match the new generic order.
- The tokio-only impl block at line 270 reordered its concrete
  type list `<R-conc, S-conc, F-conc, Tm-conc>` → `<F-conc, Tm-conc, R-conc, S-conc>`.
- `Client::new_with_spawner_and_loopback<S>` → `<Sp>` (no
  ambiguity, but consistent).
- `Client::new_with_deps<F, S, Tm>` → `<F, Tm, Sp>`.

Internal `EventPublisher<R, S, H, T>` letter unchanged — it has no
Spawner, so no collision exists inside that type. Internal
`bind_dispatch::SpawnerDispatch<F, S>` is `pub(super)`, also
unchanged.

Affected user-facing type spellings (4 sites with explicit
generics): bare_metal_server example, three integration tests
spelling out `Server<...>` in concrete form, plus a `TestServer`
type alias each in `src/server/mod.rs` and `tests/client_server.rs`.

Verification:
- `cargo check --workspace --all-features` clean.
- Standalone bare-metal canary: `cargo build -p bare_metal_client`
  and `cargo build -p bare_metal_server` clean.
- `cargo test --workspace --all-features --lib --tests
  -- --test-threads=1` — 528 lib + 26 integration tests pass.
  (Parallel flake unrelated to this change, pre-existing per
  `project_phase20_cleanup_complete.md`.)
- `cargo run -p embassy_net_client` — live host-loopback
  wire-test green; SD `OfferService(0x5BAA)` exchanged end-to-end.

Phase 21 sub-task 21a per `phase_21_api_symmetry.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two convenience layers on top of the existing struct-literal
ClientDeps / ServerDeps construction route, both purely additive:

1. `ClientDeps::tokio(interface)` and `ServerDeps::tokio()` —
   constructors that produce a fully-defaulted deps struct using
   the tokio infrastructure types (TokioTransport, TokioTimer,
   TokioSpawner, plus fresh Arc<Mutex<E2ERegistry>> /
   Arc<RwLock<…>> handles). Gated to client-tokio / server-tokio
   respectively.

2. Field-by-field fluent builders (`with_factory`, `with_timer`,
   `with_e2e_registry`, `with_interface`, `with_spawner` on
   ClientDeps; same minus the side-only fields on ServerDeps).
   Each returns a new Deps with that single generic parameter
   updated, leaving the others intact. Available under the base
   client / server features.

The motivating use case is the mid-tier customization that has no
escape hatch today: tokio transport + tokio time + custom
spawner. Previously this required hand-assembling the full
ClientDeps struct literal with all 5 fields. With 21c:

    let deps = ClientDeps::tokio(addr).with_spawner(MySpawner);

The struct-literal path stays available; nothing existing is
removed or deprecated.

Doc-tests added on each new public surface. The Server doc-test
binds the result to `Server<_, _, _, _>` so the struct's defaults
on H/Hsd/Hep kick in (those defaults don't apply at method-call
sites, only at type-position elision).

Pre-existing failure `lib.rs - transport (line 309)` is unchanged
by this commit (reproduced on stashed tip pre-21c).

Verification:
- `cargo check --workspace --all-features` clean.
- `cargo test --doc --all-features` — 11 passed (was 9), 1
  pre-existing failure unchanged.
- `cargo test --workspace --all-features --lib --tests
  -- --test-threads=1` — 528 lib + 26 integration tests pass.
- `cargo run -p embassy_net_client` — live host-loopback wire-test
  green.

Phase 21 sub-task 21c per `phase_21_api_symmetry.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds chainable `with_*` setters on `ServerConfig` so callers can
override individual fields starting from `ServerConfig::new(...)`
without rewriting the full struct literal. Pure additive — public
fields and the existing constructor stay available.

New methods:
- `with_major_version(u8)` — defaults to 1.
- `with_minor_version(u32)` — defaults to 0.
- `with_ttl(u32)` — TTL in seconds, defaults to 3.
- `with_event_group(u16)` — append-only; panics on capacity (matches
  the implicit contract of the public `event_group_ids` field).
- `try_with_event_group(u16) -> Result<Self, Self>` — fallible
  variant; on `Err` returns the unmodified config (heapless::Vec
  guarantees push doesn't mutate on overflow).

No `Default` impl — `service_id` / `instance_id` have no sensible
defaults.

Two new unit tests:
- `server_config_builder_chain_overrides_each_field` — exercises
  the chain end-to-end.
- `server_config_try_with_event_group_rejects_at_capacity` —
  locks in the no-mutation-on-overflow contract.

Verification:
- `cargo check --workspace --all-features` clean.
- `cargo test --workspace --all-features --lib --tests
  -- --test-threads=1` — 530 lib (+2) + 26 integration tests pass.

Phase 21 sub-task 21e per `phase_21_api_symmetry.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `client::ClientChannelTypes<P>`, a marker trait whose
where-clause enumerates the seven `OneshotPooled` / `BoundedPooled`
/ `UnboundedPooled` entries that `define_static_channels!` must
declare for `Client` to compile against the resulting factory. A
blanket impl makes any `ChannelFactory` that satisfies all seven
automatically satisfy the trait.

Today the trait is discoverability-only — stable Rust does not
elaborate where-clause bounds on a trait, so each `impl<…> Client<…>`
block must still repeat the seven bounds inline. The trait's value
right now is rustdoc surface: one page enumerates the required
pool entries with a copy-pasteable shape, and it is reachable from
three places:
- Crate root (re-exported in `lib.rs`).
- The `define_static_channels!` macro doc, via a new "Required
  entries for Client" cross-link.
- The trait page itself documents the elaboration limitation
  honestly so users know not to depend on it as a generic bound.

Also re-exports `ControlMessage`, `SendMessage`, `ReceivedMessage`
at crate root so the channel-pool item types are reachable as
`simple_someip::ControlMessage` etc., not only via
`simple_someip::client::ControlMessage`. (`ClientUpdate` was
already re-exported.)

Replaces the previous comment-block documentation in
`src/client/mod.rs` (lines 67-87) — the trait's rustdoc carries
the same content in a discoverable location.

When stable Rust gains where-clause elaboration on traits, the
per-impl-block repetition collapses to a single
`C: ClientChannelTypes<P>` supertrait without changing the
outward contract; the trait was designed for that future.

Verification:
- `cargo check --workspace --all-features` clean.
- `cargo test --doc --all-features` — 11 passed (unchanged by
  21d), 1 pre-existing failure (`lib.rs - transport (line 309)`)
  still pre-existing.
- `cargo test --workspace --all-features --lib --tests
  -- --test-threads=1` — 530 lib + 26 integration tests pass.

Phase 21 sub-task 21d per `phase_21_api_symmetry.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the HIGH and MEDIUM findings from the adversarial review of
21a/21c/21d/21e (full review captured in conversation; key findings
below). No new behavior, no further architecture; pure surface
hygiene + scope-completion.

## HIGH

1. **server/mod.rs: stale `S` in `publisher()` rustdoc.** 21a renamed
   the Subscription generic to `Sub` everywhere except this single
   docstring on the most-read accessor. Now reads `Arc<EventPublisher
   <R, Sub, H, T>>` consistently.

2. **`with_ttl` takes `Duration`.** 21e shipped `with_ttl(u32)` which
   leaves units ambiguous at the call site. Now takes
   `core::time::Duration`; sub-second precision truncates (rounded
   down per `Duration::as_secs`); over-`u32::MAX`-seconds saturates.
   Matches the original scoping-doc intent. Two unit tests lock the
   truncation + saturation behavior.

3. **Client struct asymmetry rationale.** Reviewer flagged that
   `Client<MessageDefinitions, R, I, C>` doesn't carry F/Tm/Sp the
   way `Server<F, Tm, R, Sub, …>` does. The asymmetry is structural
   (Client's value retains no transport/timer/spawner reference once
   constructed), not an oversight. Now documented as such on
   Client's struct rustdoc with explicit cross-reference to
   `ServerDeps`.

4. **`#[must_use]` on `try_with_event_group`.** Every other
   consuming setter on `ServerConfig` is `#[must_use]`; this one
   was missed.

5. **`ServerConfig::new` widened.** F5's stated complaint was the
   noisy 4-arg `new(interface, port, sid, iid)`. 21e shipped only the
   non-noisy override setters; the four-arg `new` remained. Now:
   `new(service_id, instance_id)` with `Ipv4Addr::UNSPECIFIED` /
   port `0` defaults, plus `with_interface` / `with_local_port`
   setters. Docstring is explicit that the defaults are dev-friendly,
   not production-ready.

   Net call-site change: 43 sites across 10 files rewritten via
   `ServerConfig::new(IFACE, PORT, SID, IID)` →
   `ServerConfig::new(SID, IID).with_interface(IFACE).with_local_port(PORT)`.
   Pure semver-major mechanical rewrite; no behavior change.

## MEDIUM

6. **`ClientChannelTypes` no longer at crate root.** Reviewer
   flagged that re-exporting at crate root tempts users into
   `fn f<C: ClientChannelTypes<P>>()` which fails on stable Rust's
   trait-elaboration limit. Trait stays in `client::` (so
   `define_static_channels!` users still find it via the macro
   cross-link), but does not appear in `simple_someip::*`
   autocomplete. Comment in `lib.rs` records the rationale.

7. **`ControlMessage`/`SendMessage`/`ReceivedMessage` no longer at
   crate root.** Same reasoning — they are
   implementation-detail-with-a-public-name (reachable for the
   `define_static_channels!` macro at `simple_someip::client::*`)
   rather than first-class crate-API types. Crate-root re-export
   would lock their shape into the public-API contract.

8. **`with_spawner` split into `with_spawner` (Send) +
   `with_local_spawner` (Local).** 21c's unbounded `with_spawner<Sp2>`
   deferred the `Spawner` / `LocalSpawner` bound check to
   `Client::new_with_deps`, producing diagnostics that pointed at
   the wrong call site. Now each `with_*` enforces its bound at the
   builder method, so type errors surface at the actual mistake.

## NOT addressed (reviewer flagged, deferred)

- Reordering `Client<MessageDefinitions, R, I, C>` itself — see #3
  above; documented as deliberate.
- 21c integration test for `ClientDeps::tokio()` + `with_*` —
  doc-tests cover type-composition; behavioral coverage can wait.
- 21e duplicate-event-group + panic-message tests — tracked, low
  risk.

## Verification

- `cargo check --workspace --all-features` clean.
- `cargo test --workspace --all-features --lib --tests
  -- --test-threads=1` — 532 lib (+2 from new with_ttl tests) +
  26 integration tests pass.
- `cargo test --doc --all-features` — 12 passed (was 11; +1 from
  new `ServerConfig::new` doc-example). Pre-existing
  `lib.rs - transport (line 309)` failure unchanged.
- `cargo run -p embassy_net_client` — live host-loopback wire-test
  green.

Phase 21 sub-task cleanup per `phase_21_api_symmetry.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
21b — Server::new and the *_with_deps variants now return a
(Server, ServerHandles, run-future) tuple mirroring Client::new.
The single returned run-future drives the receive loop *and* the
SD OfferService announcement loop concurrently via select; the
dispatcher topology where a co-located Client emits the offers
opts in via ServerConfig::with_announce(false) instead of "just
don't call this method." Server::announcement_loop /
announcement_loop_local and the AtomicBool latch they used to
fight over are removed — single entry point makes the
double-spawn failure mode structurally impossible. The previous
ServerHandles deps-bundle is renamed to ServerStorage; the
ServerHandles name now belongs to the post-construction accessor
struct. Server::set_local_port also went away — the constructor's
bind-time back-fill made it vestigial and mutating it post-bind
would lie to peers.

21F — SubscriptionHandle's subscribe / unsubscribe futures are
promoted from RPIT to named GATs (SubscribeFuture<'a> /
UnsubscribeFuture<'a>) so Server::run can declare + Send + 'static
explicitly with for<'a> Sub::SubscribeFuture<'a>: Send bounds in
its where clause. Compile errors for tokio::spawn-vs-!Send-handle
mismatches now surface at the library boundary instead of inside
tokio::spawn's bound check. for_each_subscriber stayed RPIT (no
Server::run-side Send-bound user). Constructors call a private
run_inner that's auto-trait-inferred so embassy paths
(EmbassyNetSocket: !Sync) keep using the constructors.

Run-loop logic moved out of &self methods on Server into free
async fns in src/server/runtime.rs; Server::run / run_with_buffers
clone the cheap shared-handles into an async move so the returned
future is independent of the &self borrow. The cancel-safety
SAFETY comment on the recv_from select is preserved at the new
call site.

Net diff: -254 lines despite adding GATs, the new submodule, and
a behavioral test for with_announce(false) suppression. 543 tests
pass (518 lib + 5 embassy-net + 11 client_server integration + 9
bare-metal/static-channels witnesses + a 0-test smoke + 3 ignored
vsomeip docker-deps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Phase 21 finalizes the 0.8.0 API ergonomics/symmetry pass by reshaping the Server constructor/run surface to mirror Client, aligning generic ordering, and improving configurability + docs for static channels and deps bundles.

Changes:

  • Reshapes Server::new* to return (Server, ServerHandles, run_future) and folds SD announcements into the combined run future (with ServerConfig::with_announce(false) to suppress).
  • Promotes SubscriptionHandle::{subscribe,unsubscribe} return types to GATs (SubscribeFuture/UnsubscribeFuture) to make Send bounds explicit at the API boundary.
  • Adds fluent builders (ServerConfig::with_*, ClientDeps::tokio()/ServerDeps::tokio() + with_*) and improves discoverability docs for static channel requirements.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/vsomeip_sd_compat.rs Updates SD compatibility tests to spawn the new combined server run future.
tests/client_server.rs Updates test helpers/call sites for new Server::new tuple return and config builder.
tests/bare_metal_server.rs Migrates mock SubscriptionHandle impl to GAT futures; adapts to new server constructor return.
tests/bare_metal_e2e.rs Same migration as above for the bare-metal E2E test harness.
src/static_channels/mod.rs Adds rustdoc guidance pointing users to ClientChannelTypes requirements.
src/server/subscription_manager.rs Introduces GATs on SubscriptionHandle + updates tokio/bare-metal impls accordingly.
src/server/sd_state.rs Updates unit tests to use new ServerConfig builder API.
src/server/runtime.rs New module extracting server runtime loops (recv, announce, SD handling) as free async fns.
src/server/mod.rs Major server API reshape: new ServerHandles, ServerStorage rename, new run/run_with_buffers futures, fluent ServerConfig.
src/lib.rs Updates public re-exports (ServerStorage) and documents intentional non-reexports on client channel types.
src/client/mod.rs Adds ClientChannelTypes marker trait + tokio-defaulted ClientDeps and fluent builders; reorders deps generics.
simple-someip-embassy-net/tests/loopback.rs Adapts embassy-net loopback tests to the new server run model and SubscriptionHandle GATs.
examples/embassy_net_client/src/main.rs Updates embassy-net example to spawn server via run_with_buffers and new config builder.
examples/client_server/src/main.rs Updates dispatcher-topology example to with_announce(false) and constructor-returned run future.
examples/bare_metal_server/src/main.rs Updates bare-metal server example to spawn the combined run future.
CHANGELOG.md Documents the 0.8.0 Phase 21 breaking changes and migration guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/vsomeip_sd_compat.rs Outdated
Comment thread src/client/mod.rs Outdated
Comment thread src/server/mod.rs
Comment thread src/server/runtime.rs Outdated
Comment thread tests/vsomeip_sd_compat.rs Outdated
Nothing in the 0.7.0→0.8.0 stretch was ever published (no v0.8.0
git tag, no crates.io release), so phase 20 cleanup + phase 21
ergonomics merge into the 0.8.0 baseline rather than getting
their own version bump. The "Breaking" framing in the phase 21
section was relative to a transient in-tree state nobody saw, so
it's been retitled around 0.7.0 (the actual external baseline)
where it remained meaningful, and dropped where it didn't.

Section structure: [0.8.0] now contains the phase 21 narrative
sub-section followed by Added/Changed/Fixed clusters labeled by
era (phase 20 cleanup vs. phase 17 baseline) so the chronological
context is still visible to a reader walking the file top-down.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JustinKovacich JustinKovacich force-pushed the feature/phase21_api_symmetry branch from 40f3470 to 03132d8 Compare May 1, 2026 13:58
Five comments, four substantive fixes + one prose tweak.

Multi-spawn race latch on Server::run / run_with_buffers: a caller
who spawned the constructor's run-future *and* called server.run()
or server.run_with_buffers() afterward would have two run-futures
racing on the same SD/unicast sockets and the SD session counter,
silently corrupting wire output. Adds an Arc<AtomicBool> `started`
field to Server (initialised false in all 4 constructor paths) and
a compare_exchange first-poll latch in both run_inner and
run_with_buffers — the second-to-be-polled future short-circuits
with Err(Error::InvalidUsage("server_already_running")). New unit
test second_run_future_returns_already_running locks both paths.

TransportSocket::recv_from contract: the cancel-safety requirement
the server runtime depends on lives on the trait now (its own
"# Cancel safety" rustdoc section), not as a free-floating remark
inside runtime::recv_loop. The previous in-source comment also
claimed "must stay FusedFuture + Unpin" which is wrong — the
futures are pinned in place via pin_mut! and don't need Unpin.
Comment rewritten to match.

ClientChannelTypes rustdoc: said the trait "is re-exported at
crate root" but it intentionally isn't (only at
crate::client::ClientChannelTypes — the crate root re-export was
deliberately dropped during the 21d cleanup to avoid tempting
generic users into the bound-elaboration cliff). Doc updated.

vsomeip_sd_compat tests: removed the no-op announce_handle shim
introduced during the 21b run-loop reshape. Announcements are now
folded into the combined run-future spawned as server_handle, so
keeping a dummy handle around made later abort/diagnostic logic
misleading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JustinKovacich JustinKovacich marked this pull request as ready for review May 1, 2026 15:05
`tracing-core` 0.1.33+ declares `extern crate alloc;` unconditionally
(`tracing-core-0.1.36/src/lib.rs:150`), so any target whose sysroot
ships `core` only — e.g. the AURIX TriCore LLVM-IR proxy used by the
halo build — fails to compile the dep tree even though simple-someip's
own bare-metal paths never touch alloc. Move tracing behind a feature
so those targets can opt out; std users keep tracing automatically via
the `std → tracing` implication so behavior is unchanged on host.

Internal call sites route through a new private `crate::log` module:
when `tracing` is on it re-exports `tracing::{debug,error,info,trace,
warn}` verbatim; when off it expands to a single `noop!` macro that
wraps `core::format_args!` in `if false { … }`. The dead block keeps
captured variables borrowed (no spurious `unused_variables` lints) and
optimizes out, leaving zero log code at the linker.

Six sites in `server/runtime.rs` were using tracing's structured-field
DSL (`error = %e, "msg"`); rewritten to plain format-args
(`"msg: {e}"`) since the no-op shim's `format_args!` can't accept the
DSL. Equivalent for log purposes.

Verified:
- `cargo tree --target thumbv7em-none-eabihf --no-default-features
  --features client,server,bare_metal --edges normal` shows zero
  tracing-related entries (the failing extern crate alloc no longer
  reachable).
- cortex-m4f release rlib still has zero `__rust_alloc` references.
- 532 lib + 20 integration tests pass under default features.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants