Skip to content

Commit

Permalink
sandbox - implement affinity and lifecycle for shared process workers (
Browse files Browse the repository at this point in the history
  • Loading branch information
bpasero committed Oct 17, 2021
1 parent 1bfac7c commit 729d816
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 146 deletions.
28 changes: 23 additions & 5 deletions src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel';
import { TunnelService } from 'vs/platform/remote/node/tunnelService';
import { ipcSharedProcessTunnelChannelName, ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService';
import { SharedProcessTunnelService } from 'vs/platform/remote/node/sharedProcessTunnelService';
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { ipcSharedProcessWorkerChannelName, ISharedProcessWorkerConfiguration, ISharedProcessWorkerService } from 'vs/platform/sharedProcess/common/sharedProcessWorkerService';
import { SharedProcessWorkerService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessWorkerService';

class SharedProcessMain extends Disposable {

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

private sharedProcessWorkerService: ISharedProcessWorkerService | undefined = undefined;

constructor(private configuration: ISharedProcessConfiguration) {
super();

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

private registerListeners(): void {

// Dispose on exit
// Shared process lifecycle
const onExit = () => this.dispose();
process.once('exit', onExit);
ipcRenderer.once('vscode:electron-main->shared-process=exit', onExit);

// Shared process worker lifecycle
//
// We dispose the listener when the shared process is
// disposed to avoid disposing workers when the entire
// application is shutting down anyways.
//
const eventName = 'vscode:electron-main->shared-process=disposeWorker';
const onDisposeWorker = (event: unknown, configuration: ISharedProcessWorkerConfiguration) => this.onDisposeWorker(configuration);
ipcRenderer.on(eventName, onDisposeWorker);
this._register(toDisposable(() => ipcRenderer.removeListener(eventName, onDisposeWorker)));
}

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

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

// Worker
services.set(ISharedProcessWorkerService, new SyncDescriptor(SharedProcessWorkerService));

// Environment
const environmentService = new NativeEnvironmentService(this.configuration.args, productService);
services.set(INativeEnvironmentService, environmentService);
Expand All @@ -183,6 +197,10 @@ class SharedProcessMain extends Disposable {
const logService = this._register(new FollowerLogService(logLevelClient, multiplexLogger));
services.set(ILogService, logService);

// Worker
this.sharedProcessWorkerService = new SharedProcessWorkerService(logService);
services.set(ISharedProcessWorkerService, this.sharedProcessWorkerService);

// Files
const fileService = this._register(new FileService(logService));
services.set(IFileService, fileService);
Expand Down
72 changes: 30 additions & 42 deletions src/vs/platform/files/node/diskFileSystemProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { isEqual } from 'vs/base/common/extpath';
import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { basename, dirname, normalize } from 'vs/base/common/path';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { joinPath } from 'vs/base/common/resources';
Expand Down Expand Up @@ -536,8 +536,6 @@ export class DiskFileSystemProvider extends Disposable implements
private readonly recursiveFoldersToWatch: IWatchRequest[] = [];
private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(0));

private recursiveWatcherLogLevelListener: IDisposable | undefined;

watch(resource: URI, opts: IWatchOptions): IDisposable {
if (opts.recursive) {
return this.watchRecursive(resource, opts);
Expand Down Expand Up @@ -574,6 +572,35 @@ export class DiskFileSystemProvider extends Disposable implements
});
}

private doRefreshRecursiveWatchers(): void {

// Reuse existing
if (this.recursiveWatcher) {
this.recursiveWatcher.watch(this.recursiveFoldersToWatch);
}

// Otherwise, create new if we have folders to watch
else if (this.recursiveFoldersToWatch.length > 0) {
this.recursiveWatcher = this._register(this.createRecursiveWatcher(
this.recursiveFoldersToWatch,
changes => this._onDidChangeFile.fire(toFileChanges(changes)),
msg => {
if (msg.type === 'error') {
this._onDidWatchErrorOccur.fire(msg.message);
}

this.logService[msg.type](msg.message);
},
this.logService.getLevel() === LogLevel.Trace
));

// Apply log levels dynamicaly

This comment has been minimized.

Copy link
@jogo-

jogo- Oct 18, 2021

Contributor

Typo: dynamicaly -> dynamically

this._register(this.logService.onDidChangeLogLevel(() => {
this.recursiveWatcher?.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace);
}));
}
}

protected createRecursiveWatcher(
folders: IWatchRequest[],
onChange: (changes: IDiskFileChange[]) => void,
Expand Down Expand Up @@ -632,35 +659,6 @@ export class DiskFileSystemProvider extends Disposable implements
);
}

private doRefreshRecursiveWatchers(): void {

// Reuse existing
if (this.recursiveWatcher) {
this.recursiveWatcher.watch(this.recursiveFoldersToWatch);
}

// Otherwise, create new if we have folders to watch
else if (this.recursiveFoldersToWatch.length > 0) {
this.recursiveWatcher = this.createRecursiveWatcher(
this.recursiveFoldersToWatch,
changes => this._onDidChangeFile.fire(toFileChanges(changes)),
msg => {
if (msg.type === 'error') {
this._onDidWatchErrorOccur.fire(msg.message);
}

this.logService[msg.type](msg.message);
},
this.logService.getLevel() === LogLevel.Trace
);

// Apply log levels dynamicaly
this.recursiveWatcherLogLevelListener = this.logService.onDidChangeLogLevel(() => {
this.recursiveWatcher?.setVerboseLogging(this.logService.getLevel() === LogLevel.Trace);
});
}
}

private watchNonRecursive(resource: URI): IDisposable {
const watcherService = new NodeJSWatcherService(
this.toFilePath(resource),
Expand Down Expand Up @@ -741,14 +739,4 @@ export class DiskFileSystemProvider extends Disposable implements
}

//#endregion

override dispose(): void {
super.dispose();

dispose(this.recursiveWatcher);
this.recursiveWatcher = undefined;

dispose(this.recursiveWatcherLogLevelListener);
this.recursiveWatcherLogLevelListener = undefined;
}
}
36 changes: 31 additions & 5 deletions src/vs/platform/sharedProcess/common/sharedProcessWorkerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

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

export interface ISharedProcessWorkerProcess {
Expand All @@ -28,7 +29,7 @@ export interface ISharedProcessWorkerConfiguration {

/**
* Configuration specific for how to respond with the
* communication message port.
* communication message port to the receiver window.
*/
reply: {
windowId: number;
Expand All @@ -37,6 +38,19 @@ export interface ISharedProcessWorkerConfiguration {
};
}

/**
* Converts the process configuration into a hash to
* identify processes of the same kind by taking those
* components that make the process and reply unique.
*/
export function hash(configuration: ISharedProcessWorkerConfiguration): number {
return hashObject({
moduleId: configuration.process.moduleId,
windowId: configuration.reply.windowId,
channelId: configuration.reply.channel
});
}

export const ISharedProcessWorkerService = createDecorator<ISharedProcessWorkerService>('sharedProcessWorkerService');

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

/**
* Forks the provided process from the passed in configuration inside
* the shared process and establishes a `MessagePort` communication
* channel that is being sent back to via the `reply` options of the
* configuration.
* Will fork a new process with the provided module identifier off the shared
* process and establishes a message port connection to that process. The other
* end of the message port connection will be sent back to the calling window
* as identified by the `reply` configuration.
*
* Requires the forked process to be AMD module that uses our IPC channel framework
* to respond to the provided `channelName` as a server.
*
* The process will be automatically terminated when the receiver window closes,
* crashes or loads/reloads. It can also explicitly be terminated by calling
* `disposeWorker`.
*/
createWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void>;

/**
* Terminates the process for the provided configuration if any.
*/
disposeWorker(configuration: ISharedProcessWorkerConfiguration): Promise<void>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,19 @@ import { ISharedProcessWorkerConfiguration } from 'vs/platform/sharedProcess/com

export enum SharedProcessWorkerMessages {

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

// Lifecycle
WorkerReady = 'vscode:sharedProcessWorkerReady',
WorkerReady = 'vscode:shared-process-worker->shared-process=ready',
WorkerAck = 'vscode:shared-process-worker->shared-process=ack',

// Diagnostics
WorkerTrace = 'vscode:sharedProcessWorkerTrace',
WorkerWarn = 'vscode:sharedProcessWorkerWarn',
WorkerError = 'vscode:sharedProcessWorkerError'
WorkerTrace = 'vscode:shared-process-worker->shared-process=trace',
WorkerInfo = 'vscode:shared-process-worker->shared-process=info',
WorkerWarn = 'vscode:shared-process-worker->shared-process=warn',
WorkerError = 'vscode:shared-process-worker->shared-process=error'
}

export interface ISharedProcessWorkerEnvironment {
Expand All @@ -31,10 +33,12 @@ export interface ISharedProcessWorkerEnvironment {
export interface ISharedProcessToWorkerMessage {
id: string;
configuration: ISharedProcessWorkerConfiguration;
environment: ISharedProcessWorkerEnvironment;
environment?: ISharedProcessWorkerEnvironment;
nonce?: string;
}

export interface IWorkerToSharedProcessMessage {
id: string;
message?: string;
nonce?: string;
}

0 comments on commit 729d816

Please sign in to comment.