worker.rs 11.5 KB
Newer Older
R
Ryan Dahl 已提交
1
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
A
andy finch 已提交
2
use crate::compiler::compile_async;
K
Kitson Kelly 已提交
3
use crate::compiler::ModuleMetaData;
A
Andy Hayden 已提交
4
use crate::errors::DenoError;
5
use crate::errors::RustOrJsError;
6
use crate::js_errors;
7
use crate::js_errors::JSErrorColor;
A
Andy Hayden 已提交
8
use crate::msg;
9
use crate::state::ThreadSafeState;
10
use crate::tokio_util;
11 12 13
use deno;
use deno::deno_mod;
use deno::JSError;
R
Ryan Dahl 已提交
14
use deno::StartupData;
A
andy finch 已提交
15
use futures::future::Either;
16
use futures::Async;
R
Ryan Dahl 已提交
17
use futures::Future;
18
use std::sync::atomic::Ordering;
B
Bartek Iwańczuk 已提交
19

20
/// Wraps deno::Isolate to provide source maps, ops for the CLI, and
21
/// high-level module loading
22
pub struct Worker {
23 24
  inner: deno::Isolate<ThreadSafeState>,
  state: ThreadSafeState,
25 26
}

27 28 29 30
impl Worker {
  pub fn new(
    _name: String,
    startup_data: StartupData,
31
    state: ThreadSafeState,
32
  ) -> Worker {
33
    let state_ = state.clone();
34
    Self {
35
      inner: deno::Isolate::new(startup_data, state_),
A
Andy Hayden 已提交
36
      state,
37 38 39
    }
  }

40
  /// Same as execute2() but the filename defaults to "<anonymous>".
41
  pub fn execute(&mut self, js_source: &str) -> Result<(), JSError> {
42 43 44 45 46 47
    self.execute2("<anonymous>", js_source)
  }

  /// Executes the provided JavaScript source code. The js_filename argument is
  /// provided only for debugging purposes.
  pub fn execute2(
48
    &mut self,
49 50
    js_filename: &str,
    js_source: &str,
51
  ) -> Result<(), JSError> {
52
    self.inner.execute(js_filename, js_source)
53 54 55
  }

  // TODO(ry) make this return a future.
56
  fn mod_load_deps(&self, id: deno_mod) -> Result<(), RustOrJsError> {
57 58
    // basically iterate over the imports, start loading them.

59 60 61 62
    let referrer_name = {
      let g = self.state.modules.lock().unwrap();
      g.get_name(id).unwrap().clone()
    };
63

64
    for specifier in self.inner.mod_get_imports(id) {
65 66 67
      let (name, _local_filename) = self
        .state
        .dir
68
        .resolve_module(&specifier, &referrer_name)
69 70 71
        .map_err(DenoError::from)
        .map_err(RustOrJsError::from)?;

72
      debug!("mod_load_deps {}", name);
73

74
      if !self.state.modules.lock().unwrap().is_registered(&name) {
K
Kitson Kelly 已提交
75 76
        let out = fetch_module_meta_data_and_maybe_compile(
          &self.state,
77
          &specifier,
K
Kitson Kelly 已提交
78 79
          &referrer_name,
        )?;
80 81 82 83 84
        let child_id = self.mod_new_and_register(
          false,
          &out.module_name.clone(),
          &out.js_source(),
        )?;
85

86 87 88 89 90 91 92 93 94
        // The resolved module is an alias to another module (due to redirects).
        // Save such alias to the module map.
        if out.module_redirect_source_name.is_some() {
          self.mod_alias(
            &out.module_redirect_source_name.clone().unwrap(),
            &out.module_name,
          );
        }

95 96 97 98 99 100 101
        self.mod_load_deps(child_id)?;
      }
    }

    Ok(())
  }

102
  /// Executes the provided JavaScript module.
103
  pub fn execute_mod(
104
    &mut self,
105 106
    js_filename: &str,
    is_prefetch: bool,
107
  ) -> Result<(), RustOrJsError> {
108
    // TODO move state::execute_mod impl here.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
    self
      .execute_mod_inner(js_filename, is_prefetch)
      .map_err(|err| match err {
        RustOrJsError::Js(err) => RustOrJsError::Js(self.apply_source_map(err)),
        x => x,
      })
  }

