Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4f290fa

Browse files
committed
Resolve vscode ipc sockets based on folder match.
1 parent bda6b63 commit 4f290fa

File tree

6 files changed

+439
-105
lines changed

6 files changed

+439
-105
lines changed

‎patches/store-socket.diff‎

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,37 @@ Index: code-server/lib/vscode/src/vs/workbench/api/node/extHostExtensionService.
2424
import * as performance from 'vs/base/common/performance';
2525
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
2626
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
27-
@@ -72,6 +74,10 @@ export class ExtHostExtensionService ext
28-
if (this._initData.remote.isRemote && this._initData.remote.authority) {
29-
const cliServer = this._instaService.createInstance(CLIServer);
30-
process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath;
31-
+
32-
+ fs.writeFile(path.join(os.tmpdir(), 'vscode-ipc'), cliServer.ipcHandlePath).catch((error) => {
33-
+ this._logService.error(error);
27+
@@ -17,6 +19,7 @@ import { ExtensionRuntime } from 'vs/wor
28+
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
29+
import { realpathSync } from 'vs/base/node/extpath';
30+
import { ExtHostConsoleForwarder } from 'vs/workbench/api/node/extHostConsoleForwarder';
31+
+import { IExtHostWorkspace } from '../common/extHostWorkspace';
32+
33+
class NodeModuleRequireInterceptor extends RequireInterceptor {
34+
35+
@@ -83,6 +86,25 @@ export class ExtHostExtensionService ext
36+
const configProvider = await this._extHostConfiguration.getConfigProvider();
37+
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
38+
performance.mark('code/extHost/didInitProxyResolver');
39+
+
40+
+ // TODO: confirm that this is the best place to put this.
41+
+ (async () => {
42+
+ let handlePath = process.env['VSCODE_IPC_HOOK_CLI'];
43+
+ if (!handlePath) {
44+
+ return
45+
+ }
46+
+ const workspace = this._instaService.invokeFunction((accessor) => {
47+
+ const workspaceService = accessor.get(IExtHostWorkspace);
48+
+ return workspaceService.workspace;
3449
+ });
35-
}
50+
+ const entry = {
51+
+ workspace,
52+
+ ipcHandlePath: handlePath
53+
+ };
54+
+ fs.appendFile(path.join(os.tmpdir(), 'vscode-ipc'), '\n' + JSON.stringify(entry), 'utf-8');
55+
+ })().catch(error => {
56+
+ this._logService.error(error);
57+
+ });
58+
}
3659

37-
// Module loading tricks
60+
protected _getEntryPoint(extensionDescription: IExtensionDescription): string | undefined {

‎src/node/cli.ts‎

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,9 @@ import { promises as fs } from "fs"
33
import { load } from "js-yaml"
44
import * as os from "os"
55
import * as path from "path"
6-
import {
7-
canConnect,
8-
generateCertificate,
9-
generatePassword,
10-
humanPath,
11-
paths,
12-
isNodeJSErrnoException,
13-
splitOnFirstEquals,
14-
} from "./util"
15-
16-
const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
6+
import { generateCertificate, generatePassword, humanPath, paths, splitOnFirstEquals } from "./util"
7+
import { DEFAULT_SOCKET_PATH, VscodeSocketResolver } from "./vscodeSocket"
8+
import { getResolvedPathsFromArgs } from "./main"
179

1810
export enum Feature {
1911
// No current experimental features!
@@ -725,27 +717,6 @@ function bindAddrFromAllSources(...argsConfig: UserProvidedArgs[]): Addr {
725717
return addr
726718
}
727719

728-
/**
729-
* Reads the socketPath based on path passed in.
730-
*
731-
* The one usually passed in is the DEFAULT_SOCKET_PATH.
732-
*
733-
* If it can't read the path, it throws an error and returns undefined.
734-
*/
735-
export async function readSocketPath(path: string): Promise<string | undefined> {
736-
try {
737-
return await fs.readFile(path, "utf8")
738-
} catch (error) {
739-
// If it doesn't exist, we don't care.
740-
// But if it fails for some reason, we should throw.
741-
// We want to surface that to the user.
742-
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
743-
throw error
744-
}
745-
}
746-
return undefined
747-
}
748-
749720
/**
750721
* Determine if it looks like the user is trying to open a file or folder in an
751722
* existing instance. The arguments here should be the arguments the user
@@ -758,14 +729,18 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
758729
return process.env.VSCODE_IPC_HOOK_CLI
759730
}
760731

732+
const paths = getResolvedPathsFromArgs(args)
733+
const resolver = new VscodeSocketResolver(DEFAULT_SOCKET_PATH)
734+
761735
// If these flags are set then assume the user is trying to open in an
762736
// existing instance since these flags have no effect otherwise.
763737
const openInFlagCount = ["reuse-window", "new-window"].reduce((prev, cur) => {
764738
return args[cur as keyof UserProvidedArgs] ? prev + 1 : prev
765739
}, 0)
766740
if (openInFlagCount > 0) {
767741
logger.debug("Found --reuse-window or --new-window")
768-
return readSocketPath(DEFAULT_SOCKET_PATH)
742+
await resolver.load()
743+
return await resolver.getConnectedSocketPath(paths)
769744
}
770745

771746
// It's possible the user is trying to spawn another instance of code-server.
@@ -774,8 +749,9 @@ export const shouldOpenInExistingInstance = async (args: UserProvidedArgs): Prom
774749
// 2. That a file or directory was passed.
775750
// 3. That the socket is active.
776751
if (Object.keys(args).length === 1 && typeof args._ !== "undefined" && args._.length > 0) {
777-
const socketPath = await readSocketPath(DEFAULT_SOCKET_PATH)
778-
if (socketPath && (await canConnect(socketPath))) {
752+
await resolver.load()
753+
const socketPath = await resolver.getConnectedSocketPath(paths)
754+
if (socketPath) {
779755
logger.debug("Found existing code-server socket")
780756
return socketPath
781757
}

‎src/node/main.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export const runCodeCli = async (args: DefaultedArgs): Promise<void> => {
6161
process.exit(0)
6262
}
6363

64+
export const getResolvedPathsFromArgs = (args: UserProvidedArgs): string[] => {
65+
const paths = args._ || []
66+
return paths.map((p) => path.resolve(p))
67+
}
68+
6469
export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
6570
const pipeArgs: OpenCommandPipeArgs & { fileURIs: string[] } = {
6671
type: "open",
@@ -70,9 +75,9 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
7075
forceNewWindow: args["new-window"],
7176
gotoLineMode: true,
7277
}
73-
const paths = args._||[]
78+
const paths = getResolvedPathsFromArgs(args)
7479
for (let i = 0; i < paths.length; i++) {
75-
const fp = path.resolve(paths[i])
80+
const fp = paths[i]
7681
if (await isDirectory(fp)) {
7782
pipeArgs.folderURIs.push(fp)
7883
} else {

‎src/node/vscodeSocket.ts‎

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { logger } from "@coder/logger"
2+
import * as os from "os"
3+
import * as path from "path"
4+
import { promises as fs } from "fs"
5+
import { isNodeJSErrnoException } from "./util"
6+
import { canConnect } from "./util"
7+
8+
export const DEFAULT_SOCKET_PATH = path.join(os.tmpdir(), "vscode-ipc")
9+
10+
export interface VscodeSocket {
11+
workspace: {
12+
id: string
13+
folders: {
14+
uri: {
15+
path: string
16+
}
17+
}[]
18+
}
19+
20+
socketPath: string
21+
}
22+
23+
export class VscodeSocketResolver {
24+
// Map from folder path to socket path.
25+
private pathMap = new Map<string, string[]>()
26+
27+
// Map from socket path to VscodeSocket.
28+
private entries = new Map<string, { index: number; entry: VscodeSocket }>()
29+
30+
constructor(private readonly socketRegistryFilePath: string) {}
31+
32+
async load(): Promise<void> {
33+
try {
34+
const data = await fs.readFile(this.socketRegistryFilePath, "utf8")
35+
const lines = data.split("\n")
36+
let entries = []
37+
for (const line of lines) {
38+
if (!line) {
39+
continue
40+
}
41+
entries.push(JSON.parse(line) as VscodeSocket)
42+
}
43+
this.loadFromEntries(entries)
44+
} catch (error) {
45+
// If it doesn't exist, we don't care.
46+
// But if it fails for some reason, we should throw.
47+
// We want to surface that to the user.
48+
if (!isNodeJSErrnoException(error) || error.code !== "ENOENT") {
49+
throw error
50+
}
51+
}
52+
}
53+
54+
private loadFromEntries(entries: VscodeSocket[]): void {
55+
for (let i = 0; i < entries.length; i++) {
56+
const entry = entries[i]
57+
this.entries.set(entry.socketPath, { index: i, entry })
58+
for (const folder of entry.workspace.folders) {
59+
if (this.pathMap.has(folder.uri.path)) {
60+
this.pathMap.get(folder.uri.path)!.push(entry.socketPath)
61+
} else {
62+
this.pathMap.set(folder.uri.path, [entry.socketPath])
63+
}
64+
}
65+
}
66+
}
67+
68+
/**
69+
* Returns matching and then non-matching socket paths, sorted by most recently registered.
70+
*/
71+
getSocketPaths(paths: string[] = []): string[] {
72+
const matches = new Set<string>()
73+
for (const socketPath of paths.flatMap((p) => this.getMatchingSocketPaths(p))) {
74+
matches.add(socketPath)
75+
}
76+
77+
// Sort by most recently registered, preferring matches.
78+
return Array.from(this.entries.values())
79+
.sort((a, b) => {
80+
if (matches.has(a.entry.socketPath) && matches.has(b.entry.socketPath)) {
81+
return b.index - a.index
82+
}
83+
if (matches.has(a.entry.socketPath)) {
84+
return -1
85+
}
86+
if (matches.has(b.entry.socketPath)) {
87+
return 1
88+
}
89+
return b.index - a.index
90+
})
91+
.map(({ entry }) => entry.socketPath)
92+
}
93+
94+
/**
95+
* Returns the best socket path that we can connect to.
96+
* See getSocketPaths for the order of preference.
97+
* We also delete any sockets that we can't connect to.
98+
*/
99+
async getConnectedSocketPath(paths: string[] = []): Promise<string | undefined> {
100+
const socketPaths = this.getSocketPaths(paths)
101+
let ret = undefined
102+
103+
const toDelete = new Set<string>()
104+
// Connect to the most recently registered socket first.
105+
for (let i = 0; i < socketPaths.length; i++) {
106+
const socketPath = socketPaths[i]
107+
if (await canConnect(socketPath)) {
108+
ret = socketPath
109+
break
110+
}
111+
toDelete.add(socketPath)
112+
}
113+
114+
if (toDelete.size === 0) {
115+
return ret
116+
}
117+
118+
// Remove any sockets that we couldn't connect to.
119+
for (const socketPath of toDelete) {
120+
logger.debug(`Deleting stale socket from socket registry: ${socketPath}`)
121+
this.entries.delete(socketPath)
122+
}
123+
const newEntries = Array.from(this.entries.values()).map(({ entry }) => entry)
124+
this.pathMap = new Map()
125+
this.entries = new Map()
126+
this.loadFromEntries(newEntries)
127+
await this.save()
128+
129+
return ret
130+
}
131+
132+
private getMatchingSocketPaths(p: string): string[] {
133+
return Array.from(this.pathMap.entries())
134+
.filter(([folder]) => p.startsWith(folder))
135+
.flatMap(([, socketPaths]) => socketPaths)
136+
}
137+
138+
async save(): Promise<void> {
139+
const lines = Array.from(this.entries.values()).map(({ entry }) => JSON.stringify(entry))
140+
return fs.writeFile(this.socketRegistryFilePath, lines.join("\n"))
141+
}
142+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /