Skip to content

phase 13a: feature-flag detangle (client side)#89

Closed
JustinKovacich wants to merge 1 commit into
feature/phase12_transport_socket_flexibilityfrom
feature/phase13_feature_flag_detangle
Closed

phase 13a: feature-flag detangle (client side)#89
JustinKovacich wants to merge 1 commit into
feature/phase12_transport_socket_flexibilityfrom
feature/phase13_feature_flag_detangle

Conversation

@JustinKovacich

Copy link
Copy Markdown
Contributor

Splits the client Cargo feature so consumers can depend on the crate without pulling tokio + socket2. Adds a new client-tokio feature that layers the tokio convenience defaults on top.

Cargo features

Before:
client = ["std", "dep:tokio", "dep:socket2", "dep:futures"]
server = ["std", "dep:tokio", "dep:socket2", "dep:futures"]

After:
client = ["std", "dep:futures"]
client-tokio = ["client", "dep:tokio", "dep:socket2"]
server = ["std", "dep:tokio", "dep:socket2", "dep:futures"]

server is intentionally left unchanged. src/server/sd_state.rs, src/server/subscription_manager.rs, and src/server/mod.rs still reference tokio::net::UdpSocket / tokio::sync::RwLock / socket2::Socket directly in production code; phase 14 (server parallel) is the phase that retargets the server to the trait surface, after which the same server + server-tokio split applies.

Module gates

tokio_transport flips from feature = "client" or "server" to feature = "client-tokio" or "server". The full Client / Inner / SocketManager types — including the bind / bind_discovery_seeded convenience constructors that default to TokioTransport + TokioSpawner — are gated behind client-tokio, because their bind paths still hardcode &TokioTransport and Inner calls TokioTimer.sleep directly. The trait-generic bind_with_transport / bind_discovery_seeded_with_transport paths (introduced in phase 12) work without tokio in principle, but a caller cannot construct a SocketManager without going through Inner, which is gated.

The client feature therefore exposes:

  • The trait surface (TransportFactory, TransportSocket, Timer, Spawner, ChannelFactory, E2ERegistryHandle, InterfaceHandle, etc.)
  • Data-type shells (PendingResponse, ClientUpdate, ClientUpdates, DiscoveryMessage)
  • EmbassySyncChannels (when bare_metal is also on)

It does not yet expose a constructible Client. That work — making Inner generic over TransportFactory / Timer / Spawner and ungating Client from client-tokio — is phase 13.5, not phase 13. The plan's §6 phase 13 gate text reads "client is no_std-compatible", which is partially aspirational; what this commit delivers is "client compiles without tokio."

Misfiled impls relocated

impl E2ERegistryHandle for Arc<Mutex<E2ERegistry>> and impl InterfaceHandle for Arc<RwLock<Ipv4Addr>> lived in tokio_transport.rs despite being pure std (Arc / Mutex / RwLock, no tokio). Moved to transport::std_handle_impls, gated feature = "std". This is what unblocks --features client from needing tokio_transport at all.

Default type parameters dropped

The following public types lost their = TokioChannels / = TokioSpawner / = Arc<Mutex<E2ERegistry>> defaults:

  • Client<M, R, I, C>
  • ClientUpdates<M, C>
  • PendingResponse<P, C>
  • SocketManager<P, C>
  • SendMessage<P, C>
  • Outcome<P, C>
  • ControlMessage<P, C>

This is API-breaking. Callers writing Client::<RawPayload>::new(...) must update to Client::<RawPayload, _, _, _>::new(...). Callers not using turbofish (plain Client::new(...)) are unaffected because the convenience-constructor impl block fixes the other type parameters concretely.

Inner's defaults (S = TokioSpawner, R = Arc<Mutex<...>>, C = TokioChannels) are intentionally kept; Inner is gated to client-tokio entirely, and these defaults will be revisited in phase 13.5 alongside the TransportFactory generic that lets Inner drive a non-tokio backend.

