Skip to content

Conversation

@EhabY
Copy link
Collaborator

@EhabY EhabY commented Jan 21, 2026

Summary

  • Replace multiple boolean flags with a single ConnectionState enum for clearer state management
  • Improves code readability and makes state transitions explicit

Changes

Refactored ReconnectingWebSocket to use a state machine pattern with six states:

  • IDLE - Initial state, ready to connect
  • CONNECTING - Actively running socket factory
  • CONNECTED - Socket is open and working
  • AWAITING_RETRY - Waiting for backoff timer before reconnection
  • DISCONNECTED - Temporarily paused, user must call reconnect() to resume
  • DISPOSED - Permanently closed, cannot be reused

This makes the websocket lifecycle easier to understand and debug, and prevents invalid state combinations that could
occur with independent boolean flags.

Closes #754

@EhabY EhabY self-assigned this Jan 21, 2026
@mtojek mtojek requested a review from mafredri January 23, 2026 10:19
Copy link
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

Nice refactor. Added some comments and questions, but the general theme I see that could be improved upon is the robustness of maintaining the correct state during asynchronous code execution. Right now it seems like there are a few minor edge cases where the state machine might not be fully protected against an unintended transition.

Some suggestions to tighten up the state machine include:

  • Ensure there's fewer (or ideally one) code-path for state transitions.
  • Track connection ID in async handlers to prevent stale transitions.

PS. A state machine diagram showing states and their possible transitions may be useful.

// Check if disconnected/disposed while waiting for factory
if (this.#isDisposed || this.#isDisconnected) {
// Check if state changed while waiting for factory (e.g., disconnect/dispose called)
if (this.#state !== ConnectionState.CONNECTING) {
Copy link
Member

Choose a reason for hiding this comment

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

Are we expecting something may asynchronously change this state considering we just set connecting?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, JS is single threaded but it's async so there could be a case where we await socketFactory and then during that we call reconnect/dispose... etc

@EhabY EhabY force-pushed the add-ws-state-machine branch from 67c4fa5 to a14e131 Compare January 26, 2026 10:20
@EhabY EhabY force-pushed the add-ws-state-machine branch from a14e131 to 46466c7 Compare January 26, 2026 10:39
@EhabY EhabY force-pushed the add-ws-state-machine branch from 46466c7 to 5042fa5 Compare January 26, 2026 10:58
@EhabY
Copy link
Collaborator Author

EhabY commented Jan 26, 2026

  • Ensure there's fewer (or ideally one) code-path for state transitions.

Would it make sense to add something like this here:

function reduceState(
	state: ConnectionState,
	action: StateAction,
): ConnectionState {
	switch (action.type) {
		case "CONNECT":
			switch (state) {
				case ConnectionState.IDLE:
				case ConnectionState.CONNECTED:
				case ConnectionState.AWAITING_RETRY:
				case ConnectionState.DISCONNECTED:
					return ConnectionState.CONNECTING;
				default:
					return state;
			}
...

That way it's a true state machine and if the state does not change then we return false so that can be handled

@EhabY EhabY requested a review from mafredri January 26, 2026 11:40
@mafredri
Copy link
Member

mafredri commented Jan 26, 2026

Would it make sense to add something like this here:

[...]

That way it's a true state machine and if the state does not change then we return false so that can be handled

Your suggestion makes sense to me and seems like a simple way to make the state machine easier to follow. 👍🏻

Replace direct state assignments with a pure reducer pattern:
- Add StateAction type and reduceState() pure function
- Add #dispatch() method that validates transitions
- Remove #pendingReconnect flag - reconnect() now restarts immediately
@EhabY EhabY force-pushed the add-ws-state-machine branch from 5042fa5 to 358d4d0 Compare January 26, 2026 12:02
@EhabY
Copy link
Collaborator Author

EhabY commented Jan 26, 2026

Implemented a reducer function - BTW the last commit is unrelated but I added it because it was causing WS leaks to happen more frequently (basically only reconnect if we are not connected)

@EhabY EhabY force-pushed the add-ws-state-machine branch from af8817b to 69df291 Compare January 26, 2026 16:23
@EhabY EhabY force-pushed the add-ws-state-machine branch from 26b7fe0 to 6b4fe99 Compare January 26, 2026 19:14
Copy link
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

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

LGTM, nice work!

@EhabY EhabY merged commit 31a55b9 into coder:main Jan 27, 2026
6 checks passed
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.

Only trigger WebSocket reconnection if it's not connected

3 participants