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:
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:
Bootstrap
This phase involves loading and setting up the essential components of Deno's functionality.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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:
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.
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:
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, });ifletSome(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| {ifletSome(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.
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.
Here is the implementation of bootstrapMainRuntime():
functionbootstrapMainRuntime(runtimeOptions) {if (hasBootstrapped) {thrownewError("Worker runtime already bootstrapped"); }constnodeBootstrap=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: enableTestingFeaturesFlag16: hasNodeModulesDir,17: maybeBinaryNpmCommandName, } = runtimeOptions;performance.setTimeOrigin(DateNow()); globalThis_ = globalThis;// Remove bootstrapping data from the global scopedeleteglobalThis.__bootstrap;deleteglobalThis.bootstrap;deleteglobalThis.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) {constconsoleFromV8=core.console;constconsoleFromDeno=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 expensiveif (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:
It establishes handlers for both load and unload events, allowing the system to manage the initiation and conclusion of operations efficiently.
The function prepares the deno namespace, effectively setting up the fundamental environment and groundwork for the upcoming operations.
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.
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.