5.7 Main Worker

Overview

The heart of Deno beats within the MainWorker, serving as its central hub. It plays a role similar to that of an orchestrator, coordinating various activities. Within the MainWorker, the program readies itself for action and eventually springs into operation. The MainWorker's responsibilities span from initializing the JavaScript runtime to loading modules and carrying out executions.

An essential collaborator for the MainWorker is the JavaScript (JS) runtime, a substantial component worthy of its own detailed exploration. For now, as we get into the MainWorker's domain, we'll treat the JS runtime as a mysterious black box. We'll unveil the complexities of the JS runtime in our upcoming section.

Turning our attention to the run command code, the MainWorker emerges into existence through the execution of the following lines of code:

let mut worker = worker_factory
    .create_main_worker(main_module, permissions)
    .await?;

The function "create_main_worker" is responsible for creating a CLIMainWorker. This special worker is constructed using various attributes and settings. Its primary role is to kickstart the execution of code. An interesting addition is its possession of a JS runtime instance, within which the JavaScript code operates.

The initiation and setup of the JS runtime occur right within the confines of the main worker itself. Consequently, the JS runtime doesn't need to be transferred to the main worker allocator separately. In a sense, the JS runtime becomes an integral component of the main worker's structure. This approach ensures that the main worker possesses all the necessary components, including the JS runtime, to function effectively.

Functionality

The primary worker isn't inherently complex. Instead, think of the main worker as a conductor that directs the execution of code. Let's delve into the core tasks that the main worker handles:

  1. Bootstrap

    • This phase involves loading and setting up the essential components of Deno's functionality.

  2. Initialize Ops

    • Here, the main worker takes the initiative to start and connect external operations (ops) with the JavaScript runtime (JsRuntime). This establishes the bridge between Deno's core and external functionalities.

  3. Create JsRuntime

    • The main worker's role in this step is to forge a V8 isolate, which is like a separate environment to run JavaScript code. It also creates a runtime that manages the execution of JavaScript within this isolated environment.

  4. Load Module

    • In this phase, the main worker is responsible for fetching and instantiating a module. A module can be thought of as a piece of code with specific functionality.

  5. Execute Module

    • The main worker's task here is to carry out the execution or evaluation of a module. This is where the actual code within the module gets to run.

  6. Create Inspector Session

    • For debugging purposes, the main worker takes on the responsibility of generating an inspector session. This allows developers to closely examine and troubleshoot the code's behavior.

So, while the main worker might seem like a simple orchestrator, it plays a crucial role in managing Deno's foundational processes. From initiating core functionalities to overseeing module execution and aiding in debugging, the main worker is a vital component in the inner workings of Deno.

Steps to create a worker

The process of creating a worker involves several sequential steps. It all begins with the establishment of a CLI module loader and culminates in the initiation of the runtime bootstrap. Now, let's get into each of these steps to gain a comprehensive understanding.

pub async fn create_main_worker(
    &self,
    main_module: ModuleSpecifier,
    permissions: PermissionsContainer,
  ) -> Result<CliMainWorker, AnyError> {
    self
      .create_custom_worker(
        main_module,
        permissions,
        vec![],
        Default::default(),
      )
      .await
  }

  pub async fn create_custom_worker(
    &self,
    main_module: ModuleSpecifier,
    permissions: PermissionsContainer,
    mut custom_extensions: Vec<Extension>,
    stdio: deno_runtime::deno_io::Stdio,
  ) -> Result<CliMainWorker, AnyError> {
    let shared = &self.shared;
    let (main_module, is_main_cjs) = if let Ok(package_ref) =
      NpmPackageReqReference::from_specifier(&main_module)
    {
      shared
        .npm_resolver
        .add_package_reqs(&[package_ref.req().clone()])
        .await?;
      let node_resolution =
        self.resolve_binary_entrypoint(&package_ref, &permissions)?;
      let is_main_cjs = matches!(node_resolution, NodeResolution::CommonJs(_));

      if let Some(lockfile) = &shared.maybe_lockfile {
        // For npm binary commands, ensure that the lockfile gets updated
        // so that we can re-use the npm resolution the next time it runs
        // for better performance
        lockfile
          .lock()
          .write()
          .context("Failed writing lockfile.")?;
      }

      (node_resolution.into_url(), is_main_cjs)
    } else if shared.options.is_npm_main {
      let node_resolution =
        shared.node_resolver.url_to_node_resolution(main_module)?;
      let is_main_cjs = matches!(node_resolution, NodeResolution::CommonJs(_));
      (node_resolution.into_url(), is_main_cjs)
    } else {
      (main_module, false)
    };

    let module_loader = shared
      .module_loader_factory
      .create_for_main(PermissionsContainer::allow_all(), permissions.clone());
    let maybe_source_map_getter =
      shared.module_loader_factory.create_source_map_getter();
    let maybe_inspector_server = shared.maybe_inspector_server.clone();

    let create_web_worker_cb =
      create_web_worker_callback(shared.clone(), stdio.clone());

    let maybe_storage_key = shared
      .storage_key_resolver
      .resolve_storage_key(&main_module);
    let origin_storage_dir = maybe_storage_key.as_ref().map(|key| {
      shared
        .options
        .origin_data_folder_path
        .as_ref()
        .unwrap() // must be set if storage key resolver returns a value
        .join(checksum::gen(&[key.as_bytes()]))
    });
    let cache_storage_dir = maybe_storage_key.map(|key| {
      // TODO(@satyarohith): storage quota management
      // Note: we currently use temp_dir() to avoid managing storage size.
      std::env::temp_dir()
        .join("deno_cache")
        .join(checksum::gen(&[key.as_bytes()]))
    });

    let mut extensions = ops::cli_exts(shared.npm_resolver.clone());
    extensions.append(&mut custom_extensions);

    let options = WorkerOptions {
      bootstrap: BootstrapOptions {
        args: shared.options.argv.clone(),
        cpu_count: std::thread::available_parallelism()
          .map(|p| p.get())
          .unwrap_or(1),
        log_level: shared.options.log_level,
        enable_testing_features: shared.options.enable_testing_features,
        locale: deno_core::v8::icu::get_language_tag(),
        location: shared.options.location.clone(),
        no_color: !colors::use_color(),
        is_tty: colors::is_tty(),
        runtime_version: version::deno().to_string(),
        ts_version: version::TYPESCRIPT.to_string(),
        unstable: shared.options.unstable,
        user_agent: version::get_user_agent().to_string(),
        inspect: shared.options.is_inspecting,
        has_node_modules_dir: shared.options.has_node_modules_dir,
        maybe_binary_npm_command_name: shared
          .options
          .maybe_binary_npm_command_name
          .clone(),
      },
      extensions,
      startup_snapshot: crate::js::deno_isolate_init(),
      create_params: None,
      unsafely_ignore_certificate_errors: shared
        .options
        .unsafely_ignore_certificate_errors
        .clone(),
      root_cert_store_provider: Some(shared.root_cert_store_provider.clone()),
      seed: shared.options.seed,
      source_map_getter: maybe_source_map_getter,
      format_js_error_fn: Some(Arc::new(format_js_error)),
      create_web_worker_cb,
      maybe_inspector_server,
      should_break_on_first_statement: shared.options.inspect_brk,
      should_wait_for_inspector_session: shared.options.inspect_wait,
      module_loader,
      fs: shared.fs.clone(),
      npm_resolver: Some(shared.npm_resolver.clone()),
      get_error_class_fn: Some(&errors::get_error_class_name),
      cache_storage_dir,
      origin_storage_dir,
      blob_store: shared.blob_store.clone(),
      broadcast_channel: shared.broadcast_channel.clone(),
      shared_array_buffer_store: Some(shared.shared_array_buffer_store.clone()),
      compiled_wasm_module_store: Some(
        shared.compiled_wasm_module_store.clone(),
      ),
      stdio,
    };

    let worker = MainWorker::bootstrap_from_options(
      main_module.clone(),
      permissions,
      options,
    );

    Ok(CliMainWorker {
      main_module,
      is_main_cjs,
      worker,
      shared: shared.clone(),
    })
  }

Apart from the previously mentioned function, Deno also utilizes the "bootstrap_from_options()" function to generate the real worker. Although the code within "bootstrap_from_options()" is quite extensive, it's worth taking a moment to briefly explore its intriguing details.

pub fn bootstrap_from_options(
    main_module: ModuleSpecifier,
    permissions: PermissionsContainer,
    options: WorkerOptions,
  ) -> Self {
    let bootstrap_options = options.bootstrap.clone();
    let mut worker = Self::from_options(main_module, permissions, options);
    worker.bootstrap(&bootstrap_options);
    worker
  }

  pub fn from_options(
    main_module: ModuleSpecifier,
    permissions: PermissionsContainer,
    mut options: WorkerOptions,
  ) -> Self {
    deno_core::extension!(deno_permissions_worker,
      options = {
        permissions: PermissionsContainer,
        unstable: bool,
        enable_testing_features: bool,
      },
      state = |state, options| {
        state.put::<PermissionsContainer>(options.permissions);
        state.put(ops::UnstableChecker { unstable: options.unstable });
        state.put(ops::TestingFeaturesEnabled(options.enable_testing_features));
      },
    );

    // Permissions: many ops depend on this
    let unstable = options.bootstrap.unstable;
    let enable_testing_features = options.bootstrap.enable_testing_features;
    let exit_code = ExitCode(Arc::new(AtomicI32::new(0)));
    let create_cache = options.cache_storage_dir.map(|storage_dir| {
      let create_cache_fn = move || SqliteBackedCache::new(storage_dir.clone());
      CreateCache(Arc::new(create_cache_fn))
    });

    // NOTE(bartlomieju): ordering is important here, keep it in sync with
    // `runtime/build.rs`, `runtime/web_worker.rs` and `cli/build.rs`!
    let mut extensions = vec![
      // Web APIs
      deno_webidl::deno_webidl::init_ops_and_esm(),
      deno_console::deno_console::init_ops_and_esm(),
      deno_url::deno_url::init_ops_and_esm(),
      deno_web::deno_web::init_ops_and_esm::<PermissionsContainer>(
        options.blob_store.clone(),
        options.bootstrap.location.clone(),
      ),
      deno_fetch::deno_fetch::init_ops_and_esm::<PermissionsContainer>(
        deno_fetch::Options {
          user_agent: options.bootstrap.user_agent.clone(),
          root_cert_store_provider: options.root_cert_store_provider.clone(),
          unsafely_ignore_certificate_errors: options
            .unsafely_ignore_certificate_errors
            .clone(),
          file_fetch_handler: Rc::new(deno_fetch::FsFetchHandler),
          ..Default::default()
        },
      ),
      deno_cache::deno_cache::init_ops_and_esm::<SqliteBackedCache>(
        create_cache,
      ),
      deno_websocket::deno_websocket::init_ops_and_esm::<PermissionsContainer>(
        options.bootstrap.user_agent.clone(),
        options.root_cert_store_provider.clone(),
        options.unsafely_ignore_certificate_errors.clone(),
      ),
      deno_webstorage::deno_webstorage::init_ops_and_esm(
        options.origin_storage_dir.clone(),
      ),
      deno_crypto::deno_crypto::init_ops_and_esm(options.seed),
      deno_broadcast_channel::deno_broadcast_channel::init_ops_and_esm(
        options.broadcast_channel.clone(),
        unstable,
      ),
      deno_ffi::deno_ffi::init_ops_and_esm::<PermissionsContainer>(unstable),
      deno_net::deno_net::init_ops_and_esm::<PermissionsContainer>(
        options.root_cert_store_provider.clone(),
        unstable,
        options.unsafely_ignore_certificate_errors.clone(),
      ),
      deno_tls::deno_tls::init_ops_and_esm(),
      deno_kv::deno_kv::init_ops_and_esm(
        SqliteDbHandler::<PermissionsContainer>::new(
          options.origin_storage_dir.clone(),
        ),
        unstable,
      ),
      deno_napi::deno_napi::init_ops_and_esm::<PermissionsContainer>(),
      deno_http::deno_http::init_ops_and_esm::<DefaultHttpPropertyExtractor>(),
      deno_io::deno_io::init_ops_and_esm(Some(options.stdio)),
      deno_fs::deno_fs::init_ops_and_esm::<PermissionsContainer>(
        unstable,
        options.fs.clone(),
      ),
      deno_node::deno_node::init_ops_and_esm::<PermissionsContainer>(
        options.npm_resolver,
        options.fs,
      ),
      // Ops from this crate
      ops::runtime::deno_runtime::init_ops_and_esm(main_module.clone()),
      ops::worker_host::deno_worker_host::init_ops_and_esm(
        options.create_web_worker_cb.clone(),
        options.format_js_error_fn.clone(),
      ),
      ops::fs_events::deno_fs_events::init_ops_and_esm(),
      ops::os::deno_os::init_ops_and_esm(exit_code.clone()),
      ops::permissions::deno_permissions::init_ops_and_esm(),
      ops::process::deno_process::init_ops_and_esm(),
      ops::signal::deno_signal::init_ops_and_esm(),
      ops::tty::deno_tty::init_ops_and_esm(),
      ops::http::deno_http_runtime::init_ops_and_esm(),
      deno_permissions_worker::init_ops_and_esm(
        permissions,
        unstable,
        enable_testing_features,
      ),
      runtime::init_ops_and_esm(),
    ];

    for extension in &mut extensions {
      #[cfg(not(feature = "__runtime_js_sources"))]
      {
        extension.js_files = std::borrow::Cow::Borrowed(&[]);
        extension.esm_files = std::borrow::Cow::Borrowed(&[]);
        extension.esm_entry_point = None;
      }
      #[cfg(feature = "__runtime_js_sources")]
      {
        for source in extension.esm_files.to_mut() {
          maybe_transpile_source(source).unwrap();
        }
        for source in extension.js_files.to_mut() {
          maybe_transpile_source(source).unwrap();
        }
      }
    }

    extensions.extend(std::mem::take(&mut options.extensions));

    #[cfg(all(feature = "include_js_files_for_snapshotting", feature = "dont_create_runtime_snapshot", not(feature = "__runtime_js_sources")))]
    options.startup_snapshot.as_ref().expect("Sources are not embedded, snapshotting was disabled and a user snapshot was not provided.");

    // Clear extension modules from the module map, except preserve `node:*`
    // modules.
    let preserve_snapshotted_modules =
      Some(SUPPORTED_BUILTIN_NODE_MODULES_WITH_PREFIX);

    let mut js_runtime = JsRuntime::new(RuntimeOptions {
      module_loader: Some(options.module_loader.clone()),
      startup_snapshot: options
        .startup_snapshot
        .or_else(crate::js::deno_isolate_init),
      create_params: options.create_params,
      source_map_getter: options.source_map_getter,
      get_error_class_fn: options.get_error_class_fn,
      shared_array_buffer_store: options.shared_array_buffer_store.clone(),
      compiled_wasm_module_store: options.compiled_wasm_module_store.clone(),
      extensions,
      preserve_snapshotted_modules,
      inspector: options.maybe_inspector_server.is_some(),
      is_main: true,
      ..Default::default()
    });

    if let Some(server) = options.maybe_inspector_server.clone() {
      server.register_inspector(
        main_module.to_string(),
        &mut js_runtime,
        options.should_break_on_first_statement
          || options.should_wait_for_inspector_session,
      );

      // Put inspector handle into the op state so we can put a breakpoint when
      // executing a CJS entrypoint.
      let op_state = js_runtime.op_state();
      let inspector = js_runtime.inspector();
      op_state.borrow_mut().put(inspector);
    }

    let bootstrap_fn_global = {
      let context = js_runtime.main_context();
      let scope = &mut js_runtime.handle_scope();
      let context_local = v8::Local::new(scope, context);
      let global_obj = context_local.global(scope);
      let bootstrap_str =
        v8::String::new_external_onebyte_static(scope, b"bootstrap").unwrap();
      let bootstrap_ns: v8::Local<v8::Object> = global_obj
        .get(scope, bootstrap_str.into())
        .unwrap()
        .try_into()
        .unwrap();
      let main_runtime_str =
        v8::String::new_external_onebyte_static(scope, b"mainRuntime").unwrap();
      let bootstrap_fn =
        bootstrap_ns.get(scope, main_runtime_str.into()).unwrap();
      let bootstrap_fn =
        v8::Local::<v8::Function>::try_from(bootstrap_fn).unwrap();
      v8::Global::new(scope, bootstrap_fn)
    };

    Self {
      js_runtime,
      should_break_on_first_statement: options.should_break_on_first_statement,
      should_wait_for_inspector_session: options
        .should_wait_for_inspector_session,
      exit_code,
      bootstrap_fn_global: Some(bootstrap_fn_global),
    }
  }

