Compare commits

...

1 commit

Author SHA1 Message Date
João Moreno d95ed670c1
reverse the initialization sequence for web worker ext host 2021-11-26 15:27:29 +01:00
6 changed files with 86 additions and 77 deletions

View file

@ -29,7 +29,7 @@ import { generateUuid } from 'vs/base/common/uuid';
import { canceled, onUnexpectedError } from 'vs/base/common/errors';
import { Barrier } from 'vs/base/common/async';
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
import { WorkerMessage, WorkerMessageType } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
export interface IWebWorkerExtensionHostInitData {
readonly autoStart: boolean;
@ -149,7 +149,8 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
}
private async _startInsideIframe(webWorkerExtensionHostIframeSrc: string): Promise<IMessagePassingProtocol> {
const emitter = this._register(new Emitter<VSBuffer>());
const { port1, port2 } = new MessageChannel();
const barrier = new Barrier();
const iframe = document.createElement('iframe');
iframe.setAttribute('class', 'web-worker-ext-host-iframe');
@ -159,8 +160,10 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
const vscodeWebWorkerExtHostId = generateUuid();
iframe.setAttribute('src', `${webWorkerExtensionHostIframeSrc}&vscodeWebWorkerExtHostId=${vscodeWebWorkerExtHostId}`);
const barrier = new Barrier();
let port!: MessagePort;
const iframeLoaded = new Promise(c => iframe.onload = c);
this._layoutService.container.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
let barrierError: Error | null = null;
let barrierHasError = false;
let startTimeout: any = null;
@ -174,8 +177,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
barrier.open();
};
const resolveBarrier = (messagePort: MessagePort) => {
port = messagePort;
const resolveBarrier = () => {
clearTimeout(startTimeout);
barrier.open();
};
@ -188,9 +190,11 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
if (event.source !== iframe.contentWindow) {
return;
}
if (event.data.vscodeWebWorkerExtHostId !== vscodeWebWorkerExtHostId) {
return;
}
if (event.data.error) {
const { name, message, stack } = event.data.error;
const err = new Error();
@ -199,27 +203,33 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
err.stack = stack;
return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err);
}
const { data } = event.data;
if (barrier.isOpen() || !(data instanceof MessagePort)) {
console.warn('UNEXPECTED message', event);
const err = new Error('UNEXPECTED message');
return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err);
if (barrier.isOpen()) {
console.warn('UNEXPECTED message after worker ready', event);
return;
}
resolveBarrier(data);
if (event.data.data.type === WorkerMessageType.Ready) {
resolveBarrier();
return;
}
console.warn('UNEXPECTED message', event);
const err = new Error('UNEXPECTED message');
return rejectBarrier(ExtensionHostExitCode.UnexpectedError, err);
}));
this._layoutService.container.appendChild(iframe);
this._register(toDisposable(() => iframe.remove()));
// await MessagePort and use it to directly communicate
// with the worker extension host
await iframeLoaded;
iframe.contentWindow!.postMessage(port2, '*', [port2]);
await barrier.wait();
if (barrierHasError) {
throw barrierError;
}
port.onmessage = (event) => {
const emitter = this._register(new Emitter<VSBuffer>());
port1.onmessage = (event) => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
@ -233,7 +243,7 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
port.postMessage(data, [data]);
port1.postMessage(data, [data]);
}
};
@ -241,37 +251,25 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
}
private async _startOutsideIframe(): Promise<IMessagePassingProtocol> {
const emitter = new Emitter<VSBuffer>();
const { port1, port2 } = new MessageChannel();
const barrier = new Barrier();
let port!: MessagePort;
const nestedWorker = new Map<string, Worker>();
const name = this._environmentService.debugRenderer && this._environmentService.debugExtensionHost ? 'DebugWorkerExtensionHost' : 'WorkerExtensionHost';
const worker = new DefaultWorkerFactory(name).create(
const worker = this._register(new DefaultWorkerFactory(name).create(
'vs/workbench/services/extensions/worker/extensionHostWorker',
(data: MessagePort | NewWorkerMessage | TerminateWorkerMessage | any) => {
if (data instanceof MessagePort) {
// receiving a message port which is used to communicate
// with the web worker extension host
if (barrier.isOpen()) {
console.warn('UNEXPECTED message', data);
this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, 'received a message port AFTER opening the barrier']);
return;
}
port = data;
(data: WorkerMessage | any /* this `any` is a hack */) => {
if (data?.type === WorkerMessageType.Ready) {
barrier.open();
} else if (data?.type === '_newWorker') {
} else if (data?.type === WorkerMessageType.NewWorker) {
// receiving a message to create a new nested/child worker
const worker = new Worker((ttPolicyNestedWorker?.createScriptURL(data.url) ?? data.url) as string, data.options);
worker.postMessage(data.port, [data.port]);
worker.onerror = console.error.bind(console);
nestedWorker.set(data.id, worker);
} else if (data?.type === '_terminateWorker') {
} else if (data?.type === WorkerMessageType.TerminateWorker) {
// receiving a message to terminate nested/child worker
if (nestedWorker.has(data.id)) {
nestedWorker.get(data.id)!.terminate();
@ -293,14 +291,14 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
this._onDidExit.fire([ExtensionHostExitCode.UnexpectedError, event.message || event.error]);
}
}
);
));
// await MessagePort and use it to directly communicate
// with the worker extension host
worker.postMessage(port2 as any /* this `any` is a hack */, [port2 as any /* this `any` is a hack */]);
await barrier.wait();
port.onmessage = (event) => {
const { data } = event;
const emitter = this._register(new Emitter<VSBuffer>());
port1.onmessage = ({ data }) => {
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
this._onDidExit.fire([77, 'UNKNOWN data received']);
@ -310,16 +308,11 @@ export class WebWorkerExtensionHost extends Disposable implements IExtensionHost
emitter.fire(VSBuffer.wrap(new Uint8Array(data, 0, data.byteLength)));
};
// keep for cleanup
this._register(emitter);
this._register(worker);
const protocol: IMessagePassingProtocol = {
onMessage: emitter.event,
send: vsbuf => {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
port.postMessage(data, [data]);
port1.postMessage(data, [data]);
}
};

View file

@ -3,9 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const enum WorkerMessageType {
Ready = '_workerReady',
NewWorker = '_newWorker',
TerminateWorker = '_terminateWorker'
}
export interface WorkerReadyMessage {
type: WorkerMessageType.Ready;
}
export interface NewWorkerMessage {
type: '_newWorker';
type: WorkerMessageType.NewWorker;
id: string;
port: any /* MessagePort */;
url: string;
@ -13,6 +22,8 @@ export interface NewWorkerMessage {
}
export interface TerminateWorkerMessage {
type: '_terminateWorker';
type: WorkerMessageType.TerminateWorker;
id: string;
}
export type WorkerMessage = WorkerReadyMessage | NewWorkerMessage | TerminateWorkerMessage;

View file

@ -18,6 +18,7 @@ import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/worker/extHost.worker.services';
import { FileAccess } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { WorkerMessageType } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
//#region --- Define, capture, and override some globals
@ -161,16 +162,11 @@ class ExtensionWorker {
// protocol
readonly protocol: IMessagePassingProtocol;
constructor() {
const channel = new MessageChannel();
constructor(port: MessagePort) {
const emitter = new Emitter<VSBuffer>();
let terminating = false;
// send over port2, keep port1
nativePostMessage(channel.port2, [channel.port2]);
channel.port1.onmessage = event => {
port.onmessage = event => {
const { data } = event;
if (!(data instanceof ArrayBuffer)) {
console.warn('UNKNOWN data received', data);
@ -194,7 +190,7 @@ class ExtensionWorker {
send: vsbuf => {
if (!terminating) {
const data = vsbuf.buffer.buffer.slice(vsbuf.buffer.byteOffset, vsbuf.buffer.byteOffset + vsbuf.buffer.byteLength);
channel.port1.postMessage(data, [data]);
port.postMessage(data, [data]);
}
}
};
@ -219,18 +215,23 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
let onTerminate = (reason: string) => nativeClose();
export function create(): void {
const res = new ExtensionWorker();
export function create(): { onmessage: (message: any) => void } {
performance.mark(`code/extHost/willConnectToRenderer`);
connectToRenderer(res.protocol).then(data => {
performance.mark(`code/extHost/didWaitForInitData`);
const extHostMain = new ExtensionHostMain(
data.protocol,
data.initData,
hostUtil,
null,
);
return {
onmessage(port: MessagePort) {
const res = new ExtensionWorker(port);
nativePostMessage({ type: WorkerMessageType.Ready });
connectToRenderer(res.protocol).then(data => {
performance.mark(`code/extHost/didWaitForInitData`);
const extHostMain = new ExtensionHostMain(
data.protocol,
data.initData,
hostUtil,
null,
);
onTerminate = (reason: string) => extHostMain.terminate(reason);
});
onTerminate = (reason: string) => extHostMain.terminate(reason);
});
}
};
}

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-cb2sg39EJV8ABaSNFfWu/ou8o1xVXYK7jp90oZ9vpcg=' http: https:; connect-src http: https: ws: wss:" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-K1g7FCERTTCfRLLmc8iHekkF33UnX2QZNtYpH4uQo0E=' http: https:; connect-src http: https: ws: wss:" />
</head>
<body>
<script>
@ -47,7 +47,7 @@
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data
}, '*', [data]);
}, '*');
}
};
@ -55,6 +55,8 @@
console.error(event.message, event.error);
sendError(event.error);
};
window.addEventListener('message', event => worker.postMessage(event.data, event.ports));
} catch(err) {
console.error(err);
sendError(err);

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-cb2sg39EJV8ABaSNFfWu/ou8o1xVXYK7jp90oZ9vpcg=' https:; connect-src https: wss:" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; child-src 'self' data: blob:; script-src 'unsafe-eval' 'sha256-K1g7FCERTTCfRLLmc8iHekkF33UnX2QZNtYpH4uQo0E=' https:; connect-src https: wss:" />
</head>
<body>
<script>
@ -47,7 +47,7 @@
window.parent.postMessage({
vscodeWebWorkerExtHostId,
data
}, '*', [data]);
}, '*');
}
};
@ -55,6 +55,8 @@
console.error(event.message, event.error);
sendError(event.error);
};
window.addEventListener('message', event => worker.postMessage(event.data, event.ports));
} catch(err) {
console.error(err);
sendError(err);

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NewWorkerMessage, TerminateWorkerMessage } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
import { NewWorkerMessage, TerminateWorkerMessage, WorkerMessageType } from 'vs/workbench/services/extensions/common/polyfillNestedWorker.protocol';
declare function postMessage(data: any, transferables?: Transferable[]): void;
@ -75,7 +75,7 @@ export class NestedWorker extends EventTarget implements Worker {
const id = blobUrl; // works because blob url is unique, needs ID pool otherwise
const msg: NewWorkerMessage = {
type: '_newWorker',
type: WorkerMessageType.NewWorker,
id,
port: channel.port2,
url: blobUrl,
@ -87,7 +87,7 @@ export class NestedWorker extends EventTarget implements Worker {
this.postMessage = channel.port1.postMessage.bind(channel.port1);
this.terminate = () => {
const msg: TerminateWorkerMessage = {
type: '_terminateWorker',
type: WorkerMessageType.TerminateWorker,
id
};
channel.port1.postMessage(msg);