TIL

TIL: Datastar's takeover signal, and letting the page refuse first

Field notes on Datastar SSE takeover — one stream replacing another, stale takeover signals poisoning the next stream, and why the page gets first refusal.

Field notes from dstar, a small Elixir/Phoenix library that wraps Datastar. I am mid-learning here, so this is what the commits show rather than a tutorial.

Two streams, one page

A streaming page holds one long-lived SSE connection. Open the same page in a second tab, or reconnect after a blip, and now two streams want the same page key. Datastar’s answer is a takeover: the newer stream wins, and the older one is told to stop.

The old stream has to learn about this. In dstar the takeover arrives as a message rather than an instant kill, so the library can end the stream cleanly instead of leaving a zombie stream holding the key.

First refusal is not a veto

The interesting detail in the commit log:

Give the page first refusal on the takeover signal

The page still gets the signal before the stream ends, so cleanup code the app already wrote keeps running. But “first refusal” is exactly what it sounds like — the page gets to react first, and then the stream ends either way. A takeover is not something a page can decline. If you read “first refusal” as “the page can stop the takeover”, you will be confused about why your stream ended anyway.

The bug that made this interesting

Fix a stale takeover signal poisoning the next stream (review F1)

Takeover signals are delivered to the process serving the connection. That process can be reused for the next request — and a signal that arrived after the old loop stopped listening just sits in its mailbox. When the next stream starts up in the same process, that stale signal looks identical to a takeover of the new stream.

Result: a fresh stream tears itself down. Nobody replaced it. Nothing in the logs explains why. The fix was to drain leftover takeover signals when the loop starts, before the stream is registered, so an old signal cannot be mistaken for a new one.

What I am taking from it

  • A takeover has two ends. The taker needs the key, and the old stream needs to agree to go.
  • Mailboxes outlive loops. Anything parked in one can be misread as a fresh message later.
  • Letting the page react first is a courtesy, not a decision. Do not confuse the two when you read the code.
  • A signal you cannot distinguish from a stale copy of itself is worse than no signal at all.

More as I learn it.

Enjoyed this?

I write short things mostly: TILs, expansions of tweets that needed more than 300 characters, and a log of what my AI agents ship and break.

Related posts