2019-01-21 14:03:30 -05:00
|
|
|
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
2019-03-19 20:55:59 -04:00
|
|
|
use crate::isolate::{DenoBehavior, Isolate};
|
2019-03-14 19:17:52 -04:00
|
|
|
use crate::isolate_state::WorkerChannels;
|
2019-02-28 16:19:04 -05:00
|
|
|
use crate::js_errors::JSErrorColor;
|
2019-01-14 01:30:38 -05:00
|
|
|
use crate::resources;
|
|
|
|
use crate::tokio_util;
|
2019-03-30 19:30:40 -04:00
|
|
|
use deno::Buf;
|
|
|
|
use deno::JSError;
|
2019-03-14 19:17:52 -04:00
|
|
|
use futures::future::lazy;
|
2019-01-08 14:44:06 -05:00
|
|
|
use futures::sync::mpsc;
|
|
|
|
use futures::sync::oneshot;
|
|
|
|
use futures::Future;
|
2019-03-14 19:17:52 -04:00
|
|
|
use futures::Poll;
|
2019-01-08 14:44:06 -05:00
|
|
|
use std::thread;
|
|
|
|
|
2019-03-19 20:55:59 -04:00
|
|
|
/// Behavior trait specific to workers
|
|
|
|
pub trait WorkerBehavior: DenoBehavior {
|
|
|
|
/// Used to setup internal channels at worker creation.
|
|
|
|
/// This is intended to be temporary fix.
|
|
|
|
/// TODO(afinch7) come up with a better solution to set worker channels
|
|
|
|
fn set_internal_channels(&mut self, worker_channels: WorkerChannels);
|
|
|
|
}
|
|
|
|
|
2019-01-08 14:44:06 -05:00
|
|
|
/// Rust interface for WebWorkers.
|
2019-03-19 20:55:59 -04:00
|
|
|
pub struct Worker<B: WorkerBehavior> {
|
|
|
|
isolate: Isolate<B>,
|
2019-01-08 14:44:06 -05:00
|
|
|
}
|
|
|
|
|
2019-03-19 20:55:59 -04:00
|
|
|
impl<B: WorkerBehavior> Worker<B> {
|
|
|
|
pub fn new(mut behavior: B) -> (Self, WorkerChannels) {
|
2019-01-08 14:44:06 -05:00
|
|
|
let (worker_in_tx, worker_in_rx) = mpsc::channel::<Buf>(1);
|
|
|
|
let (worker_out_tx, worker_out_rx) = mpsc::channel::<Buf>(1);
|
|
|
|
|
|
|
|
let internal_channels = (worker_out_tx, worker_in_rx);
|
|
|
|
let external_channels = (worker_in_tx, worker_out_rx);
|
|
|
|
|
2019-03-19 20:55:59 -04:00
|
|
|
behavior.set_internal_channels(internal_channels);
|
2019-01-08 14:44:06 -05:00
|
|
|
|
2019-03-19 20:55:59 -04:00
|
|
|
let isolate = Isolate::new(behavior);
|
2019-01-08 14:44:06 -05:00
|
|
|
|
|
|
|
let worker = Worker { isolate };
|
|
|
|
(worker, external_channels)
|
|
|
|
}
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
|
2019-01-08 14:44:06 -05:00
|
|
|
self.isolate.execute(js_source)
|
|
|
|
}
|
2019-03-14 19:17:52 -04:00
|
|
|
}
|
2019-01-08 14:44:06 -05:00
|
|
|
|
2019-03-19 20:55:59 -04:00
|
|
|
impl<B: WorkerBehavior> Future for Worker<B> {
|
2019-03-14 19:17:52 -04:00
|
|
|
type Item = ();
|
|
|
|
type Error = JSError;
|
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<(), JSError> {
|
|
|
|
self.isolate.poll()
|
2019-01-08 14:44:06 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-19 20:55:59 -04:00
|
|
|
pub fn spawn<B: WorkerBehavior + 'static>(
|
|
|
|
behavior: B,
|
2019-01-09 12:59:46 -05:00
|
|
|
js_source: String,
|
|
|
|
) -> resources::Resource {
|
2019-01-08 14:44:06 -05:00
|
|
|
// TODO This function should return a Future, so that the caller can retrieve
|
|
|
|
// the JSError if one is thrown. Currently it just prints to stderr and calls
|
|
|
|
// exit(1).
|
|
|
|
// let (js_error_tx, js_error_rx) = oneshot::channel::<JSError>();
|
|
|
|
let (p, c) = oneshot::channel::<resources::Resource>();
|
|
|
|
let builder = thread::Builder::new().name("worker".to_string());
|
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
let _tid = builder
|
|
|
|
.spawn(move || {
|
|
|
|
tokio_util::run(lazy(move || {
|
2019-03-19 20:55:59 -04:00
|
|
|
let (mut worker, external_channels) = Worker::new(behavior);
|
2019-03-14 19:17:52 -04:00
|
|
|
let resource = resources::add_worker(external_channels);
|
|
|
|
p.send(resource.clone()).unwrap();
|
|
|
|
|
|
|
|
worker
|
|
|
|
.execute("denoMain()")
|
|
|
|
.expect("worker denoMain failed");
|
|
|
|
worker
|
|
|
|
.execute("workerMain()")
|
|
|
|
.expect("worker workerMain failed");
|
|
|
|
worker.execute(&js_source).expect("worker js_source failed");
|
|
|
|
|
|
|
|
worker.then(move |r| -> Result<(), ()> {
|
|
|
|
resource.close();
|
|
|
|
debug!("workers.rs after resource close");
|
|
|
|
if let Err(err) = r {
|
|
|
|
eprintln!("{}", JSErrorColor(&err).to_string());
|
|
|
|
std::process::exit(1);
|
|
|
|
}
|
2019-01-08 14:44:06 -05:00
|
|
|
Ok(())
|
2019-03-14 19:17:52 -04:00
|
|
|
})
|
|
|
|
}));
|
2019-01-08 14:44:06 -05:00
|
|
|
|
2019-03-14 19:17:52 -04:00
|
|
|
debug!("workers.rs after spawn");
|
2019-01-08 14:44:06 -05:00
|
|
|
}).unwrap();
|
|
|
|
|
2019-01-15 07:06:25 -05:00
|
|
|
c.wait().unwrap()
|
2019-01-08 14:44:06 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2019-03-19 20:55:59 -04:00
|
|
|
use crate::compiler::CompilerBehavior;
|
|
|
|
use crate::isolate_state::IsolateState;
|
|
|
|
use std::sync::Arc;
|
2019-01-08 14:44:06 -05:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_spawn() {
|
|
|
|
let resource = spawn(
|
2019-03-19 20:55:59 -04:00
|
|
|
CompilerBehavior::new(Arc::new(IsolateState::mock())),
|
2019-01-08 14:44:06 -05:00
|
|
|
r#"
|
|
|
|
onmessage = function(e) {
|
|
|
|
let s = new TextDecoder().decode(e.data);;
|
|
|
|
console.log("msg from main script", s);
|
|
|
|
if (s == "exit") {
|
|
|
|
close();
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
console.assert(s === "hi");
|
|
|
|
}
|
|
|
|
postMessage(new Uint8Array([1, 2, 3]));
|
|
|
|
console.log("after postMessage");
|
|
|
|
}
|
2019-03-19 20:55:59 -04:00
|
|
|
"#.into(),
|
2019-01-08 14:44:06 -05:00
|
|
|
);
|
|
|
|
let msg = String::from("hi").into_boxed_str().into_boxed_bytes();
|
|
|
|
|
|
|
|
let r = resources::worker_post_message(resource.rid, msg).wait();
|
|
|
|
assert!(r.is_ok());
|
|
|
|
|
|
|
|
let maybe_msg =
|
|
|
|
resources::worker_recv_message(resource.rid).wait().unwrap();
|
|
|
|
assert!(maybe_msg.is_some());
|
|
|
|
assert_eq!(*maybe_msg.unwrap(), [1, 2, 3]);
|
|
|
|
|
|
|
|
let msg = String::from("exit").into_boxed_str().into_boxed_bytes();
|
|
|
|
let r = resources::worker_post_message(resource.rid, msg).wait();
|
|
|
|
assert!(r.is_ok());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn removed_from_resource_table_on_close() {
|
2019-03-01 19:25:50 -05:00
|
|
|
let resource = spawn(
|
2019-03-19 20:55:59 -04:00
|
|
|
CompilerBehavior::new(Arc::new(IsolateState::mock())),
|
2019-03-01 19:25:50 -05:00
|
|
|
"onmessage = () => close();".into(),
|
|
|
|
);
|
2019-01-08 14:44:06 -05:00
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
resources::get_type(resource.rid),
|
|
|
|
Some("worker".to_string())
|
|
|
|
);
|
|
|
|
|
|
|
|
let msg = String::from("hi").into_boxed_str().into_boxed_bytes();
|
|
|
|
let r = resources::worker_post_message(resource.rid, msg).wait();
|
|
|
|
assert!(r.is_ok());
|
|
|
|
println!("rid {:?}", resource.rid);
|
|
|
|
|
|
|
|
// TODO Need a way to get a future for when a resource closes.
|
|
|
|
// For now, just sleep for a bit.
|
2019-01-09 12:59:46 -05:00
|
|
|
// resource.close();
|
|
|
|
thread::sleep(std::time::Duration::from_millis(1000));
|
2019-01-08 14:44:06 -05:00
|
|
|
assert_eq!(resources::get_type(resource.rid), None);
|
|
|
|
}
|
|
|
|
}
|