Summary
Expose a way for downstream apps to measure round-trip latency to IB's servers on an already-established EClient session, ideally as a heartbeat the library can drive (or that the caller can drive on demand) without disturbing auth or market-data flow.
Use case
I'm building a Tauri/Svelte trading frontend on top of ibx and wanted to surface current network latency to IB in the UI:
- One value shown in the connecting modal (so the user sees link quality before placing the first order).
- A continuously-updated value in the app's top bar while the session is live.
There's currently no obvious primitive for this in ibx:
req_current_time (src/api/client/stubs.rs:32) is documented as gateway-local — it returns local system time and does not do a server round-trip, so it can't be used to measure RTT.
req_contract_details, req_mkt_data etc. do round-trip, but they're heavyweight and have side effects (subscriptions, rate-limit budgets) that make them unsuitable as a periodic ping.
What I tried (and why it failed)
Naively, I added a pre-flight + 10 s heartbeat that did:
```rust
let addr = format!("{host}:4001").to_socket_addrs()?.next()?;
let t0 = Instant::now();
TcpStream::connect_timeout(&addr, Duration::from_secs(3))?;
// drop the stream immediately — measuring just the TCP handshake
```
…to the IB auth port (AUTH_PORT = 4001, from src/config.rs:19).
This breaks auth. Opening a TCP socket to :4001 and dropping it without sending the expected NS_SECURE_CONNECT (NS_VERSION) first message looks to IB's auth server like an aborted handshake. After a few of those probes the server starts rejecting subsequent real logins with:
```
Auth error: 1;user not configured on firewall;
```
— which is the catch-all rejection code raised at src/auth/session.rs:121, not literal allowlist misconfiguration. Same issue applies post-connect: probing :4001 periodically while a session is up is an anti-abuse footgun.
So bare TCP probing of IB's ports is not a viable approach for downstream apps.
Proposal
A first-class API for in-session RTT, something like one of:
EClient::ping() -> io::Result<Duration> (or async): the library issues whatever IB-protocol message is cheapest and round-trips to a real IB server, returns the measured elapsed time. No side effects on subscriptions / contract caches / rate budgets.
- Built-in heartbeat with callback:
EClient::start_heartbeat(interval) that periodically pings and surfaces the RTT through a new Wrapper::heartbeat(rtt: Duration) callback (or an event channel). The library would also use this internally as a liveness check and could expose last_rtt() / last_heartbeat_at() getters.
- Document a recommended pattern. If
ibx already has a no-side-effects round-trip primitive I missed, a doc note in RUST_API.md covering "how to measure latency to IB on a live session" would be enough.
Option 2 is most ergonomic for downstream UIs because (a) it gives a free liveness signal alongside the latency number and (b) it centralizes the question of which IB message is safe to ping with — that decision is library-level, not app-level.
Notes
- Whatever the underlying message, it must round-trip to IB (not gateway-local) and must not consume req_id/cancellation budgets that the caller needs for real requests.
- It would be useful for both connect-time (single measurement once the session is up) and steady-state (heartbeat) use cases. A single primitive that supports both is fine.
- For the connect-time case in particular, an early callback that reports the first successful round-trip RTT before the user starts placing orders is the most useful surface for trading-app UIs.
Happy to PR if you point me at the message you'd prefer the library use.
Summary
Expose a way for downstream apps to measure round-trip latency to IB's servers on an already-established
EClientsession, ideally as a heartbeat the library can drive (or that the caller can drive on demand) without disturbing auth or market-data flow.Use case
I'm building a Tauri/Svelte trading frontend on top of
ibxand wanted to surface current network latency to IB in the UI:There's currently no obvious primitive for this in
ibx:req_current_time(src/api/client/stubs.rs:32) is documented as gateway-local — it returns local system time and does not do a server round-trip, so it can't be used to measure RTT.req_contract_details,req_mkt_dataetc. do round-trip, but they're heavyweight and have side effects (subscriptions, rate-limit budgets) that make them unsuitable as a periodic ping.What I tried (and why it failed)
Naively, I added a pre-flight + 10 s heartbeat that did:
```rust
let addr = format!("{host}:4001").to_socket_addrs()?.next()?;
let t0 = Instant::now();
TcpStream::connect_timeout(&addr, Duration::from_secs(3))?;
// drop the stream immediately — measuring just the TCP handshake
```
…to the IB auth port (
AUTH_PORT = 4001, fromsrc/config.rs:19).This breaks auth. Opening a TCP socket to
:4001and dropping it without sending the expectedNS_SECURE_CONNECT(NS_VERSION) first message looks to IB's auth server like an aborted handshake. After a few of those probes the server starts rejecting subsequent real logins with:```
Auth error: 1;user not configured on firewall;
```
— which is the catch-all rejection code raised at
src/auth/session.rs:121, not literal allowlist misconfiguration. Same issue applies post-connect: probing:4001periodically while a session is up is an anti-abuse footgun.So bare TCP probing of IB's ports is not a viable approach for downstream apps.
Proposal
A first-class API for in-session RTT, something like one of:
EClient::ping() -> io::Result<Duration>(or async): the library issues whatever IB-protocol message is cheapest and round-trips to a real IB server, returns the measured elapsed time. No side effects on subscriptions / contract caches / rate budgets.EClient::start_heartbeat(interval)that periodically pings and surfaces the RTT through a newWrapper::heartbeat(rtt: Duration)callback (or an event channel). The library would also use this internally as a liveness check and could exposelast_rtt()/last_heartbeat_at()getters.ibxalready has a no-side-effects round-trip primitive I missed, a doc note inRUST_API.mdcovering "how to measure latency to IB on a live session" would be enough.Option 2 is most ergonomic for downstream UIs because (a) it gives a free liveness signal alongside the latency number and (b) it centralizes the question of which IB message is safe to ping with — that decision is library-level, not app-level.
Notes
Happy to PR if you point me at the message you'd prefer the library use.