Tests / examples

  • [[test]] client_server requires ["client-tokio", "server"].
  • examples/client_server and examples/discovery_client updated to client-tokio. examples/bare_metal unchanged.
  • Doctests in lib.rs:73 and transport.rs:102 switched from # #[cfg(feature = "client")] to # #[cfg(feature = "client-tokio")] so they run cleanly under all per-feature doctest invocations.
  • tests/client_server.rs spells out the full Client<P, _, _, TokioChannels> type alias since the default is gone.

Verification

  • cargo test --all-features -- --test-threads=1: 455 lib + 11 integration + 9 doc + 1 bare_metal_example_builds, 0 failures.
  • cargo test --no-default-features --features client --doc: 4 passed, 0 failed.
  • cargo clippy --all-features --all-targets: clean.
  • cargo doc --all-features --no-deps: clean.
  • Feature matrix builds cleanly: '', 'std', 'bare_metal', 'client', 'client-tokio', 'client,server', 'client,bare_metal', 'client-tokio,server', 'client,server,bare_metal,client-tokio'.

Splits the `client` Cargo feature so consumers can depend on the
crate without pulling tokio + socket2. Adds a new `client-tokio`
feature that layers the tokio convenience defaults on top.

# Cargo features

Before:
    client = ["std", "dep:tokio", "dep:socket2", "dep:futures"]
    server = ["std", "dep:tokio", "dep:socket2", "dep:futures"]

After:
    client       = ["std", "dep:futures"]
    client-tokio = ["client", "dep:tokio", "dep:socket2"]
    server       = ["std", "dep:tokio", "dep:socket2", "dep:futures"]

`server` is intentionally left unchanged. `src/server/sd_state.rs`,
`src/server/subscription_manager.rs`, and `src/server/mod.rs` still
reference `tokio::net::UdpSocket` / `tokio::sync::RwLock` /
`socket2::Socket` directly in production code; phase 14 (server
parallel) is the phase that retargets the server to the trait
surface, after which the same `server` + `server-tokio` split applies.

# Module gates

`tokio_transport` flips from `feature = "client" or "server"` to
`feature = "client-tokio" or "server"`. The full `Client` /
`Inner` / `SocketManager` types — including the `bind` /
`bind_discovery_seeded` convenience constructors that default to
`TokioTransport` + `TokioSpawner` — are gated behind `client-tokio`,
because their bind paths still hardcode `&TokioTransport` and
`Inner` calls `TokioTimer.sleep` directly. The trait-generic
`bind_with_transport` / `bind_discovery_seeded_with_transport` paths
(introduced in phase 12) work without tokio in principle, but a
caller cannot construct a `SocketManager` without going through
`Inner`, which is gated.

The `client` feature therefore exposes:
- The trait surface (`TransportFactory`, `TransportSocket`, `Timer`,
  `Spawner`, `ChannelFactory`, `E2ERegistryHandle`,
  `InterfaceHandle`, etc.)
- Data-type shells (`PendingResponse`, `ClientUpdate`,
  `ClientUpdates`, `DiscoveryMessage`)
- `EmbassySyncChannels` (when `bare_metal` is also on)

It does not yet expose a constructible `Client`. That work — making
`Inner` generic over `TransportFactory` / `Timer` / `Spawner` and
ungating `Client` from `client-tokio` — is phase 13.5, not phase 13.
The plan's §6 phase 13 gate text reads "client is no_std-compatible",
which is partially aspirational; what this commit delivers is
"client compiles without tokio."

# Misfiled impls relocated

`impl E2ERegistryHandle for Arc<Mutex<E2ERegistry>>` and
`impl InterfaceHandle for Arc<RwLock<Ipv4Addr>>` lived in
`tokio_transport.rs` despite being pure std (Arc / Mutex / RwLock,
no tokio). Moved to `transport::std_handle_impls`, gated
`feature = "std"`. This is what unblocks `--features client` from
needing `tokio_transport` at all.

