lifecycle is HARD

This commit is contained in:
Joao Moreno 2016-08-04 16:40:08 +02:00
parent 94466a2da5
commit 2f4aa9dee1
4 changed files with 70 additions and 44 deletions

View file

@ -3,8 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc';
import Event, { mapEvent, filterEvent } from 'vs/base/common/event';
import { fromEventEmitter } from 'vs/base/node/event';
import { Server as IPCServer, Client as IPCClient, IServer, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc';
const Hello = 'ipc:hello';
@ -21,16 +23,14 @@ class Protocol implements IMessagePassingProtocol {
private listener: IDisposable;
constructor(private sender: Sender, private receiver: NodeJS.EventEmitter) {}
constructor(private sender: Sender, private onMessageEvent: Event<any>) {}
send(message: any): void {
this.sender.send(Message, message);
}
onMessage(callback: (message: any) => void): void {
const cb = (_, m) => callback(m);
this.receiver.on(Message, cb);
this.listener = toDisposable(() => this.receiver.removeListener(Message, cb));
this.listener = this.onMessageEvent(callback);
}
dispose(): void {
@ -38,31 +38,46 @@ class Protocol implements IMessagePassingProtocol {
}
}
interface IIPCEvent {
event: any;
message: string;
}
export class Server implements IServer, IDisposable {
private channels: { [name: string]: IChannel };
private channels: { [name: string]: IChannel } = Object.create(null);
constructor(ipc: NodeJS.EventEmitter) {
this.channels = Object.create(null);
ipc.on(Hello, ({ sender }) => {
const protocol = new Protocol(sender, ipc);
const ipcServer = new IPCServer(protocol);
Object.keys(this.channels)
.forEach(name => ipcServer.registerChannel(name, this.channels[name]));
sender.once(Goodbye, () => {
ipcServer.dispose();
protocol.dispose();
});
});
constructor(private ipc: NodeJS.EventEmitter) {
ipc.on(Hello, ({ sender }) => this.onHello(sender));
}
registerChannel(channelName: string, channel: IChannel): void {
this.channels[channelName] = channel;
}
private onHello(sender: any): void {
const senderId = sender.getId();
const onMessage = this.createScopedEvent(Message, senderId);
const protocol = new Protocol(sender, onMessage);
const ipcServer = new IPCServer(protocol);
Object.keys(this.channels)
.forEach(name => ipcServer.registerChannel(name, this.channels[name]));
const onGoodbye = this.createScopedEvent(Goodbye, senderId);
const listener = onGoodbye(() => {
listener.dispose();
ipcServer.dispose();
protocol.dispose();
});
}
private createScopedEvent(eventName: string, senderId: string) {
const onRawMessageEvent = fromEventEmitter<IIPCEvent>(this.ipc, eventName, (event, message) => ({ event, message }));
const onScopedRawMessageEvent = filterEvent<IIPCEvent>(onRawMessageEvent, ({ event }) => event.sender.getId() === senderId);
return mapEvent<IIPCEvent,string>(onScopedRawMessageEvent, ({ message }) => message);
}
dispose(): void {
this.channels = null;
}
@ -75,7 +90,9 @@ export class Client implements IClient, IDisposable {
constructor(private ipc: IPC) {
ipc.send(Hello);
this.protocol = new Protocol(ipc, ipc);
const receiverEvent = fromEventEmitter(ipc, Message, (_, message) => message);
this.protocol = new Protocol(ipc, receiverEvent);
this.ipcClient = new IPCClient(this.protocol);
}
@ -86,5 +103,6 @@ export class Client implements IClient, IDisposable {
dispose(): void {
this.ipc.send(Goodbye);
this.protocol = dispose(this.protocol);
this.ipcClient = dispose(this.ipcClient);
}
}

View file

@ -148,15 +148,17 @@ export class Server {
}
}
export class Client implements IClient {
export class Client implements IClient, IDisposable {
private state: State;
private activeRequests: Promise[];
private bufferedRequests: IRequest[];
private handlers: { [id: number]: IHandler; };
private lastRequestId: number;
constructor(private protocol: IMessagePassingProtocol) {
this.state = State.Uninitialized;
this.activeRequests = [];
this.bufferedRequests = [];
this.handlers = Object.create(null);
this.lastRequestId = 0;
@ -179,11 +181,17 @@ export class Client implements IClient {
}
};
if (this.state === State.Uninitialized) {
return this.bufferRequest(request);
}
const activeRequest = this.state === State.Uninitialized
? this.bufferRequest(request)
: this.doRequest(request);
return this.doRequest(request);
this.activeRequests.push(activeRequest);
activeRequest
.then(null, _ => null)
.done(() => this.activeRequests = this.activeRequests.filter(i => i !== activeRequest));
return activeRequest;
}
private doRequest(request: IRequest): Promise {
@ -274,6 +282,11 @@ export class Client implements IClient {
// noop
}
}
dispose(): void {
this.activeRequests.forEach(r => r.cancel());
this.activeRequests = [];
}
}
export function getDelayedChannel<T extends IChannel>(promise: TPromise<IChannel>): T {

View file

@ -5,32 +5,25 @@
'use strict';
import Event, {Emitter} from 'vs/base/common/event';
import {IDisposable, dispose, toDisposable} from 'vs/base/common/lifecycle';
import Event, {mapEvent} from 'vs/base/common/event';
import {fromEventEmitter} from 'vs/base/node/event';
import {IURLService} from 'vs/platform/url/common/url';
import {app} from 'electron';
export class URLService implements IURLService, IDisposable {
export class URLService implements IURLService {
_serviceBrand: any;
private _onOpenURL = new Emitter<string>();
onOpenURL: Event<string> = this._onOpenURL.event;
private disposables: IDisposable[] = [];
onOpenURL: Event<string>;
constructor() {
const handler = (e: Electron.Event, url: string) => {
e.preventDefault();
this._onOpenURL.fire(url);
};
const rawOnOpenUrl = fromEventEmitter(app, 'open-url', (event: Electron.Event, url: string) => ({ event, url }));
app.on('open-url', handler);
this.disposables.push(toDisposable(() => app.removeListener('open-url', handler)));
this.onOpenURL = mapEvent(rawOnOpenUrl, ({ event, url }) => {
event.preventDefault();
return url;
});
// app.setAsDefaultProtocolClient('vscode');
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}

View file

@ -206,6 +206,8 @@ export class WorkbenchShell {
}
private initServiceCollection(): [InstantiationService, ServiceCollection] {
const disposables = new Disposables();
const sharedProcess = connectNet(process.env['VSCODE_SHARED_IPC_HOOK']);
sharedProcess.done(service => {
service.onClose(() => {
@ -217,6 +219,7 @@ export class WorkbenchShell {
}, errors.onUnexpectedError);
const mainProcessClient = new ElectronIPCClient(ipcRenderer);
disposables.add(mainProcessClient);
const serviceCollection = new ServiceCollection();
serviceCollection.set(IEventService, this.eventService);
@ -224,7 +227,6 @@ export class WorkbenchShell {
serviceCollection.set(IConfigurationService, this.configurationService);
const instantiationService = new InstantiationService(serviceCollection, true);
const disposables = new Disposables();
this.windowService = instantiationService.createInstance(WindowService);
serviceCollection.set(IWindowService, this.windowService);