提交 eefcdc9d 编写于 作者: A Alex Dima

Handle better the case when web workers are not available / misconfigured

上级 604e0d3b
# Monaco Editor Change log
## [0.5.0]
### Breaking changes
- `createWebWorker` now loads the AMD module and calls `create` and passes in as first argument a context of type `monaco.worker.IWorkerContext` and as second argument the `initData`. This breaking change was needed to allow handling the case of misconfigured web workers (running of a file protocol or a cross-domain case)
- the `CodeActionProvider.provideCodeActions` now gets passed in a `CodeActionContext` that contains the markers at the relevant range.
\ No newline at end of file
......@@ -165,9 +165,21 @@ export class SimpleWorkerClient<T> extends Disposable {
constructor(workerFactory:IWorkerFactory, moduleId:string) {
super();
this._worker = this._register(workerFactory.create('vs/base/common/worker/simpleWorker', (msg:string) => {
this._protocol.handleMessage(msg);
}));
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 => {
......@@ -191,9 +203,6 @@ export class SimpleWorkerClient<T> extends Disposable {
loaderConfiguration = (<any>window).requirejs.s.contexts._.config;
}
let lazyProxyFulfill : (v:T)=>void = null;
let lazyProxyReject: (err:any)=>void = null;
this._lazyProxy = new TPromise((c, e, p) => {
lazyProxyFulfill = c;
lazyProxyReject = e;
......
......@@ -17,12 +17,21 @@ export interface IWorker {
dispose():void;
}
let webWorkerWarningLogged = false;
export function logOnceWebWorkerWarning(err:any): void {
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);
}
export interface IWorkerCallback {
(message:string):void;
}
export interface IWorkerFactory {
create(moduleId:string, callback:IWorkerCallback, onCrashCallback?:()=>void):IWorker;
create(moduleId:string, callback:IWorkerCallback, onErrorCallback:(err:any)=>void):IWorker;
}
interface IActiveRequest {
......@@ -59,7 +68,15 @@ export class WorkerClient {
this._waitingForWorkerReply = false;
this._lastTimerEvent = null;
this._worker = workerFactory.create('vs/base/common/worker/workerServer', (msg) => this._onSerializedMessage(msg));
this._worker = workerFactory.create(
'vs/base/common/worker/workerServer',
(msg) => this._onSerializedMessage(msg),
(err) => {
// reject the onModuleLoaded promise, this signals that things are bad
let promiseEntry:IActiveRequest = this._promises[1];
delete this._promises[1];
promiseEntry.error(err);
});
let loaderConfiguration:any = null;
......@@ -80,7 +97,6 @@ export class WorkerClient {
loaderConfiguration: loaderConfiguration,
GlobalEnvironment: GlobalEnvironment
});
this.onModuleLoaded.then(null, (e) => this._onError('Worker failed to load ' + moduleId, e));
this._remoteCom = new workerProtocol.RemoteCom(this);
}
......
......@@ -6,7 +6,7 @@
import * as flags from 'vs/base/common/flags';
import {IDisposable, dispose} from 'vs/base/common/lifecycle';
import {IWorker, IWorkerCallback, IWorkerFactory} from 'vs/base/common/worker/workerClient';
import {logOnceWebWorkerWarning, IWorker, IWorkerCallback, IWorkerFactory} from 'vs/base/common/worker/workerClient';
import * as dom from 'vs/base/browser/dom';
function defaultGetWorkerUrl(workerId:string, label:string): string {
......@@ -24,13 +24,16 @@ class WebWorker implements IWorker {
private id:number;
private worker:Worker;
constructor(moduleId:string, id:number, label:string, onMessageCallback:IWorkerCallback) {
constructor(moduleId:string, id:number, label:string, onMessageCallback:IWorkerCallback, onErrorCallback:(err:any)=>void) {
this.id = id;
this.worker = new Worker(getWorkerUrl('workerMain.js', label));
this.postMessage(moduleId);
this.worker.onmessage = function (ev:any) {
onMessageCallback(ev.data);
};
if (typeof this.worker.addEventListener === 'function') {
this.worker.addEventListener('error', onErrorCallback);
}
}
public getId(): number {
......@@ -125,11 +128,41 @@ export class DefaultWorkerFactory implements IWorkerFactory {
private static LAST_WORKER_ID = 0;
public create(moduleId:string, onMessageCallback:IWorkerCallback):IWorker {
var workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
if (typeof WebWorker !== 'undefined') {
return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback);
private _fallbackToIframe:boolean;
private _webWorkerFailedBeforeError:any;
constructor(fallbackToIframe:boolean) {
this._fallbackToIframe = fallbackToIframe;
this._webWorkerFailedBeforeError = false;
}
public create(moduleId:string, onMessageCallback:IWorkerCallback, onErrorCallback:(err:any)=>void):IWorker {
let workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
if (this._fallbackToIframe) {
if (this._webWorkerFailedBeforeError) {
// Avoid always trying to create web workers if they would just fail...
return new FrameWorker(moduleId, workerId, onMessageCallback);
}
try {
return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback, (err) => {
logOnceWebWorkerWarning(err);
this._webWorkerFailedBeforeError = err;
onErrorCallback(err);
});
} catch(err) {
logOnceWebWorkerWarning(err);
return new FrameWorker(moduleId, workerId, onMessageCallback);
}
}
return new FrameWorker(moduleId, workerId, onMessageCallback);
if (this._webWorkerFailedBeforeError) {
throw this._webWorkerFailedBeforeError;
}
return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback, (err) => {
logOnceWebWorkerWarning(err);
this._webWorkerFailedBeforeError = err;
onErrorCallback(err);
});
}
}
\ No newline at end of file
}
......@@ -7,6 +7,7 @@
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import {IDisposable} from 'vs/base/common/lifecycle';
import {IRequestHandler} from 'vs/base/common/worker/simpleWorker';
import {Range} from 'vs/editor/common/core/range';
import {fuzzyContiguousFilter} from 'vs/base/common/filters';
......@@ -23,13 +24,37 @@ import {createMonacoBaseAPI} from 'vs/editor/common/standalone/standaloneBase';
export interface IMirrorModel {
uri: URI;
version: number;
getText(): string;
getValue(): string;
}
export interface IWorkerContext {
/**
* Get all available mirror models in this worker.
*/
getMirrorModels(): IMirrorModel[];
}
/**
* @internal
*/
export interface ICommonModel {
uri: URI;
version: number;
getValue(): string;
getLinesContent(): string[];
getLineCount(): number;
getLineContent(lineNumber:number): string;
getWordUntilPosition(position: editorCommon.IPosition, wordDefinition:RegExp): editorCommon.IWordAtPosition;
getAllUniqueWords(wordDefinition:RegExp, skipWordOnce?:string) : string[];
getValueInRange(range:editorCommon.IRange): string;
getWordAtPosition(position:editorCommon.IPosition, wordDefinition:RegExp): Range;
}
/**
* @internal
*/
export class MirrorModel extends MirrorModel2 {
export class MirrorModel extends MirrorModel2 implements ICommonModel {
public get uri(): URI {
return this._uri;
......@@ -39,6 +64,10 @@ export class MirrorModel extends MirrorModel2 {
return this._versionId;
}
public getValue(): string {
return this.getText();
}
public getLinesContent(): string[] {
return this._lines.slice(0);
}
......@@ -146,47 +175,21 @@ export class MirrorModel extends MirrorModel2 {
/**
* @internal
*/
export class EditorSimpleWorkerImpl implements IRequestHandler {
_requestHandlerTrait: any;
private _models:{[uri:string]:MirrorModel;};
export abstract class BaseEditorSimpleWorker {
private _foreignModule: any;
constructor() {
this._models = Object.create(null);
this._foreignModule = null;
}
public getModels(): MirrorModel[] {
let all: MirrorModel[] = [];
Object.keys(this._models).forEach((key) => all.push(this._models[key]));
return all;
}
public acceptNewModel(data:IRawModelData): void {
this._models[data.url] = new MirrorModel(URI.parse(data.url), data.value.lines, data.value.EOL, data.versionId);
}
public acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[]): void {
if (!this._models[strURL]) {
return;
}
let model = this._models[strURL];
model.onEvents(events);
}
public acceptRemovedModel(strURL: string): void {
if (!this._models[strURL]) {
return;
}
delete this._models[strURL];
}
protected abstract _getModel(uri:string): ICommonModel;
protected abstract _getModels(): ICommonModel[];
// ---- BEGIN diff --------------------------------------------------------------------------
public computeDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean): TPromise<editorCommon.ILineChange[]> {
let original = this._models[originalUrl];
let modified = this._models[modifiedUrl];
let original = this._getModel(originalUrl);
let modified = this._getModel(modifiedUrl);
if (!original || !modified) {
return null;
}
......@@ -202,8 +205,8 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
}
public computeDirtyDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean):TPromise<editorCommon.IChange[]> {
let original = this._models[originalUrl];
let modified = this._models[modifiedUrl];
let original = this._getModel(originalUrl);
let modified = this._getModel(modifiedUrl);
if (!original || !modified) {
return null;
}
......@@ -221,7 +224,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
// ---- END diff --------------------------------------------------------------------------
public computeLinks(modelUrl:string):TPromise<ILink[]> {
let model = this._models[modelUrl];
let model = this._getModel(modelUrl);
if (!model) {
return null;
}
......@@ -232,7 +235,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
// ---- BEGIN suggest --------------------------------------------------------------------------
public textualSuggest(modelUrl:string, position: editorCommon.IPosition, wordDef:string, wordDefFlags:string): TPromise<ISuggestResult[]> {
let model = this._models[modelUrl];
let model = this._getModel(modelUrl);
if (!model) {
return null;
}
......@@ -240,7 +243,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
return TPromise.as(this._suggestFiltered(model, position, new RegExp(wordDef, wordDefFlags)));
}
private _suggestFiltered(model:MirrorModel, position: editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult[] {
private _suggestFiltered(model:ICommonModel, position: editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult[] {
let value = this._suggestUnfiltered(model, position, wordDefRegExp);
// filter suggestions
......@@ -251,7 +254,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
}];
}
private _suggestUnfiltered(model:MirrorModel, position:editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult {
private _suggestUnfiltered(model:ICommonModel, position:editorCommon.IPosition, wordDefRegExp: RegExp): ISuggestResult {
let currentWord = model.getWordUntilPosition(position, wordDefRegExp).word;
let allWords = model.getAllUniqueWords(wordDefRegExp, currentWord);
......@@ -275,7 +278,7 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
// ---- END suggest --------------------------------------------------------------------------
public navigateValueSet(modelUrl:string, range:editorCommon.IRange, up:boolean, wordDef:string, wordDefFlags:string): TPromise<IInplaceReplaceSupportResult> {
let model = this._models[modelUrl];
let model = this._getModel(modelUrl);
if (!model) {
return null;
}
......@@ -304,7 +307,12 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
return new TPromise<any>((c, e) => {
// Use the global require to be sure to get the global config
(<any>self).require([moduleId], (foreignModule) => {
this._foreignModule = foreignModule.create(createData);
let ctx: IWorkerContext = {
getMirrorModels: ():IMirrorModel[] => {
return this._getModels();
}
};
this._foreignModule = foreignModule.create(ctx, createData);
let methods: string[] = [];
for (let prop in this._foreignModule) {
......@@ -335,13 +343,51 @@ export class EditorSimpleWorkerImpl implements IRequestHandler {
// ---- END foreign module support --------------------------------------------------------------------------
}
const instance = new EditorSimpleWorkerImpl();
/**
* Get all available mirror models in this worker.
* @internal
*/
export function getMirrorModels(): IMirrorModel[] {
return instance.getModels();
export class EditorSimpleWorkerImpl extends BaseEditorSimpleWorker implements IRequestHandler, IDisposable {
_requestHandlerTrait: any;
private _models:{[uri:string]:MirrorModel;};
constructor() {
super();
this._models = Object.create(null);
}
public dispose(): void {
this._models = Object.create(null);
}
protected _getModel(uri:string): ICommonModel {
return this._models[uri];
}
protected _getModels(): ICommonModel[] {
let all: MirrorModel[] = [];
Object.keys(this._models).forEach((key) => all.push(this._models[key]));
return all;
}
public acceptNewModel(data:IRawModelData): void {
this._models[data.url] = new MirrorModel(URI.parse(data.url), data.value.lines, data.value.EOL, data.versionId);
}
public acceptModelChanged(strURL: string, events: editorCommon.IModelContentChangedEvent2[]): void {
if (!this._models[strURL]) {
return;
}
let model = this._models[strURL];
model.onEvents(events);
}
public acceptRemovedModel(strURL: string): void {
if (!this._models[strURL]) {
return;
}
delete this._models[strURL];
}
}
/**
......@@ -349,15 +395,11 @@ export function getMirrorModels(): IMirrorModel[] {
* @internal
*/
export function create(): IRequestHandler {
return instance;
}
function createMonacoWorkerAPI(): typeof monaco.worker {
return {
getMirrorModels: getMirrorModels
};
return new EditorSimpleWorkerImpl();
}
var global:any = self;
global.monaco = createMonacoBaseAPI();
global.monaco.worker = createMonacoWorkerAPI();
let isWebWorker = (typeof global.importScripts === 'function');
if (isWebWorker) {
global.monaco = createMonacoBaseAPI();
}
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import {IntervalTimer} from 'vs/base/common/async';
import {IntervalTimer, ShallowCancelThenPromise} from 'vs/base/common/async';
import {Disposable, IDisposable, dispose} from 'vs/base/common/lifecycle';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
......@@ -16,6 +16,7 @@ import {IInplaceReplaceSupportResult, ILink, ISuggestResult} from 'vs/editor/com
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
import {IModelService} from 'vs/editor/common/services/modelService';
import {EditorSimpleWorkerImpl} from 'vs/editor/common/services/editorSimpleWorker';
import {logOnceWebWorkerWarning} from 'vs/base/common/worker/workerClient';
/**
* Stop syncing a model to the worker if it was not needed for 1 min.
......@@ -211,31 +212,67 @@ class EditorModelManager extends Disposable {
}
}
interface IWorkerClient<T> {
getProxyObject(): TPromise<T>;
dispose(): void;
}
class SynchronousWorkerClient<T extends IDisposable> implements IWorkerClient<T> {
private _instance: T;
private _proxyObj: TPromise<T>;
constructor(instance:T) {
this._instance = instance;
this._proxyObj = TPromise.as(this._instance);
}
public dispose(): void {
this._instance.dispose();
this._instance = null;
this._proxyObj = null;
}
public getProxyObject(): TPromise<T> {
return new ShallowCancelThenPromise(this._proxyObj);
}
}
export class EditorWorkerClient extends Disposable {
private _modelService: IModelService;
private _worker: SimpleWorkerClient<EditorSimpleWorkerImpl>;
private _worker: IWorkerClient<EditorSimpleWorkerImpl>;
private _workerFactory: DefaultWorkerFactory;
private _modelManager: EditorModelManager;
constructor(modelService: IModelService) {
super();
this._modelService = modelService;
this._workerFactory = new DefaultWorkerFactory(/*do not use iframe*/false);
this._worker = null;
this._modelManager = null;
}
private _getOrCreateWorker(): SimpleWorkerClient<EditorSimpleWorkerImpl> {
private _getOrCreateWorker(): IWorkerClient<EditorSimpleWorkerImpl> {
if (!this._worker) {
this._worker = this._register(new SimpleWorkerClient<EditorSimpleWorkerImpl>(
new DefaultWorkerFactory(),
'vs/editor/common/services/editorSimpleWorker'
));
try {
this._worker = this._register(new SimpleWorkerClient<EditorSimpleWorkerImpl>(
this._workerFactory,
'vs/editor/common/services/editorSimpleWorker'
));
} catch (err) {
logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
}
}
return this._worker;
}
protected _getProxy(): TPromise<EditorSimpleWorkerImpl> {
return this._getOrCreateWorker().getProxyObject();
return this._getOrCreateWorker().getProxyObject().then(null, (err) => {
logOnceWebWorkerWarning(err);
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
return this._getOrCreateWorker().getProxyObject();
});
}
private _getOrCreateModelManager(proxy: EditorSimpleWorkerImpl): EditorModelManager {
......
......@@ -17,7 +17,6 @@ import URI from 'vs/base/common/uri';
export function createMonacoBaseAPI(): typeof monaco {
return {
editor: undefined,
worker: undefined,
languages: undefined,
CancellationTokenSource: CancellationTokenSource,
Emitter: Emitter,
......
......@@ -4725,12 +4725,14 @@ declare module monaco.worker {
export interface IMirrorModel {
uri: Uri;
version: number;
getText(): string;
getValue(): string;
}
/**
* Get all available mirror models in this worker.
*/
export function getMirrorModels(): IMirrorModel[];
export interface IWorkerContext {
/**
* Get all available mirror models in this worker.
*/
getMirrorModels(): IMirrorModel[];
}
}
\ No newline at end of file
......@@ -47,7 +47,7 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi
this._contextService = contextService;
this._workerModuleId = workerModuleId;
this._defaultWorkerCount = defaultWorkerCount;
this._workerFactory = new DefaultWorkerFactory();
this._workerFactory = new DefaultWorkerFactory(true);
if (!this.isInMainThread) {
throw new Error('Incorrect Service usage: this service must be used only in the main thread');
......@@ -105,15 +105,11 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi
});
}
private _createWorker(): void {
this._workerPool.push(this._doCreateWorker());
}
private _shortName(major: string, minor: string): string {
return major.substring(major.length - 14) + '.' + minor.substr(0, 14);
}
private _doCreateWorker(): Worker.WorkerClient {
private _createWorker(isRetry:boolean = false): void {
let worker = new Worker.WorkerClient(
this._workerFactory,
this._workerModuleId,
......@@ -131,9 +127,22 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi
configuration: this._contextService.getConfiguration(),
options: this._contextService.getOptions()
}
}).then(null, (err) => {
for (var i = 0; i < this._workerPool.length; i++) {
if (this._workerPool[i] === worker) {
this._workerPool.splice(i, 1);
break;
}
}
worker.dispose();
if (isRetry) {
console.warn('Creating the web worker already failed twice. Giving up!');
} else {
this._createWorker(true);
}
});
return worker;
this._workerPool.push(worker);
}
private _getWorkerIndex(obj: IThreadSynchronizableObject, affinity: ThreadAffinity): number {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册