History

Skip healthy env reconnects after browser resume (#2677)

Julius Marminge committed 6 months ago

Commit 90eea041 parent f92e1e13 files · +59 −2Browse files

Unified · Split

3 files
  1. apps/web/src/environments/runtime/service.threadSubscriptions.test.ts +53 −1
  2. apps/web/src/environments/runtime/service.ts +4 −1
  3. apps/web/src/rpc/wsRpcClient.ts +2 −0
apps/web/src/environments/runtime/service.threadSubscriptions.test.ts+53−1
@@ -176,6 +176,7 @@ describe("retainThreadDetailSubscription", () => { · 175 unmodified lines
176 },176 },
177 })),177 })),
178 },178 },
179 isHeartbeatFresh: vi.fn(() => true),
179 orchestration: {180 orchestration: {
180 subscribeThread: mockSubscribeThread,181 subscribeThread: mockSubscribeThread,
181 },182 },
@@ -382,7 +383,7 @@ describe("retainThreadDetailSubscription", () => { · 200 unmodified lines
382 await resetEnvironmentServiceForTests();383 await resetEnvironmentServiceForTests();
383 });384 });
384 385
385 it("reconnects environment streams when the browser resumes from the background", async () => {386 it("keeps healthy environment streams connected when the browser resumes from the background", async () => {
386 let visibilityState: DocumentVisibilityState = "visible";387 let visibilityState: DocumentVisibilityState = "visible";
387 const documentTarget = new EventTarget();388 const documentTarget = new EventTarget();
388 const windowTarget = new EventTarget();389 const windowTarget = new EventTarget();
@@ -408,6 +409,57 @@ describe("retainThreadDetailSubscription", () => { · 19 unmodified lines
408 documentTarget.dispatchEvent(new Event("visibilitychange"));409 documentTarget.dispatchEvent(new Event("visibilitychange"));
409 expect(mockConnectionReconnects[0]).not.toHaveBeenCalled();410 expect(mockConnectionReconnects[0]).not.toHaveBeenCalled();
410 411
412 visibilityState = "visible";
413 documentTarget.dispatchEvent(new Event("visibilitychange"));
414 expect(mockConnectionReconnects[0]).not.toHaveBeenCalled();
415
416 stop();
417 await resetEnvironmentServiceForTests();
418 });
419
420 it("reconnects stale environment streams when the browser resumes from the background", async () => {
421 let visibilityState: DocumentVisibilityState = "visible";
422 const documentTarget = new EventTarget();
423 const windowTarget = new EventTarget();
424 vi.stubGlobal("document", {
425 addEventListener: documentTarget.addEventListener.bind(documentTarget),
426 removeEventListener: documentTarget.removeEventListener.bind(documentTarget),
427 get visibilityState() {
428 return visibilityState;
429 },
430 });
431 vi.stubGlobal("window", {
432 addEventListener: windowTarget.addEventListener.bind(windowTarget),
433 removeEventListener: windowTarget.removeEventListener.bind(windowTarget),
434 });
435 mockCreateWsRpcClient.mockReturnValue({
436 server: {
437 getConfig: vi.fn(async () => ({
438 environment: {
439 environmentId: EnvironmentId.make("env-remote"),
440 label: "Remote env",
441 platform: { os: "darwin", arch: "arm64" },
442 serverVersion: "0.0.0-test",
443 capabilities: { repositoryIdentity: true },
444 },
445 })),
446 },
447 isHeartbeatFresh: vi.fn(() => false),
448 orchestration: {
449 subscribeThread: mockSubscribeThread,
450 },
451 });
452
453 const { resetEnvironmentServiceForTests, startEnvironmentConnectionService } =
454 await import("./service");
455
456 const stop = startEnvironmentConnectionService(new QueryClient());
457 expect(mockConnectionReconnects).toHaveLength(1);
458
459 visibilityState = "hidden";
460 documentTarget.dispatchEvent(new Event("visibilitychange"));
461 expect(mockConnectionReconnects[0]).not.toHaveBeenCalled();
462
411 visibilityState = "visible";463 visibilityState = "visible";
412 documentTarget.dispatchEvent(new Event("visibilitychange"));464 documentTarget.dispatchEvent(new Event("visibilitychange"));
413 expect(mockConnectionReconnects[0]).toHaveBeenCalledTimes(1);465 expect(mockConnectionReconnects[0]).toHaveBeenCalledTimes(1);
apps/web/src/environments/runtime/service.ts+4−1
@@ -1482,9 +1482,12 @@ function reconnectEnvironmentConnectionsAfterBrowserResume(reason: string): void · 1481 unmodified lines
1482 if (now - lastBrowserResumeReconnectAt < BROWSER_RESUME_RECONNECT_COOLDOWN_MS) {1482 if (now - lastBrowserResumeReconnectAt < BROWSER_RESUME_RECONNECT_COOLDOWN_MS) {
1483 return;1483 return;
1484 }1484 }
1485 lastBrowserResumeReconnectAt = now;
1486 1485
1487 for (const connection of environmentConnections.values()) {1486 for (const connection of environmentConnections.values()) {
1487 if (connection.client.isHeartbeatFresh()) {
1488 continue;
1489 }
1490 lastBrowserResumeReconnectAt = now;
1488 void connection.reconnect().catch((error) => {1491 void connection.reconnect().catch((error) => {
1489 console.warn("Environment reconnect after browser resume failed", {1492 console.warn("Environment reconnect after browser resume failed", {
1490 environmentId: connection.environmentId,1493 environmentId: connection.environmentId,
apps/web/src/rpc/wsRpcClient.ts+2−0
@@ -56,6 +56,7 @@ interface GitRunStackedActionOptions { · 55 unmodified lines
56export interface WsRpcClient {56export interface WsRpcClient {
57 readonly dispose: () => Promise<void>;57 readonly dispose: () => Promise<void>;
58 readonly reconnect: () => Promise<void>;58 readonly reconnect: () => Promise<void>;
59 readonly isHeartbeatFresh: () => boolean;
59 readonly terminal: {60 readonly terminal: {
60 readonly open: RpcUnaryMethod<typeof WS_METHODS.terminalOpen>;61 readonly open: RpcUnaryMethod<typeof WS_METHODS.terminalOpen>;
61 readonly write: RpcUnaryMethod<typeof WS_METHODS.terminalWrite>;62 readonly write: RpcUnaryMethod<typeof WS_METHODS.terminalWrite>;
@@ -158,6 +159,7 @@ export function createWsRpcClient(transport: WsTransport): WsRpcClient { · 96 unmodified lines
158 resetWsReconnectBackoff();159 resetWsReconnectBackoff();
159 await transport.reconnect();160 await transport.reconnect();
160 },161 },
162 isHeartbeatFresh: () => transport.isHeartbeatFresh(),
161 terminal: {163 terminal: {
162 open: (input) => transport.request((client) => client[WS_METHODS.terminalOpen](input)),164 open: (input) => transport.request((client) => client[WS_METHODS.terminalOpen](input)),
163 write: (input) => transport.request((client) => client[WS_METHODS.terminalWrite](input)),165 write: (input) => transport.request((client) => client[WS_METHODS.terminalWrite](input)),

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t