CLIModuleLoader

let module_loader = shared
      .module_loader_factory
      .create_for_main(PermissionsContainer::allow_all(), permissions.clone());

The CLIModuleLoader serves as a covering layer for the process of loading modules in Deno. Whenever a ModuleSpecifier is provided, this specialized loader becomes active, managing the tasks of loading and compiling modules. The functions within this module loader play a vital role, offering three primary actions:

  1. Resolve:

    • This function is responsible for determining the module specifier associated with an ES module. It helps in finding the correct path to the module.

  2. Prepare and Load:

    • The next significant function is the "prepare load." This action involves getting a module ready for loading. It ensures that the necessary preparations are made before the module is loaded.

  3. Load:

    • The final function, "load," takes care of the loading process itself. Once a module has been compiled, this function helps to bring it into the runtime environment, making it available for use.

As we poke deeper into our discussion on module loading, we will explore these functions in greater detail to gain a comprehensive understanding of how the CLIModuleLoader operates.

JS Runtime

MainWorker::from_options creates the main worker, and the process of creation of the main worker ends with creating an instance of the JS runtime. Js runtime needs the following things to initialize:

  • Module loader (or CLI module loader)

  • Snapshot

  • V8 Isolate

  • Shared array buffer store

  • etc.

Initialize isolate

The concept of an "Isolate" stands as a foundational pillar within Google's V8 engine. This Isolate serves as a way to compartmentalize and isolate different JavaScript code executions from one another, preventing unintended interactions. In Deno, an Isolate is established and initiated through the function deno_isolate_init(). This particular function is responsible for creating a static instance of the V8 Isolate, which offers advantages in terms of rapid loading speeds.

To dig a bit deeper into this, static isolates play a crucial role in expediting the initialization process of the V8 engine. These isolates are pre-configured and optimized, leading to quicker loading times for your applications. The static nature of these isolates also brings about a noteworthy benefit – they are included as part of the Deno package, so you don't need to manage their setup separately.

Consider these static isolates as snapshots frozen in time, capturing a moment of optimal performance. They are bundled with Deno, making it simpler for developers to harness their advantages without intricate configuration steps.

In fact, the CLI_SNAPSHOT, an essential component of Deno, is initiatialized from a .bin file. This file format encapsulates a pre-built snapshot of Deno's runtime environment, efficiently packaging essential code and functionalities. This snapshot empowers Deno to quickly start up and execute code by leveraging the pre-prepared groundwork contained within the CLI_SNAPSHOT.

For those eager to dive even deeper into the complexities of V8 isolates, a wealth of information awaits at v8.dev. This ultimate resource serves as a treasure of insights into the inner workings of V8 isolates, shedding light on their significance and how they contribute to the efficiency and effectiveness of applications running on Deno.

static RUNTIME_SNAPSHOT: &[u8] =
  include_bytes!(concat!(env!("OUT_DIR"), "/RUNTIME_SNAPSHOT.bin"));

