Skip to content

Send the fallback health check before the buffered join - #6900

Merged
SteffenDE merged 2 commits into
phoenixframework:mainfrom
oliviasculley:health-check-before-join
Nov 6, 2026
Merged

SteffenDE merged 2 commits into
phoenixframework:mainfrom
oliviasculley:health-check-before-join

Conversation

@oliviasculley

Copy link
Copy Markdown
Contributor

Fixes #6770.

Steffen, this is the change I asked about on the issue. I think there are two different waits, and this only covers one of them.

If the join reply is one big frame, the browser does not hand it to JS until the whole frame arrives. I do not see a way for phoenix.js to know that download is in progress. Making that reply smaller is what helps that case.

The other wait is before the server has sent anything. The health-check ping goes out from the open callback, and onConnOpen has already flushed the join. Channel.Server.join/4 blocks the transport process in a receive until the mount finishes, so the ping sits there for the whole mount. We had mounts of several seconds on a socket that was fine, and no frame came back before the fallback timer gave up. The tab then stored phx:fallback:LongPoll and stayed on long poll.

This sends one heartbeat from onConnOpen before flushSendBuffer, and only when a fallback timer is already running. The server can answer that while the mount is still running. Any frame on that same websocket also passes the check, including the ping reply. A long-poll frame does not. If the socket opens and no frame ever arrives, the client still falls back, and it still memorizes long poll.

The open callback still sends its own ping after the flush. A frame that arrived first clears that timer.

I am not sure this is the shape you want. Would a heartbeat before the join be acceptable here, or should apps keep doing this themselves?

The join blocks the transport process until the channel returns, so a
ping queued behind it is not answered during a slow mount. Any frame
on that websocket also passes the check.

Fixes phoenixframework#6770.
@oliviasculley
oliviasculley marked this pull request as ready for review November 6, 2026 06:06

@SteffenDE SteffenDE left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's remove the changes to the built assets in priv and then I think we can also remove the

this.ping(rtt => {
        this.log("transport", "connected to primary after", rtt)
        this.primaryPassedHealthCheck = true
        clearTimeout(this.fallbackTimer)
      })

from the existing this.fallbackRef = this.onOpen(... since we now ping twice and once should be enough :)

The open callback ping repeats the one that already goes out before
the join. That ping still logs the round trip. The built files in
priv/static stay out of this change.
@oliviasculley

Copy link
Copy Markdown
Contributor Author

Okay, I think these changes should be applied now!

@SteffenDE
SteffenDE merged commit b88540a into phoenixframework:main Nov 6, 2026
16 checks passed
@SteffenDE

Copy link
Copy Markdown
Member

Thank you!

@oliviasculley
oliviasculley deleted the health-check-before-join branch November 6, 2026 16:50
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.

phoenix.js: the fallback health-check ping can kill a healthy websocket that is busy receiving

2 participants