  /// High-level way to execute modules.
  /// This will issue HTTP requests and file system calls.
  /// Blocks. TODO(ry) Don't block.
  fn execute_mod_inner(
    &mut self,
    url: &str,
    is_prefetch: bool,
  ) -> Result<(), RustOrJsError> {
    let out = fetch_module_meta_data_and_maybe_compile(&self.state, url, ".")
      .map_err(RustOrJsError::from)?;
127

128 129 130 131
    // Be careful.
    // url might not match the actual out.module_name
    // due to the mechanism of redirection.

132
    let id = self
133
      .mod_new_and_register(true, &out.module_name.clone(), &out.js_source())
134
      .map_err(RustOrJsError::from)?;
135

136 137 138 139 140 141 142 143 144
    // The resolved module is an alias to another module (due to redirects).
    // Save such alias to the module map.
    if out.module_redirect_source_name.is_some() {
      self.mod_alias(
        &out.module_redirect_source_name.clone().unwrap(),
        &out.module_name,
      );
    }

145
    self.mod_load_deps(id)?;
146

147 148 149 150 151 152 153 154
    let state = self.state.clone();

    let mut resolve = move |specifier: &str, referrer: deno_mod| -> deno_mod {
      state.metrics.resolve_count.fetch_add(1, Ordering::Relaxed);
      let mut modules = state.modules.lock().unwrap();
      modules.resolve_cb(&state.dir, specifier, referrer)
    };

155 156
    self
      .inner
157
      .mod_instantiate(id, &mut resolve)
158
      .map_err(RustOrJsError::from)?;
159
    if !is_prefetch {
160
      self.inner.mod_evaluate(id).map_err(RustOrJsError::from)?;
161 162
    }
    Ok(())
R
Ryan Dahl 已提交
163 164
  }

165 166 167 168 169 170 171 172 173 174
  /// Wraps Isolate::mod_new but registers with modules.
  fn mod_new_and_register(
    &self,
    main: bool,
    name: &str,
    source: &str,
  ) -> Result<deno_mod, JSError> {
    let id = self.inner.mod_new(main, name, source)?;
    self.state.modules.lock().unwrap().register(id, &name);
    Ok(id)
R
Ryan Dahl 已提交
175 176
  }

177 178 179 180 181 182 183
  /// Create an alias for another module.
  /// The alias could later be used to grab the module
  /// which `target` points to.
  fn mod_alias(&self, name: &str, target: &str) {
    self.state.modules.lock().unwrap().alias(name, target);
  }

184 185 186
  pub fn print_file_info(&self, module: &str) {
    let m = self.state.modules.lock().unwrap();
    m.print_file_info(&self.state.dir, module.to_string());
R
Ryan Dahl 已提交
187 188
  }

189 190 191
  /// Applies source map to the error.
  fn apply_source_map(&self, err: JSError) -> JSError {
    js_errors::apply_source_map(&err, &self.state.dir)
R
Ryan Dahl 已提交
192
  }
193 194
}

195
impl Future for Worker {
196 197 198 199 200
  type Item = ();
  type Error = JSError;

  fn poll(&mut self) -> Result<Async<()>, Self::Error> {
    self.inner.poll().map_err(|err| self.apply_source_map(err))
201 202
  }
}
203

204
fn fetch_module_meta_data_and_maybe_compile_async(
205
  state: &ThreadSafeState,
206 207 208
  specifier: &str,
  referrer: &str,
) -> impl Future<Item = ModuleMetaData, Error = DenoError> {
209
  let use_cache = !state.flags.reload;
210 211 212 213 214
  let state_ = state.clone();
  let specifier = specifier.to_string();
  let referrer = referrer.to_string();
  state
    .dir
215
    .fetch_module_meta_data_async(&specifier, &referrer, use_cache)
A
andy finch 已提交
216
    .and_then(move |out| {
217
      if out.media_type == msg::MediaType::TypeScript
218
        && !out.has_output_code_and_source_map()
219 220
      {
        debug!(">>>>> compile_sync START");
A
andy finch 已提交
221 222 223 224 225 226 227 228 229 230 231 232 233 234
        Either::A(
          compile_async(state_.clone(), &specifier, &referrer, &out)
            .map_err(|e| {
              debug!("compiler error exiting!");
              eprintln!("{}", JSErrorColor(&e).to_string());
              std::process::exit(1);
            }).and_then(move |out| {
              debug!(">>>>> compile_sync END");
              state_.dir.code_cache(&out)?;
              Ok(out)
            }),
        )
      } else {
        Either::B(futures::future::ok(out))
235 236 237
      }
    })
}
238

K
Kitson Kelly 已提交
239
fn fetch_module_meta_data_and_maybe_compile(
240
  state: &ThreadSafeState,
R
Ryan Dahl 已提交
241 242
  specifier: &str,
  referrer: &str,
K
Kitson Kelly 已提交
243
) -> Result<ModuleMetaData, DenoError> {
244 245 246
  tokio_util::block_on(fetch_module_meta_data_and_maybe_compile_async(
    state, specifier, referrer,
  ))
R
Ryan Dahl 已提交
247 248
}

R
Ryan Dahl 已提交
249 250 251
#[cfg(test)]
mod tests {
  use super::*;
252
  use crate::flags;
A
andy finch 已提交
253
  use crate::ops::op_selector_std;
254 255
  use crate::resources;
  use crate::startup_data;
256
  use crate::state::ThreadSafeState;
257 258
  use crate::tokio_util;
  use deno::js_check;
259 260
  use futures::future::lazy;
  use std::sync::atomic::Ordering;
261 262 263 264 265 266