pub fn deno_isolate_init() -> Option<Snapshot> {
  debug!("Deno isolate init with snapshots.");
  #[cfg(not(feature = "dont_create_runtime_snapshot"))]
  {
    Some(Snapshot::Static(RUNTIME_SNAPSHOT))
  }
  #[cfg(feature = "dont_create_runtime_snapshot")]
  {
    None
  }
}

Initialize runtime

The JS runtime is a crucial element that enables the execution of JavaScript programs using the V8 engine. The JS runtime contains significant code that facilitates seamless interaction between JavaScript code and the V8 engine. Notably, each worker has its own separate JS runtime, which is essential because JS runtimes and data are not shared among workers. We will explore the JS runtime's role and significance in more detail in subsequent sections, providing a comprehensive understanding of its importance in the Deno environment.

let mut js_runtime = JsRuntime::new(RuntimeOptions {
      module_loader: Some(options.module_loader.clone()),
      startup_snapshot: options
        .startup_snapshot
        .or_else(crate::js::deno_isolate_init),
      create_params: options.create_params,
      source_map_getter: options.source_map_getter,
      get_error_class_fn: options.get_error_class_fn,
      shared_array_buffer_store: options.shared_array_buffer_store.clone(),
      compiled_wasm_module_store: options.compiled_wasm_module_store.clone(),
      extensions,
      preserve_snapshotted_modules,
      inspector: options.maybe_inspector_server.is_some(),
      is_main: true,
      ..Default::default()
    });

Ops initialization

Ops are basic operations written in Rust that serve as the foundation for Deno's advanced features. To better understand this concept, let's consider a practical example. Suppose you want to read a file from your computer's disk. This process involves several steps within Deno's architecture:

  1. High-Level Function: At the apex of this hierarchy, we encounter a user-friendly function known as Deno.copyFileSync(). This function provides a simple way to copy one file to another.

  2. Low-Level Operation: Facilitating the functionality of the high-level function, there exists a core operation named op_fs_copy_file_sync(). This operation operates at a lower level and is implemented in Rust. It's responsible for executing the file copy operation efficiently.

Zooming out, this represents the deepest layer within Deno's structure. This tiered arrangement of high-level functions and corresponding low-level ops showcases the organization of Deno's operations. If you're interested in comprehending the nuances of this distinction, examining the following code can be useful:

function copyFileSync(
  fromPath,
  toPath,
) {
  ops.op_fs_copy_file_sync(
    pathFromURL(fromPath),
    pathFromURL(toPath),
  );
}

The process of working with ops involves transitioning from the V8 engine's domain to Deno's code realm. This is essential because while copyFileSync() operates within V8, the corresponding op functions run within Deno. In order to facilitate this seamless interaction, the initialization of ops becomes a necessity.

During the initialization of ops, a critical task is the registration of these ops with the JavaScript runtime as external references. Once registered, these ops become accessible for the V8 engine to invoke. These op functions serve a crucial role as they encompass functionalities that are not directly provided by V8. Unlike the fundamental JavaScript functions that V8 meticulously adheres to, these ops expand the capabilities beyond what is defined in the ECMAScript specification.

As part of the ops initialization process, various categories of ops are established within the worker's scope. These categories help organize the different types of operations that the ops can perform. This strategic categorization aids in maintaining clarity and efficiency within the Deno runtime environment. Here are the key categories in which ops are initialized within the worker's scope:

  • runtime

  • fetch

  • timers

  • worker_host

  • crypto

  • errors

  • fs

  • fs events

  • io

  • net

  • os

  • permissions

  • plugin

  • process

  • signal

  • tls

  • tty

  • websocket

In a previous code example, we observed how, during the initialization process, Deno visits all the extensions and registers OPs (operations). Let's revisit the pertinent code once more for clarity:

