Skip to content

subscribe() against simulator never returns: receive_valid_response loops forever on save format #309

@eeformacion

Description

@eeformacion

First off, thanks for the project — we're using f1-dash extensively as the reference for porting the realtime/decoders side of a personal F1 live-timing project to TypeScript on Cloudflare Workers, and the layering has been very pleasant to follow.

While porting signalr/src/lib.rs, I think I've stumbled on a latent bug that affects only dev mode (i.e. running realtime against the local simulator):

Repro

  1. cargo run --bin simulator -- save <recording.jsonl> during a live session.
  2. ADDRESS=0.0.0.0:8000 cargo run --bin simulator -- replay <recording.jsonl>.
  3. F1_DEV_URL=ws://localhost:8000/ws cargo run --bin realtime.

Expected: realtime connects, gets the initial state, starts streaming.

Observed (per my reading of the code; haven't run it end-to-end myself): signalr::subscribe calls receive_valid_response, which loops until a text message parses as Response { i: String, r: Option<Value> } (PascalCase → JSON `I`/`R`).

But simulator/src/save.rs writes:

  • Line 1: the unwrapped `r` content (the result of signalr::subscribe's previous run): `{"Heartbeat":{...},"DriverList":{...},...}` — no `I` field.
  • Lines 2..N: raw text from `listen_raw`, all in Update shape `{"M":[{"A":[topic, data, ts]}]}` — no `I` field either.

And simulator/src/replay/server.rs just dumps every line as-is, with a 100 ms gap, ignoring any incoming Subscribe message.

So receive_valid_response should never satisfy its parse check and the await should hang indefinitely. The check at signalr/src/lib.rs:161 (if response.i != id && env::var_os(\"F1_DEV_URL\").is_none()) skips the id mismatch error in dev mode, but doesn't avoid the loop itself.

I'm aware f1-dash.com runs in production mode, so this wouldn't surface there. Wanted to flag it in case it bites someone else trying to use the simulator workflow.

A possible fix

In dev mode, skip receive_valid_response entirely (treat the first incoming message as the initial state, since that's the format the simulator sends — line 1 of the recording is the unwrapped initial). Something like:

let initial = if env::var_os(\"F1_DEV_URL\").is_some() {
    // Simulator emits the unwrapped initial as the first text frame; consume it directly.
    receive_first_text(&mut client.stream).await?
} else {
    let response = receive_valid_response(&mut client.stream).await?;
    if response.i != id { return Err(anyhow::anyhow!(\"Response ID does not match\")); }
    response.r.ok_or_else(|| anyhow::anyhow!(\"No result in response\"))?
};

Happy to send a PR if you want; otherwise feel free to close — I'm mostly leaving this as a breadcrumb. Thanks again for the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions