mirror of
https://github.com/denoland/deno.git
synced 2025-01-12 00:54:02 -05:00
refactor: split worker and worker host logic (#3722)
* split ops/worker.rs into ops/worker_host.rs and ops/web_worker.rs * refactor js/workers.ts and factor out js/worker_main.ts - entry point for WebWorker runtime * BREAKING CHANGE: remove support for blob: URL in Worker * BREAKING CHANGE: remove Deno namespace support and noDenoNamespace option in Worker constructor * introduce WebWorker struct which is a stripped down version of cli::Worker
This commit is contained in:
parent
c90036ab88
commit
7966bf14c0
21 changed files with 439 additions and 263 deletions
|
@ -231,7 +231,7 @@ impl TsCompiler {
|
||||||
fn setup_worker(global_state: ThreadSafeGlobalState) -> Worker {
|
fn setup_worker(global_state: ThreadSafeGlobalState) -> Worker {
|
||||||
let (int, ext) = ThreadSafeState::create_channels();
|
let (int, ext) = ThreadSafeState::create_channels();
|
||||||
let worker_state =
|
let worker_state =
|
||||||
ThreadSafeState::new(global_state.clone(), None, None, true, int)
|
ThreadSafeState::new(global_state.clone(), None, None, int)
|
||||||
.expect("Unable to create worker state");
|
.expect("Unable to create worker state");
|
||||||
|
|
||||||
// Count how many times we start the compiler worker.
|
// Count how many times we start the compiler worker.
|
||||||
|
|
|
@ -45,7 +45,7 @@ impl WasmCompiler {
|
||||||
fn setup_worker(global_state: ThreadSafeGlobalState) -> Worker {
|
fn setup_worker(global_state: ThreadSafeGlobalState) -> Worker {
|
||||||
let (int, ext) = ThreadSafeState::create_channels();
|
let (int, ext) = ThreadSafeState::create_channels();
|
||||||
let worker_state =
|
let worker_state =
|
||||||
ThreadSafeState::new(global_state.clone(), None, None, true, int)
|
ThreadSafeState::new(global_state.clone(), None, None, int)
|
||||||
.expect("Unable to create worker state");
|
.expect("Unable to create worker state");
|
||||||
|
|
||||||
// Count how many times we start the compiler worker.
|
// Count how many times we start the compiler worker.
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { fromTypeScriptDiagnostic } from "./diagnostics_util.ts";
|
||||||
import * as os from "./os.ts";
|
import * as os from "./os.ts";
|
||||||
import { assert } from "./util.ts";
|
import { assert } from "./util.ts";
|
||||||
import * as util from "./util.ts";
|
import * as util from "./util.ts";
|
||||||
import { postMessage, workerClose, workerMain } from "./workers.ts";
|
import { postMessage, workerClose, workerMain } from "./worker_main.ts";
|
||||||
|
|
||||||
const self = globalThis;
|
const self = globalThis;
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import * as textEncoding from "./text_encoding.ts";
|
||||||
import * as timers from "./timers.ts";
|
import * as timers from "./timers.ts";
|
||||||
import * as url from "./url.ts";
|
import * as url from "./url.ts";
|
||||||
import * as urlSearchParams from "./url_search_params.ts";
|
import * as urlSearchParams from "./url_search_params.ts";
|
||||||
|
import * as workerRuntime from "./worker_main.ts";
|
||||||
import * as workers from "./workers.ts";
|
import * as workers from "./workers.ts";
|
||||||
import * as performanceUtil from "./performance.ts";
|
import * as performanceUtil from "./performance.ts";
|
||||||
import * as request from "./request.ts";
|
import * as request from "./request.ts";
|
||||||
|
@ -194,12 +195,12 @@ const globalProperties = {
|
||||||
Response: nonEnumerable(fetchTypes.Response),
|
Response: nonEnumerable(fetchTypes.Response),
|
||||||
performance: writable(new performanceUtil.Performance()),
|
performance: writable(new performanceUtil.Performance()),
|
||||||
|
|
||||||
onmessage: writable(workers.onmessage),
|
onmessage: writable(workerRuntime.onmessage),
|
||||||
onerror: writable(workers.onerror),
|
onerror: writable(workerRuntime.onerror),
|
||||||
|
|
||||||
workerMain: nonEnumerable(workers.workerMain),
|
workerMain: nonEnumerable(workerRuntime.workerMain),
|
||||||
workerClose: nonEnumerable(workers.workerClose),
|
workerClose: nonEnumerable(workerRuntime.workerClose),
|
||||||
postMessage: writable(workers.postMessage),
|
postMessage: writable(workerRuntime.postMessage),
|
||||||
Worker: nonEnumerable(workers.WorkerImpl),
|
Worker: nonEnumerable(workers.WorkerImpl),
|
||||||
|
|
||||||
[domTypes.eventTargetHost]: nonEnumerable(null),
|
[domTypes.eventTargetHost]: nonEnumerable(null),
|
||||||
|
|
35
cli/js/lib.deno_runtime.d.ts
vendored
35
cli/js/lib.deno_runtime.d.ts
vendored
|
@ -2128,9 +2128,9 @@ declare interface Window {
|
||||||
performance: __performanceUtil.Performance;
|
performance: __performanceUtil.Performance;
|
||||||
onmessage: (e: { data: any }) => void;
|
onmessage: (e: { data: any }) => void;
|
||||||
onerror: undefined | typeof onerror;
|
onerror: undefined | typeof onerror;
|
||||||
workerMain: typeof __workers.workerMain;
|
workerMain: typeof __workerMain.workerMain;
|
||||||
workerClose: typeof __workers.workerClose;
|
workerClose: typeof __workerMain.workerClose;
|
||||||
postMessage: typeof __workers.postMessage;
|
postMessage: typeof __workerMain.postMessage;
|
||||||
Worker: typeof __workers.WorkerImpl;
|
Worker: typeof __workers.WorkerImpl;
|
||||||
addEventListener: (
|
addEventListener: (
|
||||||
type: string,
|
type: string,
|
||||||
|
@ -2187,9 +2187,9 @@ declare let onerror:
|
||||||
e: Event
|
e: Event
|
||||||
) => boolean | void)
|
) => boolean | void)
|
||||||
| undefined;
|
| undefined;
|
||||||
declare const workerMain: typeof __workers.workerMain;
|
declare const workerMain: typeof __workerMain.workerMain;
|
||||||
declare const workerClose: typeof __workers.workerClose;
|
declare const workerClose: typeof __workerMain.workerClose;
|
||||||
declare const postMessage: typeof __workers.postMessage;
|
declare const postMessage: typeof __workerMain.postMessage;
|
||||||
declare const Worker: typeof __workers.WorkerImpl;
|
declare const Worker: typeof __workers.WorkerImpl;
|
||||||
declare const addEventListener: (
|
declare const addEventListener: (
|
||||||
type: string,
|
type: string,
|
||||||
|
@ -3437,31 +3437,25 @@ declare namespace __url {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare namespace __workers {
|
declare namespace __workerMain {
|
||||||
// @url js/workers.d.ts
|
|
||||||
|
|
||||||
export function encodeMessage(data: any): Uint8Array;
|
|
||||||
export function decodeMessage(dataIntArray: Uint8Array): any;
|
|
||||||
export let onmessage: (e: { data: any }) => void;
|
export let onmessage: (e: { data: any }) => void;
|
||||||
export function postMessage(data: any): void;
|
export function postMessage(data: any): void;
|
||||||
export function getMessage(): Promise<any>;
|
export function getMessage(): Promise<any>;
|
||||||
export let isClosing: boolean;
|
export let isClosing: boolean;
|
||||||
export function workerClose(): void;
|
export function workerClose(): void;
|
||||||
export function workerMain(): Promise<void>;
|
export function workerMain(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare namespace __workers {
|
||||||
|
// @url js/workers.d.ts
|
||||||
export interface Worker {
|
export interface Worker {
|
||||||
onerror?: (e: Event) => void;
|
onerror?: (e: Event) => void;
|
||||||
onmessage?: (e: { data: any }) => void;
|
onmessage?: (e: { data: any }) => void;
|
||||||
onmessageerror?: () => void;
|
onmessageerror?: () => void;
|
||||||
postMessage(data: any): void;
|
postMessage(data: any): void;
|
||||||
closed: Promise<void>;
|
|
||||||
}
|
}
|
||||||
export interface WorkerOptions {}
|
export interface WorkerOptions {
|
||||||
/** Extended Deno Worker initialization options.
|
type?: "classic" | "module";
|
||||||
* `noDenoNamespace` hides global `window.Deno` namespace for
|
|
||||||
* spawned worker and nested workers spawned by it (default: false).
|
|
||||||
*/
|
|
||||||
export interface DenoWorkerOptions extends WorkerOptions {
|
|
||||||
noDenoNamespace?: boolean;
|
|
||||||
}
|
}
|
||||||
export class WorkerImpl implements Worker {
|
export class WorkerImpl implements Worker {
|
||||||
private readonly id;
|
private readonly id;
|
||||||
|
@ -3470,8 +3464,7 @@ declare namespace __workers {
|
||||||
onerror?: (e: Event) => void;
|
onerror?: (e: Event) => void;
|
||||||
onmessage?: (data: any) => void;
|
onmessage?: (data: any) => void;
|
||||||
onmessageerror?: () => void;
|
onmessageerror?: () => void;
|
||||||
constructor(specifier: string, options?: DenoWorkerOptions);
|
constructor(specifier: string, options?: WorkerOptions);
|
||||||
readonly closed: Promise<void>;
|
|
||||||
postMessage(data: any): void;
|
postMessage(data: any): void;
|
||||||
private run;
|
private run;
|
||||||
}
|
}
|
||||||
|
|
98
cli/js/worker_main.ts
Normal file
98
cli/js/worker_main.ts
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { core } from "./core.ts";
|
||||||
|
import * as dispatch from "./dispatch.ts";
|
||||||
|
import { sendAsync, sendSync } from "./dispatch_json.ts";
|
||||||
|
import { log } from "./util.ts";
|
||||||
|
import { TextDecoder, TextEncoder } from "./text_encoding.ts";
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
|
function encodeMessage(data: any): Uint8Array {
|
||||||
|
const dataJson = JSON.stringify(data);
|
||||||
|
return encoder.encode(dataJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeMessage(dataIntArray: Uint8Array): any {
|
||||||
|
const dataJson = decoder.decode(dataIntArray);
|
||||||
|
return JSON.parse(dataJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stuff for workers
|
||||||
|
export const onmessage: (e: { data: any }) => void = (): void => {};
|
||||||
|
export const onerror: (e: { data: any }) => void = (): void => {};
|
||||||
|
|
||||||
|
export function postMessage(data: any): void {
|
||||||
|
const dataIntArray = encodeMessage(data);
|
||||||
|
sendSync(dispatch.OP_WORKER_POST_MESSAGE, {}, dataIntArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMessage(): Promise<any> {
|
||||||
|
log("getMessage");
|
||||||
|
const res = await sendAsync(dispatch.OP_WORKER_GET_MESSAGE);
|
||||||
|
if (res.data != null) {
|
||||||
|
return decodeMessage(new Uint8Array(res.data));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let isClosing = false;
|
||||||
|
|
||||||
|
export function workerClose(): void {
|
||||||
|
isClosing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function workerMain(): Promise<void> {
|
||||||
|
const ops = core.ops();
|
||||||
|
// TODO(bartlomieju): this is a prototype, we should come up with
|
||||||
|
// something a bit more sophisticated
|
||||||
|
for (const [name, opId] of Object.entries(ops)) {
|
||||||
|
const opName = `OP_${name.toUpperCase()}`;
|
||||||
|
// Assign op ids to actual variables
|
||||||
|
// TODO(ry) This type casting is gross and should be fixed.
|
||||||
|
((dispatch as unknown) as { [key: string]: number })[opName] = opId;
|
||||||
|
core.setAsyncHandler(opId, dispatch.getAsyncHandler(opName));
|
||||||
|
}
|
||||||
|
|
||||||
|
log("workerMain");
|
||||||
|
|
||||||
|
while (!isClosing) {
|
||||||
|
const data = await getMessage();
|
||||||
|
if (data == null) {
|
||||||
|
log("workerMain got null message. quitting.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: void | Promise<void>;
|
||||||
|
const event = { data };
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!globalThis["onmessage"]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result = globalThis.onmessage!(event);
|
||||||
|
if (result && "then" in result) {
|
||||||
|
await result;
|
||||||
|
}
|
||||||
|
if (!globalThis["onmessage"]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (globalThis["onerror"]) {
|
||||||
|
const result = globalThis.onerror(
|
||||||
|
e.message,
|
||||||
|
e.fileName,
|
||||||
|
e.lineNumber,
|
||||||
|
e.columnNumber,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
if (result === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,35 +2,35 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import * as dispatch from "./dispatch.ts";
|
import * as dispatch from "./dispatch.ts";
|
||||||
import { sendAsync, sendSync } from "./dispatch_json.ts";
|
import { sendAsync, sendSync } from "./dispatch_json.ts";
|
||||||
import { log, createResolvable, Resolvable } from "./util.ts";
|
import { log } from "./util.ts";
|
||||||
import { TextDecoder, TextEncoder } from "./text_encoding.ts";
|
import { TextDecoder, TextEncoder } from "./text_encoding.ts";
|
||||||
|
/*
|
||||||
import { blobURLMap } from "./url.ts";
|
import { blobURLMap } from "./url.ts";
|
||||||
import { blobBytesWeakMap } from "./blob.ts";
|
import { blobBytesWeakMap } from "./blob.ts";
|
||||||
|
*/
|
||||||
import { Event } from "./event.ts";
|
import { Event } from "./event.ts";
|
||||||
import { EventTarget } from "./event_target.ts";
|
import { EventTarget } from "./event_target.ts";
|
||||||
|
|
||||||
const encoder = new TextEncoder();
|
const encoder = new TextEncoder();
|
||||||
const decoder = new TextDecoder();
|
const decoder = new TextDecoder();
|
||||||
|
|
||||||
export function encodeMessage(data: any): Uint8Array {
|
function encodeMessage(data: any): Uint8Array {
|
||||||
const dataJson = JSON.stringify(data);
|
const dataJson = JSON.stringify(data);
|
||||||
return encoder.encode(dataJson);
|
return encoder.encode(dataJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function decodeMessage(dataIntArray: Uint8Array): any {
|
function decodeMessage(dataIntArray: Uint8Array): any {
|
||||||
const dataJson = decoder.decode(dataIntArray);
|
const dataJson = decoder.decode(dataIntArray);
|
||||||
return JSON.parse(dataJson);
|
return JSON.parse(dataJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createWorker(
|
function createWorker(
|
||||||
specifier: string,
|
specifier: string,
|
||||||
includeDenoNamespace: boolean,
|
|
||||||
hasSourceCode: boolean,
|
hasSourceCode: boolean,
|
||||||
sourceCode: Uint8Array
|
sourceCode: Uint8Array
|
||||||
): { id: number; loaded: boolean } {
|
): { id: number; loaded: boolean } {
|
||||||
return sendSync(dispatch.OP_CREATE_WORKER, {
|
return sendSync(dispatch.OP_CREATE_WORKER, {
|
||||||
specifier,
|
specifier,
|
||||||
includeDenoNamespace,
|
|
||||||
hasSourceCode,
|
hasSourceCode,
|
||||||
sourceCode: new TextDecoder().decode(sourceCode)
|
sourceCode: new TextDecoder().decode(sourceCode)
|
||||||
});
|
});
|
||||||
|
@ -67,92 +67,15 @@ async function hostGetMessage(id: number): Promise<any> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stuff for workers
|
|
||||||
export const onmessage: (e: { data: any }) => void = (): void => {};
|
|
||||||
export const onerror: (e: { data: any }) => void = (): void => {};
|
|
||||||
|
|
||||||
export function postMessage(data: any): void {
|
|
||||||
const dataIntArray = encodeMessage(data);
|
|
||||||
sendSync(dispatch.OP_WORKER_POST_MESSAGE, {}, dataIntArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMessage(): Promise<any> {
|
|
||||||
log("getMessage");
|
|
||||||
const res = await sendAsync(dispatch.OP_WORKER_GET_MESSAGE);
|
|
||||||
if (res.data != null) {
|
|
||||||
return decodeMessage(new Uint8Array(res.data));
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export let isClosing = false;
|
|
||||||
|
|
||||||
export function workerClose(): void {
|
|
||||||
isClosing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function workerMain(): Promise<void> {
|
|
||||||
log("workerMain");
|
|
||||||
|
|
||||||
while (!isClosing) {
|
|
||||||
const data = await getMessage();
|
|
||||||
if (data == null) {
|
|
||||||
log("workerMain got null message. quitting.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result: void | Promise<void>;
|
|
||||||
const event = { data };
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!globalThis["onmessage"]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result = globalThis.onmessage!(event);
|
|
||||||
if (result && "then" in result) {
|
|
||||||
await result;
|
|
||||||
}
|
|
||||||
if (!globalThis["onmessage"]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (globalThis["onerror"]) {
|
|
||||||
const result = globalThis.onerror(
|
|
||||||
e.message,
|
|
||||||
e.fileName,
|
|
||||||
e.lineNumber,
|
|
||||||
e.columnNumber,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
if (result === true) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Worker {
|
export interface Worker {
|
||||||
onerror?: (e: any) => void;
|
onerror?: (e: any) => void;
|
||||||
onmessage?: (e: { data: any }) => void;
|
onmessage?: (e: { data: any }) => void;
|
||||||
onmessageerror?: () => void;
|
onmessageerror?: () => void;
|
||||||
postMessage(data: any): void;
|
postMessage(data: any): void;
|
||||||
// TODO(bartlomieju): remove this
|
|
||||||
closed: Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(kevinkassimo): Maybe implement reasonable web worker options?
|
export interface WorkerOptions {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
type?: "classic" | "module";
|
||||||
export interface WorkerOptions {}
|
|
||||||
|
|
||||||
/** Extended Deno Worker initialization options.
|
|
||||||
* `noDenoNamespace` hides global `globalThis.Deno` namespace for
|
|
||||||
* spawned worker and nested workers spawned by it (default: false).
|
|
||||||
*/
|
|
||||||
export interface DenoWorkerOptions extends WorkerOptions {
|
|
||||||
noDenoNamespace?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkerImpl extends EventTarget implements Worker {
|
export class WorkerImpl extends EventTarget implements Worker {
|
||||||
|
@ -160,20 +83,29 @@ export class WorkerImpl extends EventTarget implements Worker {
|
||||||
private isClosing = false;
|
private isClosing = false;
|
||||||
private messageBuffer: any[] = [];
|
private messageBuffer: any[] = [];
|
||||||
private ready = false;
|
private ready = false;
|
||||||
private readonly isClosedPromise: Resolvable<void>;
|
|
||||||
public onerror?: (e: any) => void;
|
public onerror?: (e: any) => void;
|
||||||
public onmessage?: (data: any) => void;
|
public onmessage?: (data: any) => void;
|
||||||
public onmessageerror?: () => void;
|
public onmessageerror?: () => void;
|
||||||
|
|
||||||
constructor(specifier: string, options?: DenoWorkerOptions) {
|
constructor(specifier: string, options?: WorkerOptions) {
|
||||||
super();
|
super();
|
||||||
let hasSourceCode = false;
|
|
||||||
let sourceCode = new Uint8Array();
|
|
||||||
|
|
||||||
let includeDenoNamespace = true;
|
let type = "classic";
|
||||||
if (options && options.noDenoNamespace) {
|
|
||||||
includeDenoNamespace = false;
|
if (options?.type) {
|
||||||
|
type = options.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type !== "module") {
|
||||||
|
throw new Error(
|
||||||
|
'Not yet implemented: only "module" type workers are supported'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSourceCode = false;
|
||||||
|
const sourceCode = new Uint8Array();
|
||||||
|
|
||||||
|
/* TODO(bartlomieju):
|
||||||
// Handle blob URL.
|
// Handle blob URL.
|
||||||
if (specifier.startsWith("blob:")) {
|
if (specifier.startsWith("blob:")) {
|
||||||
hasSourceCode = true;
|
hasSourceCode = true;
|
||||||
|
@ -187,23 +119,14 @@ export class WorkerImpl extends EventTarget implements Worker {
|
||||||
}
|
}
|
||||||
sourceCode = blobBytes!;
|
sourceCode = blobBytes!;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
const { id, loaded } = createWorker(
|
const { id, loaded } = createWorker(specifier, hasSourceCode, sourceCode);
|
||||||
specifier,
|
|
||||||
includeDenoNamespace,
|
|
||||||
hasSourceCode,
|
|
||||||
sourceCode
|
|
||||||
);
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.ready = loaded;
|
this.ready = loaded;
|
||||||
this.isClosedPromise = createResolvable();
|
|
||||||
this.poll();
|
this.poll();
|
||||||
}
|
}
|
||||||
|
|
||||||
get closed(): Promise<void> {
|
|
||||||
return this.isClosedPromise;
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleError(e: any): boolean {
|
private handleError(e: any): boolean {
|
||||||
// TODO: this is being handled in a type unsafe way, it should be type safe
|
// TODO: this is being handled in a type unsafe way, it should be type safe
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -259,7 +182,6 @@ export class WorkerImpl extends EventTarget implements Worker {
|
||||||
} else {
|
} else {
|
||||||
this.isClosing = true;
|
this.isClosing = true;
|
||||||
hostCloseWorker(this.id);
|
hostCloseWorker(this.id);
|
||||||
this.isClosedPromise.resolve();
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
cli/lib.rs
17
cli/lib.rs
|
@ -51,6 +51,7 @@ pub mod state;
|
||||||
pub mod test_util;
|
pub mod test_util;
|
||||||
mod tokio_util;
|
mod tokio_util;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
mod web_worker;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
||||||
use crate::deno_error::js_check;
|
use crate::deno_error::js_check;
|
||||||
|
@ -120,7 +121,6 @@ fn create_worker_and_state(
|
||||||
global_state.clone(),
|
global_state.clone(),
|
||||||
None,
|
None,
|
||||||
global_state.main_module.clone(),
|
global_state.main_module.clone(),
|
||||||
true,
|
|
||||||
int,
|
int,
|
||||||
)
|
)
|
||||||
.map_err(deno_error::print_err_and_exit)
|
.map_err(deno_error::print_err_and_exit)
|
||||||
|
@ -346,16 +346,15 @@ fn bundle_command(flags: DenoFlags) {
|
||||||
|
|
||||||
fn run_repl(flags: DenoFlags) {
|
fn run_repl(flags: DenoFlags) {
|
||||||
let (mut worker, _state) = create_worker_and_state(flags);
|
let (mut worker, _state) = create_worker_and_state(flags);
|
||||||
// Make repl continue to function under uncaught async errors.
|
|
||||||
worker.set_error_handler(Box::new(|err| {
|
|
||||||
eprintln!("{}", err.to_string());
|
|
||||||
Ok(())
|
|
||||||
}));
|
|
||||||
// Setup runtime.
|
|
||||||
js_check(worker.execute("denoMain()"));
|
js_check(worker.execute("denoMain()"));
|
||||||
let main_future = async move {
|
let main_future = async move {
|
||||||
let result = worker.await;
|
loop {
|
||||||
js_check(result);
|
let result = worker.clone().await;
|
||||||
|
if let Err(err) = result {
|
||||||
|
eprintln!("{}", err.to_string());
|
||||||
|
worker.clear_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
tokio_util::run(main_future);
|
tokio_util::run(main_future);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,4 +23,5 @@ pub mod repl;
|
||||||
pub mod resources;
|
pub mod resources;
|
||||||
pub mod timers;
|
pub mod timers;
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
pub mod workers;
|
pub mod web_worker;
|
||||||
|
pub mod worker_host;
|
||||||
|
|
77
cli/ops/web_worker.rs
Normal file
77
cli/ops/web_worker.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
use super::dispatch_json::{JsonOp, Value};
|
||||||
|
use crate::deno_error::DenoError;
|
||||||
|
use crate::deno_error::ErrorKind;
|
||||||
|
use crate::ops::json_op;
|
||||||
|
use crate::state::ThreadSafeState;
|
||||||
|
use deno_core::*;
|
||||||
|
use futures;
|
||||||
|
use futures::future::FutureExt;
|
||||||
|
use futures::sink::SinkExt;
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
use std;
|
||||||
|
use std::convert::From;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::Context;
|
||||||
|
use std::task::Poll;
|
||||||
|
|
||||||
|
pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
||||||
|
i.register_op(
|
||||||
|
"worker_post_message",
|
||||||
|
s.core_op(json_op(s.stateful_op(op_worker_post_message))),
|
||||||
|
);
|
||||||
|
i.register_op(
|
||||||
|
"worker_get_message",
|
||||||
|
s.core_op(json_op(s.stateful_op(op_worker_get_message))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GetMessageFuture {
|
||||||
|
state: ThreadSafeState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for GetMessageFuture {
|
||||||
|
type Output = Option<Buf>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let inner = self.get_mut();
|
||||||
|
let mut channels = inner.state.worker_channels.lock().unwrap();
|
||||||
|
let receiver = &mut channels.receiver;
|
||||||
|
receiver.poll_next_unpin(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get message from host as guest worker
|
||||||
|
fn op_worker_get_message(
|
||||||
|
state: &ThreadSafeState,
|
||||||
|
_args: Value,
|
||||||
|
_data: Option<PinnedBuf>,
|
||||||
|
) -> Result<JsonOp, ErrBox> {
|
||||||
|
let op = GetMessageFuture {
|
||||||
|
state: state.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let op = async move {
|
||||||
|
let maybe_buf = op.await;
|
||||||
|
debug!("op_worker_get_message");
|
||||||
|
Ok(json!({ "data": maybe_buf }))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(JsonOp::Async(op.boxed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post message to host as guest worker
|
||||||
|
fn op_worker_post_message(
|
||||||
|
state: &ThreadSafeState,
|
||||||
|
_args: Value,
|
||||||
|
data: Option<PinnedBuf>,
|
||||||
|
) -> Result<JsonOp, ErrBox> {
|
||||||
|
let d = Vec::from(data.unwrap().as_ref()).into_boxed_slice();
|
||||||
|
let mut channels = state.worker_channels.lock().unwrap();
|
||||||
|
let sender = &mut channels.sender;
|
||||||
|
futures::executor::block_on(sender.send(d))
|
||||||
|
.map_err(|e| DenoError::new(ErrorKind::Other, e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(JsonOp::Sync(json!({})))
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ use crate::fmt_errors::JSError;
|
||||||
use crate::ops::json_op;
|
use crate::ops::json_op;
|
||||||
use crate::startup_data;
|
use crate::startup_data;
|
||||||
use crate::state::ThreadSafeState;
|
use crate::state::ThreadSafeState;
|
||||||
use crate::worker::Worker;
|
use crate::web_worker::WebWorker;
|
||||||
use deno_core::*;
|
use deno_core::*;
|
||||||
use futures;
|
use futures;
|
||||||
use futures::channel::mpsc;
|
use futures::channel::mpsc;
|
||||||
|
@ -54,15 +54,6 @@ pub fn init(i: &mut Isolate, s: &ThreadSafeState) {
|
||||||
"host_get_message",
|
"host_get_message",
|
||||||
s.core_op(json_op(s.stateful_op(op_host_get_message))),
|
s.core_op(json_op(s.stateful_op(op_host_get_message))),
|
||||||
);
|
);
|
||||||
// TODO: make sure these two ops are only accessible to appropriate Worker
|
|
||||||
i.register_op(
|
|
||||||
"worker_post_message",
|
|
||||||
s.core_op(json_op(s.stateful_op(op_worker_post_message))),
|
|
||||||
);
|
|
||||||
i.register_op(
|
|
||||||
"worker_get_message",
|
|
||||||
s.core_op(json_op(s.stateful_op(op_worker_get_message))),
|
|
||||||
);
|
|
||||||
i.register_op("metrics", s.core_op(json_op(s.stateful_op(op_metrics))));
|
i.register_op("metrics", s.core_op(json_op(s.stateful_op(op_metrics))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,45 +72,10 @@ impl Future for GetMessageFuture {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get message from host as guest worker
|
|
||||||
fn op_worker_get_message(
|
|
||||||
state: &ThreadSafeState,
|
|
||||||
_args: Value,
|
|
||||||
_data: Option<PinnedBuf>,
|
|
||||||
) -> Result<JsonOp, ErrBox> {
|
|
||||||
let op = GetMessageFuture {
|
|
||||||
state: state.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let op = async move {
|
|
||||||
let maybe_buf = op.await;
|
|
||||||
debug!("op_worker_get_message");
|
|
||||||
Ok(json!({ "data": maybe_buf }))
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(JsonOp::Async(op.boxed()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Post message to host as guest worker
|
|
||||||
fn op_worker_post_message(
|
|
||||||
state: &ThreadSafeState,
|
|
||||||
_args: Value,
|
|
||||||
data: Option<PinnedBuf>,
|
|
||||||
) -> Result<JsonOp, ErrBox> {
|
|
||||||
let d = Vec::from(data.unwrap().as_ref()).into_boxed_slice();
|
|
||||||
let mut channels = state.worker_channels.lock().unwrap();
|
|
||||||
let sender = &mut channels.sender;
|
|
||||||
futures::executor::block_on(sender.send(d))
|
|
||||||
.map_err(|e| DenoError::new(ErrorKind::Other, e.to_string()))?;
|
|
||||||
|
|
||||||
Ok(JsonOp::Sync(json!({})))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct CreateWorkerArgs {
|
struct CreateWorkerArgs {
|
||||||
specifier: String,
|
specifier: String,
|
||||||
include_deno_namespace: bool,
|
|
||||||
has_source_code: bool,
|
has_source_code: bool,
|
||||||
source_code: String,
|
source_code: String,
|
||||||
}
|
}
|
||||||
|
@ -133,10 +89,6 @@ fn op_create_worker(
|
||||||
let args: CreateWorkerArgs = serde_json::from_value(args)?;
|
let args: CreateWorkerArgs = serde_json::from_value(args)?;
|
||||||
|
|
||||||
let specifier = args.specifier.as_ref();
|
let specifier = args.specifier.as_ref();
|
||||||
// Only include deno namespace if requested AND current worker
|
|
||||||
// has included namespace (to avoid escalation).
|
|
||||||
let include_deno_namespace =
|
|
||||||
args.include_deno_namespace && state.include_deno_namespace;
|
|
||||||
let has_source_code = args.has_source_code;
|
let has_source_code = args.has_source_code;
|
||||||
let source_code = args.source_code;
|
let source_code = args.source_code;
|
||||||
|
|
||||||
|
@ -156,16 +108,13 @@ fn op_create_worker(
|
||||||
state.global_state.clone(),
|
state.global_state.clone(),
|
||||||
Some(parent_state.permissions.clone()), // by default share with parent
|
Some(parent_state.permissions.clone()), // by default share with parent
|
||||||
Some(module_specifier.clone()),
|
Some(module_specifier.clone()),
|
||||||
include_deno_namespace,
|
|
||||||
int,
|
int,
|
||||||
)?;
|
)?;
|
||||||
// TODO: add a new option to make child worker not sharing permissions
|
// TODO: add a new option to make child worker not sharing permissions
|
||||||
// with parent (aka .clone(), requests from child won't reflect in parent)
|
// with parent (aka .clone(), requests from child won't reflect in parent)
|
||||||
let name = format!("USER-WORKER-{}", specifier);
|
let name = format!("USER-WORKER-{}", specifier);
|
||||||
let deno_main_call = format!("denoMain({})", include_deno_namespace);
|
|
||||||
let mut worker =
|
let mut worker =
|
||||||
Worker::new(name, startup_data::deno_isolate_init(), child_state, ext);
|
WebWorker::new(name, startup_data::deno_isolate_init(), child_state, ext);
|
||||||
js_check(worker.execute(&deno_main_call));
|
|
||||||
js_check(worker.execute("workerMain()"));
|
js_check(worker.execute("workerMain()"));
|
||||||
|
|
||||||
let worker_id = parent_state.add_child_worker(worker.clone());
|
let worker_id = parent_state.add_child_worker(worker.clone());
|
14
cli/state.rs
14
cli/state.rs
|
@ -7,7 +7,7 @@ use crate::metrics::Metrics;
|
||||||
use crate::ops::JsonOp;
|
use crate::ops::JsonOp;
|
||||||
use crate::ops::MinimalOp;
|
use crate::ops::MinimalOp;
|
||||||
use crate::permissions::DenoPermissions;
|
use crate::permissions::DenoPermissions;
|
||||||
use crate::worker::Worker;
|
use crate::web_worker::WebWorker;
|
||||||
use crate::worker::WorkerChannels;
|
use crate::worker::WorkerChannels;
|
||||||
use deno_core::Buf;
|
use deno_core::Buf;
|
||||||
use deno_core::CoreOp;
|
use deno_core::CoreOp;
|
||||||
|
@ -44,7 +44,6 @@ pub struct ThreadSafeState(Arc<State>);
|
||||||
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
#[cfg_attr(feature = "cargo-clippy", allow(stutter))]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub global_state: ThreadSafeGlobalState,
|
pub global_state: ThreadSafeGlobalState,
|
||||||
pub modules: Arc<Mutex<deno_core::Modules>>,
|
|
||||||
pub permissions: Arc<Mutex<DenoPermissions>>,
|
pub permissions: Arc<Mutex<DenoPermissions>>,
|
||||||
pub main_module: Option<ModuleSpecifier>,
|
pub main_module: Option<ModuleSpecifier>,
|
||||||
pub worker_channels: Mutex<WorkerChannels>,
|
pub worker_channels: Mutex<WorkerChannels>,
|
||||||
|
@ -53,12 +52,11 @@ pub struct State {
|
||||||
pub import_map: Option<ImportMap>,
|
pub import_map: Option<ImportMap>,
|
||||||
pub metrics: Metrics,
|
pub metrics: Metrics,
|
||||||
pub global_timer: Mutex<GlobalTimer>,
|
pub global_timer: Mutex<GlobalTimer>,
|
||||||
pub workers: Mutex<HashMap<u32, Worker>>,
|
pub workers: Mutex<HashMap<u32, WebWorker>>,
|
||||||
pub loading_workers: Mutex<HashMap<u32, mpsc::Receiver<Result<(), ErrBox>>>>,
|
pub loading_workers: Mutex<HashMap<u32, mpsc::Receiver<Result<(), ErrBox>>>>,
|
||||||
pub next_worker_id: AtomicUsize,
|
pub next_worker_id: AtomicUsize,
|
||||||
pub start_time: Instant,
|
pub start_time: Instant,
|
||||||
pub seeded_rng: Option<Mutex<StdRng>>,
|
pub seeded_rng: Option<Mutex<StdRng>>,
|
||||||
pub include_deno_namespace: bool,
|
|
||||||
pub resource_table: Mutex<ResourceTable>,
|
pub resource_table: Mutex<ResourceTable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +217,6 @@ impl ThreadSafeState {
|
||||||
// If Some(perm), use perm. Else copy from global_state.
|
// If Some(perm), use perm. Else copy from global_state.
|
||||||
shared_permissions: Option<Arc<Mutex<DenoPermissions>>>,
|
shared_permissions: Option<Arc<Mutex<DenoPermissions>>>,
|
||||||
main_module: Option<ModuleSpecifier>,
|
main_module: Option<ModuleSpecifier>,
|
||||||
include_deno_namespace: bool,
|
|
||||||
internal_channels: WorkerChannels,
|
internal_channels: WorkerChannels,
|
||||||
) -> Result<Self, ErrBox> {
|
) -> Result<Self, ErrBox> {
|
||||||
let import_map: Option<ImportMap> =
|
let import_map: Option<ImportMap> =
|
||||||
|
@ -233,7 +230,6 @@ impl ThreadSafeState {
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let modules = Arc::new(Mutex::new(deno_core::Modules::new()));
|
|
||||||
let permissions = if let Some(perm) = shared_permissions {
|
let permissions = if let Some(perm) = shared_permissions {
|
||||||
perm
|
perm
|
||||||
} else {
|
} else {
|
||||||
|
@ -242,7 +238,6 @@ impl ThreadSafeState {
|
||||||
|
|
||||||
let state = State {
|
let state = State {
|
||||||
global_state,
|
global_state,
|
||||||
modules,
|
|
||||||
main_module,
|
main_module,
|
||||||
permissions,
|
permissions,
|
||||||
import_map,
|
import_map,
|
||||||
|
@ -254,14 +249,14 @@ impl ThreadSafeState {
|
||||||
next_worker_id: AtomicUsize::new(0),
|
next_worker_id: AtomicUsize::new(0),
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
seeded_rng,
|
seeded_rng,
|
||||||
include_deno_namespace,
|
|
||||||
resource_table: Mutex::new(ResourceTable::default()),
|
resource_table: Mutex::new(ResourceTable::default()),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ThreadSafeState(Arc::new(state)))
|
Ok(ThreadSafeState(Arc::new(state)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_child_worker(&self, worker: Worker) -> u32 {
|
pub fn add_child_worker(&self, worker: WebWorker) -> u32 {
|
||||||
let worker_id = self.next_worker_id.fetch_add(1, Ordering::Relaxed) as u32;
|
let worker_id = self.next_worker_id.fetch_add(1, Ordering::Relaxed) as u32;
|
||||||
let mut workers_tl = self.workers.lock().unwrap();
|
let mut workers_tl = self.workers.lock().unwrap();
|
||||||
workers_tl.insert(worker_id, worker);
|
workers_tl.insert(worker_id, worker);
|
||||||
|
@ -344,7 +339,6 @@ impl ThreadSafeState {
|
||||||
ThreadSafeGlobalState::mock(argv),
|
ThreadSafeGlobalState::mock(argv),
|
||||||
None,
|
None,
|
||||||
module_specifier,
|
module_specifier,
|
||||||
true,
|
|
||||||
internal_channels,
|
internal_channels,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const jsWorker = new Worker("./subdir/test_worker.js");
|
const jsWorker = new Worker("./subdir/test_worker.js", { type: "module" });
|
||||||
const tsWorker = new Worker("./subdir/test_worker.ts");
|
const tsWorker = new Worker("./subdir/test_worker.ts", { type: "module" });
|
||||||
|
|
||||||
tsWorker.onmessage = (e): void => {
|
tsWorker.onmessage = (e): void => {
|
||||||
console.log("Received ts: " + e.data);
|
console.log("Received ts: " + e.data);
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
const w1 = new Worker("./039_worker_deno_ns/has_ns.ts");
|
const w1 = new Worker("./039_worker_deno_ns/has_ns.ts", { type: "module" });
|
||||||
const w2 = new Worker("./039_worker_deno_ns/no_ns.ts", {
|
const w2 = new Worker("./039_worker_deno_ns/no_ns.ts", { type: "module" });
|
||||||
noDenoNamespace: true
|
|
||||||
});
|
|
||||||
let w1MsgCount = 0;
|
let w1MsgCount = 0;
|
||||||
let w2MsgCount = 0;
|
let w2MsgCount = 0;
|
||||||
w1.onmessage = (msg): void => {
|
w1.onmessage = (msg): void => {
|
||||||
|
|
|
@ -301,6 +301,7 @@ itest!(_038_checkjs {
|
||||||
output: "038_checkjs.js.out",
|
output: "038_checkjs.js.out",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* TODO(bartlomieju):
|
||||||
itest!(_039_worker_deno_ns {
|
itest!(_039_worker_deno_ns {
|
||||||
args: "run --reload 039_worker_deno_ns.ts",
|
args: "run --reload 039_worker_deno_ns.ts",
|
||||||
output: "039_worker_deno_ns.ts.out",
|
output: "039_worker_deno_ns.ts.out",
|
||||||
|
@ -310,6 +311,7 @@ itest!(_040_worker_blob {
|
||||||
args: "run --reload 040_worker_blob.ts",
|
args: "run --reload 040_worker_blob.ts",
|
||||||
output: "040_worker_blob.ts.out",
|
output: "040_worker_blob.ts.out",
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
itest!(_041_dyn_import_eval {
|
itest!(_041_dyn_import_eval {
|
||||||
args: "eval import('./subdir/mod4.js').then(console.log)",
|
args: "eval import('./subdir/mod4.js').then(console.log)",
|
||||||
|
@ -567,12 +569,14 @@ itest!(error_type_definitions {
|
||||||
output: "error_type_definitions.ts.out",
|
output: "error_type_definitions.ts.out",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* TODO(bartlomieju)
|
||||||
itest!(error_worker_dynamic {
|
itest!(error_worker_dynamic {
|
||||||
args: "run --reload error_worker_dynamic.ts",
|
args: "run --reload error_worker_dynamic.ts",
|
||||||
check_stderr: true,
|
check_stderr: true,
|
||||||
exit_code: 1,
|
exit_code: 1,
|
||||||
output: "error_worker_dynamic.ts.out",
|
output: "error_worker_dynamic.ts.out",
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
itest!(exit_error42 {
|
itest!(exit_error42 {
|
||||||
exit_code: 42,
|
exit_code: 42,
|
||||||
|
|
|
@ -14,6 +14,7 @@ onmessage = function(e): void {
|
||||||
postMessage({ cmdId });
|
postMessage({ cmdId });
|
||||||
break;
|
break;
|
||||||
case 3: // Close
|
case 3: // Close
|
||||||
|
postMessage({ cmdId: 3 });
|
||||||
workerClose();
|
workerClose();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,12 +37,11 @@ function handleAsyncMsgFromWorker(
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const workers: Array<[Map<number, Resolvable<string>>, Worker]> = [];
|
const workers: Array<[Map<number, Resolvable<string>>, Worker]> = [];
|
||||||
for (let i = 1; i <= workerCount; ++i) {
|
for (let i = 1; i <= workerCount; ++i) {
|
||||||
const worker = new Worker("./subdir/bench_worker.ts");
|
const worker = new Worker("./subdir/bench_worker.ts", { type: "module" });
|
||||||
const promise = new Promise((resolve): void => {
|
const promise = createResolvable<void>();
|
||||||
worker.onmessage = (e): void => {
|
worker.onmessage = (e): void => {
|
||||||
if (e.data.cmdId === 0) resolve();
|
if (e.data.cmdId === 0) promise.resolve();
|
||||||
};
|
};
|
||||||
});
|
|
||||||
worker.postMessage({ cmdId: 0, action: 2 });
|
worker.postMessage({ cmdId: 0, action: 2 });
|
||||||
await promise;
|
await promise;
|
||||||
workers.push([new Map(), worker]);
|
workers.push([new Map(), worker]);
|
||||||
|
@ -66,8 +65,12 @@ async function main(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [, worker] of workers) {
|
for (const [, worker] of workers) {
|
||||||
|
const promise = createResolvable<void>();
|
||||||
|
worker.onmessage = (e): void => {
|
||||||
|
if (e.data.cmdId === 3) promise.resolve();
|
||||||
|
};
|
||||||
worker.postMessage({ action: 3 });
|
worker.postMessage({ action: 3 });
|
||||||
await worker.closed; // Required to avoid a cmdId not in table error.
|
await promise;
|
||||||
}
|
}
|
||||||
console.log("Finished!");
|
console.log("Finished!");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ const workerCount = 50;
|
||||||
async function bench(): Promise<void> {
|
async function bench(): Promise<void> {
|
||||||
const workers: Worker[] = [];
|
const workers: Worker[] = [];
|
||||||
for (let i = 1; i <= workerCount; ++i) {
|
for (let i = 1; i <= workerCount; ++i) {
|
||||||
const worker = new Worker("./subdir/bench_worker.ts");
|
const worker = new Worker("./subdir/bench_worker.ts", { type: "module" });
|
||||||
const promise = new Promise((resolve): void => {
|
const promise = new Promise((resolve): void => {
|
||||||
worker.onmessage = (e): void => {
|
worker.onmessage = (e): void => {
|
||||||
if (e.data.cmdId === 0) resolve();
|
if (e.data.cmdId === 0) resolve();
|
||||||
|
@ -16,8 +16,13 @@ async function bench(): Promise<void> {
|
||||||
}
|
}
|
||||||
console.log("Done creating workers closing workers!");
|
console.log("Done creating workers closing workers!");
|
||||||
for (const worker of workers) {
|
for (const worker of workers) {
|
||||||
|
const promise = new Promise((resolve): void => {
|
||||||
|
worker.onmessage = (e): void => {
|
||||||
|
if (e.data.cmdId === 3) resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
worker.postMessage({ action: 3 });
|
worker.postMessage({ action: 3 });
|
||||||
await worker.closed; // Required to avoid a cmdId not in table error.
|
await promise;
|
||||||
}
|
}
|
||||||
console.log("Finished!");
|
console.log("Finished!");
|
||||||
}
|
}
|
||||||
|
|
145
cli/web_worker.rs
Normal file
145
cli/web_worker.rs
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
use crate::fmt_errors::JSError;
|
||||||
|
use crate::ops;
|
||||||
|
use crate::state::ThreadSafeState;
|
||||||
|
use crate::worker::WorkerChannels;
|
||||||
|
use crate::worker::WorkerReceiver;
|
||||||
|
use deno_core;
|
||||||
|
use deno_core::Buf;
|
||||||
|
use deno_core::ErrBox;
|
||||||
|
use deno_core::ModuleSpecifier;
|
||||||
|
use deno_core::StartupData;
|
||||||
|
use futures::future::FutureExt;
|
||||||
|
use futures::future::TryFutureExt;
|
||||||
|
use futures::sink::SinkExt;
|
||||||
|
use futures::task::AtomicWaker;
|
||||||
|
use std::env;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::task::Context;
|
||||||
|
use std::task::Poll;
|
||||||
|
use tokio::sync::Mutex as AsyncMutex;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct WebWorker {
|
||||||
|
pub name: String,
|
||||||
|
pub isolate: Arc<AsyncMutex<Box<deno_core::EsIsolate>>>,
|
||||||
|
pub state: ThreadSafeState,
|
||||||
|
external_channels: Arc<Mutex<WorkerChannels>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebWorker {
|
||||||
|
pub fn new(
|
||||||
|
name: String,
|
||||||
|
startup_data: StartupData,
|
||||||
|
state: ThreadSafeState,
|
||||||
|
external_channels: WorkerChannels,
|
||||||
|
) -> Self {
|
||||||
|
let mut isolate =
|
||||||
|
deno_core::EsIsolate::new(Box::new(state.clone()), startup_data, false);
|
||||||
|
|
||||||
|
ops::web_worker::init(&mut isolate, &state);
|
||||||
|
ops::worker_host::init(&mut isolate, &state);
|
||||||
|
|
||||||
|
let global_state_ = state.global_state.clone();
|
||||||
|
isolate.set_js_error_create(move |v8_exception| {
|
||||||
|
JSError::from_v8_exception(v8_exception, &global_state_.ts_compiler)
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
isolate: Arc::new(AsyncMutex::new(isolate)),
|
||||||
|
state,
|
||||||
|
external_channels: Arc::new(Mutex::new(external_channels)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
|
||||||
|
pub fn execute(&mut self, js_source: &str) -> Result<(), ErrBox> {
|
||||||
|
let path = env::current_dir().unwrap().join("__anonymous__");
|
||||||
|
let url = Url::from_file_path(path).unwrap();
|
||||||
|
self.execute2(url.as_str(), js_source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the provided JavaScript source code. The js_filename argument is
|
||||||
|
/// provided only for debugging purposes.
|
||||||
|
fn execute2(
|
||||||
|
&mut self,
|
||||||
|
js_filename: &str,
|
||||||
|
js_source: &str,
|
||||||
|
) -> Result<(), ErrBox> {
|
||||||
|
let mut isolate = self.isolate.try_lock().unwrap();
|
||||||
|
isolate.execute(js_filename, js_source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes the provided JavaScript module.
|
||||||
|
///
|
||||||
|
/// Takes ownership of the isolate behind mutex.
|
||||||
|
pub async fn execute_mod_async(
|
||||||
|
&mut self,
|
||||||
|
module_specifier: &ModuleSpecifier,
|
||||||
|
maybe_code: Option<String>,
|
||||||
|
is_prefetch: bool,
|
||||||
|
) -> Result<(), ErrBox> {
|
||||||
|
let specifier = module_specifier.to_string();
|
||||||
|
let worker = self.clone();
|
||||||
|
|
||||||
|
let mut isolate = self.isolate.lock().await;
|
||||||
|
let id = isolate.load_module(&specifier, maybe_code).await?;
|
||||||
|
worker.state.global_state.progress.done();
|
||||||
|
|
||||||
|
if !is_prefetch {
|
||||||
|
return isolate.mod_evaluate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post message to worker as a host.
|
||||||
|
///
|
||||||
|
/// This method blocks current thread.
|
||||||
|
pub fn post_message(
|
||||||
|
&self,
|
||||||
|
buf: Buf,
|
||||||
|
) -> impl Future<Output = Result<(), ErrBox>> {
|
||||||
|
let channels = self.external_channels.lock().unwrap();
|
||||||
|
let mut sender = channels.sender.clone();
|
||||||
|
async move {
|
||||||
|
let result = sender.send(buf).map_err(ErrBox::from).await;
|
||||||
|
drop(sender);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get message from worker as a host.
|
||||||
|
pub fn get_message(&self) -> WorkerReceiver {
|
||||||
|
WorkerReceiver {
|
||||||
|
channels: self.external_channels.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_exception(&mut self) {
|
||||||
|
let mut isolate = self.isolate.try_lock().unwrap();
|
||||||
|
isolate.clear_exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for WebWorker {
|
||||||
|
type Output = Result<(), ErrBox>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
|
||||||
|
let inner = self.get_mut();
|
||||||
|
let waker = AtomicWaker::new();
|
||||||
|
waker.register(cx.waker());
|
||||||
|
match inner.isolate.try_lock() {
|
||||||
|
Ok(mut isolate) => isolate.poll_unpin(cx),
|
||||||
|
Err(_) => {
|
||||||
|
waker.wake();
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,55 +48,42 @@ impl Worker {
|
||||||
state: ThreadSafeState,
|
state: ThreadSafeState,
|
||||||
external_channels: WorkerChannels,
|
external_channels: WorkerChannels,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let isolate = Arc::new(AsyncMutex::new(deno_core::EsIsolate::new(
|
let mut isolate =
|
||||||
Box::new(state.clone()),
|
deno_core::EsIsolate::new(Box::new(state.clone()), startup_data, false);
|
||||||
startup_data,
|
let op_registry = isolate.op_registry.clone();
|
||||||
false,
|
|
||||||
)));
|
|
||||||
{
|
|
||||||
let mut i = isolate.try_lock().unwrap();
|
|
||||||
let op_registry = i.op_registry.clone();
|
|
||||||
|
|
||||||
ops::compiler::init(&mut i, &state);
|
ops::compiler::init(&mut isolate, &state);
|
||||||
ops::errors::init(&mut i, &state);
|
ops::errors::init(&mut isolate, &state);
|
||||||
ops::fetch::init(&mut i, &state);
|
ops::fetch::init(&mut isolate, &state);
|
||||||
ops::files::init(&mut i, &state);
|
ops::files::init(&mut isolate, &state);
|
||||||
ops::fs::init(&mut i, &state);
|
ops::fs::init(&mut isolate, &state);
|
||||||
ops::io::init(&mut i, &state);
|
ops::io::init(&mut isolate, &state);
|
||||||
ops::plugins::init(&mut i, &state, op_registry);
|
ops::plugins::init(&mut isolate, &state, op_registry);
|
||||||
ops::net::init(&mut i, &state);
|
ops::net::init(&mut isolate, &state);
|
||||||
ops::tls::init(&mut i, &state);
|
ops::tls::init(&mut isolate, &state);
|
||||||
ops::os::init(&mut i, &state);
|
ops::os::init(&mut isolate, &state);
|
||||||
ops::permissions::init(&mut i, &state);
|
ops::permissions::init(&mut isolate, &state);
|
||||||
ops::process::init(&mut i, &state);
|
ops::process::init(&mut isolate, &state);
|
||||||
ops::random::init(&mut i, &state);
|
ops::random::init(&mut isolate, &state);
|
||||||
ops::repl::init(&mut i, &state);
|
ops::repl::init(&mut isolate, &state);
|
||||||
ops::resources::init(&mut i, &state);
|
ops::resources::init(&mut isolate, &state);
|
||||||
ops::timers::init(&mut i, &state);
|
ops::timers::init(&mut isolate, &state);
|
||||||
ops::workers::init(&mut i, &state);
|
ops::worker_host::init(&mut isolate, &state);
|
||||||
|
ops::web_worker::init(&mut isolate, &state);
|
||||||
|
|
||||||
let global_state_ = state.global_state.clone();
|
let global_state_ = state.global_state.clone();
|
||||||
i.set_js_error_create(move |v8_exception| {
|
isolate.set_js_error_create(move |v8_exception| {
|
||||||
JSError::from_v8_exception(v8_exception, &global_state_.ts_compiler)
|
JSError::from_v8_exception(v8_exception, &global_state_.ts_compiler)
|
||||||
})
|
});
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
name,
|
name,
|
||||||
isolate,
|
isolate: Arc::new(AsyncMutex::new(isolate)),
|
||||||
state,
|
state,
|
||||||
external_channels: Arc::new(Mutex::new(external_channels)),
|
external_channels: Arc::new(Mutex::new(external_channels)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_error_handler(
|
|
||||||
&mut self,
|
|
||||||
handler: Box<dyn FnMut(ErrBox) -> Result<(), ErrBox>>,
|
|
||||||
) {
|
|
||||||
let mut i = self.isolate.try_lock().unwrap();
|
|
||||||
i.set_error_handler(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
|
/// Same as execute2() but the filename defaults to "$CWD/__anonymous__".
|
||||||
pub fn execute(&mut self, js_source: &str) -> Result<(), ErrBox> {
|
pub fn execute(&mut self, js_source: &str) -> Result<(), ErrBox> {
|
||||||
let path = env::current_dir().unwrap().join("__anonymous__");
|
let path = env::current_dir().unwrap().join("__anonymous__");
|
||||||
|
@ -188,7 +175,7 @@ impl Future for Worker {
|
||||||
/// that will return message received from worker or None
|
/// that will return message received from worker or None
|
||||||
/// if worker's channel has been closed.
|
/// if worker's channel has been closed.
|
||||||
pub struct WorkerReceiver {
|
pub struct WorkerReceiver {
|
||||||
channels: Arc<Mutex<WorkerChannels>>,
|
pub channels: Arc<Mutex<WorkerChannels>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Future for WorkerReceiver {
|
impl Future for WorkerReceiver {
|
||||||
|
@ -255,7 +242,6 @@ mod tests {
|
||||||
global_state,
|
global_state,
|
||||||
None,
|
None,
|
||||||
Some(module_specifier.clone()),
|
Some(module_specifier.clone()),
|
||||||
true,
|
|
||||||
int,
|
int,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -299,7 +285,6 @@ mod tests {
|
||||||
global_state,
|
global_state,
|
||||||
None,
|
None,
|
||||||
Some(module_specifier.clone()),
|
Some(module_specifier.clone()),
|
||||||
true,
|
|
||||||
int,
|
int,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -342,7 +327,6 @@ mod tests {
|
||||||
global_state.clone(),
|
global_state.clone(),
|
||||||
None,
|
None,
|
||||||
Some(module_specifier.clone()),
|
Some(module_specifier.clone()),
|
||||||
true,
|
|
||||||
int,
|
int,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -440,6 +440,8 @@ impl Isolate {
|
||||||
isolate.exit();
|
isolate.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(bartlomieju): `error_handler` should be removed
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn set_error_handler(&mut self, handler: Box<IsolateErrorHandleFn>) {
|
pub fn set_error_handler(&mut self, handler: Box<IsolateErrorHandleFn>) {
|
||||||
self.error_handler = Some(handler);
|
self.error_handler = Some(handler);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue