|
3 | 3 | * Licensed under the MIT License. See License.txt in the project root for license information.
|
4 | 4 | *--------------------------------------------------------------------------------------------*/
|
5 | 5 |
|
6 |
| -import { ChildProcess, fork, ForkOptions } from 'child_process'; |
| 6 | +import { ChildProcess, fork, ForkOptions, SendHandle } from 'child_process'; |
7 | 7 | import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
|
8 | 8 | import { Delayer, createCancelablePromise } from 'vs/base/common/async';
|
9 | 9 | import { deepClone } from 'vs/base/common/objects';
|
10 | 10 | import { Emitter, Event } from 'vs/base/common/event';
|
11 | 11 | import { createQueuedSender } from 'vs/base/node/processes';
|
12 | 12 | import { IChannel, ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient } from 'vs/base/parts/ipc/common/ipc';
|
13 |
| -import { isRemoteConsoleLog, log } from 'vs/base/common/console'; |
| 13 | +import { IRemoteConsoleLog, isRemoteConsoleLog, log } from 'vs/base/common/console'; |
14 | 14 | import { CancellationToken } from 'vs/base/common/cancellation';
|
15 | 15 | import * as errors from 'vs/base/common/errors';
|
16 | 16 | import { VSBuffer } from 'vs/base/common/buffer';
|
17 | 17 | import { isMacintosh } from 'vs/base/common/platform';
|
| 18 | +// eslint-disable-next-line code-import-patterns |
| 19 | +import { IExtHostMessage, IExtHostReadyMessage } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; |
18 | 20 |
|
19 | 21 | /**
|
20 | 22 | * This implementation doesn't perform well since it uses base64 encoding for buffers.
|
@@ -290,3 +292,101 @@ export class Client implements IChannelClient, IDisposable {
|
290 | 292 | this.activeRequests.clear();
|
291 | 293 | }
|
292 | 294 | }
|
| 295 | + |
| 296 | +export type ExtensionHostMessage = IRemoteConsoleLog | IExtHostReadyMessage; |
| 297 | +export class ExtensionHost implements IDisposable { |
| 298 | + private child: ChildProcess | null = null; |
| 299 | + private readonly _onDidProcessExit = new Emitter<{ code: number, signal: string }>(); |
| 300 | + public readonly onDidProcessExit = this._onDidProcessExit.event; |
| 301 | + |
| 302 | + private readonly _onReadyMessage = new Emitter<IExtHostReadyMessage>(); |
| 303 | + public readonly onReady = this._onReadyMessage.event; |
| 304 | + |
| 305 | + private disposeClient() { |
| 306 | + if (this.child) { |
| 307 | + this.child.kill(); |
| 308 | + this.child = null; |
| 309 | + } |
| 310 | + } |
| 311 | + |
| 312 | + dispose() { |
| 313 | + this._onDidProcessExit.dispose(); |
| 314 | + |
| 315 | + this.disposeClient(); |
| 316 | + } |
| 317 | + |
| 318 | + sendIPCMessage(message: IExtHostMessage, sendHandle?: SendHandle) { |
| 319 | + if (this.child && this.child.connected) { |
| 320 | + return this.child.send(message, sendHandle); |
| 321 | + } |
| 322 | + |
| 323 | + return false; |
| 324 | + } |
| 325 | + |
| 326 | + constructor(private modulePath: string, private options: IIPCOptions) { |
| 327 | + const args = options && options.args ? this.options.args : []; |
| 328 | + const forkOpts: ForkOptions = Object.create(null); |
| 329 | + |
| 330 | + forkOpts.env = { ...deepClone(process.env), 'VSCODE_PARENT_PID': String(process.pid) }; |
| 331 | + |
| 332 | + if (this.options && this.options.env) { |
| 333 | + forkOpts.env = { ...forkOpts.env, ...this.options.env }; |
| 334 | + } |
| 335 | + |
| 336 | + if (this.options && this.options.freshExecArgv) { |
| 337 | + forkOpts.execArgv = []; |
| 338 | + } |
| 339 | + |
| 340 | + if (this.options && typeof this.options.debug === 'number') { |
| 341 | + forkOpts.execArgv = ['--nolazy', '--inspect=' + this.options.debug]; |
| 342 | + } |
| 343 | + |
| 344 | + if (this.options && typeof this.options.debugBrk === 'number') { |
| 345 | + forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk]; |
| 346 | + } |
| 347 | + |
| 348 | + if (forkOpts.execArgv === undefined) { |
| 349 | + // if not set, the forked process inherits the execArgv of the parent process |
| 350 | + // --inspect and --inspect-brk can not be inherited as the port would conflict |
| 351 | + forkOpts.execArgv = process.execArgv.filter(a => !/^--inspect(-brk)?=/.test(a)); // remove |
| 352 | + } |
| 353 | + |
| 354 | + if (isMacintosh && forkOpts.env) { |
| 355 | + // Unset `DYLD_LIBRARY_PATH`, as it leads to process crashes |
| 356 | + // See https://github.com/microsoft/vscode/issues/105848 |
| 357 | + delete forkOpts.env['DYLD_LIBRARY_PATH']; |
| 358 | + } |
| 359 | + |
| 360 | + this.child = fork(this.modulePath, args, forkOpts); |
| 361 | + |
| 362 | + const onRawMessage = Event.fromNodeEventEmitter<ExtensionHostMessage>(this.child, 'message', msg => msg); |
| 363 | + |
| 364 | + onRawMessage(msg => { |
| 365 | + // Handle remote console logs specially |
| 366 | + if (isRemoteConsoleLog(msg)) { |
| 367 | + log(msg, `IPC Library: ${this.options.serverName}`); |
| 368 | + return; |
| 369 | + } |
| 370 | + |
| 371 | + if (msg.type === 'VSCODE_EXTHOST_IPC_READY') { |
| 372 | + this._onReadyMessage.fire(msg); |
| 373 | + } |
| 374 | + }); |
| 375 | + |
| 376 | + const onExit = () => this.disposeClient(); |
| 377 | + process.once('exit', onExit); |
| 378 | + |
| 379 | + this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err)); |
| 380 | + |
| 381 | + this.child.on('exit', (code: any, signal: any) => { |
| 382 | + process.removeListener('exit' as 'loaded', onExit); // https://github.com/electron/electron/issues/21475 |
| 383 | + |
| 384 | + |
| 385 | + if (code !== 0 && signal !== 'SIGTERM') { |
| 386 | + console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal); |
| 387 | + } |
| 388 | + |
| 389 | + this._onDidProcessExit.fire({ code, signal }); |
| 390 | + }); |
| 391 | + } |
| 392 | +} |
0 commit comments