Skip to content

Commit

Permalink
Merge pull request microsoft#174245 from microsoft/ben/utility-proces…
Browse files Browse the repository at this point in the history
…s-file-watcher

Allow to run file watcher in utility process
  • Loading branch information
bpasero authored and c-claeys committed Feb 16, 2023
2 parents c33dd46 + 777e77d commit 5eaeb63
Show file tree
Hide file tree
Showing 25 changed files with 536 additions and 196 deletions.
7 changes: 6 additions & 1 deletion src/vs/base/node/ps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
const UTILITY_EXTENSION_HOST_HINT = /--utility-sub-type=node.mojom.NodeService/;
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/;
const UTILITY_FILE_WATCHER_HOST_HINT = /--vscode-utility-kind=fileWatcher/;
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
const WINDOWS_PTY = /\\pipe\\winpty-control/;
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
Expand Down Expand Up @@ -98,6 +99,10 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) {
return 'extension-host';
}

if (UTILITY_FILE_WATCHER_HOST_HINT.exec(cmd)) {
return 'file-watcher';
}
} else if (matches[1] === 'extensionHost') {
return 'extension-host'; // normalize remote extension host type
}
Expand Down
69 changes: 69 additions & 0 deletions src/vs/base/parts/ipc/node/ipc.mp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { MessagePortMain, isUtilityProcess, MessageEvent } from 'vs/base/parts/sandbox/node/electronTypes';
import { VSBuffer } from 'vs/base/common/buffer';
import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { Emitter, Event } from 'vs/base/common/event';
import { assertType } from 'vs/base/common/types';

/**
* The MessagePort `Protocol` leverages MessagePortMain style IPC communication
* for the implementation of the `IMessagePassingProtocol`.
*/
class Protocol implements IMessagePassingProtocol {

readonly onMessage = Event.fromNodeEventEmitter<VSBuffer>(this.port, 'message', (e: MessageEvent) => VSBuffer.wrap(e.data));

constructor(private port: MessagePortMain) {

// we must call start() to ensure messages are flowing
port.start();
}

send(message: VSBuffer): void {
this.port.postMessage(message.buffer);
}

disconnect(): void {
this.port.close();
}
}

/**
* An implementation of a `IPCServer` on top of MessagePort style IPC communication.
* The clients register themselves via Electron Utility Process IPC transfer.
*/
export class Server extends IPCServer {

private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
assertType(isUtilityProcess(process), 'Electron Utility Process');

const onCreateMessageChannel = new Emitter<MessagePortMain>();

process.parentPort.on('message', (e: Electron.MessageEvent) => {
const ports = e.ports;
onCreateMessageChannel.fire(ports[0]);
});

return Event.map(onCreateMessageChannel.event, port => {
const protocol = new Protocol(port);

const result: ClientConnectionEvent = {
protocol,
// Not part of the standard spec, but in Electron we get a `close` event
// when the other side closes. We can use this to detect disconnects
// (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close)
onDidClientDisconnect: Event.fromNodeEventEmitter(port, 'close')
};

return result;
});
}

constructor() {
super(Server.getOnDidClientConnect());
}
}
51 changes: 46 additions & 5 deletions src/vs/base/parts/sandbox/node/electronTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,44 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