# Default type parameters dropped

The following public types lost their `= TokioChannels` /
`= TokioSpawner` / `= Arc<Mutex<E2ERegistry>>` defaults:
- `Client<M, R, I, C>`
- `ClientUpdates<M, C>`
- `PendingResponse<P, C>`
- `SocketManager<P, C>`
- `SendMessage<P, C>`
- `Outcome<P, C>`
- `ControlMessage<P, C>`

This is API-breaking. Callers writing `Client::<RawPayload>::new(...)`
must update to `Client::<RawPayload, _, _, _>::new(...)`. Callers
not using turbofish (plain `Client::new(...)`) are unaffected because
the convenience-constructor impl block fixes the other type
parameters concretely.

`Inner`'s defaults (`S = TokioSpawner`, `R = Arc<Mutex<...>>`,
`C = TokioChannels`) are intentionally kept; `Inner` is gated to
`client-tokio` entirely, and these defaults will be revisited in
phase 13.5 alongside the `TransportFactory` generic that lets
`Inner` drive a non-tokio backend.

# Tests / examples

- `[[test]] client_server` requires `["client-tokio", "server"]`.
- `examples/client_server` and `examples/discovery_client` updated
  to `client-tokio`. `examples/bare_metal` unchanged.
- Doctests in `lib.rs:73` and `transport.rs:102` switched from
  `# #[cfg(feature = "client")]` to `# #[cfg(feature = "client-tokio")]`
  so they run cleanly under all per-feature doctest invocations.
- `tests/client_server.rs` spells out the full `Client<P, _, _, TokioChannels>`
  type alias since the default is gone.

# Verification

- `cargo test --all-features -- --test-threads=1`: 455 lib + 11 integration + 9 doc + 1 bare_metal_example_builds, 0 failures.
- `cargo test --no-default-features --features client --doc`: 4 passed, 0 failed.
- `cargo clippy --all-features --all-targets`: clean.
- `cargo doc --all-features --no-deps`: clean.
- Feature matrix builds cleanly: '', 'std', 'bare_metal', 'client', 'client-tokio', 'client,server', 'client,bare_metal', 'client-tokio,server', 'client,server,bare_metal,client-tokio'.

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors Cargo feature flags to split the client API into a tokio-free trait surface (client) and a tokio/socket2 convenience layer (client-tokio), reducing dependency footprint for consumers who don’t need tokio.

Changes:

  • Split client into client (std + futures only) and client-tokio (adds tokio + socket2), and updated module/re-export gating accordingly.
  • Moved std-only E2ERegistryHandle / InterfaceHandle impls out of tokio_transport into transport to avoid pulling the tokio backend unnecessarily.
  • Updated tests, examples, and doctests for the removed default type parameters and the new feature flag.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Cargo.toml Introduces client-tokio feature and adjusts client deps; updates integration test feature requirements.
src/lib.rs Re-gates tokio_transport and re-exports to align with client-tokio vs client split; updates crate-level docs/doctests.
src/transport.rs Adds std-backed handle impls and updates channel abstraction documentation.
src/tokio_transport.rs Removes std-only handle impls now hosted in transport; keeps tokio backend focused on tokio/socket2 concerns.
src/client/mod.rs Gates Client/inner modules behind client-tokio and removes default type params from public client-facing types.
src/client/inner.rs Propagates removal of default channel type parameters into internal control types and socket manager usage.
src/client/socket_manager.rs Removes tokio-defaulted generic params from public structs/enums; updates tests to specify TokioChannels.
tests/client_server.rs Updates test aliases/usages to specify concrete generic parameters and TokioChannels.
examples/client_server/Cargo.toml Switches example dependency from client to client-tokio (plus server).
examples/client_server/src/main.rs Updates Client turbofish to include new generic placeholders.
examples/discovery_client/Cargo.toml Switches example dependency from client to client-tokio.
examples/discovery_client/src/main.rs Updates Client turbofish to include new generic placeholders.
examples/bare_metal/src/main.rs Updates docs to reflect the phase-13 client-side split and remaining server-side work.

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

