Skip to content

Commit 729d816

Browse files
committed
sandbox - implement affinity and lifecycle for shared process workers (#132282)
1 parent 1bfac7c commit 729d816

File tree

9 files changed

+417
-146
lines changed

9 files changed

+417
-146
lines changed

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

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,15 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
9494
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
9595
import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService';
9696
import { SharedProcessTunnelService } from 'vs/platform/remote/node/sharedProcessTunnelService';
97-
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
97+
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
9898
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';
9999

100100
class SharedProcessMain extends Disposable {
101101

102102
private server = this._register(new MessagePortServer());
103103

104+
private sharedProcessWorkerService: ISharedProcessWorkerService | undefined = undefined;
105+
104106
constructor(private configuration: ISharedProcessConfiguration) {
105107
super();
106108

@@ -112,10 +114,25 @@ class SharedProcessMain extends Disposable {
112114

113115
private registerListeners(): void {
114116

115-
// Dispose on exit
117+
// Shared process lifecycle
116118
const onExit = () => this.dispose();
117119
process.once('exit', onExit);
118120
ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit);
121+
122+
// Shared process worker lifecycle
123+
//
124+
// We dispose the listener when the shared process is
125+
// disposed to avoid disposing workers when the entire
126+
// application is shutting down anyways.
127+
//
128+
const eventName = 'vscode:electron-main->shared-process=disposeWorker';
129+
const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => this.onDisposeWorker(configuration);
130+
ipcRenderer.on(eventName, onDisposeWorker);
131+
this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker)));
132+
}
133+
134+
private onDisposeWorker(configuration: ISharedProcessWorkerConfiguration): void {
135+
this.sharedProcessWorkerService?.disposeWorker(configuration);
119136
}
120137

121138
async open(): Promise<void> {
@@ -162,9 +179,6 @@ class SharedProcessMain extends Disposable {
162179
const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter);
163180
services.set(IMainProcessService, mainProcessService);
164181

165-
// Worker
166-
services.set(ISharedProcessWorkerService, new SyncDescriptor(SharedProcessWorkerService));
167-
168182
// Environment
169183
const environmentService = new NativeEnvironmentService(this.configuration.args, productService);
170184
services.set(INativeEnvironmentService, environmentService);
@@ -183,6 +197,10 @@ class SharedProcessMain extends Disposable {
183197
const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger));
184198
services.set(ILogService, logService);
185199

200+
// Worker
201+
this.sharedProcessWorkerService = new SharedProcessWorkerService(logService);
202+
services.set(ISharedProcessWorkerService, this.sharedProcessWorkerService);
203+
186204
// Files
187205
const fileService = this._register(new FileService(logService));
188206
services.set(IFileService, fileService);

src/vs/platform/files/node/diskFileSystemProvider.ts

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
1010
import { CancellationToken } from 'vs/base/common/cancellation';
1111
import { Emitter, Event } from 'vs/base/common/event';
1212
import { isEqual } from 'vs/base/common/extpath';
13-
import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
13+
import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
1414
import { basename, dirname, normalize } from 'vs/base/common/path';
1515
import { isLinux, isWindows } from 'vs/base/common/platform';
1616
import { joinPath } from 'vs/base/common/resources';
@@ -536,8 +536,6 @@ export class DiskFileSystemProvider extends Disposable implements
536536
private readonly recursiveFoldersToWatch: IWatchRequest[] = [];
537537
private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(0));
538538

539-
private recursiveWatcherLogLevelListener: IDisposable | undefined;
540-
541539
watch(resource: URI, opts: IWatchOptions): IDisposable {
542540
if (opts.recursive) {
543541
return this.watchRecursive(resource, opts);
@@ -574,6 +572,35 @@ export class DiskFileSystemProvider extends Disposable implements
574572
});
575573
}
576574

575+
private doRefreshRecursiveWatchers(): void {
576+
577+
// Reuse existing
578+
if (this.recursiveWatcher) {
579+
this.recursiveWatcher.watch(this.recursiveFoldersToWatch);
580+
}
581+
582+
// Otherwise, create new if we have folders to watch
583+
else if (this.recursiveFoldersToWatch.length > 0) {
584+
this.recursiveWatcher = this._register(this.createRecursiveWatcher(
585+
this.recursiveFoldersToWatch,
586+
changes => this._onDidChangeFile.fire(toFileChanges(changes)),
587+
msg => {
588+
if (msg.type === 'error') {
589+
this._onDidWatchErrorOccur.fire(msg.message);
590+
}
591+
592+
this.logService[msg.type](msg.message);
593+
},
594+
this.logService.getLevel() === LogLevel.Trace
595+
));
596+
597+
// Apply log levels dynamicaly
598+
this._register(this.logService.onDidChangeLogLevel(() => {
599+
this.recursiveWatcher?.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace);
600+
}));
601+
}
602+
}
603+
577604
protected createRecursiveWatcher(
578605
folders: IWatchRequest[],
579606
onChange: (changes: IDiskFileChange[]) => void,
@@ -632,35 +659,6 @@ export class DiskFileSystemProvider extends Disposable implements
632659
);
633660
}
634661

