ipc.cp.ts 5.4 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

J
Joao Moreno 已提交
6
import { ChildProcess, fork } from 'child_process';
E
Erich Gamma 已提交
7
import { IDisposable } from 'vs/base/common/lifecycle';
8
import { Promise} from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
9 10
import { Delayer } from 'vs/base/common/async';
import { clone, assign } from 'vs/base/common/objects';
J
Joao Moreno 已提交
11
import { Server as IPCServer, Client as IPCClient, IClient, IChannel } from 'vs/base/parts/ipc/common/ipc';
E
Erich Gamma 已提交
12 13 14 15

export class Server extends IPCServer {
	constructor() {
		super({
J
Joao Moreno 已提交
16
			send: r => { try { process.send(r); } catch (e) { /* not much to do */ } },
E
Erich Gamma 已提交
17 18 19 20 21 22 23
			onMessage: cb => process.on('message', cb)
		});

		process.once('disconnect', () => this.dispose());
	}
}

J
Joao Moreno 已提交
24
export interface IIPCOptions {
E
Erich Gamma 已提交
25 26 27 28 29 30 31

	/**
	 * A descriptive name for the server this connection is to. Used in logging.
	 */
	serverName: string;

	/**
J
Joao Moreno 已提交
32
	 * Time in millies before killing the ipc process. The next request after killing will start it again.
E
Erich Gamma 已提交
33 34 35 36 37 38 39 40 41
	 */
	timeout?:number;

	/**
	 * Arguments to the module to execute.
	 */
	args?:string[];

	/**
J
Joao Moreno 已提交
42
	 * Environment key-value pairs to be passed to the process that gets spawned for the ipc.
E
Erich Gamma 已提交
43 44 45 46 47 48 49 50 51 52 53 54 55 56
	 */
	env?:any;

	/**
	 * Allows to assign a debug port for debugging the application executed.
	 */
	debug?:number;

	/**
	 * Allows to assign a debug port for debugging the application and breaking it on the first line.
	 */
	debugBrk?:number;
}

J
Joao Moreno 已提交
57
export class Client implements IClient, IDisposable {
E
Erich Gamma 已提交
58 59 60

	private disposeDelayer: Delayer<void>;
	private activeRequests: Promise[];
61 62 63
	private child: ChildProcess;
	private _client: IPCClient;
	private channels: { [name: string]: IChannel };
E
Erich Gamma 已提交
64

J
Joao Moreno 已提交
65 66
	constructor(private modulePath: string, private options: IIPCOptions) {
		const timeout = options && options.timeout ? options.timeout : 60000;
E
Erich Gamma 已提交
67 68
		this.disposeDelayer = new Delayer<void>(timeout);
		this.activeRequests = [];
69
		this.child = null;
E
Erich Gamma 已提交
70
		this._client = null;
J
Joao Moreno 已提交
71
		this.channels = Object.create(null);
E
Erich Gamma 已提交
72 73
	}

J
Joao Moreno 已提交
74
	getChannel<T extends IChannel>(channelName: string): T {
J
Joao Moreno 已提交
75
		const call = (command, arg) => this.request(channelName, command, arg);
J
Joao Moreno 已提交
76
		return { call } as T;
E
Erich Gamma 已提交
77 78
	}

J
Joao Moreno 已提交
79
	protected request(channelName: string, name: string, arg: any): Promise {
E
Erich Gamma 已提交
80 81
		this.disposeDelayer.cancel();

82 83
		const channel = this.channels[channelName] || (this.channels[channelName] = this.client.getChannel(channelName));
		const request: Promise = channel.call(name, arg);
E
Erich Gamma 已提交
84 85 86 87

		// Progress doesn't propagate across 'then', we need to create a promise wrapper
		const result = new Promise((c, e, p) => {
			request.then(c, e, p).done(() => {
J
Joao Moreno 已提交
88 89 90 91
				if (!this.activeRequests) {
					return;
				}

E
Erich Gamma 已提交
92
				this.activeRequests.splice(this.activeRequests.indexOf(result), 1);
93 94 95 96

				if (this.activeRequests.length === 0) {
					this.disposeDelayer.trigger(() => this.disposeClient());
				}
E
Erich Gamma 已提交
97 98 99 100 101 102 103
			});
		}, () => request.cancel());

		this.activeRequests.push(result);
		return result;
	}

104
	private get client(): IPCClient {
E
Erich Gamma 已提交
105
		if (!this._client) {
106 107
			const args = this.options && this.options.args ? this.options.args : [];
			let forkOpts:any = undefined;
E
Erich Gamma 已提交
108

109 110
			if (this.options) {
				forkOpts = Object.create(null);
E
Erich Gamma 已提交
111

112 113 114
				if (this.options.env) {
					forkOpts.env = assign(clone(process.env), this.options.env);
				}
E
Erich Gamma 已提交
115

116 117
				if (typeof this.options.debug === 'number') {
					forkOpts.execArgv = ['--nolazy', '--debug=' + this.options.debug];
E
Erich Gamma 已提交
118
				}
119

120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
				if (typeof this.options.debugBrk === 'number') {
					forkOpts.execArgv = ['--nolazy', '--debug-brk=' + this.options.debugBrk];
				}
			}

			this.child = fork(this.modulePath, args, forkOpts);
			this._client = new IPCClient({
				send: r => this.child && this.child.connected && this.child.send(r),
				onMessage: cb => {
					this.child.on('message', (msg) => {

						// Handle console logs specially
						if (msg && msg.type === '__$console') {
							let args = ['%c[IPC Library: ' + this.options.serverName + ']', 'color: darkgreen'];
							try {
								const parsed = JSON.parse(msg.arguments);
								args = args.concat(Object.getOwnPropertyNames(parsed).map(o => parsed[o]));
							} catch (error) {
								args.push(msg.arguments);
E
Erich Gamma 已提交
139 140
							}

141 142
							console[msg.severity].apply(console, args);
						}
E
Erich Gamma 已提交
143

144 145 146 147 148 149 150
						// Anything else goes to the outside
						else {
							cb(msg);
						}
					});
				}
			});
E
Erich Gamma 已提交
151

152 153
			const onExit = () => this.disposeClient();
			process.once('exit', onExit);
E
Erich Gamma 已提交
154

155
			this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err));
E
Erich Gamma 已提交
156

157 158
			this.child.on('exit', (code: any, signal: any) => {
				process.removeListener('exit', onExit);
E
Erich Gamma 已提交
159

160 161 162 163
				if (this.activeRequests) {
					this.activeRequests.forEach(req => req.cancel());
					this.activeRequests = [];
				}
E
Erich Gamma 已提交
164

165 166 167 168 169
				if (code && signal !== 'SIGTERM') {
					console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code);
					this.disposeDelayer.cancel();
					this.disposeClient();
				}
E
Erich Gamma 已提交
170 171 172 173 174 175 176 177
			});
		}

		return this._client;
	}

	private disposeClient() {
		if (this._client) {
178 179
			this.child.kill();
			this.child = null;
180
			this._client = null;
181
			this.channels = Object.create(null);
E
Erich Gamma 已提交
182 183 184 185 186 187 188 189 190 191
		}
	}

	dispose() {
		this.disposeDelayer.cancel();
		this.disposeDelayer = null;
		this.disposeClient();
		this.activeRequests = null;
	}
}