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 delve into the MainWorker's domain, we'll treat the JS runtime as a mysterious black box. We'll unveil the intricacies 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 crafting 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 delve into each of these steps to gain a comprehensive understanding.
Apart from the previously mentioned function, we also utilize 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 delve 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 delve 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 crafted 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 swiftly 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 intricacies of V8 isolates, a wealth of information awaits at v8.dev. This resource serves as a treasure trove 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 serves as a crucial element, facilitating the execution of JavaScript programs via the V8 engine. Within the JS runtime, there exists a significant amount of v8 interfacing code that ensures the seamless interaction between the JavaScript code and the V8 engine. Interestingly, each worker is assigned its own distinct JS runtime. This separation is employed because JS runtimes are not shared among the various workers. Further insights into the intricacies of the JS runtime and its role will be delved into in subsequent sections, offering a comprehensive understanding of its significance in the Deno environment.
Ops refer to the fundamental operations coded in Rust, which serve as the building blocks for Deno's more advanced features. To grasp this concept more effectively, let's delve into an illustrative example. Consider the task of reading a file from your computer's disk. This process involves a series of 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 enlightening:
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:
Here's the code snippet again that demonstrates this process:
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 delve 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 delve 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 covered the worker aspect, let's transition to the next step: understanding how to initiate the code execution. But before we delve into running the code, it's important to take a closer look at the functionalities of the JavaScript runtime. This will provide us with a clearer understanding of the processes happening behind the scenes as our code runs.