mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 09:35:40 -05:00
395 lines
10 KiB
TypeScript
395 lines
10 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
'use strict';
|
|
|
|
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';
|
|
|
|
const INITIALIZE = '$initialize';
|
|
|
|
export interface IWorker {
|
|
getId(): number;
|
|
postMessage(message: string): void;
|
|
dispose(): void;
|
|
}
|
|
|
|
export interface IWorkerCallback {
|
|
(message: string): void;
|
|
}
|
|
|
|
export interface IWorkerFactory {
|
|
create(moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 {
|
|
sendMessage(msg: string): void;
|
|
handleMessage(method: string, args: any[]): TPromise<any>;
|
|
}
|
|
|
|
class SimpleWorkerProtocol {
|
|
|
|
private _workerId: number;
|
|
private _lastSentReq: number;
|
|
private _pendingReplies: { [req: string]: IMessageReply; };
|
|
private _handler: IMessageHandler;
|
|
|
|
constructor(handler: IMessageHandler) {
|
|
this._workerId = -1;
|
|
this._handler = handler;
|
|
this._lastSentReq = 0;
|
|
this._pendingReplies = Object.create(null);
|
|
}
|
|
|
|
public setWorkerId(workerId: number): void {
|
|
this._workerId = workerId;
|
|
}
|
|
|
|
public sendMessage(method: string, args: any[]): TPromise<any> {
|
|
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;
|
|
}
|
|
|
|
public handleMessage(serializedMessage: string): void {
|
|
let message: IMessage;
|
|
try {
|
|
message = JSON.parse(serializedMessage);
|
|
} catch (e) {
|
|
// nothing
|
|
}
|
|
if (!message || !message.vsWorker) {
|
|
return;
|
|
}
|
|
if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
|
|
return;
|
|
}
|
|
this._handleMessage(message);
|
|
}
|
|
|
|
private _handleMessage(msg: IMessage): void {
|
|
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) => {
|
|
if (e.detail instanceof Error) {
|
|
// Loading errors have a detail property that points to the actual error
|
|
e.detail = transformErrorForSerialization(e.detail);
|
|
}
|
|
this._send({
|
|
vsWorker: this._workerId,
|
|
seq: req,
|
|
res: undefined,
|
|
err: transformErrorForSerialization(e)
|
|
});
|
|
});
|
|
}
|
|
|
|
private _send(msg: IRequestMessage | IReplyMessage): void {
|
|
let strMsg = JSON.stringify(msg);
|
|
// console.log('SENDING: ' + strMsg);
|
|
this._handler.sendMessage(strMsg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main thread side
|
|
*/
|
|
export class SimpleWorkerClient<T> extends Disposable {
|
|
|
|
private _worker: IWorker;
|
|
private _onModuleLoaded: TPromise<string[]>;
|
|
private _protocol: SimpleWorkerProtocol;
|
|
private _lazyProxy: TPromise<T>;
|
|
|
|
constructor(workerFactory: IWorkerFactory, moduleId: string) {
|
|
super();
|
|
|
|
let lazyProxyFulfill: (v: T) => void = null;
|
|
let lazyProxyReject: (err: any) => void = null;
|
|
|
|
this._worker = this._register(workerFactory.create(
|
|
'vs/base/common/worker/simpleWorker',
|
|
(msg: string) => {
|
|
this._protocol.handleMessage(msg);
|
|
},
|
|
(err: any) => {
|
|
// in Firefox, web workers fail lazily :(
|
|
// we will reject the proxy
|
|
lazyProxyReject(err);
|
|
}
|
|
));
|
|
|
|
this._protocol = new SimpleWorkerProtocol({
|
|
sendMessage: (msg: string): void => {
|
|
this._worker.postMessage(msg);
|
|
},
|
|
handleMessage: (method: string, args: any[]): TPromise<any> => {
|
|
// Intentionally not supporting worker -> main requests
|
|
return TPromise.as(null);
|
|
}
|
|
});
|
|
this._protocol.setWorkerId(this._worker.getId());
|
|
|
|
// Gather loader configuration
|
|
let loaderConfiguration: any = null;
|
|
if (typeof (<any>self).require !== 'undefined' && typeof (<any>self).require.getConfig === 'function') {
|
|
// Get the configuration from the Monaco AMD Loader
|
|
loaderConfiguration = (<any>self).require.getConfig();
|
|
} else if (typeof (<any>self).requirejs !== 'undefined') {
|
|
// Get the configuration from requirejs
|
|
loaderConfiguration = (<any>self).requirejs.s.contexts._.config;
|
|
}
|
|
|
|
this._lazyProxy = new TPromise<T>((c, e, p) => {
|
|
lazyProxyFulfill = c;
|
|
lazyProxyReject = e;
|
|
}, () => { /* no cancel */ });
|
|
|
|
// Send initialize message
|
|
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
|
|
this._worker.getId(),
|
|
moduleId,
|
|
loaderConfiguration
|
|
]);
|
|
this._onModuleLoaded.then((availableMethods: string[]) => {
|
|
let proxy = <T>{};
|
|
for (let i = 0; i < availableMethods.length; i++) {
|
|
(proxy as any)[availableMethods[i]] = createProxyMethod(availableMethods[i], proxyMethodRequest);
|
|
}
|
|
lazyProxyFulfill(proxy);
|
|
}, (e) => {
|
|
lazyProxyReject(e);
|
|
this._onError('Worker failed to load ' + moduleId, e);
|
|
});
|
|
|
|
// Create proxy to loaded code
|
|
let proxyMethodRequest = (method: string, args: any[]): TPromise<any> => {
|
|
return this._request(method, args);
|
|
};
|
|
|
|
let createProxyMethod = (method: string, proxyMethodRequest: (method: string, args: any[]) => TPromise<any>): Function => {
|
|
return function () {
|
|
let args = Array.prototype.slice.call(arguments, 0);
|
|
return proxyMethodRequest(method, args);
|
|
};
|
|
};
|
|
}
|
|
|
|
public getProxyObject(): TPromise<T> {
|
|
// Do not allow chaining promises to cancel the proxy creation
|
|
return new ShallowCancelThenPromise(this._lazyProxy);
|
|
}
|
|
|
|
private _request(method: string, args: any[]): TPromise<any> {
|
|
return new TPromise<any>((c, e, p) => {
|
|
this._onModuleLoaded.then(() => {
|
|
this._protocol.sendMessage(method, args).then(c, e);
|
|
}, e);
|
|
}, () => {
|
|
// Cancel intentionally not supported
|
|
});
|
|
}
|
|
|
|
private _onError(message: string, error?: any): void {
|
|
console.error(message);
|
|
console.info(error);
|
|
}
|
|
}
|
|
|
|
export interface IRequestHandler {
|
|
_requestHandlerBrand: any;
|
|
}
|
|
|
|
/**
|
|
* Worker side
|
|
*/
|
|
export class SimpleWorkerServer {
|
|
|
|
private _requestHandler: IRequestHandler;
|
|
private _protocol: SimpleWorkerProtocol;
|
|
|
|
constructor(postSerializedMessage: (msg: string) => void, requestHandler: IRequestHandler) {
|
|
this._requestHandler = requestHandler;
|
|
this._protocol = new SimpleWorkerProtocol({
|
|
sendMessage: (msg: string): void => {
|
|
postSerializedMessage(msg);
|
|
},
|
|
handleMessage: (method: string, args: any[]): TPromise<any> => this._handleMessage(method, args)
|
|
});
|
|
}
|
|
|
|
public onmessage(msg: string): void {
|
|
this._protocol.handleMessage(msg);
|
|
}
|
|
|
|
private _handleMessage(method: string, args: any[]): TPromise<any> {
|
|
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);
|
|
}
|
|
}
|
|
|
|
private initialize(workerId: number, moduleId: string, loaderConfig: any): TPromise<any> {
|
|
this._protocol.setWorkerId(workerId);
|
|
|
|
if (this._requestHandler) {
|
|
// static request handler
|
|
let methods: string[] = [];
|
|
for (let prop in this._requestHandler) {
|
|
if (typeof this._requestHandler[prop] === 'function') {
|
|
methods.push(prop);
|
|
}
|
|
}
|
|
return TPromise.as(methods);
|
|
}
|
|
|
|
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'];
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
});
|
|
|
|
// Use the global require to be sure to get the global config
|
|
(<any>self).require([moduleId], (...result: any[]) => {
|
|
let handlerModule = result[0];
|
|
this._requestHandler = handlerModule.create();
|
|
|
|
let methods: string[] = [];
|
|
for (let prop in this._requestHandler) {
|
|
if (typeof this._requestHandler[prop] === 'function') {
|
|
methods.push(prop);
|
|
}
|
|
}
|
|
|
|
cc(methods);
|
|
}, ee);
|
|
|
|
return r;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called on the worker side
|
|
*/
|
|
export function create(postMessage: (msg: string) => void): SimpleWorkerServer {
|
|
return new SimpleWorkerServer(postMessage, null);
|
|
}
|