Skip to content

Commit 5eaeb63

Browse files
bpaseroc-claeys
authored andcommitted
Merge pull request microsoft#174245 from microsoft/ben/utility-process-file-watcher
Allow to run file watcher in utility process
2 parents c33dd46 + 777e77d commit 5eaeb63

File tree

25 files changed

+536
-196
lines changed

25 files changed

+536
-196
lines changed

src/vs/base/node/ps.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
5252
const ISSUE_REPORTER_HINT = /--vscode-window-kind=issue-reporter/;
5353
const PROCESS_EXPLORER_HINT = /--vscode-window-kind=process-explorer/;
5454
const UTILITY_NETWORK_HINT = /--utility-sub-type=network/;
55-
const UTILITY_EXTENSION_HOST_HINT = /--utility-sub-type=node.mojom.NodeService/;
55+
const UTILITY_EXTENSION_HOST_HINT = /--vscode-utility-kind=extensionHost/;
56+
const UTILITY_FILE_WATCHER_HOST_HINT = /--vscode-utility-kind=fileWatcher/;
5657
const WINDOWS_CRASH_REPORTER = /--crashes-directory/;
5758
const WINDOWS_PTY = /\\pipe\\winpty-control/;
5859
const WINDOWS_CONSOLE_HOST = /conhost\.exe/;
@@ -98,6 +99,10 @@ export function listProcesses(rootPid: number): Promise<ProcessItem> {
9899
if (UTILITY_EXTENSION_HOST_HINT.exec(cmd)) {
99100
return 'extension-host';
100101
}
102+
103+
if (UTILITY_FILE_WATCHER_HOST_HINT.exec(cmd)) {
104+
return 'file-watcher';
105+
}
101106
} else if (matches[1] === 'extensionHost') {
102107
return 'extension-host'; // normalize remote extension host type
103108
}

src/vs/base/parts/ipc/node/ipc.mp.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { MessagePortMain, isUtilityProcess, MessageEvent } from 'vs/base/parts/sandbox/node/electronTypes';
7+
import { VSBuffer } from 'vs/base/common/buffer';
8+
import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer } from 'vs/base/parts/ipc/common/ipc';
9+
import { Emitter, Event } from 'vs/base/common/event';
10+
import { assertType } from 'vs/base/common/types';
11+
12+
/**
13+
* The MessagePort `Protocol` leverages MessagePortMain style IPC communication
14+
* for the implementation of the `IMessagePassingProtocol`.
15+
*/
16+
class Protocol implements IMessagePassingProtocol {
17+
18+
readonly onMessage = Event.fromNodeEventEmitter<VSBuffer>(this.port, 'message', (e: MessageEvent) => VSBuffer.wrap(e.data));
19+
20+
constructor(private port: MessagePortMain) {
21+
22+
// we must call start() to ensure messages are flowing
23+
port.start();
24+
}
25+
26+
send(message: VSBuffer): void {
27+
this.port.postMessage(message.buffer);
28+
}
29+
30+
disconnect(): void {
31+
this.port.close();
32+
}
33+
}
34+
35+
/**
36+
* An implementation of a `IPCServer` on top of MessagePort style IPC communication.
37+
* The clients register themselves via Electron Utility Process IPC transfer.
38+
*/
39+
export class Server extends IPCServer {
40+
41+
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
42+
assertType(isUtilityProcess(process), 'Electron Utility Process');
43+
44+
const onCreateMessageChannel = new Emitter<MessagePortMain>();
45+
46+
process.parentPort.on('message', (e: Electron.MessageEvent) => {
47+
const ports = e.ports;
48+
onCreateMessageChannel.fire(ports[0]);
49+
});
50+
51+
return Event.map(onCreateMessageChannel.event, port => {
52+
const protocol = new Protocol(port);
53+
54+
const result: ClientConnectionEvent = {
55+
protocol,
56+
// Not part of the standard spec, but in Electron we get a `close` event
57+
// when the other side closes. We can use this to detect disconnects
58+
// (https://github.com/electron/electron/blob/11-x-y/docs/api/message-port-main.md#event-close)
59+
onDidClientDisconnect: Event.fromNodeEventEmitter(port, 'close')
60+
};
61+
62+
return result;
63+
});
64+
}
65+
66+
constructor() {
67+
super(Server.getOnDidClientConnect());
68+
}
69+
}

