web_worker.rs 9.9 KB
Newer Older
1 2
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
use crate::ops;
3
use crate::state::State;
4
use crate::worker::Worker;
5
use crate::worker::WorkerEvent;
6 7
use crate::worker::WorkerHandle;
use deno_core::v8;
8 9
use deno_core::ErrBox;
use deno_core::StartupData;
10
use futures::channel::mpsc;
11
use futures::future::FutureExt;
12
use futures::stream::StreamExt;
13
use std::future::Future;
14 15
use std::ops::Deref;
use std::ops::DerefMut;
16
use std::pin::Pin;
17 18 19
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
20 21
use std::task::Context;
use std::task::Poll;
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

/// Wrapper for `WorkerHandle` that adds functionality
/// for terminating workers.
///
/// This struct is used by host as well as worker itself.
///
/// Host uses it to communicate with worker and terminate it,
/// while worker uses it only to finish execution on `self.close()`.
#[derive(Clone)]
pub struct WebWorkerHandle {
  worker_handle: WorkerHandle,
  terminate_tx: mpsc::Sender<()>,
  terminated: Arc<AtomicBool>,
  isolate_handle: v8::IsolateHandle,
}

impl Deref for WebWorkerHandle {
  type Target = WorkerHandle;
  fn deref(&self) -> &Self::Target {
    &self.worker_handle
  }
}

impl DerefMut for WebWorkerHandle {
  fn deref_mut(&mut self) -> &mut Self::Target {
    &mut self.worker_handle
  }
}

impl WebWorkerHandle {
  pub fn terminate(&self) {
    // This function can be called multiple times by whomever holds
    // the handle. However only a single "termination" should occur so
    // we need a guard here.
    let already_terminated = self.terminated.swap(true, Ordering::Relaxed);

    if !already_terminated {
      self.isolate_handle.terminate_execution();
      let mut sender = self.terminate_tx.clone();
      // This call should be infallible hence the `expect`.
      // This might change in the future.
      sender.try_send(()).expect("Failed to terminate");
    }
  }
}

68 69 70 71 72 73 74
/// This worker is implementation of `Worker` Web API
///
/// At the moment this type of worker supports only
/// communication with parent and creating new workers.
///
/// Each `WebWorker` is either a child of `MainWorker` or other
/// `WebWorker`.
75 76
pub struct WebWorker {
  worker: Worker,
77 78 79
  event_loop_idle: bool,
  terminate_rx: mpsc::Receiver<()>,
  handle: WebWorkerHandle,
80
  pub has_deno_namespace: bool,
81
}
82 83

impl WebWorker {
84 85 86 87 88 89
  pub fn new(
    name: String,
    startup_data: StartupData,
    state: State,
    has_deno_namespace: bool,
  ) -> Self {
90
    let state_ = state.clone();
91
    let mut worker = Worker::new(name, startup_data, state_);
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113

    let terminated = Arc::new(AtomicBool::new(false));
    let isolate_handle = worker
      .isolate
      .v8_isolate
      .as_mut()
      .unwrap()
      .thread_safe_handle();
    let (terminate_tx, terminate_rx) = mpsc::channel::<()>(1);

    let handle = WebWorkerHandle {
      worker_handle: worker.thread_safe_handle(),
      terminated,
      isolate_handle,
      terminate_tx,
    };

    let mut web_worker = Self {
      worker,
      event_loop_idle: false,
      terminate_rx,
      handle,
114
      has_deno_namespace,
115 116 117 118
    };

    let handle = web_worker.thread_safe_handle();

119
    {
120
      let isolate = &mut web_worker.worker.isolate;
121
      ops::runtime::init(isolate, &state);
122 123 124 125 126 127
      ops::web_worker::init(
        isolate,
        &state,
        &web_worker.worker.internal_channels.sender,
        handle,
      );
128
      ops::worker_host::init(isolate, &state);
129
      ops::io::init(isolate, &state);
130
      ops::resources::init(isolate, &state);
131 132 133
      ops::errors::init(isolate, &state);
      ops::timers::init(isolate, &state);
      ops::fetch::init(isolate, &state);
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149

      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);
      }
150 151
    }

152 153 154 155 156 157 158 159
    web_worker
  }
}

impl WebWorker {
  /// Returns a way to communicate with the Worker from other threads.
  pub fn thread_safe_handle(&self) -> WebWorkerHandle {
    self.handle.clone()
160
  }
161
}
162

163 164 165
impl Deref for WebWorker {
  type Target = Worker;
  fn deref(&self) -> &Self::Target {
166
    &self.worker
167
  }
168
}
169

170 171
impl DerefMut for WebWorker {
  fn deref_mut(&mut self) -> &mut Self::Target {
172
    &mut self.worker
173 174 175 176 177 178 179 180
  }
}

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();
181 182
    let worker = &mut inner.worker;

183 184 185 186 187 188 189
    let terminated = inner.handle.terminated.load(Ordering::Relaxed);

    if terminated {
      return Poll::Ready(Ok(()));
    }

    if !inner.event_loop_idle {
190 191
      match worker.poll_unpin(cx) {
        Poll::Ready(r) => {
192 193 194 195 196
          let terminated = inner.handle.terminated.load(Ordering::Relaxed);
          if terminated {
            return Poll::Ready(Ok(()));
          }

197 198
          if let Err(e) = r {
            let mut sender = worker.internal_channels.sender.clone();
199 200
            sender
              .try_send(WorkerEvent::Error(e))
201 202
              .expect("Failed to post message to host");
          }
203
          inner.event_loop_idle = true;
204 205 206 207 208
        }
        Poll::Pending => {}
      }
    }

209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
    if let Poll::Ready(r) = inner.terminate_rx.poll_next_unpin(cx) {
      // terminate_rx should never be closed
      assert!(r.is_some());
      return Poll::Ready(Ok(()));
    }

    if let Poll::Ready(r) =
      worker.internal_channels.receiver.poll_next_unpin(cx)
    {
      match r {
        Some(msg) => {
          let msg = String::from_utf8(msg.to_vec()).unwrap();
          debug!("received message from host: {}", msg);
          let script = format!("workerMessageRecvCallback({})", msg);

          if let Err(e) = worker.execute(&script) {
            // If execution was terminated during message callback then
            // just ignore it
            if inner.handle.terminated.load(Ordering::Relaxed) {
              return Poll::Ready(Ok(()));
            }

            // Otherwise forward error to host
            let mut sender = worker.internal_channels.sender.clone();
            sender
              .try_send(WorkerEvent::Error(e))
              .expect("Failed to post message to host");
236 237
          }

238 239 240 241 242 243
          // Let event loop be polled again
          inner.event_loop_idle = false;
          worker.waker.wake();
        }
        None => unreachable!(),
      }
244 245 246
    }

    Poll::Pending
247 248
  }
}
249 250 251 252 253

#[cfg(test)]
mod tests {
  use super::*;
  use crate::startup_data;
254
  use crate::state::State;
255
  use crate::tokio_util;
256
  use crate::worker::WorkerEvent;
257 258

  fn create_test_worker() -> WebWorker {
259
    let state = State::mock("./hello.js");
260 261 262 263
    let mut worker = WebWorker::new(
      "TEST".to_string(),
      startup_data::deno_isolate_init(),
      state,
264
      false,
265
    );
266 267 268
    worker
      .execute("bootstrapWorkerRuntime(\"TEST\", false)")
      .unwrap();
269 270 271 272
    worker
  }
  #[test]
  fn test_worker_messages() {
273
    let (handle_sender, handle_receiver) =
274
      std::sync::mpsc::sync_channel::<WebWorkerHandle>(1);
275 276 277 278 279 280 281 282 283 284 285 286 287

    let join_handle = std::thread::spawn(move || {
      let mut worker = create_test_worker();
      let source = r#"
          onmessage = function(e) {
            console.log("msg from main script", e.data);
            if (e.data == "exit") {
              return close();
            } else {
              console.assert(e.data === "hi");
            }
            postMessage([1, 2, 3]);
            console.log("after postMessage");
288
          }
289 290 291 292
          "#;
      worker.execute(source).unwrap();
      let handle = worker.thread_safe_handle();
      handle_sender.send(handle).unwrap();
293
      let r = tokio_util::run_basic(worker);
294
      assert!(r.is_ok())
295
    });
296

297
    let mut handle = handle_receiver.recv().unwrap();
298

299 300
    tokio_util::run_basic(async move {
      let msg = json!("hi").to_string().into_boxed_str().into_boxed_bytes();
301
      let r = handle.post_message(msg.clone());
302
      assert!(r.is_ok());
303

304 305
      let maybe_msg = handle.get_event().await;
      assert!(maybe_msg.is_some());
306

307
      let r = handle.post_message(msg.clone());
308
      assert!(r.is_ok());
309 310 311 312 313 314 315 316 317 318 319 320 321 322

      let maybe_msg = handle.get_event().await;
      assert!(maybe_msg.is_some());
      match maybe_msg {
        Some(WorkerEvent::Message(buf)) => {
          assert_eq!(*buf, *b"[1,2,3]");
        }
        _ => unreachable!(),
      }

      let msg = json!("exit")
        .to_string()
        .into_boxed_str()
        .into_boxed_bytes();
323
      let r = handle.post_message(msg);
324 325 326 327 328 329 330 331 332 333 334
      assert!(r.is_ok());
      let event = handle.get_event().await;
      assert!(event.is_none());
      handle.sender.close_channel();
    });
    join_handle.join().expect("Failed to join worker thread");
  }

  #[test]
  fn removed_from_resource_table_on_close() {
    let (handle_sender, handle_receiver) =
335
      std::sync::mpsc::sync_channel::<WebWorkerHandle>(1);
336 337 338 339 340 341

    let join_handle = std::thread::spawn(move || {
      let mut worker = create_test_worker();
      worker.execute("onmessage = () => { close(); }").unwrap();
      let handle = worker.thread_safe_handle();
      handle_sender.send(handle).unwrap();
342
      let r = tokio_util::run_basic(worker);
343
      assert!(r.is_ok())
344
    });
345

346 347
    let mut handle = handle_receiver.recv().unwrap();

348
    tokio_util::run_basic(async move {
349
      let msg = json!("hi").to_string().into_boxed_str().into_boxed_bytes();
350
      let r = handle.post_message(msg.clone());
351
      assert!(r.is_ok());
352 353 354
      let event = handle.get_event().await;
      assert!(event.is_none());
      handle.sender.close_channel();
355
    });
356
    join_handle.join().expect("Failed to join worker thread");
357 358
  }
}