fix(sdk-rust): share one TLS config across WebSocket connects
The Rust SDK's WebSocket client builds its rustls ClientConfig from the platform's native roots once per process and hands it to every wss:// connect and reconnect in ws.rs.
How it works
ws_tls::connect(private) wrapsconnect_async_tls_with_configwithConnector::Rustls(shared config)forwss://;ws://connects never read roots.- The config sits behind a
tokio::sync::Mutex, so concurrent first connects wait for a single load instead of each reading the store. The load runs on the blocking pool (spawn_blocking), off the runtime's workers. - An empty root store fails that connect with
io::ErrorKind::NotFound(the same "no native root CA certificates found" error tokio-tungstenite returned) and is not cached, so the next connect or reconnect reads the store again. rustlsandrustls-native-certsare direct dependencies at the versions tokio-tungstenite already links; no new crypto provider or feature is enabled.
Public API is unchanged.
Before / after (macOS, arm64)
Release build of a scratch binary that calls WsClient::connect() 20 times against a local wss://127.0.0.1 listener that drops each socket, so every connect reaches TLS setup and fails:
| first connect | each later connect | process CPU (user+sys) | instructions retired | |
|---|---|---|---|---|
origin/main | 128 ms | 120 ms | 1.54 s | 21.9 B |
| this branch | 128 ms | 0.1 ms | 0.08 s | 1.2 B |
A 3 s sample of the same loop running continuously: on origin/main 2,486 samples sit under rustls_native_certs::macos::load_native_certs (the keychain enumeration and per-certificate trust evaluation); on this branch there are none.
Unit tests in src/ws_tls.rs:
concurrent_first_connects_read_the_roots_once: 8 tasks on a 4-worker runtime race the first connect; the loader runs once and every task gets the sameArc<ClientConfig>.no_roots_fail_the_connect_and_are_asked_for_again: an empty store errors withNotFound; the next call loads again and its config is then reused (2 loads for 3 calls).reconnects_reuse_the_first_connects_configuration: two realwss://connects throughconnect_with(the body ofconnect) with a fixed root store load once and share one config. None of the tests reads the host's certificates.
Other SDKs
None reads a root store per connect:
- TypeScript uses the runtime's global
WebSocket(Node/undici or the browser), which holds its CA store per process. - Swift uses
URLSession.webSocketTask; trust evaluation is the system's, with no SDK-side store load. - Python: the SDK was removed in #589.
Semver
This change is a patch on its own: an internal performance fix with no public API change. It touches only the relaycast crate, so no npm package (the lockstep v12.1.0 line) needs a release for it.
The crate's next crates.io version is 11.0.0 (major), because its baseline is the crate's own version line, not v12.1.0:
- crates.io
relaycast10.0.0 was published from095a346e(itssrc/matches that commit exactly) and still haspub plan: StringonWorkspace. - #590 removed
Workspace::planfrom the crate after that publish. That change is insidev12.1.0, andmainequalsv12.1.0, but the crate itself has not been released since 10.0.0 (Cargo.tomlstill reads10.0.0; the latestsdk-rust-v*tag issdk-rust-v9.0.1). - Dispatching
publish-rust.ymlwithpatchfrommainwould ship that field removal as 10.0.1. Dispatch withmajor→ 11.0.0. The workflow rebases its release commit ontomainand pushes it there, so it does not support a 10.0.1 cut from a side branch. - relay-desktop reads
info.plan(probe/src/relay/client.rs:47,probe/src/relay/daemon/workspace_verification.rs:71), so its bump to 11.0.0 drops those reads.
🤖 Generated with Claude Code
Agent Relay sessions
claudesession38a7c19b· opened viagh pr create· last active 2026-10-28