simpleWorker.ts 10.1 KB
Newer Older
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

J
Johannes Rieken 已提交
7 8 9 10 11
import { transformErrorForSerialization } from 'vs/base/common/errors';
import { Disposable } from 'vs/base/common/lifecycle';
import { ErrorCallback, TPromise, ValueCallback } from 'vs/base/common/winjs.base';
import { ShallowCancelThenPromise } from 'vs/base/common/async';
import { isWeb } from 'vs/base/common/platform';
12 13 14

const INITIALIZE = '$initialize';

15
export interface IWorker {
J
Johannes Rieken 已提交
16 17 18
	getId(): number;
	postMessage(message: string): void;
	dispose(): void;
19 20 21
}

export interface IWorkerCallback {
J
Johannes Rieken 已提交
22
	(message: string): void;
23 24 25
}

export interface IWorkerFactory {
J
Johannes Rieken 已提交
26
	create(moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker;
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
}

let webWorkerWarningLogged = false;
export function logOnceWebWorkerWarning(err: any): void {
	if (!isWeb) {
		// running tests
		return;
	}
	if (!webWorkerWarningLogged) {
		webWorkerWarningLogged = true;
		console.warn('Could not create web worker(s). Falling back to loading web worker code in main thread, which might cause UI freezes. Please see https://github.com/Microsoft/monaco-editor#faq');
	}
	console.warn(err.message);
}

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
interface IMessage {
	vsWorker: number;
	req?: string;
	seq?: string;
}

interface IRequestMessage extends IMessage {
	req: string;
	method: string;
	args: any[];
}

interface IReplyMessage extends IMessage {
	seq: string;
	err: any;
	res: any;
}

interface IMessageReply {
	c: ValueCallback;
	e: ErrorCallback;
}

interface IMessageHandler {
J
Johannes Rieken 已提交
66 67
	sendMessage(msg: string): void;
	handleMessage(method: string, args: any[]): TPromise<any>;
68 69 70 71 72 73
}

class SimpleWorkerProtocol {

	private _workerId: number;
	private _lastSentReq: number;
J
Johannes Rieken 已提交
74 75
	private _pendingReplies: { [req: string]: IMessageReply; };
	private _handler: IMessageHandler;
76

J
Johannes Rieken 已提交
77
	constructor(handler: IMessageHandler) {
78 79 80 81 82 83
		this._workerId = -1;
		this._handler = handler;
		this._lastSentReq = 0;
		this._pendingReplies = Object.create(null);
	}

J
Johannes Rieken 已提交
84
	public setWorkerId(workerId: number): void {
85 86 87
		this._workerId = workerId;
	}

J
Johannes Rieken 已提交
88
	public sendMessage(method: string, args: any[]): TPromise<any> {
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
		let req = String(++this._lastSentReq);
		let reply: IMessageReply = {
			c: null,
			e: null
		};
		let result = new TPromise<any>((c, e, p) => {
			reply.c = c;
			reply.e = e;
		}, () => {
			// Cancel not supported
		});
		this._pendingReplies[req] = reply;

		this._send({
			vsWorker: this._workerId,
			req: req,
			method: method,
			args: args
		});

		return result;
	}

J
Johannes Rieken 已提交
112 113
	public handleMessage(serializedMessage: string): void {
		let message: IMessage;
114 115
		try {
			message = JSON.parse(serializedMessage);
J
Johannes Rieken 已提交
116
		} catch (e) {
117 118 119 120 121 122 123 124 125 126 127
			// nothing
		}
		if (!message.vsWorker) {
			return;
		}
		if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
			return;
		}
		this._handleMessage(message);
	}

J
Johannes Rieken 已提交
128
	private _handleMessage(msg: IMessage): void {
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
		if (msg.seq) {
			let replyMessage = <IReplyMessage>msg;
			if (!this._pendingReplies[replyMessage.seq]) {
				console.warn('Got reply to unknown seq');
				return;
			}

			let reply = this._pendingReplies[replyMessage.seq];
			delete this._pendingReplies[replyMessage.seq];

			if (replyMessage.err) {
				let err = replyMessage.err;
				if (replyMessage.err.$isError) {
					err = new Error();
					err.name = replyMessage.err.name;
					err.message = replyMessage.err.message;
					err.stack = replyMessage.err.stack;
				}
				reply.e(err);
				return;
			}

			reply.c(replyMessage.res);
			return;
		}

		let requestMessage = <IRequestMessage>msg;
		let req = requestMessage.req;
		let result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
		result.then((r) => {
			this._send({
				vsWorker: this._workerId,
				seq: req,
				res: r,
				err: undefined
			});
		}, (e) => {
			this._send({
				vsWorker: this._workerId,
				seq: req,
				res: undefined,
A
Cleanup  
Alex Dima 已提交
170
				err: transformErrorForSerialization(e)
171 172 173 174
			});
		});
	}

J
Johannes Rieken 已提交
175
	private _send(msg: IRequestMessage | IReplyMessage): void {
176 177 178 179 180 181 182 183 184
		let strMsg = JSON.stringify(msg);
		// console.log('SENDING: ' + strMsg);
		this._handler.sendMessage(strMsg);
	}
}