src/vs/base/parts/sandbox/node/electronTypes.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,44 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
// TODO@bpasero remove me once we are on Electron 22
6+
export interface MessagePortMain extends NodeJS.EventEmitter {
7+
8+
// Docs: https://electronjs.org/docs/api/message-port-main
9+
10+
/**
11+
* Emitted when the remote end of a MessagePortMain object becomes disconnected.
12+
*/
13+
on(event: 'close', listener: Function): this;
14+
once(event: 'close', listener: Function): this;
15+
addListener(event: 'close', listener: Function): this;
16+
removeListener(event: 'close', listener: Function): this;
17+
/**
18+
* Emitted when a MessagePortMain object receives a message.
19+
*/
20+
on(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
21+
once(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
22+
addListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
23+
removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
24+
/**
25+
* Disconnects the port, so it is no longer active.
26+
*/
27+
close(): void;
28+
/**
29+
* Sends a message from the port, and optionally, transfers ownership of objects to
30+
* other browsing contexts.
31+
*/
32+
postMessage(message: any, transfer?: MessagePortMain[]): void;
33+
/**
34+
* Starts the sending of messages queued on the port. Messages will be queued until
35+
* this method is called.
36+
*/
37+
start(): void;
38+
}
39+
40+
export interface MessageEvent {
41+
data: any;
42+
ports: MessagePortMain[];
43+
}
744

845
export interface ParentPort extends NodeJS.EventEmitter {
946

@@ -13,10 +50,10 @@ export interface ParentPort extends NodeJS.EventEmitter {
1350
* Emitted when the process receives a message. Messages received on this port will
1451
* be queued up until a handler is registered for this event.
1552
*/
16-
on(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
17-
once(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
18-
addListener(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
19-
removeListener(event: 'message', listener: (messageEvent: Electron.MessageEvent) => void): this;
53+
on(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
54+
once(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
55+
addListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
56+
removeListener(event: 'message', listener: (messageEvent: MessageEvent) => void): this;
2057
/**
2158
* Sends a message from the process to its parent.
2259
*/
@@ -31,3 +68,7 @@ export interface UtilityNodeJSProcess extends NodeJS.Process {
3168
*/
3269
parentPort: ParentPort;
3370
}
71+
72+
export function isUtilityProcess(process: NodeJS.Process): process is UtilityNodeJSProcess {
73+
return !!(process as UtilityNodeJSProcess).parentPort;
74+
}

src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ import { ISharedTunnelsService } from 'vs/platform/tunnel/common/tunnel';
8989
import { SharedTunnelsService } from 'vs/platform/tunnel/node/tunnelService';
9090
import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService';
9191
import { SharedProcessTunnelService } from 'vs/platform/tunnel/node/sharedProcessTunnelService';
92-
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
92+
import { ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
9393
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';
9494
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
9595
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
@@ -116,6 +116,7 @@ import { ExtensionsContributions } from 'vs/code/electron-browser/sharedProcess/
116116
import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/electron-sandbox/extensionsProfileScannerService';
117117
import { localize } from 'vs/nls';
118118
import { LogService } from 'vs/platform/log/common/logService';
119+
import { ipcUtilityProcessWorkerChannelName, IUtilityProcessWorkerConfiguration } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService';
119120

120121
class SharedProcessMain extends Disposable {
121122

@@ -148,12 +149,12 @@ class SharedProcessMain extends Disposable {
148149
// application is shutting down anyways.
149150
//
150151
const eventName = 'vscode:electron-main->shared-process=disposeWorker';
151-
const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); };
152+
const onDisposeWorker = (event: unknown, configuration: IUtilityProcessWorkerConfiguration) => { this.onDisposeWorker(configuration); };
152153
ipcRenderer.on(eventName, onDisposeWorker);
153154
this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker)));
154155
}
155156

156-
private onDisposeWorker(configuration: ISharedProcessWorkerConfiguration): void {
157+
private onDisposeWorker(configuration: IUtilityProcessWorkerConfiguration): void {
157158
this.sharedProcessWorkerService?.disposeWorker(configuration);
158159
}
159160

@@ -446,12 +447,11 @@ class SharedProcessMain extends Disposable {
446447

447448
// Worker
448449
const sharedProcessWorkerChannel = ProxyChannel.fromService(accessor.get(ISharedProcessWorkerService));
449-
this.server.registerChannel(ipcSharedProcessWorkerChannelName, sharedProcessWorkerChannel);
450+
this.server.registerChannel(ipcUtilityProcessWorkerChannelName, sharedProcessWorkerChannel);
450451

451452
// Remote Tunnel
452453
const remoteTunnelChannel = ProxyChannel.fromService(accessor.get(IRemoteTunnelService));
453454
this.server.registerChannel('remoteTunnel', remoteTunnelChannel);
454-
455455
}
456456

457457
private registerErrorHandler(logService: ILogService): void {

src/vs/code/electron-main/app.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc';
113113
import { ILoggerMainService } from 'vs/platform/log/electron-main/loggerService';
114114
import { IInitialProtocolUrls, IProtocolUrl } from 'vs/platform/url/electron-main/url';
115115
import { massageMessageBoxOptions } from 'vs/platform/dialogs/common/dialogs';
116+
import { IUtilityProcessWorkerMainService, UtilityProcessWorkerMainService } from 'vs/platform/utilityProcess/electron-main/utilityProcessWorkerMainService';
117+
import { ipcUtilityProcessWorkerChannelName } from 'vs/platform/utilityProcess/common/utilityProcessWorkerService';
116118

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

955+
// Utility Process Worker
956+
services.set(IUtilityProcessWorkerMainService, new SyncDescriptor(UtilityProcessWorkerMainService, undefined, true));
957+
953958
// Init services that require it
954959
await Promises.settled([
955960
backupMainService.initialize(),
@@ -1068,6 +1073,10 @@ export class CodeApplication extends Disposable {
10681073
// Extension Host Starter
10691074
const extensionHostStarterChannel = ProxyChannel.fromService(accessor.get(IExtensionHostStarter));
10701075
mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel);
1076+
1077+
// Utility Process Worker
1078+
const utilityProcessWorkerChannel = ProxyChannel.fromService(accessor.get(IUtilityProcessWorkerMainService));
1079+
mainProcessElectronServer.registerChannel(ipcUtilityProcessWorkerChannelName, utilityProcessWorkerChannel);
10711080
}
10721081

10731082
private async openFirstWindow(accessor: ServicesAccessor, initialProtocolUrls: IInitialProtocolUrls | undefined): Promise<ICodeWindow[]> {

src/vs/platform/extensions/electron-main/extensionHostStarter.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,14 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
3232

3333
constructor(
3434
@ILogService private readonly _logService: ILogService,
35-
@ILifecycleMainService lifecycleMainService: ILifecycleMainService,
35+
@ILifecycleMainService private readonly _lifecycleMainService: ILifecycleMainService,
3636
@IWindowsMainService private readonly _windowsMainService: IWindowsMainService,
3737
@ITelemetryService private readonly _telemetryService: ITelemetryService,
3838
) {
3939
this._extHosts = new Map<string, ExtensionHostProcess | UtilityProcess>();
4040

4141
// On shutdown: gracefully await extension host shutdowns
42-
lifecycleMainService.onWillShutdown((e) => {
42+
this._lifecycleMainService.onWillShutdown((e) => {
4343
this._shutdown = true;
4444
e.join('extHostStarter', this._waitForAllExit(6000));
4545
});
@@ -96,7 +96,7 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
9696
if (!canUseUtilityProcess) {
9797
throw new Error(`Cannot use UtilityProcess!`);
9898
}
99-
extHost = new UtilityProcess(this._logService, this._windowsMainService, this._telemetryService);
99+
extHost = new UtilityProcess(this._logService, this._windowsMainService, this._telemetryService, this._lifecycleMainService);
100100
} else {
101101
extHost = new ExtensionHostProcess(id, this._logService);
102102
}
@@ -115,11 +115,11 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter
115115
if (this._shutdown) {
116116
throw canceled();
117117
}
118-
return this._getExtHost(id).start({
118+
this._getExtHost(id).start({
119119
...opts,
120120
type: 'extensionHost',
121121
args: ['--skipWorkspaceStorageLock'],
122-
execArgv: opts.execArgv ?? [],
122+
execArgv: opts.execArgv,
123123
allowLoadingUnsignedLibraries: true,
124124
correlationId: id
125125
});

src/vs/platform/files/node/watcher/watcherMain.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc';
7-
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
7+
import { Server as ChildProcessServer } from 'vs/base/parts/ipc/node/ipc.cp';
8+
import { Server as UtilityProcessServer } from 'vs/base/parts/ipc/node/ipc.mp';
9+
import { isUtilityProcess } from 'vs/base/parts/sandbox/node/electronTypes';
810
import { UniversalWatcher } from 'vs/platform/files/node/watcher/watcher';
911

10-
const server = new Server('watcher');
12+
let server: ChildProcessServer<string> | UtilityProcessServer;
13+
if (isUtilityProcess(process)) {
14+
server = new UtilityProcessServer();
15+
} else {
16+
server = new ChildProcessServer('watcher');
17+
}
18+
1119
const service = new UniversalWatcher();
1220
server.registerChannel('watcher', ProxyChannel.fromService(service));

0 commit comments

Comments
 (0)