  #[test]
  fn execute_mod() {
    let filename = std::env::current_dir()
      .unwrap()
      .join("tests/esm_imports_a.js");
267
    let filename = filename.to_str().unwrap().to_string();
268

269
    let argv = vec![String::from("./deno"), filename.clone()];
270
    let (flags, rest_argv) = flags::set_flags(argv).unwrap();
271

A
andy finch 已提交
272
    let state = ThreadSafeState::new(flags, rest_argv, op_selector_std);
273 274
    let state_ = state.clone();
    tokio_util::run(lazy(move || {
275 276
      let mut worker =
        Worker::new("TEST".to_string(), StartupData::None, state);
277
      if let Err(err) = worker.execute_mod(&filename, false) {
278 279
        eprintln!("execute_mod err {:?}", err);
      }
280
      tokio_util::panic_on_error(worker)
281 282 283
    }));

    let metrics = &state_.metrics;
284 285 286 287 288 289
    assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 1);
  }

  #[test]
  fn execute_mod_circular() {
    let filename = std::env::current_dir().unwrap().join("tests/circular1.js");
290
    let filename = filename.to_str().unwrap().to_string();
291

292
    let argv = vec![String::from("./deno"), filename.clone()];
293
    let (flags, rest_argv) = flags::set_flags(argv).unwrap();
294

A
andy finch 已提交
295
    let state = ThreadSafeState::new(flags, rest_argv, op_selector_std);
296 297
    let state_ = state.clone();
    tokio_util::run(lazy(move || {
298 299
      let mut worker =
        Worker::new("TEST".to_string(), StartupData::None, state);
300
      if let Err(err) = worker.execute_mod(&filename, false) {
301 302
        eprintln!("execute_mod err {:?}", err);
      }
303
      tokio_util::panic_on_error(worker)
304 305 306
    }));

    let metrics = &state_.metrics;
307
    assert_eq!(metrics.resolve_count.load(Ordering::SeqCst), 2);
308
  }
309 310

  fn create_test_worker() -> Worker {
311
    let state = ThreadSafeState::mock();
312
    let mut worker =
313
      Worker::new("TEST".to_string(), startup_data::deno_isolate_init(), state);
314 315 316 317 318 319 320 321 322 323 324 325 326
    js_check(worker.execute("denoMain()"));
    js_check(worker.execute("workerMain()"));
    worker
  }

  #[test]
  fn test_worker_messages() {
    tokio_util::init(|| {
      let mut worker = create_test_worker();
      let source = r#"
        onmessage = function(e) {
          console.log("msg from main script", e.data);
          if (e.data == "exit") {
327
            delete window.onmessage;
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
            return;
          } else {
            console.assert(e.data === "hi");
          }
          postMessage([1, 2, 3]);
          console.log("after postMessage");
        }
        "#;
      js_check(worker.execute(source));

      let resource = worker.state.resource.clone();
      let resource_ = resource.clone();

      tokio::spawn(lazy(move || {
        worker.then(move |r| -> Result<(), ()> {
          resource_.close();
          js_check(r);
          Ok(())
        })
      }));

      let msg = json!("hi").to_string().into_boxed_str().into_boxed_bytes();

      let r = resources::post_message_to_worker(resource.rid, msg).wait();
      assert!(r.is_ok());

      let maybe_msg = resources::get_message_from_worker(resource.rid)
        .wait()
        .unwrap();
      assert!(maybe_msg.is_some());
      // Check if message received is [1, 2, 3] in json
      assert_eq!(*maybe_msg.unwrap(), *b"[1,2,3]");

      let msg = json!("exit")
        .to_string()
        .into_boxed_str()
        .into_boxed_bytes();
      let r = resources::post_message_to_worker(resource.rid, msg).wait();
      assert!(r.is_ok());
    })
  }

  #[test]
  fn removed_from_resource_table_on_close() {
    tokio_util::init(|| {
      let mut worker = create_test_worker();
      js_check(
        worker.execute("onmessage = () => { delete window['onmessage']; }"),
      );

      let resource = worker.state.resource.clone();
      let rid = resource.rid;

381 382
      let worker_future = worker
        .then(move |r| -> Result<(), ()> {
383 384 385 386
          resource.close();
          println!("workers.rs after resource close");
          js_check(r);
          Ok(())
387 388 389 390
        }).shared();

      let worker_future_ = worker_future.clone();
      tokio::spawn(lazy(move || worker_future_.then(|_| Ok(()))));
391 392 393 394 395 396 397 398

      assert_eq!(resources::get_type(rid), Some("worker".to_string()));

      let msg = json!("hi").to_string().into_boxed_str().into_boxed_bytes();
      let r = resources::post_message_to_worker(rid, msg).wait();
      assert!(r.is_ok());
      debug!("rid {:?}", rid);

399
      worker_future.wait().unwrap();
400 401 402
      assert_eq!(resources::get_type(rid), None);
    })
  }
403
}