635-
private doRefreshRecursiveWatchers(): void {
636-
637-
// Reuse existing
638-
if (this.recursiveWatcher) {
639-
this.recursiveWatcher.watch(this.recursiveFoldersToWatch);
640-
}
641-
642-
// Otherwise, create new if we have folders to watch
643-
else if (this.recursiveFoldersToWatch.length > 0) {
644-
this.recursiveWatcher = this.createRecursiveWatcher(
645-
this.recursiveFoldersToWatch,
646-
changes => this._onDidChangeFile.fire(toFileChanges(changes)),
647-
msg => {
648-
if (msg.type === 'error') {
649-
this._onDidWatchErrorOccur.fire(msg.message);
650-
}
651-
652-
this.logService[msg.type](msg.message);
653-
},
654-
this.logService.getLevel() === LogLevel.Trace
655-
);
656-
657-
// Apply log levels dynamicaly
658-
this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(() => {
659-
this.recursiveWatcher?.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace);
660-
});
661-
}
662-
}
663-
664662
private watchNonRecursive(resource: URI): IDisposable {
665663
const watcherService = new NodeJSWatcherService(
666664
this.toFilePath(resource),
@@ -741,14 +739,4 @@ export class DiskFileSystemProvider extends Disposable implements
741739
}
742740

743741
//#endregion
744-
745-
override dispose(): void {
746-
super.dispose();
747-
748-
dispose(this.recursiveWatcher);
749-
this.recursiveWatcher = undefined;
750-
751-
dispose(this.recursiveWatcherLogLevelListener);
752-
this.recursiveWatcherLogLevelListener = undefined;
753-
}
754742
}

src/vs/platform/sharedProcess/common/sharedProcessWorkerService.ts

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

6+
import { hash as hashObject } from 'vs/base/common/hash';
67
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
78

89
export interface ISharedProcessWorkerProcess {
@@ -28,7 +29,7 @@ export interface ISharedProcessWorkerConfiguration {
2829

2930
/**
3031
* Configuration specific for how to respond with the
31-
* communication message port.
32+
* communication message port to the receiver window.
3233
*/
3334
reply: {
3435
windowId: number;
@@ -37,6 +38,19 @@ export interface ISharedProcessWorkerConfiguration {
3738
};
3839
}
3940

41+
/**
42+
* Converts the process configuration into a hash to
43+
* identify processes of the same kind by taking those
44+
* components that make the process and reply unique.
45+
*/
46+
export function hash(configuration: ISharedProcessWorkerConfiguration): number {
47+
return hashObject({
48+
moduleId: configuration.process.moduleId,
49+
windowId: configuration.reply.windowId,
50+
channelId: configuration.reply.channel
51+
});
52+
}
53+
4054
export const ISharedProcessWorkerService = createDecorator<ISharedProcessWorkerService>('sharedProcessWorkerService');
4155

4256
export const ipcSharedProcessWorkerChannelName = 'sharedProcessWorker';
@@ -46,10 +60,22 @@ export interface ISharedProcessWorkerService {
4660
readonly _serviceBrand: undefined;
4761

4862
/**
49-
* Forks the provided process from the passed in configuration inside
50-
* the shared process and establishes a `MessagePort` communication
51-
* channel that is being sent back to via the `reply` options of the
52-
* configuration.
63+
* Will fork a new process with the provided module identifier off the shared
64+
* process and establishes a message port connection to that process. The other
65+
* end of the message port connection will be sent back to the calling window
66+
* as identified by the `reply` configuration.
67+
*
68+
* Requires the forked process to be AMD module that uses our IPC channel framework
69+
* to respond to the provided `channelName` as a server.
70+
*
71+
* The process will be automatically terminated when the receiver window closes,
72+
* crashes or loads/reloads. It can also explicitly be terminated by calling
73+
* `disposeWorker`.
5374
*/
5475
createWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void>;
76+
77+
/**
78+
* Terminates the process for the provided configuration if any.
79+
*/
80+
disposeWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void>;
5581
}

src/vs/platform/sharedProcess/electron-browser/sharedProcessWorker.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import { ISharedProcessWorkerConfiguration } from 'vs/platform/sharedProcess/com
77

88
export enum SharedProcessWorkerMessages {
99

10-
// Message Port Exchange
11-
RequestPort = 'vscode:requestSharedProcessWorkerPort',
12-
ReceivePort = 'vscode:receiveSharedProcessWorkerPort',
10+
// Process
11+
WorkerSpawn = 'vscode:shared-process->shared-process-worker=spawn',
12+
WorkerTerminate = 'vscode:shared-process->shared-process-worker=terminate',
1313

1414
// Lifecycle
15-
WorkerReady = 'vscode:sharedProcessWorkerReady',
15+
WorkerReady = 'vscode:shared-process-worker->shared-process=ready',
16+
WorkerAck = 'vscode:shared-process-worker->shared-process=ack',
1617

1718
// Diagnostics
18-
WorkerTrace = 'vscode:sharedProcessWorkerTrace',
19-
WorkerWarn = 'vscode:sharedProcessWorkerWarn',
20-
WorkerError = 'vscode:sharedProcessWorkerError'
19+
WorkerTrace = 'vscode:shared-process-worker->shared-process=trace',
20+
WorkerInfo = 'vscode:shared-process-worker->shared-process=info',
21+
WorkerWarn = 'vscode:shared-process-worker->shared-process=warn',
22+
WorkerError = 'vscode:shared-process-worker->shared-process=error'
2123
}
2224

2325
export interface ISharedProcessWorkerEnvironment {
@@ -31,10 +33,12 @@ export interface ISharedProcessWorkerEnvironment {
3133
export interface ISharedProcessToWorkerMessage {
3234
id: string;
3335
configuration: ISharedProcessWorkerConfiguration;
34-
environment: ISharedProcessWorkerEnvironment;
36+
environment?: ISharedProcessWorkerEnvironment;
37+
nonce?: string;
3538
}
3639

3740
export interface IWorkerToSharedProcessMessage {
3841
id: string;
3942
message?: string;
43+
nonce?: string;
4044
}

0 commit comments

Comments
 (0)