mirror of
https://github.com/denoland/deno
synced 2024-08-24 18:47:06 +00:00
feat(compile): Add support for web workers in standalone mode (#17657)
This commit adds support for spawning Web Workers in self-contained binaries created with "deno compile" subcommand. As long as module requested in "new Worker" constructor is part of the eszip (by means of statically importing it beforehand, or using "--include" flag), then the worker can be spawned.
This commit is contained in:
parent
bf149d047f
commit
090169cfbc
|
@ -13,6 +13,7 @@ use deno_core::anyhow::Context;
|
|||
use deno_core::error::type_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures::io::AllowStdIo;
|
||||
use deno_core::futures::task::LocalFutureObj;
|
||||
use deno_core::futures::AsyncReadExt;
|
||||
use deno_core::futures::AsyncSeekExt;
|
||||
use deno_core::futures::FutureExt;
|
||||
|
@ -26,12 +27,14 @@ use deno_core::ModuleLoader;
|
|||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::ResolutionKind;
|
||||
use deno_graph::source::Resolver;
|
||||
use deno_runtime::deno_broadcast_channel::InMemoryBroadcastChannel;
|
||||
use deno_runtime::deno_web::BlobStore;
|
||||
use deno_runtime::fmt_errors::format_js_error;
|
||||
use deno_runtime::ops::worker_host::CreateWebWorkerCb;
|
||||
use deno_runtime::ops::worker_host::WorkerEventCb;
|
||||
use deno_runtime::permissions::Permissions;
|
||||
use deno_runtime::permissions::PermissionsContainer;
|
||||
use deno_runtime::permissions::PermissionsOptions;
|
||||
use deno_runtime::web_worker::WebWorker;
|
||||
use deno_runtime::web_worker::WebWorkerOptions;
|
||||
use deno_runtime::worker::MainWorker;
|
||||
use deno_runtime::worker::WorkerOptions;
|
||||
use deno_runtime::BootstrapOptions;
|
||||
|
@ -125,9 +128,10 @@ fn u64_from_bytes(arr: &[u8]) -> Result<u64, AnyError> {
|
|||
Ok(u64::from_be_bytes(*fixed_arr))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EmbeddedModuleLoader {
|
||||
eszip: eszip::EszipV2,
|
||||
maybe_import_map_resolver: Option<CliGraphResolver>,
|
||||
eszip: Arc<eszip::EszipV2>,
|
||||
maybe_import_map_resolver: Option<Arc<CliGraphResolver>>,
|
||||
}
|
||||
|
||||
impl ModuleLoader for EmbeddedModuleLoader {
|
||||
|
@ -223,6 +227,79 @@ fn metadata_to_flags(metadata: &Metadata) -> Flags {
|
|||
}
|
||||
}
|
||||
|
||||
fn web_worker_callback() -> Arc<WorkerEventCb> {
|
||||
Arc::new(|worker| {
|
||||
let fut = async move { Ok(worker) };
|
||||
LocalFutureObj::new(Box::new(fut))
|
||||
})
|
||||
}
|
||||
|
||||
fn create_web_worker_callback(
|
||||
ps: &ProcState,
|
||||
module_loader: &Rc<EmbeddedModuleLoader>,
|
||||
) -> Arc<CreateWebWorkerCb> {
|
||||
let ps = ps.clone();
|
||||
let module_loader = module_loader.as_ref().clone();
|
||||
Arc::new(move |args| {
|
||||
let module_loader = Rc::new(module_loader.clone());
|
||||
|
||||
let create_web_worker_cb = create_web_worker_callback(&ps, &module_loader);
|
||||
let web_worker_cb = web_worker_callback();
|
||||
|
||||
let options = WebWorkerOptions {
|
||||
bootstrap: BootstrapOptions {
|
||||
args: ps.options.argv().clone(),
|
||||
cpu_count: std::thread::available_parallelism()
|
||||
.map(|p| p.get())
|
||||
.unwrap_or(1),
|
||||
debug_flag: ps.options.log_level().map_or(false, |l| l == Level::Debug),
|
||||
enable_testing_features: false,
|
||||
locale: deno_core::v8::icu::get_language_tag(),
|
||||
location: Some(args.main_module.clone()),
|
||||
no_color: !colors::use_color(),
|
||||
is_tty: colors::is_tty(),
|
||||
runtime_version: version::deno(),
|
||||
ts_version: version::TYPESCRIPT.to_string(),
|
||||
unstable: ps.options.unstable(),
|
||||
user_agent: version::get_user_agent(),
|
||||
inspect: ps.options.is_inspecting(),
|
||||
},
|
||||
extensions: ops::cli_exts(ps.clone()),
|
||||
startup_snapshot: Some(crate::js::deno_isolate_init()),
|
||||
unsafely_ignore_certificate_errors: ps
|
||||
.options
|
||||
.unsafely_ignore_certificate_errors()
|
||||
.clone(),
|
||||
root_cert_store: Some(ps.root_cert_store.clone()),
|
||||
seed: ps.options.seed(),
|
||||
module_loader,
|
||||
npm_resolver: None, // not currently supported
|
||||
create_web_worker_cb,
|
||||
preload_module_cb: web_worker_cb.clone(),
|
||||
pre_execute_module_cb: web_worker_cb,
|
||||
format_js_error_fn: Some(Arc::new(format_js_error)),
|
||||
source_map_getter: None,
|
||||
worker_type: args.worker_type,
|
||||
maybe_inspector_server: None,
|
||||
get_error_class_fn: Some(&get_error_class_name),
|
||||
blob_store: ps.blob_store.clone(),
|
||||
broadcast_channel: ps.broadcast_channel.clone(),
|
||||
shared_array_buffer_store: Some(ps.shared_array_buffer_store.clone()),
|
||||
compiled_wasm_module_store: Some(ps.compiled_wasm_module_store.clone()),
|
||||
cache_storage_dir: None,
|
||||
stdio: Default::default(),
|
||||
};
|
||||
|
||||
WebWorker::bootstrap_from_options(
|
||||
args.name,
|
||||
args.permissions,
|
||||
args.main_module,
|
||||
args.worker_id,
|
||||
options,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
eszip: eszip::EszipV2,
|
||||
metadata: Metadata,
|
||||
|
@ -233,13 +310,11 @@ pub async fn run(
|
|||
let permissions = PermissionsContainer::new(Permissions::from_options(
|
||||
&metadata.permissions,
|
||||
)?);
|
||||
let blob_store = BlobStore::default();
|
||||
let broadcast_channel = InMemoryBroadcastChannel::default();
|
||||
let module_loader = Rc::new(EmbeddedModuleLoader {
|
||||
eszip,
|
||||
eszip: Arc::new(eszip),
|
||||
maybe_import_map_resolver: metadata.maybe_import_map.map(
|
||||
|(base, source)| {
|
||||
CliGraphResolver::new(
|
||||
Arc::new(CliGraphResolver::new(
|
||||
None,
|
||||
Some(Arc::new(
|
||||
parse_from_json(&base, &source).unwrap().import_map,
|
||||
|
@ -248,21 +323,15 @@ pub async fn run(
|
|||
ps.npm_api.clone(),
|
||||
ps.npm_resolution.clone(),
|
||||
ps.package_json_deps_installer.clone(),
|
||||
)
|
||||
))
|
||||
},
|
||||
),
|
||||
});
|
||||
let create_web_worker_cb = Arc::new(|_| {
|
||||
todo!("Workers are currently not supported in standalone binaries");
|
||||
});
|
||||
let web_worker_cb = Arc::new(|_| {
|
||||
todo!("Workers are currently not supported in standalone binaries");
|
||||
});
|
||||
let create_web_worker_cb = create_web_worker_callback(&ps, &module_loader);
|
||||
let web_worker_cb = web_worker_callback();
|
||||
|
||||
v8_set_flags(construct_v8_flags(&metadata.v8_flags, vec![]));
|
||||
|
||||
let root_cert_store = ps.root_cert_store.clone();
|
||||
|
||||
let options = WorkerOptions {
|
||||
bootstrap: BootstrapOptions {
|
||||
args: metadata.argv,
|
||||
|
@ -284,11 +353,11 @@ pub async fn run(
|
|||
user_agent: version::get_user_agent(),
|
||||
inspect: ps.options.is_inspecting(),
|
||||
},
|
||||
extensions: ops::cli_exts(ps),
|
||||
extensions: ops::cli_exts(ps.clone()),
|
||||
startup_snapshot: Some(crate::js::deno_isolate_init()),
|
||||
unsafely_ignore_certificate_errors: metadata
|
||||
.unsafely_ignore_certificate_errors,
|
||||
root_cert_store: Some(root_cert_store),
|
||||
root_cert_store: Some(ps.root_cert_store.clone()),
|
||||
seed: metadata.seed,
|
||||
source_map_getter: None,
|
||||
format_js_error_fn: Some(Arc::new(format_js_error)),
|
||||
|
@ -303,10 +372,10 @@ pub async fn run(
|
|||
get_error_class_fn: Some(&get_error_class_name),
|
||||
cache_storage_dir: None,
|
||||
origin_storage_dir: None,
|
||||
blob_store,
|
||||
broadcast_channel,
|
||||
shared_array_buffer_store: None,
|
||||
compiled_wasm_module_store: None,
|
||||
blob_store: ps.blob_store.clone(),
|
||||
broadcast_channel: ps.broadcast_channel.clone(),
|
||||
shared_array_buffer_store: Some(ps.shared_array_buffer_store.clone()),
|
||||
compiled_wasm_module_store: Some(ps.compiled_wasm_module_store.clone()),
|
||||
stdio: Default::default(),
|
||||
};
|
||||
let mut worker = MainWorker::bootstrap_from_options(
|
||||
|
|
|
@ -565,6 +565,91 @@ fn check_local_by_default2() {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workers_basic() {
|
||||
let _guard = util::http_server();
|
||||
let dir = TempDir::new();
|
||||
let exe = if cfg!(windows) {
|
||||
dir.path().join("basic.exe")
|
||||
} else {
|
||||
dir.path().join("basic")
|
||||
};
|
||||
let output = util::deno_cmd()
|
||||
.current_dir(util::root_path())
|
||||
.arg("compile")
|
||||
.arg("--no-check")
|
||||
.arg("--output")
|
||||
.arg(&exe)
|
||||
.arg(util::testdata_path().join("./compile/workers/basic.ts"))
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(output.status.success());
|
||||
|
||||
let output = Command::new(&exe).output().unwrap();
|
||||
assert!(output.status.success());
|
||||
let expected = std::fs::read_to_string(
|
||||
util::testdata_path().join("./compile/workers/basic.out"),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(String::from_utf8(output.stdout).unwrap(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workers_not_in_module_map() {
|
||||
let _guard = util::http_server();
|
||||
let dir = TempDir::new();
|
||||
let exe = if cfg!(windows) {
|
||||
dir.path().join("not_in_module_map.exe")
|
||||
} else {
|
||||
dir.path().join("not_in_module_map")
|
||||
};
|
||||
let output = util::deno_cmd()
|
||||
.current_dir(util::root_path())
|
||||
.arg("compile")
|
||||
.arg("--output")
|
||||
.arg(&exe)
|
||||
.arg(util::testdata_path().join("./compile/workers/not_in_module_map.ts"))
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(output.status.success());
|
||||
|
||||
let output = Command::new(&exe).env("NO_COLOR", "").output().unwrap();
|
||||
assert!(!output.status.success());
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
assert!(stderr.starts_with(concat!(
|
||||
"error: Uncaught (in worker \"\") Module not found\n",
|
||||
"error: Uncaught (in promise) Error: Unhandled error in child worker.\n"
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workers_with_include_flag() {
|
||||
let _guard = util::http_server();
|
||||
let dir = TempDir::new();
|
||||
let exe = if cfg!(windows) {
|
||||
dir.path().join("workers_with_include_flag.exe")
|
||||
} else {
|
||||
dir.path().join("workers_with_include_flag")
|
||||
};
|
||||
let output = util::deno_cmd()
|
||||
.current_dir(util::root_path())
|
||||
.arg("compile")
|
||||
.arg("--include")
|
||||
.arg(util::testdata_path().join("./compile/workers/worker.ts"))
|
||||
.arg("--output")
|
||||
.arg(&exe)
|
||||
.arg(util::testdata_path().join("./compile/workers/not_in_module_map.ts"))
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(output.status.success());
|
||||
|
||||
let output = Command::new(&exe).env("NO_COLOR", "").output().unwrap();
|
||||
assert!(output.status.success());
|
||||
let expected_stdout =
|
||||
concat!("Hello from worker!\n", "Received 42\n", "Closing\n");
|
||||
assert_eq!(&String::from_utf8(output.stdout).unwrap(), expected_stdout);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dynamic_import() {
|
||||
let _guard = util::http_server();
|
||||
|
|
5
cli/tests/testdata/compile/workers/basic.out
vendored
Normal file
5
cli/tests/testdata/compile/workers/basic.out
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
worker.js imported from main thread
|
||||
Starting worker
|
||||
Hello from worker!
|
||||
Received 42
|
||||
Closing
|
11
cli/tests/testdata/compile/workers/basic.ts
vendored
Normal file
11
cli/tests/testdata/compile/workers/basic.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
import "./worker.ts";
|
||||
|
||||
console.log("Starting worker");
|
||||
const worker = new Worker(
|
||||
new URL("./worker.ts", import.meta.url),
|
||||
{ type: "module" },
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
worker.postMessage(42);
|
||||
}, 500);
|
11
cli/tests/testdata/compile/workers/not_in_module_map.ts
vendored
Normal file
11
cli/tests/testdata/compile/workers/not_in_module_map.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
// This time ./worker.ts is not in the module map, so the worker
|
||||
// initialization will fail unless worker.js is passed as a side module.
|
||||
|
||||
const worker = new Worker(
|
||||
new URL("./worker.ts", import.meta.url),
|
||||
{ type: "module" },
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
worker.postMessage(42);
|
||||
}, 500);
|
14
cli/tests/testdata/compile/workers/worker.ts
vendored
Normal file
14
cli/tests/testdata/compile/workers/worker.ts
vendored
Normal file
|
@ -0,0 +1,14 @@
|
|||
/// <reference no-default-lib="true" />
|
||||
/// <reference lib="deno.worker" />
|
||||
|
||||
if (import.meta.main) {
|
||||
console.log("Hello from worker!");
|
||||
|
||||
addEventListener("message", (evt) => {
|
||||
console.log(`Received ${evt.data}`);
|
||||
console.log("Closing");
|
||||
self.close();
|
||||
});
|
||||
} else {
|
||||
console.log("worker.js imported from main thread");
|
||||
}
|
Loading…
Reference in a new issue