Skip to content

Commit 40840d1

Browse files
authored
Add initial support for --socket-path on Windows (microsoft#151174)
1 parent 42b35cb commit 40840d1

File tree

2 files changed

+108
-29
lines changed

2 files changed

+108
-29
lines changed

src/vs/server/node/extensionHostConnection.ts

Lines changed: 105 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { join, delimiter } from 'vs/base/common/path';
1111
import { VSBuffer } from 'vs/base/common/buffer';
1212
import { IRemoteConsoleLog } from 'vs/base/common/console';
1313
import { Emitter, Event } from 'vs/base/common/event';
14-
import { NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
14+
import { createRandomIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
1515
import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv';
1616
import { ILogService } from 'vs/platform/log/common/log';
1717
import { IRemoteExtensionHostStartParams } from 'vs/platform/remote/common/remoteAgentConnection';
@@ -21,6 +21,7 @@ import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
2121
import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil';
2222
import { removeDangerousEnvVariables } from 'vs/base/common/processes';
2323
import { IExtensionHostStatusService } from 'vs/server/node/extensionHostStatusService';
24+
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
2425

2526
export async function buildUserEnvironment(startParamsEnv: { [key: string]: string | null } = {}, withUserShellEnvironment: boolean, language: string, isDebug: boolean, environmentService: IServerEnvironmentService, logService: ILogService): Promise<IProcessEnvironment> {
2627
const nlsConfig = await getNLSConfiguration(language, environmentService.userDataPath);
@@ -44,7 +45,6 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri
4445
VSCODE_AMD_ENTRYPOINT: 'vs/workbench/api/node/extensionHostProcess',
4546
VSCODE_PIPE_LOGGING: 'true',
4647
VSCODE_VERBOSE_LOGGING: 'true',
47-
VSCODE_EXTHOST_WILL_SEND_SOCKET: 'true',
4848
VSCODE_HANDLES_UNCAUGHT_ERRORS: 'true',
4949
VSCODE_LOG_STACK: 'false',
5050
VSCODE_NLS_CONFIG: JSON.stringify(nlsConfig, undefined, 0)
@@ -73,21 +73,36 @@ export async function buildUserEnvironment(startParamsEnv: { [key: string]: stri
7373

7474
class ConnectionData {
7575
constructor(
76-
public readonly socket: net.Socket,
77-
public readonly socketDrain: Promise<void>,
78-
public readonly initialDataChunk: VSBuffer,
79-
public readonly skipWebSocketFrames: boolean,
80-
public readonly permessageDeflate: boolean,
81-
public readonly inflateBytes: VSBuffer,
76+
public readonly socket: NodeSocket | WebSocketNodeSocket,
77+
public readonly initialDataChunk: VSBuffer
8278
) { }
8379

80+
public socketDrain(): Promise<void> {
81+
return this.socket.drain();
82+
}
83+
8484
public toIExtHostSocketMessage(): IExtHostSocketMessage {
85+
86+
let skipWebSocketFrames: boolean;
87+
let permessageDeflate: boolean;
88+
let inflateBytes: VSBuffer;
89+
90+
if (this.socket instanceof NodeSocket) {
91+
skipWebSocketFrames = true;
92+
permessageDeflate = false;
93+
inflateBytes = VSBuffer.alloc(0);
94+
} else {
95+
skipWebSocketFrames = false;
96+
permessageDeflate = this.socket.permessageDeflate;
97+
inflateBytes = this.socket.recordedInflateBytes;
98+
}
99+
85100
return {
86101
type: 'VSCODE_EXTHOST_IPC_SOCKET',
87102
initialDataChunk: (<Buffer>this.initialDataChunk.buffer).toString('base64'),
88-
skipWebSocketFrames: this.skipWebSocketFrames,
89-
permessageDeflate: this.permessageDeflate,
90-
inflateBytes: (<Buffer>this.inflateBytes.buffer).toString('base64'),
103+
skipWebSocketFrames: skipWebSocketFrames,
104+
permessageDeflate: permessageDeflate,
105+
inflateBytes: (<Buffer>inflateBytes.buffer).toString('base64'),
91106
};
92107
}
93108
}
@@ -97,6 +112,7 @@ export class ExtensionHostConnection {
97112
private _onClose = new Emitter<void>();
98113
readonly onClose: Event<void> = this._onClose.event;
99114

115+
private readonly _canSendSocket: boolean;
100116
private _disposed: boolean;
101117
private _remoteAddress: string;
102118
private _extensionHostProcess: cp.ChildProcess | null;
@@ -111,10 +127,11 @@ export class ExtensionHostConnection {
111127
@ILogService private readonly _logService: ILogService,
112128
@IExtensionHostStatusService private readonly _extensionHostStatusService: IExtensionHostStatusService,
113129
) {
130+
this._canSendSocket = (!isWindows || !this._environmentService.args['socket-path']);
114131
this._disposed = false;
115132
this._remoteAddress = remoteAddress;
116133
this._extensionHostProcess = null;
117-
this._connectionData = ExtensionHostConnection._toConnectionData(socket, initialDataChunk);
134+
this._connectionData = new ConnectionData(socket, initialDataChunk);
118135

119136
this._log(`New connection established.`);
120137
}
@@ -131,19 +148,46 @@ export class ExtensionHostConnection {
131148
this._logService.error(`${this._logPrefix}${_str}`);
132149
}
133150

134-
private static _toConnectionData(socket: NodeSocket | WebSocketNodeSocket, initialDataChunk: VSBuffer): ConnectionData {
135-
if (socket instanceof NodeSocket) {
136-
return new ConnectionData(socket.socket, socket.drain(), initialDataChunk, true, false, VSBuffer.alloc(0));
137-
} else {
138-
return new ConnectionData(socket.socket.socket, socket.drain(), initialDataChunk, false, socket.permessageDeflate, socket.recordedInflateBytes);
151+
private async _pipeSockets(extHostSocket: net.Socket, connectionData: ConnectionData): Promise<void> {
152+
153+
const disposables = new DisposableStore();
154+
disposables.add(connectionData.socket);
155+
disposables.add(toDisposable(() => {
156+
extHostSocket.destroy();
157+
}));
158+
159+
const stopAndCleanup = () => {
160+
disposables.dispose();
161+
};
162+
163+
disposables.add(connectionData.socket.onEnd(stopAndCleanup));
164+
disposables.add(connectionData.socket.onClose(stopAndCleanup));
165+
166+
disposables.add(Event.fromNodeEventEmitter<void>(extHostSocket, 'end')(stopAndCleanup));
167+
disposables.add(Event.fromNodeEventEmitter<void>(extHostSocket, 'close')(stopAndCleanup));
168+
disposables.add(Event.fromNodeEventEmitter<void>(extHostSocket, 'error')(stopAndCleanup));
169+
170+
disposables.add(connectionData.socket.onData((e) => extHostSocket.write(e.buffer)));
171+
disposables.add(Event.fromNodeEventEmitter<Buffer>(extHostSocket, 'data')((e) => {
172+
connectionData.socket.write(VSBuffer.wrap(e));
173+
}));
174+
175+
if (connectionData.initialDataChunk.byteLength > 0) {
176+
extHostSocket.write(connectionData.initialDataChunk.buffer);
139177
}
140178
}
141179

142180
private async _sendSocketToExtensionHost(extensionHostProcess: cp.ChildProcess, connectionData: ConnectionData): Promise<void> {
143181
// Make sure all outstanding writes have been drained before sending the socket
144-
await connectionData.socketDrain;
182+
await connectionData.socketDrain();
145183
const msg = connectionData.toIExtHostSocketMessage();
146-
extensionHostProcess.send(msg, connectionData.socket);
184+
let socket: net.Socket;
185+
if (connectionData.socket instanceof NodeSocket) {
186+
socket = connectionData.socket.socket;
187+
} else {
188+
socket = connectionData.socket.socket.socket;
189+
}
190+
extensionHostProcess.send(msg, socket);
147191
}
148192

149193
public shortenReconnectionGraceTimeIfNecessary(): void {
@@ -159,7 +203,7 @@ export class ExtensionHostConnection {
159203
public acceptReconnection(remoteAddress: string, _socket: NodeSocket | WebSocketNodeSocket, initialDataChunk: VSBuffer): void {
160204
this._remoteAddress = remoteAddress;
161205
this._log(`The client has reconnected.`);
162-
const connectionData = ExtensionHostConnection._toConnectionData(_socket, initialDataChunk);
206+
const connectionData = new ConnectionData(_socket, initialDataChunk);
163207

164208
if (!this._extensionHostProcess) {
165209
// The extension host didn't even start up yet
@@ -197,6 +241,17 @@ export class ExtensionHostConnection {
197241
const env = await buildUserEnvironment(startParams.env, true, startParams.language, !!startParams.debugId, this._environmentService, this._logService);
198242
removeDangerousEnvVariables(env);
199243

244+
let extHostNamedPipeServer: net.Server | null;
245+
246+
if (this._canSendSocket) {
247+
env['VSCODE_EXTHOST_WILL_SEND_SOCKET'] = 'true';
248+
extHostNamedPipeServer = null;
249+
} else {
250+
const { namedPipeServer, pipeName } = await this._listenOnPipe();
251+
env['VSCODE_IPC_HOOK_EXTHOST'] = pipeName;
252+
extHostNamedPipeServer = namedPipeServer;
253+
}
254+
200255
const opts = {
201256
env,
202257
execArgv,
@@ -240,14 +295,21 @@ export class ExtensionHostConnection {
240295
this._cleanResources();
241296
});
242297

243-
const messageListener = (msg: IExtHostReadyMessage) => {
244-
if (msg.type === 'VSCODE_EXTHOST_IPC_READY') {
245-
this._extensionHostProcess!.removeListener('message', messageListener);
246-
this._sendSocketToExtensionHost(this._extensionHostProcess!, this._connectionData!);
247-
this._connectionData = null;
248-
}
249-
};
250-
this._extensionHostProcess.on('message', messageListener);
298+
if (extHostNamedPipeServer) {
299+
extHostNamedPipeServer.on('connection', (socket) => {
300+
extHostNamedPipeServer!.close();
301+
this._pipeSockets(socket, this._connectionData!);
302+
});
303+
} else {
304+
const messageListener = (msg: IExtHostReadyMessage) => {
305+
if (msg.type === 'VSCODE_EXTHOST_IPC_READY') {
306+
this._extensionHostProcess!.removeListener('message', messageListener);
307+
this._sendSocketToExtensionHost(this._extensionHostProcess!, this._connectionData!);
308+
this._connectionData = null;
309+
}
310+
};
311+
this._extensionHostProcess.on('message', messageListener);
312+
}
251313

252314
} catch (error) {
253315
console.error('ExtensionHostConnection errored');
@@ -256,6 +318,21 @@ export class ExtensionHostConnection {
256318
}
257319
}
258320
}
321+
322+
private _listenOnPipe(): Promise<{ pipeName: string; namedPipeServer: net.Server }> {
323+
return new Promise<{ pipeName: string; namedPipeServer: net.Server }>((resolve, reject) => {
324+
const pipeName = createRandomIPCHandle();
325+
326+
const namedPipeServer = net.createServer();
327+
namedPipeServer.on('error', reject);
328+
namedPipeServer.listen(pipeName, () => {
329+
if (namedPipeServer) {
330+
namedPipeServer.removeListener('error', reject);
331+
}
332+
resolve({ pipeName, namedPipeServer });
333+
});
334+
});
335+
}
259336
}
260337

261338
function readCaseInsensitive(env: { [key: string]: string | undefined }, key: string): string | undefined {

src/vs/workbench/api/node/extensionHostProcess.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,9 @@ function _createExtHostProtocol(): Promise<IMessagePassingProtocol> {
210210

211211
const socket = net.createConnection(pipeName, () => {
212212
socket.removeListener('error', reject);
213-
resolve(new PersistentProtocol(new NodeSocket(socket, 'extHost-renderer')));
213+
const protocol = new PersistentProtocol(new NodeSocket(socket, 'extHost-renderer'));
214+
protocol.sendResume();
215+
resolve(protocol);
214216
});
215217
socket.once('error', reject);
216218

0 commit comments

Comments
 (0)