for ctx in ops {
    let ctx_ptr = ctx as *const OpCtx as _;
    references.push(v8::ExternalReference { pointer: ctx_ptr });
    references.push(v8::ExternalReference {
      function: ctx.decl.v8_fn_ptr,
    });
    if let Some(fast_fn) = &ctx.decl.fast_fn {
      references.push(v8::ExternalReference {
        pointer: fast_fn.function as _,
      });
      references.push(v8::ExternalReference {
        pointer: ctx.fast_fn_c_info.unwrap().as_ptr() as _,
      });
    }
  }

That's all in ops initialization. We'll see how ops get called in detail later.

Add streams

The second last step in worker initialization is to add standard streams to the resource table (code is from the io extension):

state = |state, options| {
    if let Some(stdio) = options.stdio {
      let t = &mut state.resource_table;

      let rid = t.add(fs::FileResource::new(
        Rc::new(match stdio.stdin {
          StdioPipe::Inherit => StdFileResourceInner::new(
            StdFileResourceKind::Stdin,
            STDIN_HANDLE.try_clone().unwrap(),
          ),
          StdioPipe::File(pipe) => StdFileResourceInner::file(pipe),
        }),
        "stdin".to_string(),
      ));
      assert_eq!(rid, 0, "stdin must have ResourceId 0");

      let rid = t.add(FileResource::new(
        Rc::new(match stdio.stdout {
          StdioPipe::Inherit => StdFileResourceInner::new(
            StdFileResourceKind::Stdout,
            STDOUT_HANDLE.try_clone().unwrap(),
          ),
          StdioPipe::File(pipe) => StdFileResourceInner::file(pipe),
        }),
        "stdout".to_string(),
      ));
      assert_eq!(rid, 1, "stdout must have ResourceId 1");

      let rid = t.add(FileResource::new(
        Rc::new(match stdio.stderr {
          StdioPipe::Inherit => StdFileResourceInner::new(
            StdFileResourceKind::Stderr,
            STDERR_HANDLE.try_clone().unwrap(),
          ),
          StdioPipe::File(pipe) => StdFileResourceInner::file(pipe),
        }),
        "stderr".to_string(),
      ));
      assert_eq!(rid, 2, "stderr must have ResourceId 2");
    }

Bootstrap

At this point, the worker is ready with the JS runtime. However, it's still not ready to process the user program. Bootstrapping makes Deno ready to start executing the user program. Here is the call to the bootstrap function which is present at the very end of the create_main_worker function.

worker.bootstrap(&bootstrap_options);
pub fn bootstrap(&mut self, options: &BootstrapOptions) {
    let scope = &mut self.js_runtime.handle_scope();
    let args = options.as_v8(scope);
    let bootstrap_fn = self.bootstrap_fn_global.take().unwrap();
    let bootstrap_fn = v8::Local::new(scope, bootstrap_fn);
    let undefined = v8::undefined(scope);
    bootstrap_fn.call(scope, undefined.into(), &[args]).unwrap();
}

The last stage of worker creation, known as bootstrapping, involves carrying out a small script's execution. In this phase, the worker's bootstrap function generates certain options and subsequently runs a concise script named bootstrap.mainRuntime({}) within the freshly initialized JavaScript runtime.

The worker's execute function serves the purpose of running JavaScript code. We will scrabble into the specifics of script execution shortly. For the time being, let's progress forward and shift our attention to the JavaScript bootstrap function.

The code snippet bootstrap.mainRuntime() represents a piece of JavaScript code that aims to invoke the mainRuntime function within the bootstrap namespace. To achieve this, the script seeks to establish a call to the aforementioned function. As we proceed, we'll get deeper into these concepts to gain a more comprehensive understanding.

globalThis.bootstrap = {
  mainRuntime: bootstrapMainRuntime,
  workerRuntime: bootstrapWorkerRuntime,
};

Here is the implementation of bootstrapMainRuntime():

function bootstrapMainRuntime(runtimeOptions) {
  if (hasBootstrapped) {
    throw new Error("Worker runtime already bootstrapped");
  }
  const nodeBootstrap = globalThis.nodeBootstrap;

  const {
    0: args,
    1: cpuCount,
    2: logLevel,
    3: denoVersion,
    4: locale,
    5: location_,
    6: noColor,
    7: isTty,
    8: tsVersion,
    9: unstableFlag,
    10: pid,
    11: target,
    12: v8Version,
    13: userAgent,
    14: inspectFlag,
    // 15: enableTestingFeaturesFlag
    16: hasNodeModulesDir,
    17: maybeBinaryNpmCommandName,
  } = runtimeOptions;

  performance.setTimeOrigin(DateNow());
  globalThis_ = globalThis;

  // Remove bootstrapping data from the global scope
  delete globalThis.__bootstrap;
  delete globalThis.bootstrap;
  delete globalThis.nodeBootstrap;
  hasBootstrapped = true;

  // If the `--location` flag isn't set, make `globalThis.location` `undefined` and
  // writable, so that they can mock it themselves if they like. If the flag was
  // set, define `globalThis.location`, using the provided value.
  if (location_ == null) {
    mainRuntimeGlobalProperties.location = {
      writable: true,
    };
  } else {
    location.setLocationHref(location_);
  }

  if (unstableFlag) {
    ObjectDefineProperties(globalThis, unstableWindowOrWorkerGlobalScope);
  }
  ObjectDefineProperties(globalThis, mainRuntimeGlobalProperties);
  ObjectDefineProperties(globalThis, {
    close: util.writable(windowClose),
    closed: util.getterOnly(() => windowIsClosing),
  });
  ObjectSetPrototypeOf(globalThis, Window.prototype);

  if (inspectFlag) {
    const consoleFromV8 = core.console;
    const consoleFromDeno = globalThis.console;
    wrapConsole(consoleFromDeno, consoleFromV8);
  }

  event.setEventTargetData(globalThis);
  event.saveGlobalThisReference(globalThis);

  event.defineEventHandler(globalThis, "error");
  event.defineEventHandler(globalThis, "load");
  event.defineEventHandler(globalThis, "beforeunload");
  event.defineEventHandler(globalThis, "unload");
  event.defineEventHandler(globalThis, "unhandledrejection");

  core.setPromiseRejectCallback(promiseRejectCallback);

  runtimeStart(
    denoVersion,
    v8Version,
    tsVersion,
    target,
    logLevel,
    noColor,
    isTty,
  );

  setNumCpus(cpuCount);
  setUserAgent(userAgent);
  setLanguage(locale);

  let ppid = undefined;
  ObjectDefineProperties(finalDenoNs, {
    pid: util.readOnly(pid),
    ppid: util.getterOnly(() => {
      // lazy because it's expensive
      if (ppid === undefined) {
        ppid = ops.op_ppid();
      }
      return ppid;
    }),
    noColor: util.readOnly(noColor),
    args: util.readOnly(ObjectFreeze(args)),
    mainModule: util.getterOnly(opMainModule),
  });

  if (unstableFlag) {
    ObjectAssign(finalDenoNs, denoNsUnstable);
  }

  // Setup `Deno` global - we're actually overriding already existing global
  // `Deno` with `Deno` namespace from "./deno.ts".
  ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs));

  util.log("args", args);

  if (nodeBootstrap) {
    nodeBootstrap(hasNodeModulesDir, maybeBinaryNpmCommandName);
  }
}

In essence, the function bootStrapMainRuntime carries out a series of essential tasks:

  1. It establishes handlers for both load and unload events, allowing the system to manage the initiation and conclusion of operations efficiently.

  2. The function prepares the deno namespace, effectively setting up the fundamental environment and groundwork for the upcoming operations.

  3. Additionally, it solidifies core objects such as Deno, Deno.core, and Deno.core.sharedQueue, ensuring that these central components remain unalterable and consistent throughout the runtime.

  4. Once this bootstrapping process is successfully completed, the primary worker becomes fully prepared to undertake the execution of the main module or the user's designated program.

--

Now that we've discussed workers, let's move on to initiating code execution. Before we run the code, it's essential to understand the JavaScript runtime's functionalities. This will give us a clearer understanding of the background processes that occur as our code runs.

Last updated