diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 7b3a5043154da..94b22026a0771 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -65,7 +65,6 @@ const vscodeResources = [ 'out-build/vs/base/browser/ui/codicons/codicon/**', 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', 'out-build/vs/platform/environment/node/userDataPath.js', - 'out-build/vs/platform/extensions/node/extensionHostStarterWorkerMain.js', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', diff --git a/src/buildfile.js b/src/buildfile.js index 8c30339da6ee1..6b49aa30083ae 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -38,10 +38,6 @@ exports.base = [ }, { name: 'vs/base/common/worker/simpleWorker', - }, - { - name: 'vs/platform/extensions/node/extensionHostStarterWorker', - exclude: ['vs/base/common/worker/simpleWorker'] } ]; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4bc4a432ceb9e..651c9ea09e2ec 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -46,7 +46,7 @@ import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; -import { WorkerMainProcessExtensionHostStarter } from 'vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter'; +import { ExtensionHostStarter } from 'vs/platform/extensions/electron-main/extensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/files/common/diskFileSystemProviderClient'; @@ -641,7 +641,7 @@ export class CodeApplication extends Disposable { services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); // Extension Host Starter - services.set(IExtensionHostStarter, new SyncDescriptor(WorkerMainProcessExtensionHostStarter)); + services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts similarity index 78% rename from src/vs/platform/extensions/node/extensionHostStarterWorker.ts rename to src/vs/platform/extensions/electron-main/extensionHostStarter.ts index e0f8d8b03e451..493168507e84f 100644 --- a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -3,21 +3,134 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { canceled, SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { StopWatch } from 'vs/base/common/stopwatch'; import { ChildProcess, fork } from 'child_process'; import { StringDecoder } from 'string_decoder'; import { Promises, timeout } from 'vs/base/common/async'; -import { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -export interface IExtensionHostStarterWorkerHost { - logInfo(message: string): Promise; +export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { + _serviceBrand: undefined; + + private static _lastId: number = 0; + + protected readonly _extHosts: Map; + private _shutdown = false; + + constructor( + @ILogService private readonly _logService: ILogService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService + ) { + this._extHosts = new Map(); + + // On shutdown: gracefully await extension host shutdowns + lifecycleMainService.onWillShutdown((e) => { + this._shutdown = true; + e.join(this._waitForAllExit(6000)); + }); + } + + dispose(): void { + // Intentionally not killing the extension host processes + } + + private _getExtHost(id: string): ExtensionHostProcess { + const extHostProcess = this._extHosts.get(id); + if (!extHostProcess) { + throw new Error(`Unknown extension host!`); + } + return extHostProcess; + } + + onDynamicStdout(id: string): Event { + return this._getExtHost(id).onStdout; + } + + onDynamicStderr(id: string): Event { + return this._getExtHost(id).onStderr; + } + + onDynamicMessage(id: string): Event { + return this._getExtHost(id).onMessage; + } + + onDynamicError(id: string): Event<{ error: SerializedError }> { + return this._getExtHost(id).onError; + } + + onDynamicExit(id: string): Event<{ code: number; signal: string }> { + return this._getExtHost(id).onExit; + } + + async createExtensionHost(): Promise<{ id: string }> { + if (this._shutdown) { + throw canceled(); + } + const id = String(++ExtensionHostStarter._lastId); + const extHost = new ExtensionHostProcess(id, this._logService); + this._extHosts.set(id, extHost); + extHost.onExit(({ pid, code, signal }) => { + this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); + setTimeout(() => { + extHost.dispose(); + this._extHosts.delete(id); + }); + }); + return { id }; + } + + async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { + if (this._shutdown) { + throw canceled(); + } + return this._getExtHost(id).start(opts); + } + + async enableInspectPort(id: string): Promise { + if (this._shutdown) { + throw canceled(); + } + const extHostProcess = this._extHosts.get(id); + if (!extHostProcess) { + return false; + } + return extHostProcess.enableInspectPort(); + } + + async kill(id: string): Promise { + if (this._shutdown) { + throw canceled(); + } + const extHostProcess = this._extHosts.get(id); + if (!extHostProcess) { + // already gone! + return; + } + extHostProcess.kill(); + } + + async _killAllNow(): Promise { + for (const [, extHost] of this._extHosts) { + extHost.kill(); + } + } + + async _waitForAllExit(maxWaitTimeMs: number): Promise { + const exitPromises: Promise[] = []; + for (const [, extHost] of this._extHosts) { + exitPromises.push(extHost.waitForExit(maxWaitTimeMs)); + } + return Promises.settled(exitPromises).then(() => { }); + } } class ExtensionHostProcess extends Disposable { @@ -42,14 +155,14 @@ class ExtensionHostProcess extends Disposable { constructor( public readonly id: string, - private readonly _host: IExtensionHostStarterWorkerHost + @ILogService private readonly _logService: ILogService, ) { super(); } start(opts: IExtensionHostProcessOptions): { pid: number } { if (platform.isCI) { - this._host.logInfo(`Calling fork to start extension host...`); + this._logService.info(`Calling fork to start extension host...`); } const sw = StopWatch.create(false); this._process = fork( @@ -60,7 +173,7 @@ class ExtensionHostProcess extends Disposable { const forkTime = sw.elapsed(); const pid = this._process.pid!; - this._host.logInfo(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); + this._logService.info(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); const stdoutDecoder = new StringDecoder('utf-8'); this._process.stdout?.on('data', (chunk) => { @@ -95,7 +208,7 @@ class ExtensionHostProcess extends Disposable { return false; } - this._host.logInfo(`Enabling inspect port on extension host with pid ${this._process.pid}.`); + this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`); interface ProcessExt { _debugProcess?(n: number): any; @@ -119,7 +232,7 @@ class ExtensionHostProcess extends Disposable { if (!this._process) { return; } - this._host.logInfo(`Killing extension host with pid ${this._process.pid}.`); + this._logService.info(`Killing extension host with pid ${this._process.pid}.`); this._process.kill(); } @@ -128,116 +241,13 @@ class ExtensionHostProcess extends Disposable { return; } const pid = this._process.pid; - this._host.logInfo(`Waiting for extension host with pid ${pid} to exit.`); + this._logService.info(`Waiting for extension host with pid ${pid} to exit.`); await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); if (!this._hasExited) { // looks like we timed out - this._host.logInfo(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); + this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); this._process.kill(); } } } - -export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { - _serviceBrand: undefined; - - private static _lastId: number = 0; - - protected readonly _extHosts: Map; - - constructor( - private readonly _host: IExtensionHostStarterWorkerHost - ) { - this._extHosts = new Map(); - } - - dispose(): void { - // Intentionally not killing the extension host processes - } - - private _getExtHost(id: string): ExtensionHostProcess { - const extHostProcess = this._extHosts.get(id); - if (!extHostProcess) { - throw new Error(`Unknown extension host!`); - } - return extHostProcess; - } - - onDynamicStdout(id: string): Event { - return this._getExtHost(id).onStdout; - } - - onDynamicStderr(id: string): Event { - return this._getExtHost(id).onStderr; - } - - onDynamicMessage(id: string): Event { - return this._getExtHost(id).onMessage; - } - - onDynamicError(id: string): Event<{ error: SerializedError }> { - return this._getExtHost(id).onError; - } - - onDynamicExit(id: string): Event<{ code: number; signal: string }> { - return this._getExtHost(id).onExit; - } - - async createExtensionHost(): Promise<{ id: string }> { - const id = String(++ExtensionHostStarter._lastId); - const extHost = new ExtensionHostProcess(id, this._host); - this._extHosts.set(id, extHost); - extHost.onExit(({ pid, code, signal }) => { - this._host.logInfo(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); - setTimeout(() => { - extHost.dispose(); - this._extHosts.delete(id); - }); - }); - return { id }; - } - - async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { - return this._getExtHost(id).start(opts); - } - - async enableInspectPort(id: string): Promise { - const extHostProcess = this._extHosts.get(id); - if (!extHostProcess) { - return false; - } - return extHostProcess.enableInspectPort(); - } - - async kill(id: string): Promise { - const extHostProcess = this._extHosts.get(id); - if (!extHostProcess) { - // already gone! - return; - } - extHostProcess.kill(); - } - - async killAllNow(): Promise { - for (const [, extHost] of this._extHosts) { - extHost.kill(); - } - } - - async waitForAllExit(maxWaitTimeMs: number): Promise { - const exitPromises: Promise[] = []; - for (const [, extHost] of this._extHosts) { - exitPromises.push(extHost.waitForExit(maxWaitTimeMs)); - } - return Promises.settled(exitPromises).then(() => { }); - } -} - -/** - * The `create` function needs to be there by convention because - * we are loaded via the `vs/base/common/worker/simpleWorker` utility. - */ -export function create(host: IExtensionHostStarterWorkerHost) { - return new ExtensionHostStarter(host); -} diff --git a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts deleted file mode 100644 index f78d3e2db1f50..0000000000000 --- a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts +++ /dev/null @@ -1,173 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { canceled, SerializedError } from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { Event } from 'vs/base/common/event'; -import { FileAccess } from 'vs/base/common/network'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Worker } from 'worker_threads'; -import { IWorker, IWorkerCallback, IWorkerFactory, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import type { ExtensionHostStarter, IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker'; -import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { StopWatch } from 'vs/base/common/stopwatch'; - -class NodeWorker implements IWorker { - - private readonly _worker: Worker; - - public readonly onError: Event; - public readonly onExit: Event; - public readonly onMessageError: Event; - - constructor(callback: IWorkerCallback, onErrorCallback: (err: any) => void) { - this._worker = new Worker( - FileAccess.asFileUri('vs/platform/extensions/node/extensionHostStarterWorkerMain.js', require).fsPath, - ); - this._worker.on('message', callback); - this._worker.on('error', onErrorCallback); - this.onError = Event.fromNodeEventEmitter(this._worker, 'error'); - this.onExit = Event.fromNodeEventEmitter(this._worker, 'exit'); - this.onMessageError = Event.fromNodeEventEmitter(this._worker, 'messageerror'); - } - - getId(): number { - return 1; - } - - postMessage(message: any, transfer: ArrayBuffer[]): void { - this._worker.postMessage(message, transfer); - } - - dispose(): void { - this._worker.terminate(); - } -} - -class ExtensionHostStarterWorkerHost implements IExtensionHostStarterWorkerHost { - constructor( - @ILogService private readonly _logService: ILogService - ) { } - - public async logInfo(message: string): Promise { - this._logService.info(message); - } -} - -export class WorkerMainProcessExtensionHostStarter implements IDisposable, IExtensionHostStarter { - _serviceBrand: undefined; - - private _proxy: ExtensionHostStarter | null; - private readonly _worker: SimpleWorkerClient; - private _shutdown = false; - - constructor( - @ILogService private readonly _logService: ILogService, - @ILifecycleMainService lifecycleMainService: ILifecycleMainService - ) { - this._proxy = null; - - const workerFactory: IWorkerFactory = { - create: (moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker => { - const worker = new NodeWorker(callback, onErrorCallback); - worker.onError((err) => { - this._logService.error(`ExtensionHostStarterWorker has encountered an error:`); - this._logService.error(err); - }); - worker.onMessageError((err) => { - this._logService.error(`ExtensionHostStarterWorker has encountered a message error:`); - this._logService.error(err); - }); - worker.onExit((exitCode) => this._logService.info(`ExtensionHostStarterWorker exited with code ${exitCode}.`)); - worker.postMessage(moduleId, []); - return worker; - } - }; - this._worker = new SimpleWorkerClient( - workerFactory, - 'vs/platform/extensions/node/extensionHostStarterWorker', - new ExtensionHostStarterWorkerHost(this._logService) - ); - this._initialize(); - - // On shutdown: gracefully await extension host shutdowns - lifecycleMainService.onWillShutdown((e) => { - this._shutdown = true; - if (this._proxy) { - e.join(this._proxy.waitForAllExit(6000)); - } - }); - } - - dispose(): void { - // Intentionally not killing the extension host processes - } - - async _initialize(): Promise { - this._proxy = await this._worker.getProxyObject(); - this._logService.info(`ExtensionHostStarterWorker created`); - } - - onDynamicStdout(id: string): Event { - return this._proxy!.onDynamicStdout(id); - } - - onDynamicStderr(id: string): Event { - return this._proxy!.onDynamicStderr(id); - } - - onDynamicMessage(id: string): Event { - return this._proxy!.onDynamicMessage(id); - } - - onDynamicError(id: string): Event<{ error: SerializedError }> { - return this._proxy!.onDynamicError(id); - } - - onDynamicExit(id: string): Event<{ code: number; signal: string }> { - return this._proxy!.onDynamicExit(id); - } - - async createExtensionHost(): Promise<{ id: string }> { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.createExtensionHost(); - } - - async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { - const sw = StopWatch.create(false); - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - const timeout = setTimeout(() => { - this._logService.info(`ExtensionHostStarterWorker.start() did not return within 30s. This might be a problem.`); - }, 30000); - const result = await proxy.start(id, opts); - const duration = sw.elapsed(); - this._logService.info(`ExtensionHostStarterWorker.start() took ${duration} ms.`); - clearTimeout(timeout); - return result; - } - - async enableInspectPort(id: string): Promise { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.enableInspectPort(id); - } - - async kill(id: string): Promise { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.kill(id); - } -} diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts deleted file mode 100644 index b4efa0c798fa8..0000000000000 --- a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -(function () { - 'use strict'; - - const loader = require('../../../loader'); - const bootstrap = require('../../../../bootstrap'); - const path = require('path'); - const parentPort = require('worker_threads').parentPort; - - // Bootstrap: NLS - const nlsConfig = bootstrap.setupNLS(); - - // Bootstrap: Loader - loader.config({ - baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../../'), { isWindows: process.platform === 'win32' }), - catchError: true, - nodeRequire: require, - nodeMain: __filename, - 'vs/nls': nlsConfig, - amdModulesPattern: /^vs\//, - recordStats: true - }); - - let isFirstMessage = true; - let beforeReadyMessages: any[] = []; - - const initialMessageHandler = (data: any) => { - if (!isFirstMessage) { - beforeReadyMessages.push(data); - return; - } - - isFirstMessage = false; - loadCode(data); - }; - - parentPort.on('message', initialMessageHandler); - - const loadCode = function (moduleId: string) { - loader([moduleId], function (ws: any) { - setTimeout(() => { - - const messageHandler = ws.create((msg: any, transfer?: ArrayBuffer[]) => { - parentPort.postMessage(msg, transfer); - }, null); - parentPort.off('message', initialMessageHandler); - parentPort.on('message', (data: any) => { - messageHandler.onmessage(data); - }); - while (beforeReadyMessages.length > 0) { - const msg = beforeReadyMessages.shift()!; - messageHandler.onmessage(msg); - } - - }); - }, (err: any) => console.error(err)); - }; - - parentPort.on('messageerror', (err: Error) => { - console.error(err); - }); -})();