提交 3e6535d8 编写于 作者: B Benjamin Pasero

shared process - implement message port connections and wire in

上级 cd906568
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
/**
* An implementation of a `IPCClient` on top of DOM `MessagePort`.
*/
export class Client extends MessagePortClient implements IDisposable {
/**
* @param clientId a way to uniquely identify this client among
* other clients. this is important for routing because every
* client can also be a server
*/
constructor(port: MessagePort, clientId: string) {
super(port, clientId);
}
}
...@@ -28,7 +28,7 @@ export class Protocol implements IMessagePassingProtocol { ...@@ -28,7 +28,7 @@ export class Protocol implements IMessagePassingProtocol {
} }
} }
dispose(): void { disconnect(): void {
this.sender.send('vscode:disconnect', null); this.sender.send('vscode:disconnect', null);
} }
} }
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
import { IDisposable } from 'vs/base/common/lifecycle';
import { VSBuffer } from 'vs/base/common/buffer';
/**
* Declare minimal `MessageEvent` and `MessagePort` interfaces here
* so that this utility can be used both from `browser` and
* `electron-main` namespace where message ports are available.
*/
export interface MessageEvent {
/**
* For our use we only consider `Uint8Array` and `disconnect`
* a valid data transfer via message ports. Our protocol
* implementation is buffer based and we only need the explicit
* `disconnect` because message ports currently have no way of
* indicating when their connection is closed.
*/
data: Uint8Array | 'disconnect';
}
export interface MessagePort {
addEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
removeEventListener(type: 'message', listener: (this: MessagePort, e: MessageEvent) => unknown): void;
postMessage(message: Uint8Array | 'disconnect'): void;
start(): void;
close(): void;
}
/**
* The MessagePort `Protocol` leverages MessagePort style IPC communication
* for the implementation of the `IMessagePassingProtocol`. That style of API
* is a simple `onmessage` / `postMessage` pattern.
*/
export class Protocol implements IMessagePassingProtocol {
private readonly onRawData = Event.fromDOMEventEmitter<VSBuffer | 'disconnect'>(this.port, 'message', (e: MessageEvent) => e.data === 'disconnect' ? e.data : VSBuffer.wrap(e.data));
readonly onMessage = Event.filter(this.onRawData, data => data !== 'disconnect') as Event<VSBuffer>;
readonly onDisconnect = Event.signal(Event.filter(this.onRawData, data => data === 'disconnect'));
constructor(private port: MessagePort) {
// we must call start() to ensure messages are flowing
port.start();
// when the other end disconnects, ensure that we close
// our end as well to stay in sync
Event.once(this.onDisconnect)(() => port.close());
}
send(message: VSBuffer): void {
this.port.postMessage(message.buffer);
}
disconnect(): void {
this.port.postMessage('disconnect');
this.port.close();
}
}
/**
* An implementation of a `IPCClient` on top of MessagePort style IPC communication.
*/
export class Client extends IPCClient implements IDisposable {
private protocol: Protocol;
constructor(port: MessagePort, clientId: string) {
const protocol = new Protocol(port);
super(protocol, clientId);
this.protocol = protocol;
}
dispose(): void {
this.protocol.disconnect();
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { BrowserWindow, ipcMain, IpcMainEvent, MessagePortMain } from 'electron';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { generateUuid } from 'vs/base/common/uuid';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
/**
* An implementation of a `IPCClient` on top of Electron `MessagePortMain`.
*/
export class Client extends MessagePortClient implements IDisposable {
/**
* @param clientId a way to uniquely identify this client among
* other clients. this is important for routing because every
* client can also be a server
*/
constructor(port: MessagePortMain, clientId: string) {
super({
addEventListener: (type, listener) => port.addListener(type, listener),
removeEventListener: (type, listener) => port.removeListener(type, listener),
postMessage: message => port.postMessage(message),
start: () => port.start(),
close: () => port.close()
}, clientId);
}
}
/**
* This method opens a message channel connection
* in the target window. The target window needs
* to use the `Server` from `electron-sandbox/ipc.mp`.
*/
export async function connect(window: BrowserWindow): Promise<MessagePortMain> {
// Ask to create message channel inside the window
// and send over a UUID to correlate the response
const nonce = generateUuid();
window.webContents.send('vscode:createMessageChannel', nonce);
// Wait until the window has returned the `MessagePort`
// We need to filter by the `nonce` to ensure we listen
// to the right response.
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePortMain }>(ipcMain, 'vscode:createMessageChannelResult', (e: IpcMainEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
return port;
}
...@@ -33,6 +33,6 @@ export class Client extends IPCClient implements IDisposable { ...@@ -33,6 +33,6 @@ export class Client extends IPCClient implements IDisposable {
} }
dispose(): void { dispose(): void {
this.protocol.dispose(); this.protocol.disconnect();
} }
} }
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { Event } from 'vs/base/common/event';
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { Protocol as MessagePortProtocol } from 'vs/base/parts/ipc/common/ipc.mp';
/**
* An implementation of a `IPCServer` on top of MessagePort style IPC communication.
* The clients register themselves via Electron IPC transfer.
*/
export class Server extends IPCServer {
private static getOnDidClientConnect(): Event<ClientConnectionEvent> {
// Clients connect via `vscode:createMessageChannel` to get a
// `MessagePort` that is ready to be used. For every connection
// we create a pair of message ports and send it back.
//
// The `nonce` is included so that the main side has a chance to
// correlate the response back to the sender.
const onCreateMessageChannel = Event.fromNodeEventEmitter<string>(ipcRenderer, 'vscode:createMessageChannel', (_, nonce: string) => nonce);
return Event.map(onCreateMessageChannel, nonce => {
// Create a new pair of ports and protocol for this connection
const { port1: incomingPort, port2: outgoingPort } = new MessageChannel();
const protocol = new MessagePortProtocol(incomingPort);
const result: ClientConnectionEvent = { protocol, onDidClientDisconnect: protocol.onDisconnect };
// Send one port back to the requestor
ipcRenderer.postMessage('vscode:createMessageChannelResult', nonce, [outgoingPort]);
return result;
});
}
constructor() {
super(Server.getOnDidClientConnect());
}
}
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
import product from 'vs/platform/product/common/product'; import product from 'vs/platform/product/common/product';
import * as fs from 'fs'; import * as fs from 'fs';
import { gracefulify } from 'graceful-fs'; import { gracefulify } from 'graceful-fs';
import { isWindows } from 'vs/base/common/platform'; import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
import { IChannel, IServerChannel, StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc'; import { StaticRouter, createChannelSender, createChannelReceiver } from 'vs/base/parts/ipc/common/ipc';
import { serve as nodeIPCServe, Server as NodeIPCServer, connect as nodeIPCConnect } from 'vs/base/parts/ipc/node/ipc.net';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
...@@ -40,7 +39,7 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co ...@@ -40,7 +39,7 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { IMainProcessService, MessagePortMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { SpdLogService } from 'vs/platform/log/node/spdlogService'; import { SpdLogService } from 'vs/platform/log/node/spdlogService';
import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; import { DiagnosticsService, IDiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService';
import { FileService } from 'vs/platform/files/common/fileService'; import { FileService } from 'vs/platform/files/common/fileService';
...@@ -79,34 +78,16 @@ import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProc ...@@ -79,34 +78,16 @@ import { DeprecatedExtensionsCleaner } from 'vs/code/electron-browser/sharedProc
import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage'; import { toErrorMessage } from 'vs/base/common/errorMessage';
class MainProcessService implements IMainProcessService {
declare readonly _serviceBrand: undefined;
constructor(
private server: NodeIPCServer,
private mainRouter: StaticRouter
) { }
getChannel(channelName: string): IChannel {
return this.server.getChannel(channelName, this.mainRouter);
}
registerChannel(channelName: string, channel: IServerChannel<string>): void {
this.server.registerChannel(channelName, channel);
}
}
class SharedProcessMain extends Disposable { class SharedProcessMain extends Disposable {
constructor(private server: NodeIPCServer, private configuration: ISharedProcessConfiguration) { private server = this._register(new MessagePortServer());
constructor(private configuration: ISharedProcessConfiguration) {
super(); super();
// Enable gracefulFs // Enable gracefulFs
gracefulify(fs); gracefulify(fs);
this._register(this.server);
this.registerListeners(); this.registerListeners();
} }
...@@ -160,7 +141,7 @@ class SharedProcessMain extends Disposable { ...@@ -160,7 +141,7 @@ class SharedProcessMain extends Disposable {
// Log // Log
const mainRouter = new StaticRouter(ctx => ctx === 'main'); const mainRouter = new StaticRouter(ctx => ctx === 'main');
const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); const loggerClient = new LoggerChannelClient(this.server.getChannel('logger', mainRouter)); // we only use this for log levels
const multiplexLogger = this._register(new MultiplexLogService([ const multiplexLogger = this._register(new MultiplexLogService([
this._register(new ConsoleLogService(this.configuration.logLevel)), this._register(new ConsoleLogService(this.configuration.logLevel)),
this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel)) this._register(new SpdLogService('sharedprocess', environmentService.logsPath, this.configuration.logLevel))
...@@ -170,7 +151,7 @@ class SharedProcessMain extends Disposable { ...@@ -170,7 +151,7 @@ class SharedProcessMain extends Disposable {
services.set(ILogService, logService); services.set(ILogService, logService);
// Main Process // Main Process
const mainProcessService = new MainProcessService(this.server, mainRouter); const mainProcessService = new MessagePortMainProcessService(this.server, mainRouter);
services.set(IMainProcessService, mainProcessService); services.set(IMainProcessService, mainProcessService);
// Files // Files
...@@ -345,48 +326,14 @@ class SharedProcessMain extends Disposable { ...@@ -345,48 +326,14 @@ class SharedProcessMain extends Disposable {
} }
} }
function setupNodeIPC(hook: string): Promise<NodeIPCServer> {
function setup(retry: boolean): Promise<NodeIPCServer> {
return nodeIPCServe(hook).then(null, err => {
if (!retry || isWindows || err.code !== 'EADDRINUSE') {
return Promise.reject(err);
}
// should retry, not windows and eaddrinuse
return nodeIPCConnect(hook, '').then(
client => {
// we could connect to a running instance. this is not good, abort
client.dispose();
return Promise.reject(new Error('There is an instance already running.'));
},
err => {
// it happens on Linux and OS X that the pipe is left behind
// let's delete it, since we can't connect to it
// and the retry the whole thing
try {
fs.unlinkSync(hook);
} catch (e) {
return Promise.reject(new Error('Error deleting the shared ipc hook.'));
}
return setup(false);
}
);
});
}
return setup(true);
}
export async function main(configuration: ISharedProcessConfiguration): Promise<void> { export async function main(configuration: ISharedProcessConfiguration): Promise<void> {
// await IPC connection and signal this back to electron-main // create shared process and signal back to main that we are
const server = await setupNodeIPC(configuration.sharedIPCHandle); // ready to accept message ports as client connections
const sharedProcess = new SharedProcessMain(configuration);
ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready'); ipcRenderer.send('vscode:shared-process->electron-main=ipc-ready');
// await initialization and signal this back to electron-main // await initialization and signal this back to electron-main
const sharedProcess = new SharedProcessMain(server, configuration);
await sharedProcess.open(); await sharedProcess.open();
ipcRenderer.send('vscode:shared-process->electron-main=init-done'); ipcRenderer.send('vscode:shared-process->electron-main=init-done');
} }
...@@ -13,8 +13,8 @@ import { IUpdateService } from 'vs/platform/update/common/update'; ...@@ -13,8 +13,8 @@ import { IUpdateService } from 'vs/platform/update/common/update';
import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc'; import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc';
import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc'; import { getDelayedChannel, StaticRouter, createChannelReceiver, createChannelSender } from 'vs/base/parts/ipc/common/ipc';
import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron';
import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net'; import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net';
import { Server as NodeIPCServer, connect as nodeIPCConnect } from 'vs/base/parts/ipc/node/ipc.net'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; import { SharedProcess } from 'vs/code/electron-main/sharedProcess';
import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService'; import { LaunchMainService, ILaunchMainService } from 'vs/platform/launch/electron-main/launchMainService';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
...@@ -426,16 +426,20 @@ export class CodeApplication extends Disposable { ...@@ -426,16 +426,20 @@ export class CodeApplication extends Disposable {
// Spawn shared process after the first window has opened and 3s have passed // Spawn shared process after the first window has opened and 3s have passed
const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv);
const sharedProcessClient = sharedProcess.whenIpcReady().then(() => { const sharedProcessClient = (async () => {
this.logService.trace('Shared process: IPC ready'); this.logService.trace('Main->SharedProcess#connect');
return nodeIPCConnect(this.environmentService.sharedIPCHandle, 'main'); const port = await sharedProcess.connect();
});
const sharedProcessReady = sharedProcess.whenReady().then(() => { this.logService.trace('Main->SharedProcess#connect: connection established');
this.logService.trace('Shared process: init ready');
return new MessagePortClient(port, 'main');
})();
const sharedProcessReady = (async () => {
await sharedProcess.whenReady();
return sharedProcessClient; return sharedProcessClient;
}); })();
this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => {
this._register(new RunOnceScheduler(async () => { this._register(new RunOnceScheduler(async () => {
sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env)); sharedProcess.spawn(await resolveShellEnv(this.logService, this.environmentService.args, process.env));
...@@ -482,7 +486,7 @@ export class CodeApplication extends Disposable { ...@@ -482,7 +486,7 @@ export class CodeApplication extends Disposable {
return machineId; return machineId;
} }
private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<NodeIPCClient<string>>): Promise<IInstantiationService> { private async createServices(machineId: string, sharedProcess: SharedProcess, sharedProcessReady: Promise<MessagePortClient>): Promise<IInstantiationService> {
const services = new ServiceCollection(); const services = new ServiceCollection();
switch (process.platform) { switch (process.platform) {
...@@ -584,7 +588,7 @@ export class CodeApplication extends Disposable { ...@@ -584,7 +588,7 @@ export class CodeApplication extends Disposable {
}); });
} }
private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<NodeIPCClient<string>>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] { private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise<MessagePortClient>, fileProtocolHandler: FileProtocolHandler): ICodeWindow[] {
// Register more Main IPC services // Register more Main IPC services
const launchMainService = accessor.get(ILaunchMainService); const launchMainService = accessor.get(ILaunchMainService);
......
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { memoize } from 'vs/base/common/decorators'; import { BrowserWindow, ipcMain, Event, MessagePortMain } from 'electron';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { BrowserWindow, ipcMain, Event } from 'electron';
import { ISharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcessMainService'; import { ISharedProcess } from 'vs/platform/sharedProcess/electron-main/sharedProcessMainService';
import { Barrier } from 'vs/base/common/async'; import { Barrier } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
...@@ -15,14 +14,13 @@ import { FileAccess } from 'vs/base/common/network'; ...@@ -15,14 +14,13 @@ import { FileAccess } from 'vs/base/common/network';
import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform'; import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform';
import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess'; import { ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { assertIsDefined } from 'vs/base/common/types';
export class SharedProcess extends Disposable implements ISharedProcess { export class SharedProcess extends Disposable implements ISharedProcess {
private readonly whenSpawnedBarrier = new Barrier(); private readonly whenSpawnedBarrier = new Barrier();
// overall ready promise when shared process signals initialization is done
private readonly _whenReady = new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=init-done', () => resolve()));
private window: BrowserWindow | undefined = undefined; private window: BrowserWindow | undefined = undefined;
private windowCloseListener: ((event: Event) => void) | undefined = undefined; private windowCloseListener: ((event: Event) => void) | undefined = undefined;
...@@ -40,7 +38,16 @@ export class SharedProcess extends Disposable implements ISharedProcess { ...@@ -40,7 +38,16 @@ export class SharedProcess extends Disposable implements ISharedProcess {
} }
private registerListeners(): void { private registerListeners(): void {
// Lifecycle
this._register(this.lifecycleMainService.onWillShutdown(() => this.onWillShutdown())); this._register(this.lifecycleMainService.onWillShutdown(() => this.onWillShutdown()));
// Shared process connections
ipcMain.on('vscode:createSharedProcessMessageChannel', async (e, nonce: string) => {
const port = await this.connect();
e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]);
});
} }
private onWillShutdown(): void { private onWillShutdown(): void {
...@@ -50,7 +57,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { ...@@ -50,7 +57,7 @@ export class SharedProcess extends Disposable implements ISharedProcess {
} }
// Signal exit to shared process when shutting down // Signal exit to shared process when shutting down
window.webContents.send('vscode:electron-main->shared-process=exit'); // TODO verify call window.webContents.send('vscode:electron-main->shared-process=exit');
// Shut the shared process down when we are quitting // Shut the shared process down when we are quitting
// //
...@@ -75,17 +82,45 @@ export class SharedProcess extends Disposable implements ISharedProcess { ...@@ -75,17 +82,45 @@ export class SharedProcess extends Disposable implements ISharedProcess {
}, 0); }, 0);
} }
@memoize private _whenReady: Promise<void> | undefined = undefined;
private get _whenIpcReady(): Promise<void> { whenReady(): Promise<void> {
if (!this._whenReady) {
// Overall signal that the shared process window was loaded and
// all services within have been created.
this._whenReady = new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=init-done', () => {
this.logService.trace('SharedProcess: Overall ready');
resolve();
}));
}
return this._whenReady;
}
private _whenIpcReady: Promise<void> | undefined = undefined;
private get whenIpcReady() {
if (!this._whenIpcReady) {
this._whenIpcReady = (async () => {
// Always wait for `spawn()`
await this.whenSpawnedBarrier.wait();
// Create window for shared process
this.createWindow();
// Create window for shared process // Listeners
this.createWindow(); this.registerWindowListeners();
// Listeners // Wait for window indicating that IPC connections are accepted
this.registerWindowListeners(); await new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => {
this.logService.trace('SharedProcess: IPC ready');
resolve();
}));
})();
}
// complete IPC-ready promise when shared process signals this to us return this._whenIpcReady;
return new Promise<void>(resolve => ipcMain.once('vscode:shared-process->electron-main=ipc-ready', () => resolve(undefined)));
} }
private createWindow(): void { private createWindow(): void {
...@@ -151,7 +186,7 @@ export class SharedProcess extends Disposable implements ISharedProcess { ...@@ -151,7 +186,7 @@ export class SharedProcess extends Disposable implements ISharedProcess {
// Crashes & Unrsponsive & Failed to load // Crashes & Unrsponsive & Failed to load
this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`[VS Code]: sharedProcess crashed (detail: ${details?.reason})`)); this.window.webContents.on('render-process-gone', (event, details) => this.logService.error(`[VS Code]: sharedProcess crashed (detail: ${details?.reason})`));
this.window.on('unresponsive', () => this.logService.error('[VS Code]: detected unresponsive sharedProcess window')); this.window.on('unresponsive', () => this.logService.error('[VS Code]: detected unresponsive sharedProcess window'));
this.window.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string) => this.logService.warn('[VS Code]: fail to load sharedProcess window, ', errorDescription)); this.window.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('[VS Code]: fail to load sharedProcess window, ', errorDescription));
} }
spawn(userEnv: NodeJS.ProcessEnv): void { spawn(userEnv: NodeJS.ProcessEnv): void {
...@@ -161,20 +196,14 @@ export class SharedProcess extends Disposable implements ISharedProcess { ...@@ -161,20 +196,14 @@ export class SharedProcess extends Disposable implements ISharedProcess {
this.whenSpawnedBarrier.open(); this.whenSpawnedBarrier.open();
} }
async whenReady(): Promise<void> { async connect(): Promise<MessagePortMain> {
// Always wait for `spawn()`
await this.whenSpawnedBarrier.wait();
await this._whenReady;
}
async whenIpcReady(): Promise<void> {
// Always wait for `spawn()` // Wait for shared process being ready to accept connection
await this.whenSpawnedBarrier.wait(); await this.whenIpcReady;
await this._whenIpcReady; // Connect and return message port
const window = assertIsDefined(this.window);
return connectMessagePort(window);
} }
toggle(): void { toggle(): void {
......
...@@ -415,7 +415,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { ...@@ -415,7 +415,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Crashes & Unrsponsive & Failed to load // Crashes & Unrsponsive & Failed to load
this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details)); this._win.webContents.on('render-process-gone', (event, details) => this.onWindowError(WindowError.CRASHED, details));
this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE)); this._win.on('unresponsive', () => this.onWindowError(WindowError.UNRESPONSIVE));
this._win.webContents.on('did-fail-load', (event: Event, errorCode: number, errorDescription: string) => this.logService.warn('[VS Code]: fail to load workbench window, ', errorDescription)); this._win.webContents.on('did-fail-load', (event, errorCode, errorDescription) => this.logService.warn('[VS Code]: fail to load workbench window, ', errorDescription));
// Window close // Window close
this._win.on('closed', () => { this._win.on('closed', () => {
......
...@@ -22,7 +22,7 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; ...@@ -22,7 +22,7 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
import { Codicon } from 'vs/base/common/codicons'; import { Codicon } from 'vs/base/common/codicons';
...@@ -266,7 +266,7 @@ export class IssueReporter extends Disposable { ...@@ -266,7 +266,7 @@ export class IssueReporter extends Disposable {
private initServices(configuration: IssueReporterConfiguration): void { private initServices(configuration: IssueReporterConfiguration): void {
const serviceCollection = new ServiceCollection(); const serviceCollection = new ServiceCollection();
const mainProcessService = new MainProcessService(configuration.windowId); const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId);
serviceCollection.set(IMainProcessService, mainProcessService); serviceCollection.set(IMainProcessService, mainProcessService);
this.nativeHostService = new NativeHostService(configuration.windowId, mainProcessService) as INativeHostService; this.nativeHostService = new NativeHostService(configuration.windowId, mainProcessService) as INativeHostService;
......
...@@ -17,7 +17,7 @@ import { ProcessItem } from 'vs/base/common/processes'; ...@@ -17,7 +17,7 @@ import { ProcessItem } from 'vs/base/common/processes';
import * as dom from 'vs/base/browser/dom'; import * as dom from 'vs/base/browser/dom';
import { DisposableStore } from 'vs/base/common/lifecycle'; import { DisposableStore } from 'vs/base/common/lifecycle';
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics'; import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
import { MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ByteSize } from 'vs/platform/files/common/files'; import { ByteSize } from 'vs/platform/files/common/files';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
...@@ -233,7 +233,7 @@ class ProcessExplorer { ...@@ -233,7 +233,7 @@ class ProcessExplorer {
private tree: DataTree<any, ProcessTree | MachineProcessInformation | ProcessItem | ProcessInformation | IRemoteDiagnosticError, any> | undefined; private tree: DataTree<any, ProcessTree | MachineProcessInformation | ProcessItem | ProcessInformation | IRemoteDiagnosticError, any> | undefined;
constructor(windowId: number, private data: ProcessExplorerData) { constructor(windowId: number, private data: ProcessExplorerData) {
const mainProcessService = new MainProcessService(windowId); const mainProcessService = new ElectronIPCMainProcessService(windowId);
this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService; this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService;
this.applyStyles(data.styles); this.applyStyles(data.styles);
......
...@@ -3,10 +3,11 @@ ...@@ -3,10 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { Client as IPCElectronClient } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron'; import { Client as IPCElectronClient } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Server } from 'vs/base/parts/ipc/electron-sandbox/ipc.mp';
export const IMainProcessService = createDecorator<IMainProcessService>('mainProcessService'); export const IMainProcessService = createDecorator<IMainProcessService>('mainProcessService');
...@@ -19,7 +20,10 @@ export interface IMainProcessService { ...@@ -19,7 +20,10 @@ export interface IMainProcessService {
registerChannel(channelName: string, channel: IServerChannel<string>): void; registerChannel(channelName: string, channel: IServerChannel<string>): void;
} }
export class MainProcessService extends Disposable implements IMainProcessService { /**
* An implementation of `IMainProcessService` that leverages Electron's IPC.
*/
export class ElectronIPCMainProcessService extends Disposable implements IMainProcessService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
...@@ -41,3 +45,24 @@ export class MainProcessService extends Disposable implements IMainProcessServic ...@@ -41,3 +45,24 @@ export class MainProcessService extends Disposable implements IMainProcessServic
this.mainProcessConnection.registerChannel(channelName, channel); this.mainProcessConnection.registerChannel(channelName, channel);
} }
} }
/**
* An implementation of `IMainProcessService` that leverages MessagePorts.
*/
export class MessagePortMainProcessService implements IMainProcessService {
declare readonly _serviceBrand: undefined;
constructor(
private server: Server,
private router: StaticRouter
) { }
getChannel(channelName: string): IChannel {
return this.server.getChannel(channelName, this.router);
}
registerChannel(channelName: string, channel: IServerChannel<string>): void {
this.server.registerChannel(channelName, channel);
}
}
...@@ -32,7 +32,7 @@ import { IWorkbenchConfigurationService } from 'vs/workbench/services/configurat ...@@ -32,7 +32,7 @@ import { IWorkbenchConfigurationService } from 'vs/workbench/services/configurat
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl';
...@@ -181,7 +181,7 @@ class DesktopMain extends Disposable { ...@@ -181,7 +181,7 @@ class DesktopMain extends Disposable {
// Main Process // Main Process
const mainProcessService = this._register(new MainProcessService(this.configuration.windowId)); const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
serviceCollection.set(IMainProcessService, mainProcessService); serviceCollection.set(IMainProcessService, mainProcessService);
// Environment // Environment
......
...@@ -19,7 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace ...@@ -19,7 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { FileService } from 'vs/platform/files/common/fileService'; import { FileService } from 'vs/platform/files/common/fileService';
...@@ -153,7 +153,7 @@ class DesktopMain extends Disposable { ...@@ -153,7 +153,7 @@ class DesktopMain extends Disposable {
// Main Process // Main Process
const mainProcessService = this._register(new MainProcessService(this.configuration.windowId)); const mainProcessService = this._register(new ElectronIPCMainProcessService(this.configuration.windowId));
serviceCollection.set(IMainProcessService, mainProcessService); serviceCollection.set(IMainProcessService, mainProcessService);
// Environment // Environment
......
...@@ -3,38 +3,66 @@ ...@@ -3,38 +3,66 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Client as NodeIPCClient } from 'vs/base/parts/ipc/common/ipc.net'; import { Event } from 'vs/base/common/event';
import { connect as nodeIPCConnect } from 'vs/base/parts/ipc/node/ipc.net'; import { IpcRendererEvent } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes';
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp';
import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc'; import { IChannel, IServerChannel, getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
import { ISharedProcessService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessService'; import { ISharedProcessService } from 'vs/platform/sharedProcess/electron-browser/sharedProcessService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
import { generateUuid } from 'vs/base/common/uuid';
import { ILogService } from 'vs/platform/log/common/log';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
export class SharedProcessService implements ISharedProcessService { export class SharedProcessService extends Disposable implements ISharedProcessService {
declare readonly _serviceBrand: undefined; declare readonly _serviceBrand: undefined;
private withSharedProcessConnection: Promise<NodeIPCClient<string>>; private readonly sharedProcessMainChannel = this.mainProcessService.getChannel('sharedProcess');
private sharedProcessMainChannel: IChannel; private readonly withSharedProcessConnection: Promise<MessagePortClient>;
constructor( constructor(
@IMainProcessService mainProcessService: IMainProcessService, @IMainProcessService private readonly mainProcessService: IMainProcessService,
@INativeHostService nativeHostService: INativeHostService, @INativeHostService private readonly nativeHostService: INativeHostService,
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService @ILogService private readonly logService: ILogService,
@ILifecycleService private readonly lifecycleService: ILifecycleService
) { ) {
this.sharedProcessMainChannel = mainProcessService.getChannel('sharedProcess'); super();
this.withSharedProcessConnection = (async () => { this.withSharedProcessConnection = this.connect();
await this.whenReady();
return nodeIPCConnect(environmentService.sharedIPCHandle, `window:${nativeHostService.windowId}`); this.registerListeners();
})();
} }
private whenReady(): Promise<void> { private registerListeners(): void {
return this.sharedProcessMainChannel.call('whenReady');
// Lifecycle
this.lifecycleService.onWillShutdown(() => this.dispose());
}
private async connect(): Promise<MessagePortClient> {
this.logService.trace('Workbench->SharedProcess#connect');
// await the shared process to be ready
await this.sharedProcessMainChannel.call('whenReady');
// Ask to create message channel inside the window
// and send over a UUID to correlate the response
const nonce = generateUuid();
ipcRenderer.send('vscode:createSharedProcessMessageChannel', nonce);
// Wait until the main side has returned the `MessagePort`
// We need to filter by the `nonce` to ensure we listen
// to the right response.
const onMessageChannelResult = Event.fromNodeEventEmitter<{ nonce: string, port: MessagePort }>(ipcRenderer, 'vscode:createSharedProcessMessageChannelResult', (e: IpcRendererEvent, nonce: string) => ({ nonce, port: e.ports[0] }));
const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce)));
this.logService.trace('Workbench->SharedProcess#connect: connection established');
return this._register(new MessagePortClient(port, `window:${this.nativeHostService.windowId}`));
} }
getChannel(channelName: string): IChannel { getChannel(channelName: string): IChannel {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册