Skip to content

Commit 5b09d2b

Browse files
committed
feat: set reconnectionGraceTime to 8h, extract settings helper
VS Code's default reconnection grace time of 3 hours is too short for Coder workspaces that go offline overnight. Set 8 hours (28800s) when the user hasn't configured a value. Extract the inline jsonc settings manipulation from remote.ts into a reusable applySettingOverrides() function with dedicated tests.
1 parent 55e8486 commit 5b09d2b

File tree

3 files changed

+347
-79
lines changed

3 files changed

+347
-79
lines changed

src/remote/remote.ts

Lines changed: 11 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
type Workspace,
55
type WorkspaceAgent,
66
} from "coder/site/src/api/typesGenerated";
7-
import * as jsonc from "jsonc-parser";
87
import * as fs from "node:fs/promises";
98
import * as os from "node:os";
109
import * as path from "node:path";
@@ -60,6 +59,7 @@ import {
6059
} from "./sshConfig";
6160
import { SshProcessMonitor } from "./sshProcess";
6261
import { computeSshProperties, sshSupportsSetEnv } from "./sshSupport";
62+
import { applySettingOverrides, buildSshOverrides } from "./userSettings";
6363
import { WorkspaceStateMachine } from "./workspaceStateMachine";
6464

6565
export interface RemoteDetails extends vscode.Disposable {
@@ -459,85 +459,17 @@ export class Remote {
459459
const inbox = await Inbox.create(workspace, workspaceClient, this.logger);
460460
disposables.push(inbox);
461461

462-
// Do some janky setting manipulation.
463462
this.logger.info("Modifying settings...");
464-
const remotePlatforms = vscodeProposed.workspace
465-
.getConfiguration()
466-
.get<Record<string, string>>("remote.SSH.remotePlatform", {});
467-
const connTimeout = vscodeProposed.workspace
468-
.getConfiguration()
469-
.get<number | undefined>("remote.SSH.connectTimeout");
470-
471-
// We have to directly munge the settings file with jsonc because trying to
472-
// update properly through the extension API hangs indefinitely. Possibly
473-
// VS Code is trying to update configuration on the remote, which cannot
474-
// connect until we finish here leading to a deadlock. We need to update it
475-
// locally, anyway, and it does not seem possible to force that via API.
476-
let settingsContent = "{}";
477-
try {
478-
settingsContent = await fs.readFile(
479-
this.pathResolver.getUserSettingsPath(),
480-
"utf8",
481-
);
482-
} catch {
483-
// Ignore! It's probably because the file doesn't exist.
484-
}
485-
486-
// Add the remote platform for this host to bypass a step where VS Code asks
487-
// the user for the platform.
488-
let mungedPlatforms = false;
489-
if (
490-
!remotePlatforms[parts.sshHost] ||
491-
remotePlatforms[parts.sshHost] !== agent.operating_system
492-
) {
493-
remotePlatforms[parts.sshHost] = agent.operating_system;
494-
settingsContent = jsonc.applyEdits(
495-
settingsContent,
496-
jsonc.modify(
497-
settingsContent,
498-
["remote.SSH.remotePlatform"],
499-
remotePlatforms,
500-
{},
501-
),
502-
);
503-
mungedPlatforms = true;
504-
}
505-
506-
// VS Code ignores the connect timeout in the SSH config and uses a default
507-
// of 15 seconds, which can be too short in the case where we wait for
508-
// startup scripts. For now we hardcode a longer value. Because this is
509-
// potentially overwriting user configuration, it feels a bit sketchy. If
510-
// microsoft/vscode-remote-release#8519 is resolved we can remove this.
511-
const minConnTimeout = 1800;
512-
let mungedConnTimeout = false;
513-
if (!connTimeout || connTimeout < minConnTimeout) {
514-
settingsContent = jsonc.applyEdits(
515-
settingsContent,
516-
jsonc.modify(
517-
settingsContent,
518-
["remote.SSH.connectTimeout"],
519-
minConnTimeout,
520-
{},
521-
),
522-
);
523-
mungedConnTimeout = true;
524-
}
525-
526-
if (mungedPlatforms || mungedConnTimeout) {
527-
try {
528-
await fs.writeFile(
529-
this.pathResolver.getUserSettingsPath(),
530-
settingsContent,
531-
);
532-
} catch (ex) {
533-
// This could be because the user's settings.json is read-only. This is
534-
// the case when using home-manager on NixOS, for example. Failure to
535-
// write here is not necessarily catastrophic since the user will be
536-
// asked for the platform and the default timeout might be sufficient.
537-
mungedPlatforms = mungedConnTimeout = false;
538-
this.logger.warn("Failed to configure settings", ex);
539-
}
540-
}
463+
const overrides = buildSshOverrides(
464+
vscodeProposed.workspace.getConfiguration(),
465+
parts.sshHost,
466+
agent.operating_system,
467+
);
468+
await applySettingOverrides(
469+
this.pathResolver.getUserSettingsPath(),
470+
overrides,
471+
this.logger,
472+
);
541473

542474
const logDir = this.getLogDir(featureSet);
543475

src/remote/userSettings.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import * as jsonc from "jsonc-parser";
2+
import * as fs from "node:fs/promises";
3+
4+
import type { WorkspaceConfiguration } from "vscode";
5+
6+
import type { Logger } from "../logging/logger";
7+
8+
export interface SettingOverride {
9+
key: string;
10+
value: unknown;
11+
}
12+
13+
/**
14+
* Build the list of VS Code setting overrides needed for a remote SSH
15+
* connection to a Coder workspace.
16+
*/
17+
export function buildSshOverrides(
18+
config: Pick<WorkspaceConfiguration, "get">,
19+
sshHost: string,
20+
agentOS: string,
21+
): SettingOverride[] {
22+
const overrides: SettingOverride[] = [];
23+
24+
// Bypass the platform prompt by setting the remote platform for this host.
25+
const remotePlatforms = config.get<Record<string, string>>(
26+
"remote.SSH.remotePlatform",
27+
{},
28+
);
29+
if (remotePlatforms[sshHost] !== agentOS) {
30+
overrides.push({
31+
key: "remote.SSH.remotePlatform",
32+
value: { ...remotePlatforms, [sshHost]: agentOS },
33+
});
34+
}
35+
36+
// VS Code's default connect timeout of 15s is too short when waiting for
37+
// startup scripts. Enforce a minimum.
38+
const minConnTimeout = 1800;
39+
const connTimeout = config.get<number>("remote.SSH.connectTimeout");
40+
if (!connTimeout || connTimeout < minConnTimeout) {
41+
overrides.push({
42+
key: "remote.SSH.connectTimeout",
43+
value: minConnTimeout,
44+
});
45+
}
46+
47+
// VS Code's default reconnection grace time (ProtocolConstants.ReconnectionGraceTime)
48+
// is 3 hours (10800s). Coder workspaces commonly go offline overnight, so we
49+
// bump to 8 hours. See https://github.com/microsoft/vscode/blob/main/src/vs/base/parts/ipc/common/ipc.net.ts
50+
if (config.get<number>("remote.SSH.reconnectionGraceTime") === undefined) {
51+
overrides.push({
52+
key: "remote.SSH.reconnectionGraceTime",
53+
value: 28800, // 8 hours in seconds
54+
});
55+
}
56+
57+
return overrides;
58+
}
59+
60+
/**
61+
* Apply setting overrides to the user's settings.json file.
62+
*
63+
* We munge the file directly with jsonc instead of using the VS Code API
64+
* because the API hangs indefinitely during remote connection setup (likely
65+
* a deadlock from trying to update config on the not-yet-connected remote).
66+
*/
67+
export async function applySettingOverrides(
68+
settingsFilePath: string,
69+
overrides: SettingOverride[],
70+
logger: Logger,
71+
): Promise<boolean> {
72+
if (overrides.length === 0) {
73+
return false;
74+
}
75+
76+
let settingsContent = "{}";
77+
try {
78+
settingsContent = await fs.readFile(settingsFilePath, "utf8");
79+
} catch {
80+
// File probably doesn't exist yet.
81+
}
82+
83+
for (const { key, value } of overrides) {
84+
settingsContent = jsonc.applyEdits(
85+
settingsContent,
86+
jsonc.modify(settingsContent, [key], value, {}),
87+
);
88+
}
89+
90+
try {
91+
await fs.writeFile(settingsFilePath, settingsContent);
92+
return true;
93+
} catch (ex) {
94+
// Could be read-only (e.g. home-manager on NixOS). Not catastrophic.
95+
logger.warn("Failed to configure settings", ex);
96+
return false;
97+
}
98+
}

0 commit comments

Comments
 (0)