Skip to content

Commit 5042fa5

Browse files
committed
Only trigger reconnection if the WS is not connected
1 parent 93355b9 commit 5042fa5

File tree

3 files changed

+43
-23
lines changed

3 files changed

+43
-23
lines changed

src/api/coderApi.ts

Lines changed: 8 additions & 4 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,22 @@ 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+
* Skips connected sockets since they pick up new settings on next reconnect.
168169
*/
169170
private watchConfigChanges(): vscode.Disposable {
170171
const settings = webSocketConfigSettings.map((setting) => ({
171172
setting,
172173
getValue: () => vscode.workspace.getConfiguration().get(setting),
173174
}));
174175
return watchConfigurationChanges(settings, () => {
175-
if (this.reconnectingSockets.size > 0) {
176+
const socketsToReconnect = [...this.reconnectingSockets].filter(
177+
(socket) => socket.state !== ConnectionState.CONNECTED,
178+
);
179+
if (socketsToReconnect.length > 0) {
176180
this.output.info(
177-
`Configuration changed, reconnecting ${this.reconnectingSockets.size} WebSocket(s)`,
181+
`Configuration changed, reconnecting ${socketsToReconnect.length} WebSocket(s)`,
178182
);
179-
for (const socket of this.reconnectingSockets) {
183+
for (const socket of socketsToReconnect) {
180184
socket.reconnect();
181185
}
182186
}

src/websocket/reconnectingWebSocket.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class ReconnectingWebSocket<
112112
/**
113113
* Returns the current connection state.
114114
*/
115-
get state(): string {
115+
get state(): ConnectionState {
116116
return this.#state;
117117
}
118118

test/unit/api/coderApi.test.ts

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,11 @@ describe("CoderApi", () => {
591591

592592
const setupAutoOpeningWebSocket = () => {
593593
const sockets: Array<Partial<Ws>> = [];
594+
const handlers: Record<string, (...args: unknown[]) => void> = {};
594595
vi.mocked(Ws).mockImplementation(function (url: string | URL) {
595596
const mockWs = createMockWebSocket(String(url), {
596-
on: vi.fn((event, handler) => {
597+
on: vi.fn((event: string, handler: (...args: unknown[]) => void) => {
598+
handlers[event] = handler;
597599
if (event === "open") {
598600
setImmediate(() => handler());
599601
}
@@ -603,12 +605,12 @@ describe("CoderApi", () => {
603605
sockets.push(mockWs);
604606
return mockWs as Ws;
605607
});
606-
return sockets;
608+
return { sockets, handlers };
607609
};
608610

609611
describe("Reconnection on Host/Token Changes", () => {
610612
it("triggers reconnection when session token changes", async () => {
611-
const sockets = setupAutoOpeningWebSocket();
613+
const { sockets } = setupAutoOpeningWebSocket();
612614
api = createApi(CODER_URL, AXIOS_TOKEN);
613615
await api.watchAgentMetadata(AGENT_ID);
614616

@@ -623,7 +625,7 @@ describe("CoderApi", () => {
623625
});
624626

625627
it("triggers reconnection when host changes", async () => {
626-
const sockets = setupAutoOpeningWebSocket();
628+
const { sockets } = setupAutoOpeningWebSocket();
627629
api = createApi(CODER_URL, AXIOS_TOKEN);
628630
const wsWrap = await api.watchAgentMetadata(AGENT_ID);
629631
expect(wsWrap.url).toContain(CODER_URL.replace("http", "ws"));
@@ -642,7 +644,7 @@ describe("CoderApi", () => {
642644
});
643645

644646
it("does not reconnect when token or host are unchanged", async () => {
645-
const sockets = setupAutoOpeningWebSocket();
647+
const { sockets } = setupAutoOpeningWebSocket();
646648
api = createApi(CODER_URL, AXIOS_TOKEN);
647649
await api.watchAgentMetadata(AGENT_ID);
648650

@@ -655,7 +657,7 @@ describe("CoderApi", () => {
655657
});
656658

657659
it("suspends sockets when host is set to empty string (logout)", async () => {
658-
const sockets = setupAutoOpeningWebSocket();
660+
const { sockets } = setupAutoOpeningWebSocket();
659661
api = createApi(CODER_URL, AXIOS_TOKEN);
660662
await api.watchAgentMetadata(AGENT_ID);
661663

@@ -668,7 +670,7 @@ describe("CoderApi", () => {
668670
});
669671

670672
it("does not reconnect when setting token after clearing host", async () => {
671-
const sockets = setupAutoOpeningWebSocket();
673+
const { sockets } = setupAutoOpeningWebSocket();
672674
api = createApi(CODER_URL, AXIOS_TOKEN);
673675
await api.watchAgentMetadata(AGENT_ID);
674676

@@ -682,7 +684,7 @@ describe("CoderApi", () => {
682684
});
683685

684686
it("setCredentials sets both host and token together", async () => {
685-
const sockets = setupAutoOpeningWebSocket();
687+
const { sockets } = setupAutoOpeningWebSocket();
686688
api = createApi(CODER_URL, AXIOS_TOKEN);
687689
await api.watchAgentMetadata(AGENT_ID);
688690

@@ -695,7 +697,7 @@ describe("CoderApi", () => {
695697
});
696698

697699
it("setCredentials suspends when host is cleared", async () => {
698-
const sockets = setupAutoOpeningWebSocket();
700+
const { sockets } = setupAutoOpeningWebSocket();
699701
api = createApi(CODER_URL, AXIOS_TOKEN);
700702
await api.watchAgentMetadata(AGENT_ID);
701703

@@ -796,28 +798,25 @@ describe("CoderApi", () => {
796798
describe("Configuration Change Reconnection", () => {
797799
const tick = () => new Promise((resolve) => setImmediate(resolve));
798800

799-
it("reconnects sockets when watched config value changes", async () => {
801+
it("does not reconnect connected sockets when config value changes", async () => {
800802
mockConfig.set("coder.insecure", false);
801-
const sockets = setupAutoOpeningWebSocket();
803+
const { sockets } = setupAutoOpeningWebSocket();
802804
api = createApi(CODER_URL, AXIOS_TOKEN);
803805
await api.watchAgentMetadata(AGENT_ID);
804806

805807
mockConfig.set("coder.insecure", true);
806808
await tick();
807809

808-
expect(sockets[0].close).toHaveBeenCalledWith(
809-
1000,
810-
"Replacing connection",
811-
);
812-
expect(sockets).toHaveLength(2);
810+
expect(sockets[0].close).not.toHaveBeenCalled();
811+
expect(sockets).toHaveLength(1);
813812
});
814813

815814
it.each([
816815
["unchanged value", "coder.insecure", false],
817816
["unrelated setting", "unrelated.setting", "new-value"],
818817
])("does not reconnect for %s", async (_desc, key, value) => {
819818
mockConfig.set("coder.insecure", false);
820-
const sockets = setupAutoOpeningWebSocket();
819+
const { sockets } = setupAutoOpeningWebSocket();
821820
api = createApi(CODER_URL, AXIOS_TOKEN);
822821
await api.watchAgentMetadata(AGENT_ID);
823822

@@ -830,7 +829,7 @@ describe("CoderApi", () => {
830829

831830
it("stops watching after dispose", async () => {
832831
mockConfig.set("coder.insecure", false);
833-
const sockets = setupAutoOpeningWebSocket();
832+
const { sockets } = setupAutoOpeningWebSocket();
834833
api = createApi(CODER_URL, AXIOS_TOKEN);
835834
await api.watchAgentMetadata(AGENT_ID);
836835

@@ -840,6 +839,23 @@ describe("CoderApi", () => {
840839

841840
expect(sockets).toHaveLength(1);
842841
});
842+
843+
it("reconnects sockets in AWAITING_RETRY state when config changes", async () => {
844+
mockConfig.set("coder.insecure", false);
845+
const { sockets, handlers } = setupAutoOpeningWebSocket();
846+
api = createApi(CODER_URL, AXIOS_TOKEN);
847+
await api.watchAgentMetadata(AGENT_ID);
848+
849+
// Trigger close with abnormal code to put socket in AWAITING_RETRY
850+
handlers["close"]?.({ code: 1006, reason: "Abnormal closure" });
851+
await tick();
852+
853+
mockConfig.set("coder.insecure", true);
854+
await tick();
855+
856+
expect(sockets[0].close).toHaveBeenCalled();
857+
expect(sockets).toHaveLength(2);
858+
});
843859
});
844860
});
845861

0 commit comments

Comments
 (0)