|
| 1 | +Preserve the remote session when the browser goes away. |
| 2 | + |
| 3 | +--reconnection-grace-time lets an operator say how long a disconnected session |
| 4 | +should be kept. Two behaviours inherited from Code make that setting unable to |
| 5 | +deliver on its promise: |
| 6 | + |
| 7 | +1. Closing the tab runs the browser workbench's shutdown, which disposes the |
| 8 | + remote connection *gracefully*. The server reads a graceful dispose as "the |
| 9 | + client is finished" and cleans up immediately, so the grace time is never |
| 10 | + consulted at all -- the session dies with the tab no matter how the flag is |
| 11 | + set. |
| 12 | + |
| 13 | +2. Any new connection shortens every disconnected session's grace time to |
| 14 | + ProtocolConstants.ReconnectionShortGraceTime (5 minutes). Opening a second |
| 15 | + tab is enough to cut a deliberately long grace time down to five minutes. |
| 16 | + |
| 17 | +This patch makes the configured grace time authoritative: |
| 18 | + |
| 19 | +- pagehide persists UI state instead of unloading, and a browser-driven unload |
| 20 | + (tab close, navigation) no longer tears the workbench down. An explicit, |
| 21 | + in-product shutdown still unloads normally, and beforeunload vetoes are still |
| 22 | + honoured. |
| 23 | + |
| 24 | +- the grace time is only shortened when it was left at or below the default, so |
| 25 | + installations that never touched the flag keep Code's stock behaviour. |
| 26 | + |
| 27 | +Index: code-server/lib/vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts |
| 28 | +=================================================================== |
| 29 | +--- code-server.orig/lib/vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts |
| 30 | ++++ code-server/lib/vscode/src/vs/server/node/remoteExtensionHostAgentServer.ts |
| 31 | +@@ -24,7 +24,7 @@ import { generateUuid } from '../../base |
| 32 | + import { getOSReleaseInfo } from '../../base/node/osReleaseInfo.js'; |
| 33 | + import { findFreePort } from '../../base/node/ports.js'; |
| 34 | + import { addUNCHostToAllowlist, disableUNCAccessRestrictions } from '../../base/node/unc.js'; |
| 35 | +-import { PersistentProtocol } from '../../base/parts/ipc/common/ipc.net.js'; |
| 36 | ++import { PersistentProtocol, ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js'; |
| 37 | + import { NodeSocket, upgradeToISocket, WebSocketNodeSocket } from '../../base/parts/ipc/node/ipc.net.js'; |
| 38 | + import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; |
| 39 | + import { IInstantiationService } from '../../platform/instantiation/common/instantiation.js'; |
| 40 | +@@ -365,13 +365,20 @@ class RemoteExtensionHostAgentServer ext |
| 41 | + // We have received a new connection. |
| 42 | + // This indicates that the server owner has connectivity. |
| 43 | + // Therefore we will shorten the reconnection grace period for disconnected connections! |
| 44 | +- for (const key in this._managementConnections) { |
| 45 | +- const managementConnection = this._managementConnections[key]; |
| 46 | +- managementConnection.shortenReconnectionGraceTimeIfNecessary(); |
| 47 | +- } |
| 48 | +- for (const key in this._extHostConnections) { |
| 49 | +- const extHostConnection = this._extHostConnections[key]; |
| 50 | +- extHostConnection.shortenReconnectionGraceTimeIfNecessary(); |
| 51 | ++ // |
| 52 | ++ // Unless the grace time was deliberately raised above the default: |
| 53 | ++ // an operator who asks for a long grace time wants disconnected |
| 54 | ++ // sessions to survive, and cutting them back to the short grace |
| 55 | ++ // every time a tab is opened would make the setting meaningless. |
| 56 | ++ if (this._reconnectionGraceTime <= ProtocolConstants.ReconnectionGraceTime) { |
| 57 | ++ for (const key in this._managementConnections) { |
| 58 | ++ const managementConnection = this._managementConnections[key]; |
| 59 | ++ managementConnection.shortenReconnectionGraceTimeIfNecessary(); |
| 60 | ++ } |
| 61 | ++ for (const key in this._extHostConnections) { |
| 62 | ++ const extHostConnection = this._extHostConnections[key]; |
| 63 | ++ extHostConnection.shortenReconnectionGraceTimeIfNecessary(); |
| 64 | ++ } |
| 65 | + } |
| 66 | + |
| 67 | + state = State.Done; |
| 68 | +Index: code-server/lib/vscode/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts |
| 69 | +=================================================================== |
| 70 | +--- code-server.orig/lib/vscode/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts |
| 71 | ++++ code-server/lib/vscode/src/vs/workbench/services/lifecycle/browser/lifecycleService.ts |
| 72 | +@@ -37,12 +37,18 @@ export class BrowserLifecycleService ext |
| 73 | + // Listen to `beforeUnload` to support to veto |
| 74 | + this.beforeUnloadListener = addDisposableListener(mainWindow, EventType.BEFORE_UNLOAD, (e: BeforeUnloadEvent) => this.onBeforeUnload(e)); |
| 75 | + |
| 76 | +- // Listen to `pagehide` to support orderly shutdown |
| 77 | ++ // Listen to `pagehide` to persist state, but do not shut down. |
| 78 | ++ // The workbench is remote: the session lives on the server and must |
| 79 | ++ // outlive the browser. Unloading here would dispose the connection |
| 80 | ++ // gracefully, which the server reads as "the client is done" and cleans |
| 81 | ++ // up immediately -- bypassing --reconnection-grace-time entirely. |
| 82 | + // We explicitly do not listen to `unload` event |
| 83 | + // which would disable certain browser caching. |
| 84 | +- // We currently do not handle the `persisted` property |
| 85 | +- // (https://github.com/microsoft/vscode/issues/136216) |
| 86 | +- this.unloadListener = addDisposableListener(mainWindow, EventType.PAGE_HIDE, () => this.onUnload()); |
| 87 | ++ this.unloadListener = addDisposableListener(mainWindow, EventType.PAGE_HIDE, () => { |
| 88 | ++ this.logService.info('[lifecycle] pagehide: persisting state, preserving the remote session'); |
| 89 | ++ |
| 90 | ++ this.storageService.flush(WillSaveStateReason.SHUTDOWN); |
| 91 | ++ }); |
| 92 | + } |
| 93 | + |
| 94 | + private onBeforeUnload(event: BeforeUnloadEvent): void { |
| 95 | +@@ -146,12 +152,14 @@ export class BrowserLifecycleService ext |
| 96 | + } |
| 97 | + }); |
| 98 | + |
| 99 | +- // Veto: handle if provided |
| 100 | +- if (veto && typeof vetoShutdown === 'function') { |
| 101 | +- return vetoShutdown(); |
| 102 | ++ // A veto handler is only provided when the browser is driving the unload |
| 103 | ++ // (closing the tab, navigating away). Honour a veto, but never unload: |
| 104 | ++ // the session is on the server and has to survive the browser leaving. |
| 105 | ++ if (typeof vetoShutdown === 'function') { |
| 106 | ++ return veto ? vetoShutdown() : undefined; |
| 107 | + } |
| 108 | + |
| 109 | +- // No veto, continue to shutdown |
| 110 | ++ // No veto handling means an explicit, in-product shutdown: unload |
| 111 | + return this.onUnload(); |
| 112 | + } |
| 113 | + |
0 commit comments