// TODO@bpasero remove me once we are on Electron 22
export interface MessagePortMain extends NodeJS.EventEmitter {

// Docs: https://electronjs.org/docs/api/message-port-main

/**
* Emitted when the remote end of a MessagePortMain object becomes disconnected.
*/
on(event: 'close', listener: Function): this;
once(event: 'close', listener: Function): this;
addListener(event: 'close', listener: Function): this;
removeListener(event: 'close', listener: Function): this;
/**
* Emitted when a MessagePortMain object receives a message.
*/
on(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
once(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
addListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
/**
* Disconnects the port, so it is no longer active.
*/
close(): void;
/**
* Sends a message from the port, and optionally, transfers ownership of objects to
* other browsing contexts.
*/
postMessage(message: any, transfer?: MessagePortMain[]): void;
/**
* Starts the sending of messages queued on the port. Messages will be queued until
* this method is called.
*/
start(): void;
}

export interface MessageEvent {
data: any;
ports: MessagePortMain[];
}

export interface ParentPort extends NodeJS.EventEmitter {

Expand All @@ -13,10 +50,10 @@ export interface ParentPort extends NodeJS.EventEmitter {
* Emitted when the process receives a message. Messages received on this port will
* be queued up until a handler is registered for this event.
*/
on(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
once(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
addListener(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
removeListener(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
on(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
once(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
addListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
/**
* Sends a message from the process to its parent.
*/
Expand All @@ -31,3 +68,7 @@ export interface UtilityNodeJSProcess extends NodeJS.Process {
*/
parentPort: ParentPort;
}

export function isUtilityProcess(process: NodeJS.Process): process is UtilityNodeJSProcess {
return !!(process as UtilityNodeJSProcess).parentPort;
}
10 changes: 5 additions & 5 deletions src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ import { ISharedTunnelsService } from 'vs/platform/tunnel/common/tunnel';
import { SharedTunnelsService } from 'vs/platform/tunnel/node/tunnelService';
import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService';
import { SharedProcessTunnelService } from 'vs/platform/tunnel/node/sharedProcessTunnelService';
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
Expand All @@ -116,6 +116,7 @@ import { ExtensionsContributions } from 'vs/code/electron-browser/sharedProcess/
import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService';
import { localize } from 'vs/nls';
import { LogService } from 'vs/platform/log/common/logService';
import { ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerConfiguration } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService';

class SharedProcessMain extends Disposable {

Expand Down Expand Up @@ -148,12 +149,12 @@ class SharedProcessMain extends Disposable {
// application is shutting down anyways.
//
const eventName = 'vscode:electron-main->shared-process=disposeWorker';
const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); };
const onDisposeWorker = (event: unknown, configuration: IUtilityProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); };
ipcRenderer.on(eventName, onDisposeWorker);
this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker)));
}

private onDisposeWorker(configuration: ISharedProcessWorkerConfiguration): void {
private onDisposeWorker(configuration: IUtilityProcessWorkerConfiguration): void {
this.sharedProcessWorkerService?.disposeWorker(configuration);
}

Expand Down Expand Up @@ -446,12 +447,11 @@ class SharedProcessMain extends Disposable {

// Worker
const sharedProcessWorkerChannel = ProxyChannel.fromService(accessor.get(ISharedProcessWorkerService));
this.server.registerChannel(ipcSharedProcessWorkerChannelName, sharedProcessWorkerChannel);
this.server.registerChannel(ipcUtilityProcessWorkerChannelName, sharedProcessWorkerChannel);

// Remote Tunnel
const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService));
this.server.registerChannel('remoteTunnel', remoteTunnelChannel);

}

private registerErrorHandler(logService: ILogService): void {
Expand Down
9 changes: 9 additions & 0 deletions src/vs/code/electron-main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc';
import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService';
import { IInitialProtocolUrls, IProtocolUrl } from 'vs/platform/url/electron-main/url';
import { massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs';
import { IUtilityProcessWorkerMainService, UtilityProcessWorkerMainService } from 'vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService';
import { ipcUtilityProcessWorkerChannelName } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService';

/**
* The main VS Code application. There will only ever be one instance,
Expand Down Expand Up @@ -950,6 +952,9 @@ export class CodeApplication extends Disposable {
services.set(IExtensionsProfileScannerService, new SyncDescriptor(ExtensionsProfileScannerService, undefined, true));
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));

// Utility Process Worker
services.set(IUtilityProcessWorkerMainService, new SyncDescriptor(UtilityProcessWorkerMainService, undefined, true));

// Init services that require it
await Promises.settled([
backupMainService.initialize(),
Expand Down Expand Up @@ -1068,6 +1073,10 @@ export class CodeApplication extends Disposable {
// Extension Host Starter
const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter));
mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel);

// Utility Process Worker
const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService));
mainProcessElectronServer.registerChannel(ipcUtilityProcessWorkerChannelName, utilityProcessWorkerChannel);
}

private async openFirstWindow(accessor: ServicesAccessor, initialProtocolUrls: IInitialProtocolUrls | undefined): Promise<ICodeWindow[]> {
Expand Down
10 changes: 5 additions & 5 deletions src/vs/platform/extensions/electron-main/extensionHostStarter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter

constructor(
@ILogService private readonly _logService: ILogService,
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
@IWindowsMainService private readonly _windowsMainService: IWindowsMainService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
) {
this._extHosts = new Map<string, ExtensionHostProcess | UtilityProcess>();

// On shutdown: gracefully await extension host shutdowns
lifecycleMainService.onWillShutdown((e) => {
this._lifecycleMainService.onWillShutdown((e) => {
this._shutdown = true;
e.join('extHostStarter', this._waitForAllExit(6000));
});
Expand Down Expand Up @@ -96,7 +96,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
if (!canUseUtilityProcess) {
throw new Error(`Cannot use UtilityProcess!`);
}
extHost = new UtilityProcess(this._logService, this._windowsMainService, this._telemetryService);
extHost = new UtilityProcess(this._logService, this._windowsMainService, this._telemetryService, this._lifecycleMainService);
} else {
extHost = new ExtensionHostProcess(id, this._logService);
}
Expand All @@ -115,11 +115,11 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
if (this._shutdown) {
throw canceled();
}
return this._getExtHost(id).start({
this._getExtHost(id).start({
...opts,
type: 'extensionHost',
args: ['--skipWorkspaceStorageLock'],
execArgv: opts.execArgv ?? [],
execArgv: opts.execArgv,
allowLoadingUnsignedLibraries: true,
correlationId: id
});
Expand Down
12 changes: 10 additions & 2 deletions src/vs/platform/files/node/watcher/watcherMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,17 @@
*--------------------------------------------------------------------------------------------*/

import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
import { Server as ChildProcessServer } from 'vs/base/parts/ipc/node/ipc.cp';
import { Server as UtilityProcessServer } from 'vs/base/parts/ipc/node/ipc.mp';
import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes';
import { UniversalWatcher } from 'vs/platform/files/node/watcher/watcher';

const server = new Server('watcher');
let server: ChildProcessServer<string> | UtilityProcessServer;
if (isUtilityProcess(process)) {
server = new UtilityProcessServer();
} else {
server = new ChildProcessServer('watcher');
}

const service = new UniversalWatcher();
server.registerChannel('watcher', ProxyChannel.fromService(service));
Loading

0 comments on commit 5eaeb63

Please sign in to comment.