Comment thread src/lib.rs
Comment on lines +154 to 155
#[cfg(any(feature = "client-tokio", feature = "server"))]
pub mod tokio_transport;

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

tokio_transport currently contains the EmbassySyncChannels (bare_metal) channel backend, but the module itself is now gated behind client-tokio/server. That makes EmbassySyncChannels unavailable in a --no-default-features --features bare_metal (and even client,bare_metal) build, contradicting the crate/docs statements that it’s the no-tokio channel option. Consider moving the embassy channel backend into a tokio-free module gated by bare_metal (and re-exporting it), or update the docs/feature docs to reflect the new availability.

Copilot uses AI. Check for mistakes.
Comment thread src/transport.rs
Comment on lines +759 to +762
// the channel primitive used by the client. `TokioChannels` (in
// `tokio_transport`) is the default for `std + tokio` builds;
// `EmbassySyncChannels` (in `tokio_transport`, gated behind `bare_metal`)
// is the alternative for no-tokio / no_std builds.

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

The channel-abstraction docs say EmbassySyncChannels lives in tokio_transport and is the alternative for “no-tokio / no_std builds”, but tokio_transport is feature-gated behind client-tokio/server and won’t be present in a bare-metal build. Update this comment (or relocate/re-export EmbassySyncChannels) so it matches the actual feature-gating.

Suggested change
// the channel primitive used by the client. `TokioChannels` (in
// `tokio_transport`) is the default for `std + tokio` builds;
// `EmbassySyncChannels` (in `tokio_transport`, gated behind `bare_metal`)
// is the alternative for no-tokio / no_std builds.
// the channel primitive used by the client. `TokioChannels` is the default
// for `std + tokio` builds. For no-tokio / no_std / bare-metal builds, use
// the channel backend exposed by the corresponding feature-gated transport
// module or re-export available in that configuration.

Copilot uses AI. Check for mistakes.
Comment thread src/client/mod.rs
Comment on lines +224 to +226
/// `tokio` + `socket2`. Bare-metal callers use
/// [`Self::new_with_spawner_and_loopback`] (always available under `client`)
/// and supply their own channel factory + spawner.

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

This doc comment suggests bare-metal callers can use Client::new_with_spawner_and_loopback “always available under client”, but the Client type itself is #[cfg(feature = "client-tokio")] (and the impl block here is also client-tokio). Please adjust the wording to avoid implying Client is usable under client/bare-metal today (e.g., point bare-metal users at the transport trait surface instead).

Suggested change
/// `tokio` + `socket2`. Bare-metal callers use
/// [`Self::new_with_spawner_and_loopback`] (always available under `client`)
/// and supply their own channel factory + spawner.
/// `tokio` + `socket2`.
///
/// Bare-metal / non-`tokio` integrations should use the `transport` trait
/// surface directly today; this `Client` type and its constructors are gated
/// on `client-tokio`.

Copilot uses AI. Check for mistakes.
Comment thread Cargo.toml
Comment on lines +69 to +70
client = ["std", "dep:futures"]
client-tokio = ["client", "dep:tokio", "dep:socket2"]

Copilot AI Apr 27, 2026

Copy link

Choose a reason for hiding this comment

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

Now that client is std + futures and client-tokio layers tokio/socket2 on top, the bare-metal feature docs later in this section (notably the statement that EmbassySyncChannels is available without tokio) don’t match the current module gating (EmbassySyncChannels still lives under tokio_transport). Please reconcile this: either move/re-export the embassy channel backend for bare_metal builds, or update the feature docs to reflect its actual availability.

Copilot uses AI. Check for mistakes.
@JustinKovacich

Copy link
Copy Markdown
Contributor Author

Closing without merge to declutter the stack: this phase's changes are carried in full by the consolidated lineage under PR #114 (phase 21), which the next development stack builds on. Branch is retained.

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