ipc.cp.ts 8.3 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.
 *--------------------------------------------------------------------------------------------*/

6
import { ChildProcess, fork, ForkOptions } from 'child_process';
J
Joao Moreno 已提交
7
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
8
import { TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
9
import { Delayer, always, createCancelablePromise } from 'vs/base/common/async';
J
Johannes Rieken 已提交
10
import { deepClone, assign } from 'vs/base/common/objects';
J
Joao Moreno 已提交
11
import { Emitter, fromNodeEventEmitter, Event } from 'vs/base/common/event';
R
roblou 已提交
12
import { createQueuedSender } from 'vs/base/node/processes';
J
Joao Moreno 已提交
13
import { ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient, IChannel } from 'vs/base/parts/ipc/node/ipc';
14
import { isRemoteConsoleLog, log } from 'vs/base/node/console';
15 16
import { CancellationToken } from 'vs/base/common/cancellation';
import * as errors from 'vs/base/common/errors';
E
Erich Gamma 已提交
17

18 19 20 21 22
/**
 * This implementation doesn't perform well since it uses base64 encoding for buffers.
 * We should move all implementations to use named ipc.net, so we stop depending on cp.fork.
 */

E
Erich Gamma 已提交
23 24 25
export class Server extends IPCServer {
	constructor() {
		super({
26 27
			send: r => { try { process.send(r.toString('base64')); } catch (e) { /* not much to do */ } },
			onMessage: fromNodeEventEmitter(process, 'message', msg => Buffer.from(msg, 'base64'))
E
Erich Gamma 已提交
28 29 30 31 32 33
		});

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

J
Joao Moreno 已提交
34
export interface IIPCOptions {
E
Erich Gamma 已提交
35 36 37 38 39 40 41

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

	/**
J
Joao Moreno 已提交
42
	 * Time in millies before killing the ipc process. The next request after killing will start it again.
E
Erich Gamma 已提交
43
	 */
J
Johannes Rieken 已提交
44
	timeout?: number;
E
Erich Gamma 已提交
45 46 47 48

	/**
	 * Arguments to the module to execute.
	 */
J
Johannes Rieken 已提交
49
	args?: string[];
E
Erich Gamma 已提交
50 51

	/**
J
Joao Moreno 已提交
52
	 * Environment key-value pairs to be passed to the process that gets spawned for the ipc.
E
Erich Gamma 已提交
53
	 */
J
Johannes Rieken 已提交
54
	env?: any;
E
Erich Gamma 已提交
55 56 57 58

	/**
	 * Allows to assign a debug port for debugging the application executed.
	 */
J
Johannes Rieken 已提交
59
	debug?: number;
E
Erich Gamma 已提交
60 61 62 63

	/**
	 * Allows to assign a debug port for debugging the application and breaking it on the first line.
	 */
J
Johannes Rieken 已提交
64
	debugBrk?: number;
65

66 67 68
	/**
	 * See https://github.com/Microsoft/vscode/issues/27665
	 * Allows to pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`.
B
Benjamin Pasero 已提交
69 70
	 * e.g. Launching the extension host process with `--inspect-brk=xxx` and then forking a process from the extension host
	 * results in the forked process inheriting `--inspect-brk=xxx`.
71 72 73
	 */
	freshExecArgv?: boolean;

74 75 76 77 78
	/**
	 * Enables our createQueuedSender helper for this Client. Uses a queue when the internal Node.js queue is
	 * full of messages - see notes on that method.
	 */
	useQueue?: boolean;
E
Erich Gamma 已提交
79 80
}

J
Joao Moreno 已提交
81
export class Client implements IChannelClient, IDisposable {
E
Erich Gamma 已提交
82 83

	private disposeDelayer: Delayer<void>;
J
Joao Moreno 已提交
84
	private activeRequests = new Set<IDisposable>();
85 86
	private child: ChildProcess;
	private _client: IPCClient;
J
Joao Moreno 已提交
87
	private channels = new Map<string, IChannel>();
E
Erich Gamma 已提交
88

89 90 91
	private _onDidProcessExit = new Emitter<{ code: number, signal: string }>();
	readonly onDidProcessExit = this._onDidProcessExit.event;

J
Joao Moreno 已提交
92 93
	constructor(private modulePath: string, private options: IIPCOptions) {
		const timeout = options && options.timeout ? options.timeout : 60000;
E
Erich Gamma 已提交
94
		this.disposeDelayer = new Delayer<void>(timeout);
95
		this.child = null;
E
Erich Gamma 已提交
96 97 98
		this._client = null;
	}

J
Joao Moreno 已提交
99
	getChannel<T extends IChannel>(channelName: string): T {
100 101 102
		const that = this;

		return {
J
Joao Moreno 已提交
103 104
			call<T>(command: string, arg?: any, cancellationToken?: CancellationToken): Thenable<T> {
				return that.requestPromise<T>(channelName, command, arg, cancellationToken);
105 106 107 108 109
			},
			listen(event: string, arg?: any) {
				return that.requestEvent(channelName, event, arg);
			}
		} as T;
E
Erich Gamma 已提交
110 111
	}

J
Joao Moreno 已提交
112
	protected requestPromise<T>(channelName: string, name: string, arg?: any, cancellationToken = CancellationToken.None): Thenable<T> {
J
Joao Moreno 已提交
113
		if (!this.disposeDelayer) {
114
			return TPromise.wrapError(new Error('disposed'));
J
Joao Moreno 已提交
115 116
		}

117 118 119 120
		if (cancellationToken.isCancellationRequested) {
			return TPromise.wrapError(errors.canceled());
		}

E
Erich Gamma 已提交
121 122
		this.disposeDelayer.cancel();

J
Joao Moreno 已提交
123
		const channel = this.getCachedChannel(channelName);
J
Joao Moreno 已提交
124
		const result = createCancelablePromise(token => channel.call<T>(name, arg, token));
125
		const cancellationTokenListener = cancellationToken.onCancellationRequested(() => result.cancel());
E
Erich Gamma 已提交
126

J
Joao Moreno 已提交
127
		const disposable = toDisposable(() => result.cancel());
J
Joao Moreno 已提交
128
		this.activeRequests.add(disposable);
129 130 131 132 133 134 135 136 137 138

		always(result, () => {
			cancellationTokenListener.dispose();
			this.activeRequests.delete(disposable);

			if (this.activeRequests.size === 0) {
				this.disposeDelayer.trigger(() => this.disposeClient());
			}
		});

E
Erich Gamma 已提交
139 140 141
		return result;
	}

142
	protected requestEvent<T>(channelName: string, name: string, arg?: any): Event<T> {
J
Joao Moreno 已提交
143 144 145 146 147 148 149 150 151
		if (!this.disposeDelayer) {
			return Event.None;
		}

		this.disposeDelayer.cancel();

		let listener: IDisposable;
		const emitter = new Emitter<any>({
			onFirstListenerAdd: () => {
J
Joao Moreno 已提交
152
				const channel = this.getCachedChannel(channelName);
J
Joao Moreno 已提交
153 154 155
				const event: Event<T> = channel.listen(name, arg);

				listener = event(emitter.fire, emitter);
J
Joao Moreno 已提交
156
				this.activeRequests.add(listener);
J
Joao Moreno 已提交
157 158
			},
			onLastListenerRemove: () => {
J
Joao Moreno 已提交
159
				this.activeRequests.delete(listener);
160
				listener.dispose();
J
Joao Moreno 已提交
161

R
Rob Lourens 已提交
162
				if (this.activeRequests.size === 0 && this.disposeDelayer) {
J
Joao Moreno 已提交
163 164 165 166 167 168 169 170
					this.disposeDelayer.trigger(() => this.disposeClient());
				}
			}
		});

		return emitter.event;
	}

171
	private get client(): IPCClient {
E
Erich Gamma 已提交
172
		if (!this._client) {
173
			const args = this.options && this.options.args ? this.options.args : [];
174
			const forkOpts: ForkOptions = Object.create(null);
E
Erich Gamma 已提交
175

J
Johannes Rieken 已提交
176
			forkOpts.env = assign(deepClone(process.env), { 'VSCODE_PARENT_PID': String(process.pid) });
E
Erich Gamma 已提交
177

J
Joao Moreno 已提交
178 179 180
			if (this.options && this.options.env) {
				forkOpts.env = assign(forkOpts.env, this.options.env);
			}
E
Erich Gamma 已提交
181

182 183 184 185
			if (this.options && this.options.freshExecArgv) {
				forkOpts.execArgv = [];
			}

J
Joao Moreno 已提交
186
			if (this.options && typeof this.options.debug === 'number') {
B
Benjamin Pasero 已提交
187
				forkOpts.execArgv = ['--nolazy', '--inspect=' + this.options.debug];
J
Joao Moreno 已提交
188
			}
189

J
Joao Moreno 已提交
190
			if (this.options && typeof this.options.debugBrk === 'number') {
B
Benjamin Pasero 已提交
191
				forkOpts.execArgv = ['--nolazy', '--inspect-brk=' + this.options.debugBrk];
192 193 194
			}

			this.child = fork(this.modulePath, args, forkOpts);
J
Joao Moreno 已提交
195

196
			const onMessageEmitter = new Emitter<Buffer>();
J
Joao Moreno 已提交
197
			const onRawMessage = fromNodeEventEmitter(this.child, 'message', msg => msg);
J
Joao Moreno 已提交
198 199

			onRawMessage(msg => {
200 201 202 203

				// Handle remote console logs specially
				if (isRemoteConsoleLog(msg)) {
					log(msg, `IPC Library: ${this.options.serverName}`);
J
Joao Moreno 已提交
204 205 206 207
					return null;
				}

				// Anything else goes to the outside
208
				onMessageEmitter.fire(Buffer.from(msg, 'base64'));
209
			});
E
Erich Gamma 已提交
210

211
			const sender = this.options.useQueue ? createQueuedSender(this.child) : this.child;
212
			const send = (r: Buffer) => this.child && this.child.connected && sender.send(r.toString('base64'));
J
Joao Moreno 已提交
213 214 215 216 217
			const onMessage = onMessageEmitter.event;
			const protocol = { send, onMessage };

			this._client = new IPCClient(protocol);

218 219
			const onExit = () => this.disposeClient();
			process.once('exit', onExit);
E
Erich Gamma 已提交
220

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

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

R
Rob Lourens 已提交
226
				this.activeRequests.forEach(r => dispose(r));
J
Joao Moreno 已提交
227
				this.activeRequests.clear();
E
Erich Gamma 已提交
228

J
Joao Moreno 已提交
229
				if (code !== 0 && signal !== 'SIGTERM') {
230
					console.warn('IPC "' + this.options.serverName + '" crashed with exit code ' + code + ' and signal ' + signal);
231 232 233
					this.disposeDelayer.cancel();
					this.disposeClient();
				}
234 235

				this._onDidProcessExit.fire({ code, signal });
E
Erich Gamma 已提交
236 237 238 239 240 241
			});
		}

		return this._client;
	}

J
Joao Moreno 已提交
242 243 244 245 246 247 248 249 250 251 252
	private getCachedChannel(name: string): IChannel {
		let channel = this.channels.get(name);

		if (!channel) {
			channel = this.client.getChannel(name);
			this.channels.set(name, channel);
		}

		return channel;
	}

E
Erich Gamma 已提交
253 254
	private disposeClient() {
		if (this._client) {
255 256
			this.child.kill();
			this.child = null;
257
			this._client = null;
J
Joao Moreno 已提交
258
			this.channels.clear();
E
Erich Gamma 已提交
259 260 261 262
		}
	}

	dispose() {
J
Joao Moreno 已提交
263
		this._onDidProcessExit.dispose();
E
Erich Gamma 已提交
264 265 266
		this.disposeDelayer.cancel();
		this.disposeDelayer = null;
		this.disposeClient();
J
Joao Moreno 已提交
267
		this.activeRequests.clear();
E
Erich Gamma 已提交
268
	}
269
}