1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 07:14:47 -05:00

feat: support Deno namespace in Worker API (#4784)

This commit is contained in:
Bartek Iwańczuk 2020-04-16 23:40:29 +02:00 committed by GitHub
parent 1cd1f7de70
commit d359789c52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 190 additions and 21 deletions

View file

@ -30,7 +30,7 @@ pub struct CompilerWorker(WebWorker);
impl CompilerWorker { impl CompilerWorker {
pub fn new(name: String, startup_data: StartupData, state: State) -> Self { pub fn new(name: String, startup_data: StartupData, state: State) -> Self {
let state_ = state.clone(); let state_ = state.clone();
let mut worker = WebWorker::new(name, startup_data, state_); let mut worker = WebWorker::new(name, startup_data, state_, false);
{ {
let isolate = &mut worker.isolate; let isolate = &mut worker.isolate;
ops::compiler::init(isolate, &state); ops::compiler::init(isolate, &state);

View file

@ -391,12 +391,12 @@ async function wasmCompilerOnMessage({
} }
function bootstrapTsCompilerRuntime(): void { function bootstrapTsCompilerRuntime(): void {
bootstrapWorkerRuntime("TS"); bootstrapWorkerRuntime("TS", false);
globalThis.onmessage = tsCompilerOnMessage; globalThis.onmessage = tsCompilerOnMessage;
} }
function bootstrapWasmCompilerRuntime(): void { function bootstrapWasmCompilerRuntime(): void {
bootstrapWorkerRuntime("WASM"); bootstrapWorkerRuntime("WASM", false);
globalThis.onmessage = wasmCompilerOnMessage; globalThis.onmessage = wasmCompilerOnMessage;
} }

View file

@ -1080,6 +1080,46 @@ declare class Worker extends EventTarget {
options?: { options?: {
type?: "classic" | "module"; type?: "classic" | "module";
name?: string; name?: string;
/** UNSTABLE: New API. Expect many changes; most likely this
* field will be made into an object for more granular
* configuration of worker thread (permissions, import map, etc.).
*
* Set to `true` to make `Deno` namespace and all of its methods
* available to worker thread.
*
* Currently worker inherits permissions from main thread (permissions
* given using `--allow-*` flags).
* Configurable permissions are on the roadmap to be implemented.
*
* Example:
* // mod.ts
* const worker = new Worker("./deno_worker.ts", { type: "module", deno: true });
* worker.postMessage({ cmd: "readFile", fileName: "./log.txt" });
*
* // deno_worker.ts
*
*
* self.onmessage = async function (e) {
* const { cmd, fileName } = e.data;
* if (cmd !== "readFile") {
* throw new Error("Invalid command");
* }
* const buf = await Deno.readFile(fileName);
* const fileContents = new TextDecoder().decode(buf);
* console.log(fileContents);
* }
*
* // log.txt
* hello world
* hello world 2
*
* // run program
* $ deno run --allow-read mod.ts
* hello world
* hello world2
*
*/
deno?: boolean;
} }
); );
postMessage(message: any, transfer: ArrayBuffer[]): void; postMessage(message: any, transfer: ArrayBuffer[]): void;

View file

@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */
/// <reference no-default-lib="true" /> /// <reference no-default-lib="true" />
/// <reference lib="deno.ns" />
/// <reference lib="deno.shared_globals" /> /// <reference lib="deno.shared_globals" />
/// <reference lib="esnext" /> /// <reference lib="esnext" />
@ -15,6 +16,7 @@ declare interface DedicatedWorkerGlobalScope {
name: typeof __workerMain.name; name: typeof __workerMain.name;
close: typeof __workerMain.close; close: typeof __workerMain.close;
postMessage: typeof __workerMain.postMessage; postMessage: typeof __workerMain.postMessage;
Deno: typeof Deno;
} }
declare const self: DedicatedWorkerGlobalScope & typeof globalThis; declare const self: DedicatedWorkerGlobalScope & typeof globalThis;

View file

@ -6,6 +6,7 @@ export function createWorker(
specifier: string, specifier: string,
hasSourceCode: boolean, hasSourceCode: boolean,
sourceCode: string, sourceCode: string,
useDenoNamespace: boolean,
name?: string name?: string
): { id: number } { ): { id: number } {
return sendSync("op_create_worker", { return sendSync("op_create_worker", {
@ -13,6 +14,7 @@ export function createWorker(
hasSourceCode, hasSourceCode,
sourceCode, sourceCode,
name, name,
useDenoNamespace,
}); });
} }

View file

@ -17,12 +17,23 @@ import {
eventTargetProperties, eventTargetProperties,
setEventTargetData, setEventTargetData,
} from "./globals.ts"; } from "./globals.ts";
import * as Deno from "./deno.ts";
import * as webWorkerOps from "./ops/web_worker.ts"; import * as webWorkerOps from "./ops/web_worker.ts";
import { LocationImpl } from "./web/location.ts"; import { LocationImpl } from "./web/location.ts";
import { log, assert, immutableDefine } from "./util.ts"; import { log, assert, immutableDefine } from "./util.ts";
import { MessageEvent, ErrorEvent } from "./web/workers.ts"; import { MessageEvent, ErrorEvent } from "./web/workers.ts";
import { TextEncoder } from "./web/text_encoding.ts"; import { TextEncoder } from "./web/text_encoding.ts";
import * as runtime from "./runtime.ts"; import * as runtime from "./runtime.ts";
import { internalObject } from "./internals.ts";
import { symbols } from "./symbols.ts";
import { setSignals } from "./signals.ts";
// FIXME(bartlomieju): duplicated in `runtime_main.ts`
// TODO: factor out `Deno` global assignment to separate function
// Add internal object to Deno object.
// This is not exposed as part of the Deno types.
// @ts-ignore
Deno[symbols.internal] = internalObject;
const encoder = new TextEncoder(); const encoder = new TextEncoder();
@ -109,6 +120,7 @@ export const workerRuntimeGlobalProperties = {
export function bootstrapWorkerRuntime( export function bootstrapWorkerRuntime(
name: string, name: string,
useDenoNamespace: boolean,
internalName?: string internalName?: string
): void { ): void {
if (hasBootstrapped) { if (hasBootstrapped) {
@ -128,7 +140,21 @@ export function bootstrapWorkerRuntime(
immutableDefine(globalThis, "location", location); immutableDefine(globalThis, "location", location);
Object.freeze(globalThis.location); Object.freeze(globalThis.location);
// globalThis.Deno is not available in worker scope if (useDenoNamespace) {
delete globalThis.Deno; Object.defineProperties(Deno, {
assert(globalThis.Deno === undefined); pid: readOnly(s.pid),
noColor: readOnly(s.noColor),
args: readOnly(Object.freeze(s.args)),
});
// Setup `Deno` global - we're actually overriding already
// existing global `Deno` with `Deno` namespace from "./deno.ts".
immutableDefine(globalThis, "Deno", Deno);
Object.freeze(globalThis.Deno);
Object.freeze(globalThis.Deno.core);
Object.freeze(globalThis.Deno.core.sharedQueue);
setSignals();
} else {
delete globalThis.Deno;
assert(globalThis.Deno === undefined);
}
} }

View file

@ -105,6 +105,7 @@ export interface Worker {
export interface WorkerOptions { export interface WorkerOptions {
type?: "classic" | "module"; type?: "classic" | "module";
name?: string; name?: string;
deno?: boolean;
} }
export class WorkerImpl extends EventTarget implements Worker { export class WorkerImpl extends EventTarget implements Worker {
@ -146,10 +147,13 @@ export class WorkerImpl extends EventTarget implements Worker {
} }
*/ */
const useDenoNamespace = options ? !!options.deno : false;
const { id } = createWorker( const { id } = createWorker(
specifier, specifier,
hasSourceCode, hasSourceCode,
sourceCode, sourceCode,
useDenoNamespace,
options?.name options?.name
); );
this.#id = id; this.#id = id;

View file

@ -3,6 +3,7 @@ use super::dispatch_json::{Deserialize, JsonOp, Value};
use crate::fmt_errors::JSError; use crate::fmt_errors::JSError;
use crate::global_state::GlobalState; use crate::global_state::GlobalState;
use crate::op_error::OpError; use crate::op_error::OpError;
use crate::ops::io::get_stdio;
use crate::permissions::DenoPermissions; use crate::permissions::DenoPermissions;
use crate::startup_data; use crate::startup_data;
use crate::state::State; use crate::state::State;
@ -37,17 +38,31 @@ fn create_web_worker(
global_state: GlobalState, global_state: GlobalState,
permissions: DenoPermissions, permissions: DenoPermissions,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
has_deno_namespace: bool,
) -> Result<WebWorker, ErrBox> { ) -> Result<WebWorker, ErrBox> {
let state = let state =
State::new_for_worker(global_state, Some(permissions), specifier)?; State::new_for_worker(global_state, Some(permissions), specifier)?;
let mut worker = if has_deno_namespace {
WebWorker::new(name.to_string(), startup_data::deno_isolate_init(), state); let mut s = state.borrow_mut();
let (stdin, stdout, stderr) = get_stdio();
s.resource_table.add("stdin", Box::new(stdin));
s.resource_table.add("stdout", Box::new(stdout));
s.resource_table.add("stderr", Box::new(stderr));
}
let mut worker = WebWorker::new(
name.clone(),
startup_data::deno_isolate_init(),
state,
has_deno_namespace,
);
// Instead of using name for log we use `worker-${id}` because // Instead of using name for log we use `worker-${id}` because
// WebWorkers can have empty string as name. // WebWorkers can have empty string as name.
let script = format!( let script = format!(
"bootstrapWorkerRuntime(\"{}\", \"worker-{}\")", "bootstrapWorkerRuntime(\"{}\", {}, \"worker-{}\")",
name, worker_id name, worker.has_deno_namespace, worker_id
); );
worker.execute(&script)?; worker.execute(&script)?;
@ -61,8 +76,8 @@ fn run_worker_thread(
global_state: GlobalState, global_state: GlobalState,
permissions: DenoPermissions, permissions: DenoPermissions,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
has_source_code: bool, has_deno_namespace: bool,
source_code: String, maybe_source_code: Option<String>,
) -> Result<(JoinHandle<()>, WebWorkerHandle), ErrBox> { ) -> Result<(JoinHandle<()>, WebWorkerHandle), ErrBox> {
let (handle_sender, handle_receiver) = let (handle_sender, handle_receiver) =
std::sync::mpsc::sync_channel::<Result<WebWorkerHandle, ErrBox>>(1); std::sync::mpsc::sync_channel::<Result<WebWorkerHandle, ErrBox>>(1);
@ -80,6 +95,7 @@ fn run_worker_thread(
global_state, global_state,
permissions, permissions,
specifier.clone(), specifier.clone(),
has_deno_namespace,
); );
if let Err(err) = result { if let Err(err) = result {
@ -108,7 +124,7 @@ fn run_worker_thread(
// TODO: run with using select with terminate // TODO: run with using select with terminate
// Execute provided source code immediately // Execute provided source code immediately
let result = if has_source_code { let result = if let Some(source_code) = maybe_source_code {
worker.execute(&source_code) worker.execute(&source_code)
} else { } else {
// TODO(bartlomieju): add "type": "classic", ie. ability to load // TODO(bartlomieju): add "type": "classic", ie. ability to load
@ -146,6 +162,7 @@ struct CreateWorkerArgs {
specifier: String, specifier: String,
has_source_code: bool, has_source_code: bool,
source_code: String, source_code: String,
use_deno_namespace: bool,
} }
/// Create worker as the host /// Create worker as the host
@ -157,9 +174,13 @@ fn op_create_worker(
let args: CreateWorkerArgs = serde_json::from_value(args)?; let args: CreateWorkerArgs = serde_json::from_value(args)?;
let specifier = args.specifier.clone(); let specifier = args.specifier.clone();
let has_source_code = args.has_source_code; let maybe_source_code = if args.has_source_code {
let source_code = args.source_code.clone(); Some(args.source_code.clone())
} else {
None
};
let args_name = args.name; let args_name = args.name;
let use_deno_namespace = args.use_deno_namespace;
let parent_state = state.clone(); let parent_state = state.clone();
let mut state = state.borrow_mut(); let mut state = state.borrow_mut();
let global_state = state.global_state.clone(); let global_state = state.global_state.clone();
@ -179,8 +200,8 @@ fn op_create_worker(
global_state, global_state,
permissions, permissions,
module_specifier, module_specifier,
has_source_code, use_deno_namespace,
source_code, maybe_source_code,
) )
.map_err(|e| OpError::other(e.to_string()))?; .map_err(|e| OpError::other(e.to_string()))?;
// At this point all interactions with worker happen using thread // At this point all interactions with worker happen using thread

View file

@ -0,0 +1,7 @@
onmessage = function (e): void {
if (typeof self.Deno === "undefined") {
throw new Error("Deno namespace not available in worker");
}
postMessage(e.data);
};

View file

@ -0,0 +1,7 @@
onmessage = function (e) {
if (typeof self.Deno !== "undefined") {
throw new Error("Deno namespace unexpectedly available in worker");
}
postMessage(e.data);
};

View file

@ -1,4 +1,4 @@
running 8 tests running 9 tests
test worker terminate ... ok [WILDCARD] test worker terminate ... ok [WILDCARD]
test worker nested ... ok [WILDCARD] test worker nested ... ok [WILDCARD]
test worker throws when executing ... ok [WILDCARD] test worker throws when executing ... ok [WILDCARD]
@ -7,5 +7,6 @@ test worker terminate busy loop ... ok [WILDCARD]
test worker race condition ... ok [WILDCARD] test worker race condition ... ok [WILDCARD]
test worker is event listener ... ok [WILDCARD] test worker is event listener ... ok [WILDCARD]
test worker scope is event listener ... ok [WILDCARD] test worker scope is event listener ... ok [WILDCARD]
test worker with Deno namespace ... ok [WILDCARD]
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD] test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out [WILDCARD]

View file

@ -257,3 +257,36 @@ Deno.test({
worker.terminate(); worker.terminate();
}, },
}); });
Deno.test({
name: "worker with Deno namespace",
fn: async function (): Promise<void> {
const promise = createResolvable();
const promise2 = createResolvable();
const regularWorker = new Worker("../tests/subdir/non_deno_worker.js", {
type: "module",
});
const denoWorker = new Worker("../tests/subdir/deno_worker.ts", {
type: "module",
deno: true,
});
regularWorker.onmessage = (e): void => {
assertEquals(e.data, "Hello World");
regularWorker.terminate();
promise.resolve();
};
denoWorker.onmessage = (e): void => {
assertEquals(e.data, "Hello World");
denoWorker.terminate();
promise2.resolve();
};
regularWorker.postMessage("Hello World");
await promise;
denoWorker.postMessage("Hello World");
await promise2;
},
});

View file

@ -77,10 +77,16 @@ pub struct WebWorker {
event_loop_idle: bool, event_loop_idle: bool,
terminate_rx: mpsc::Receiver<()>, terminate_rx: mpsc::Receiver<()>,
handle: WebWorkerHandle, handle: WebWorkerHandle,
pub has_deno_namespace: bool,
} }
impl WebWorker { impl WebWorker {
pub fn new(name: String, startup_data: StartupData, state: State) -> Self { pub fn new(
name: String,
startup_data: StartupData,
state: State,
has_deno_namespace: bool,
) -> Self {
let state_ = state.clone(); let state_ = state.clone();
let mut worker = Worker::new(name, startup_data, state_); let mut worker = Worker::new(name, startup_data, state_);
@ -105,6 +111,7 @@ impl WebWorker {
event_loop_idle: false, event_loop_idle: false,
terminate_rx, terminate_rx,
handle, handle,
has_deno_namespace,
}; };
let handle = web_worker.thread_safe_handle(); let handle = web_worker.thread_safe_handle();
@ -124,6 +131,22 @@ impl WebWorker {
ops::errors::init(isolate, &state); ops::errors::init(isolate, &state);
ops::timers::init(isolate, &state); ops::timers::init(isolate, &state);
ops::fetch::init(isolate, &state); ops::fetch::init(isolate, &state);
if has_deno_namespace {
let op_registry = isolate.op_registry.clone();
ops::runtime_compiler::init(isolate, &state);
ops::fs::init(isolate, &state);
ops::fs_events::init(isolate, &state);
ops::plugins::init(isolate, &state, op_registry);
ops::net::init(isolate, &state);
ops::tls::init(isolate, &state);
ops::os::init(isolate, &state);
ops::permissions::init(isolate, &state);
ops::process::init(isolate, &state);
ops::random::init(isolate, &state);
ops::signal::init(isolate, &state);
ops::tty::init(isolate, &state);
}
} }
web_worker web_worker
@ -238,8 +261,11 @@ mod tests {
"TEST".to_string(), "TEST".to_string(),
startup_data::deno_isolate_init(), startup_data::deno_isolate_init(),
state, state,
false,
); );
worker.execute("bootstrapWorkerRuntime(\"TEST\")").unwrap(); worker
.execute("bootstrapWorkerRuntime(\"TEST\", false)")
.unwrap();
worker worker
} }
#[test] #[test]