/**
 * Main thread side
 */
185
export class SimpleWorkerClient<T> extends Disposable {
186

J
Johannes Rieken 已提交
187 188
	private _worker: IWorker;
	private _onModuleLoaded: TPromise<string[]>;
189
	private _protocol: SimpleWorkerProtocol;
190
	private _lazyProxy: TPromise<T>;
J
Johannes Rieken 已提交
191
	private _lastRequestTimestamp = -1;
192

J
Johannes Rieken 已提交
193
	constructor(workerFactory: IWorkerFactory, moduleId: string) {
194
		super();
195

J
Johannes Rieken 已提交
196 197
		let lazyProxyFulfill: (v: T) => void = null;
		let lazyProxyReject: (err: any) => void = null;
198 199 200

		this._worker = this._register(workerFactory.create(
			'vs/base/common/worker/simpleWorker',
J
Johannes Rieken 已提交
201
			(msg: string) => {
202 203
				this._protocol.handleMessage(msg);
			},
J
Johannes Rieken 已提交
204
			(err: any) => {
205 206 207 208 209
				// in Firefox, web workers fail lazily :(
				// we will reject the proxy
				lazyProxyReject(err);
			}
		));
210 211

		this._protocol = new SimpleWorkerProtocol({
J
Johannes Rieken 已提交
212
			sendMessage: (msg: string): void => {
213 214
				this._worker.postMessage(msg);
			},
J
Johannes Rieken 已提交
215
			handleMessage: (method: string, args: any[]): TPromise<any> => {
216 217 218 219 220 221 222
				// Intentionally not supporting worker -> main requests
				return TPromise.as(null);
			}
		});
		this._protocol.setWorkerId(this._worker.getId());

		// Gather loader configuration
J
Johannes Rieken 已提交
223
		let loaderConfiguration: any = null;
224
		let globalRequire = (<any>self).require;
225 226 227
		if (typeof globalRequire.getConfig === 'function') {
			// Get the configuration from the Monaco AMD Loader
			loaderConfiguration = globalRequire.getConfig();
228
		} else if (typeof (<any>self).requirejs !== 'undefined') {
229
			// Get the configuration from requirejs
230
			loaderConfiguration = (<any>self).requirejs.s.contexts._.config;
231 232
		}

233
		this._lazyProxy = new TPromise<T>((c, e, p) => {
234 235 236 237
			lazyProxyFulfill = c;
			lazyProxyReject = e;
		}, () => { /* no cancel */ });

238 239 240 241 242 243
		// Send initialize message
		this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
			this._worker.getId(),
			moduleId,
			loaderConfiguration
		]);
J
Johannes Rieken 已提交
244
		this._onModuleLoaded.then((availableMethods: string[]) => {
245
			let proxy = <T>{};
246
			for (let i = 0; i < availableMethods.length; i++) {
M
Matt Bierner 已提交
247
				(proxy as any)[availableMethods[i]] = createProxyMethod(availableMethods[i], proxyMethodRequest);
248 249 250 251 252 253
			}
			lazyProxyFulfill(proxy);
		}, (e) => {
			lazyProxyReject(e);
			this._onError('Worker failed to load ' + moduleId, e);
		});
254 255

		// Create proxy to loaded code
J
Johannes Rieken 已提交
256
		let proxyMethodRequest = (method: string, args: any[]): TPromise<any> => {
257 258 259
			return this._request(method, args);
		};

J
Johannes Rieken 已提交
260
		let createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => TPromise<any>): Function => {
261 262 263 264 265 266 267
			return function () {
				let args = Array.prototype.slice.call(arguments, 0);
				return proxyMethodRequest(method, args);
			};
		};
	}

268
	public getProxyObject(): TPromise<T> {
269 270
		// Do not allow chaining promises to cancel the proxy creation
		return new ShallowCancelThenPromise(this._lazyProxy);
271 272
	}

J
Johannes Rieken 已提交
273 274 275 276
	public getLastRequestTimestamp(): number {
		return this._lastRequestTimestamp;
	}

J
Johannes Rieken 已提交
277
	private _request(method: string, args: any[]): TPromise<any> {
278 279
		return new TPromise<any>((c, e, p) => {
			this._onModuleLoaded.then(() => {
J
Johannes Rieken 已提交
280
				this._lastRequestTimestamp = Date.now();
281 282 283 284
				this._protocol.sendMessage(method, args).then(c, e);
			}, e);
		}, () => {
			// Cancel intentionally not supported
285 286 287
		});
	}

J
Johannes Rieken 已提交
288
	private _onError(message: string, error?: any): void {
289 290 291 292 293 294
		console.error(message);
		console.info(error);
	}
}

export interface IRequestHandler {
A
Alex Dima 已提交
295
	_requestHandlerBrand: any;
296 297 298 299 300 301 302 303 304 305
}

/**
 * Worker side
 */
export class SimpleWorkerServer {

	private _protocol: SimpleWorkerProtocol;
	private _requestHandler: IRequestHandler;

J
Johannes Rieken 已提交
306
	constructor(postSerializedMessage: (msg: string) => void) {
307
		this._protocol = new SimpleWorkerProtocol({
J
Johannes Rieken 已提交
308
			sendMessage: (msg: string): void => {
309 310
				postSerializedMessage(msg);
			},
J
Johannes Rieken 已提交
311
			handleMessage: (method: string, args: any[]): TPromise<any> => this._handleMessage(method, args)
312 313 314
		});
	}

J
Johannes Rieken 已提交
315
	public onmessage(msg: string): void {
316 317 318
		this._protocol.handleMessage(msg);
	}

J
Johannes Rieken 已提交
319
	private _handleMessage(method: string, args: any[]): TPromise<any> {
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
		if (method === INITIALIZE) {
			return this.initialize(<number>args[0], <string>args[1], <any>args[2]);
		}

		if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
			return TPromise.wrapError(new Error('Missing requestHandler or method: ' + method));
		}

		try {
			return TPromise.as(this._requestHandler[method].apply(this._requestHandler, args));
		} catch (e) {
			return TPromise.wrapError(e);
		}
	}

J
Johannes Rieken 已提交
335
	private initialize(workerId: number, moduleId: string, loaderConfig: any): TPromise<any> {
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
		this._protocol.setWorkerId(workerId);

		if (loaderConfig) {
			// Remove 'baseUrl', handling it is beyond scope for now
			if (typeof loaderConfig.baseUrl !== 'undefined') {
				delete loaderConfig['baseUrl'];
			}
			if (typeof loaderConfig.paths !== 'undefined') {
				if (typeof loaderConfig.paths.vs !== 'undefined') {
					delete loaderConfig.paths['vs'];
				}
			}
			let nlsConfig = loaderConfig['vs/nls'];
			// We need to have pseudo translation
			if (nlsConfig && nlsConfig.pseudo) {
J
Johannes Rieken 已提交
351
				require(['vs/nls'], function (nlsPlugin) {
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
					nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
				});
			}

			// Since this is in a web worker, enable catching errors
			loaderConfig.catchError = true;
			(<any>self).require.config(loaderConfig);
		}

		let cc: ValueCallback;
		let ee: ErrorCallback;
		let r = new TPromise<any>((c, e, p) => {
			cc = c;
			ee = e;
		});

368
		// Use the global require to be sure to get the global config
J
Johannes Rieken 已提交
369
		(<any>self).require([moduleId], (...result: any[]) => {
370 371
			let handlerModule = result[0];
			this._requestHandler = handlerModule.create();
372 373 374 375 376 377 378 379 380

			let methods: string[] = [];
			for (let prop in this._requestHandler) {
				if (typeof this._requestHandler[prop] === 'function') {
					methods.push(prop);
				}
			}

			cc(methods);
381 382 383 384 385 386 387 388 389
		}, ee);

		return r;
	}
}

/**
 * Called on the worker side
 */
J
Johannes Rieken 已提交
390
export function create(postMessage: (msg: string) => void): SimpleWorkerServer {
391 392
	return new SimpleWorkerServer(postMessage);
}