Skip to content

Commit 5c19962

Browse files
authored
Set session socket into environment variable (#6282)
* Avoid spawning code-server with --reuse-window and --new-window These flags mean the user explicitly wants to open in an existing instance so if the socket is down it should error rather than try to spawn code-server normally. * Set session socket into environment variable While I was at it I added a CLI flag to override the default. I also swapped the default to --user-data-dir. The value is set on an environment variable so it can be used by the extension host similar to VSCODE_IPC_HOOK_CLI. * Add e2e test for opening files externally
1 parent 56d10d8 commit 5c19962

File tree

11 files changed

+168
-98
lines changed

11 files changed

+168
-98
lines changed

CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ Code v99.99.999
2424

2525
Code v1.79.2
2626

27+
### Fixed
28+
29+
- Fix being unable to launch multiple instances of code-server for different
30+
users.
31+
32+
### Added
33+
34+
- `--session-socket` CLI flag to configure the location of the session socket.
35+
By default it will be placed in `--user-data-dir`.
36+
2737
## [4.14.0](https://github.com/coder/code-server/releases/tag/v4.14.0) - 2023-06-16
2838

2939
Code v1.79.2

patches/store-socket.diff

+9-13
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,31 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
1515
===================================================================
1616
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
1717
+++ code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.ts
18-
@@ -2,7 +2,9 @@
18+
@@ -2,7 +2,7 @@
1919
* Copyright (c) Microsoft Corporation. All rights reserved.
2020
* Licensed under the MIT License. See License.txt in the project root for license information.
2121
*--------------------------------------------------------------------------------------------*/
2222
-
23-
+import * as os from 'os';
2423
+import * as _http from 'http';
25-
+import * as path from 'vs/base/common/path';
2624
import * as performance from 'vs/base/common/performance';
2725
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
2826
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
29-
@@ -17,6 +19,7 @@ import { ExtensionRuntime } from 'vs/wor
27+
@@ -17,6 +17,7 @@ import { ExtensionRuntime } from 'vs/wor
3028
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
3129
import { realpathSync } from 'vs/base/node/extpath';
3230
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
3331
+import { IExtHostWorkspace } from '../common/extHostWorkspace';
3432
import { ExtHostDiskFileSystemProvider } from 'vs/workbench/api/node/extHostDiskFileSystemProvider';
3533

3634
class NodeModuleRequireInterceptor extends RequireInterceptor {
37-
@@ -83,6 +86,52 @@ export class ExtHostExtensionService ext
35+
@@ -83,6 +84,52 @@ export class ExtHostExtensionService ext
3836
await interceptor.install();
3937
performance.mark('code/extHost/didInitAPI');
4038

4139
+ (async () => {
4240
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
43-
+ if (!socketPath) {
41+
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
42+
+ if (!socketPath || !codeServerSocketPath) {
4443
+ return;
4544
+ }
4645
+ const workspace = this._instaService.invokeFunction((accessor) => {
@@ -52,7 +51,6 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
5251
+ socketPath
5352
+ };
5453
+ const message = JSON.stringify({entry});
55-
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
5654
+ await new Promise<void>((resolve, reject) => {
5755
+ const opts: _http.RequestOptions = {
5856
+ path: '/add-session',
@@ -91,17 +89,15 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
9189
===================================================================
9290
--- code-server.orig/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
9391
+++ code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
94-
@@ -3,6 +3,9 @@
92+
@@ -3,6 +3,7 @@
9593
* Licensed under the MIT License. See License.txt in the project root for license information.
9694
*--------------------------------------------------------------------------------------------*/
9795

98-
+import * as os from 'os';
9996
+import * as _http from 'http';
100-
+import * as path from 'vs/base/common/path';
10197
import * as nativeWatchdog from 'native-watchdog';
10298
import * as net from 'net';
10399
import * as minimist from 'minimist';
104-
@@ -400,7 +403,28 @@ async function startExtensionHostProcess
100+
@@ -400,7 +401,28 @@ async function startExtensionHostProcess
105101
);
106102

107103
// rewrite onTerminate-function to be a proper shutdown
@@ -110,11 +106,11 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extensionHostProcess.ts
110106
+ extensionHostMain.terminate(reason);
111107
+
112108
+ const socketPath = process.env['VSCODE_IPC_HOOK_CLI'];
113-
+ if (!socketPath) {
109+
+ const codeServerSocketPath = process.env['CODE_SERVER_SESSION_SOCKET']
110+
+ if (!socketPath || !codeServerSocketPath) {
114111
+ return;
115112
+ }
116113
+ const message = JSON.stringify({socketPath});
117-
+ const codeServerSocketPath = path.join(os.tmpdir(), 'code-server-ipc.sock');
118114
+ const opts: _http.RequestOptions = {
119115
+ path: '/delete-session',
120116
+ socketPath: codeServerSocketPath,

src/node/app.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as util from "../common/util"
99
import { DefaultedArgs } from "./cli"
1010
import { disposer } from "./http"
1111
import { isNodeJSErrnoException } from "./util"
12-
import { DEFAULT_SOCKET_PATH, EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
12+
import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSocket"
1313
import { handleUpgrade } from "./wsRouter"
1414

1515
type SocketOptions = { socket: string; "socket-mode"?: string }
@@ -88,7 +88,7 @@ export const createApp = async (args: DefaultedArgs): Promise<App> => {
8888
handleUpgrade(wsRouter, server)
8989

9090
const editorSessionManager = new EditorSessionManager()
91-
const editorSessionManagerServer = await makeEditorSessionManagerServer(DEFAULT_SOCKET_PATH, editorSessionManager)
91+
const editorSessionManagerServer = await makeEditorSessionManagerServer(args["session-socket"], editorSessionManager)
9292
const disposeEditorSessionManagerServer = disposer(editorSessionManagerServer)
9393

9494
const dispose = async () => {

src/node/cli.ts

+28-10
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { load } from "js-yaml"
44
import * as os from "os"
55
import * as path from "path"
66
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
7-
import { DEFAULT_SOCKET_PATH, EditorSessionManagerClient } from "./vscodeSocket"
7+
import { EditorSessionManagerClient } from "./vscodeSocket"
88

99
export enum Feature {
1010
// No current experimental features!
@@ -51,6 +51,7 @@ export interface UserProvidedCodeArgs {
5151
"disable-file-downloads"?: boolean
5252
"disable-workspace-trust"?: boolean
5353
"disable-getting-started-override"?: boolean
54+
"session-socket"?: string
5455
}
5556

5657
/**
@@ -160,6 +161,9 @@ export const options: Options<Required<UserProvidedArgs>> = {
160161
"Disable update check. Without this flag, code-server checks every 6 hours against the latest github release and \n" +
161162
"then notifies you once every week that a new release is available.",
162163
},
164+
"session-socket": {
165+
type: "string",
166+
},
163167
"disable-file-downloads": {
164168
type: "boolean",
165169
description:
@@ -459,6 +463,7 @@ export interface DefaultedArgs extends ConfigArgs {
459463
usingEnvHashedPassword: boolean
460464
"extensions-dir": string
461465
"user-data-dir": string
466+
"session-socket": string
462467
/* Positional arguments. */
463468
_: string[]
464469
}
@@ -479,6 +484,11 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
479484
args["extensions-dir"] = path.join(args["user-data-dir"], "extensions")
480485
}
481486

487+
if (!args["session-socket"]) {
488+
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
489+
}
490+
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
491+
482492
// --verbose takes priority over --log and --log takes priority over the
483493
// environment variable.
484494
if (args.verbose) {
@@ -739,37 +749,45 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
739749
* existing instance. The arguments here should be the arguments the user
740750
* explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
741751
*/
742-
export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Promise<string | undefined> => {
752+
export const shouldOpenInExistingInstance = async (
753+
args: UserProvidedArgs,
754+
sessionSocket: string,
755+
): Promise<string | undefined> => {
743756
// Always use the existing instance if we're running from VS Code's terminal.
744757
if (process.env.VSCODE_IPC_HOOK_CLI) {
745758
logger.debug("Found VSCODE_IPC_HOOK_CLI")
746759
return process.env.VSCODE_IPC_HOOK_CLI
747760
}
748761

749762
const paths = getResolvedPathsFromArgs(args)
750-
const client = new EditorSessionManagerClient(DEFAULT_SOCKET_PATH)
751-
752-
// If we can't connect to the socket then there's no existing instance.
753-
if (!(await client.canConnect())) {
754-
return undefined
755-
}
763+
const client = new EditorSessionManagerClient(sessionSocket)
756764

757765
// If these flags are set then assume the user is trying to open in an
758-
// existing instance since these flags have no effect otherwise.
766+
// existing instance since these flags have no effect otherwise. That means
767+
// if there is no existing instance we should error rather than falling back
768+
// to spawning code-server normally.
759769
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
760770
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
761771
}, 0)
762772
if (openInFlagCount > 0) {
763773
logger.debug("Found --reuse-window or --new-window")
764-
return await client.getConnectedSocketPath(paths[0])
774+
const socketPath = await client.getConnectedSocketPath(paths[0])
775+
if (!socketPath) {
776+
throw new Error(`No opened code-server instances found to handle ${paths[0]}`)
777+
}
778+
return socketPath
765779
}
766780

767781
// It's possible the user is trying to spawn another instance of code-server.
768782
// 1. Check if any unrelated flags are set (this should only run when
769783
// code-server is invoked exactly like this: `code-server my-file`).
770784
// 2. That a file or directory was passed.
771785
// 3. That the socket is active.
786+
// 4. That an instance exists to handle the path (implied by #3).
772787
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
788+
if (!(await client.canConnect())) {
789+
return undefined
790+
}
773791
const socketPath = await client.getConnectedSocketPath(paths[0])
774792
if (socketPath) {
775793
logger.debug("Found existing code-server socket")

src/node/entry.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ async function entry(): Promise<void> {
5151
return runCodeCli(args)
5252
}
5353

54-
const socketPath = await shouldOpenInExistingInstance(cliArgs)
54+
const socketPath = await shouldOpenInExistingInstance(cliArgs, args["session-socket"])
5555
if (socketPath) {
5656
logger.debug("Trying to open in existing instance")
5757
return openInExistingInstance(args, socketPath)

src/node/vscodeSocket.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ import * as http from "http"
44
import * as path from "path"
55
import { HttpCode } from "../common/http"
66
import { listen } from "./app"
7-
import { canConnect, paths } from "./util"
8-
9-
// Socket path of the daemonized code-server instance.
10-
export const DEFAULT_SOCKET_PATH = path.join(paths.data, `code-server-ipc.sock`)
7+
import { canConnect } from "./util"
118

129
export interface EditorSessionEntry {
1310
workspace: {

test/e2e/models/CodeServer.ts

+78-32
Original file line numberDiff line numberDiff line change
@@ -117,40 +117,26 @@ export class CodeServer {
117117
* directories.
118118
*/
119119
private async spawn(): Promise<CodeServerProcess> {
120-
// This will be used both as the workspace and data directory to ensure
121-
// instances don't bleed into each other.
122120
const dir = await this.createWorkspace()
123-
121+
const args = await this.argsWithDefaults([
122+
"--auth",
123+
"none",
124+
// The workspace to open.
125+
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
126+
...this.args,
127+
// Using port zero will spawn on a random port.
128+
"--bind-addr",
129+
"127.0.0.1:0",
130+
])
124131
return new Promise((resolve, reject) => {
125-
const args = [
126-
this.entry,
127-
"--extensions-dir",
128-
path.join(dir, "extensions"),
129-
"--auth",
130-
"none",
131-
// The workspace to open.
132-
...(this.args.includes("--ignore-last-opened") ? [] : [dir]),
133-
...this.args,
134-
// Using port zero will spawn on a random port.
135-
"--bind-addr",
136-
"127.0.0.1:0",
137-
// Setting the XDG variables would be easier and more thorough but the
138-
// modules we import ignores those variables for non-Linux operating
139-
// systems so use these flags instead.
140-
"--config",
141-
path.join(dir, "config.yaml"),
142-
"--user-data-dir",
143-
dir,
144-
]
145132
this.logger.debug("spawning `node " + args.join(" ") + "`")
146133
const proc = cp.spawn("node", args, {
147134
cwd: path.join(__dirname, "../../.."),
148135
env: {
149136
...process.env,
150137
...this.env,
151-
// Set to empty string to prevent code-server from
152-
// using the existing instance when running the e2e tests
153-
// from an integrated terminal.
138+
// Prevent code-server from using the existing instance when running
139+
// the e2e tests from an integrated terminal.
154140
VSCODE_IPC_HOOK_CLI: "",
155141
PASSWORD,
156142
},
@@ -173,11 +159,15 @@ export class CodeServer {
173159
reject(error)
174160
})
175161

162+
// Tracks when the HTTP and session servers are ready.
163+
let httpAddress: string | undefined
164+
let sessionAddress: string | undefined
165+
176166
let resolved = false
177167
proc.stdout.setEncoding("utf8")
178168
onLine(proc, (line) => {
179169
// As long as we are actively getting input reset the timer. If we stop
180-
// getting input and still have not found the address the timer will
170+
// getting input and still have not found the addresses the timer will
181171
// reject.
182172
timer.reset()
183173

@@ -186,20 +176,69 @@ export class CodeServer {
186176
if (resolved) {
187177
return
188178
}
189-
const match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
179+
180+
let match = line.trim().match(/HTTPS? server listening on (https?:\/\/[.:\d]+)\/?$/)
190181
if (match) {
191-
// Cookies don't seem to work on IP address so swap to localhost.
182+
// Cookies don't seem to work on IP addresses so swap to localhost.
192183
// TODO: Investigate whether this is a bug with code-server.
193-
const address = match[1].replace("127.0.0.1", "localhost")
194-
this.logger.debug(`spawned on ${address}`)
184+
httpAddress = match[1].replace("127.0.0.1", "localhost")
185+
}
186+
187+
match = line.trim().match(/Session server listening on (.+)$/)
188+
if (match) {
189+
sessionAddress = match[1]
190+
}
191+
192+
if (typeof httpAddress !== "undefined" && typeof sessionAddress !== "undefined") {
195193
resolved = true
196194
timer.dispose()
197-
resolve({ process: proc, address })
195+
this.logger.debug(`code-server is ready: ${httpAddress} ${sessionAddress}`)
196+
resolve({ process: proc, address: httpAddress })
198197
}
199198
})
200199
})
201200
}
202201

202+
/**
203+
* Execute a short-lived command.
204+
*/
205+
async run(args: string[]): Promise<void> {
206+
args = await this.argsWithDefaults(args)
207+
this.logger.debug("executing `node " + args.join(" ") + "`")
208+
await util.promisify(cp.exec)("node " + args.join(" "), {
209+
cwd: path.join(__dirname, "../../.."),
210+
env: {
211+
...process.env,
212+
...this.env,
213+
// Prevent code-server from using the existing instance when running
214+
// the e2e tests from an integrated terminal.
215+
VSCODE_IPC_HOOK_CLI: "",
216+
},
217+
})
218+
}
219+
220+
/**
221+
* Combine arguments with defaults.
222+
*/
223+
private async argsWithDefaults(args: string[]): Promise<string[]> {
224+
// This will be used both as the workspace and data directory to ensure
225+
// instances don't bleed into each other.
226+
const dir = await this.workspaceDir
227+
return [
228+
this.entry,
229+
"--extensions-dir",
230+
path.join(dir, "extensions"),
231+
...args,
232+
// Setting the XDG variables would be easier and more thorough but the
233+
// modules we import ignores those variables for non-Linux operating
234+
// systems so use these flags instead.
235+
"--config",
236+
path.join(dir, "config.yaml"),
237+
"--user-data-dir",
238+
dir,
239+
]
240+
}
241+
203242
/**
204243
* Close the code-server process.
205244
*/
@@ -364,6 +403,13 @@ export class CodeServerPage {
364403
await this.waitForTab(file)
365404
}
366405

406+
/**
407+
* Open a file through an external command.
408+
*/
409+
async openFileExternally(file: string) {
410+
await this.codeServer.run(["--reuse-window", file])
411+
}
412+
367413
/**
368414
* Wait for a tab to open for the specified file.
369415
*/

0 commit comments

Comments
 (0)