Skip to content

Improve websocket liveness checks and idle timeout handling#5517

Open
aasoni wants to merge 1 commit into
masterfrom
aasoni/ws-idle-timeout-rework
Open

Improve websocket liveness checks and idle timeout handling#5517
aasoni wants to merge 1 commit into
masterfrom
aasoni/ws-idle-timeout-rework

Conversation

@aasoni

@aasoni aasoni commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description of Changes

  • Idle timeout no longer requires a pong to restart, the send loop extends the idle deadline whenever it makes write progress (rate-limited to once per second). Note: it has been pointed out that if a client dies, write progress can still be made because the LB will continue to read from the socket until its own buffer fills up. This is considered ok as it will avoid aborting slow clients while keeping around idle clients for some extra time while the LB buffer fills up.

  • On idle timeout, initiate a proper close handshake carrying an "idle timeout" reason instead of abruptly tearing down the connection, so well-behaved clients can tell why they were disconnected. The connection is torn down if the handshake does not complete within a 10s grace period.

  • The "websocket connection aborted" warning and ws_clients_aborted metric now fire only when the client actor task is actually aborted or panics, not on every normal disconnect.

  • Fix the kick log message to report the channel's configured capacity instead of its remaining capacity (always 0 at that point).

API and ABI breaking changes

No

Expected complexity level and risk

3

Testing

These changes were made on the BitCraft cluster and were tested by hundreds of players connecting and subscribing to large amounts of data. I am fairly confident the change doesn't break things. I am less confident it's the disconnection silver bullet, but it may help.

- Idle timeout no longer requires receive activity alone: the send loop
  extends the idle deadline whenever it makes write progress
  (rate-limited to once per second).

- On idle timeout, initiate a proper close handshake carrying an
  "idle timeout" reason instead of abruptly tearing down the
  connection, so well-behaved clients can tell why they were
  disconnected. The connection is torn down if the handshake does not
  complete within a 10s grace period.

- The "websocket connection aborted" warning and ws_clients_aborted
  metric now fire only when the client actor task is actually aborted
  or panics, not on every normal disconnect.

- Fix the kick log message to report the channel's configured capacity
  instead of its remaining capacity (always 0 at that point).
@aasoni aasoni force-pushed the aasoni/ws-idle-timeout-rework branch from e4fc66d to 234f6c9 Compare July 10, 2026 14:30
@aasoni aasoni requested review from jsdt and kim July 10, 2026 14:32

@jsdt jsdt left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I drafted a PR on top of this branch to make the idle tracking more straightforward (IMO): #5521

I discussed it in another comment, but I don't think we want to bump the idle timeout on writes.

fut.await
fut.await;

scopeguard::ScopeGuard::into_inner(abort_guard);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't we need the changes to the scope guard. I updated the other PR so that we only log a warning if we hadn't previously recorded a more specific cause ( https://github.com/clockworklabs/SpacetimeDB/pull/5495/changes#diff-cde382f76a7190bac09b3ca01dc55e56abb79225fb34ec657d6e43ba7792da0eR892), and I'd rather maintain that the ws_clients_aborted metric always gets incremented if the ws_clients_spawned counter gets incremented. Once we deploy these changes and verify that the new stats are adding up to match ws_clients_aborted we could just remove it entirely.

log::warn!("error sending frame: {e:#}");
break 'outer;
}
// Writing succeeded, so the client is making progress:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If I'm reading this correctly, completing a ws.feed doesn't mean that the client is making progress. It just means that the buffer isn't full.

It seems like that could result in us leaving a connection open for a pretty long time, as long as the client is periodically being sent very small messages. It also means our threshold for being active depends on buffer sizes (including any proxies), which makes it easy to accidentally change the behavior.

I think it would be better for us to just increase the idle timeout rather than counting these writes as activity.

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.

2 participants