Skip to content

Commit 31a55b9

Browse files
authored
Refactor ReconnectingWebSocket with state machine and fix config change reconnection (#744)
Introduce a formal state machine pattern for WebSocket connection management: - Add ConnectionState enum (IDLE, CONNECTING, CONNECTED, AWAITING_RETRY, DISCONNECTED, DISPOSED) to explicitly model the connection lifecycle - Implement pure reducer function (reduceState) with action-based transitions - Replace ad-hoc boolean flags (#isDisconnected, #isDisposed, #isConnecting, #pendingReconnect) with single #state property Fix excessive reconnection on configuration changes: - Previously, config changes triggered reconnect() on all active sockets - Now only sockets in DISCONNECTED state are reconnected, since connected sockets will pick up settings naturally on their next reconnection cycle - Prevents unnecessary connection churn when users modify settings Closes #754
1 parent b52e7a4 commit 31a55b9

File tree

7 files changed

+329
-102
lines changed

7 files changed

+329
-102
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Unreleased
44

5+
### Changed
6+
7+
- WebSocket connections are now more robust and reconnect less frequently, only when truly
8+
necessary, reducing unnecessary disconnections and improving stability.
9+
510
## [v1.12.1](https://github.com/coder/vscode-coder/releases/tag/v1.12.1) 2026-01-23
611

712
### Fixed

src/api/coderApi.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {
4646
type OneWayWebSocketInit,
4747
} from "../websocket/oneWayWebSocket";
4848
import {
49+
ConnectionState,
4950
ReconnectingWebSocket,
5051
type SocketFactory,
5152
} from "../websocket/reconnectingWebSocket";
@@ -164,19 +165,24 @@ export class CoderApi extends Api implements vscode.Disposable {
164165

165166
/**
166167
* Watch for configuration changes that affect WebSocket connections.
167-
* When any watched setting changes, all active WebSockets are reconnected.
168+
* Only reconnects DISCONNECTED sockets since they require an explicit reconnect() call.
169+
* Other states will pick up settings naturally.
168170
*/
169171
private watchConfigChanges(): vscode.Disposable {
170172
const settings = webSocketConfigSettings.map((setting) => ({
171173
setting,
172174
getValue: () => vscode.workspace.getConfiguration().get(setting),
173175
}));
174176
return watchConfigurationChanges(settings, () => {
175-
if (this.reconnectingSockets.size > 0) {
176-
this.output.info(
177-
`Configuration changed, reconnecting ${this.reconnectingSockets.size} WebSocket(s)`,
177+
const socketsToReconnect = [...this.reconnectingSockets].filter(
178+
(socket) => socket.state === ConnectionState.DISCONNECTED,
179+
);
180+
if (socketsToReconnect.length) {
181+
this.output.debug(
182+
`Configuration changed, ${socketsToReconnect.length}/${this.reconnectingSockets.size} socket(s) in DISCONNECTED state`,
178183
);
179-
for (const socket of this.reconnectingSockets) {
184+
for (const socket of socketsToReconnect) {
185+
this.output.debug(`Reconnecting WebSocket: ${socket.url}`);
180186
socket.reconnect();
181187
}
182188
}

src/inbox.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,6 @@ export class Inbox implements vscode.Disposable {
5050
logger.info("Listening to Coder Inbox");
5151
});
5252

53-
socket.addEventListener("error", () => {
54-
// Errors are already logged internally
55-
inbox.dispose();
56-
});
57-
5853
socket.addEventListener("message", (data) => {
5954
if (data.parseError) {
6055
logger.error("Failed to parse inbox message", data.parseError);

0 commit comments

Comments
 (0)