Process Lifecycle
Rust main() Entry Point
A Deno process starts with the main
function in src/main.rs
:
After processing the flags, Deno first creates a V8 isolate (an isolated V8 VM instance with its own heap). We also pass in the snapshot here, which contains serialized heap data about TypeScript compiler and frontend code. This allows much faster startup speed.
Immediately afterwards, we setup Tokio, and ask the isolate to execute a function called denoMain
. Only after it has finished running this function does Deno starting its event loop. Once the event loop stops, the Deno process dies.
denoMain()
denoMain
is located in js/main.ts
.
First, we tell libdeno
, which is the exposed API from the middle-end, that whenever there is a message sent from the Rust side, please forward the buffer to a function called handleAsyncMsgFromRust
. Then, a Start
message is sent to Rust side, signaling that we are starting and receiving information including the current working directory, or cwd
. We then decide whether the user is running a script, or using the REPL. If we have an input file name, Deno would then try to let the runner
, which contains the TypeScript compiler
, to try running the file (going deep into its definition, you'll eventually find a secret eval/globalEval
called somewhere). denoMain
only exits when the runner
finish running the file.
isolate.event_loop()
With denoMain
running to its end, a set of asynchronous calls might have been made without any of them being responded. Deno would then call the isolate.event_loop()
to start the loop.
Code for event_loop
is located in src/isolate.rs
:
This is literally a loop. Every single time, we check if there is still some events we haven't responded, with self.is_idle()
. If yes, we will wait for self.rx
, which is the receiving end of a message channel, to receive some message, or wait for a timer, created by setTimeout
, to expire.
If self.rx
received some message, this means that one of the task in the Tokio thread pool has completed its execution. We would then process its execution result, which is a pair of req_id
(the task id) and buf
(the buffer containing serialized response), by sending them to self.complete_op
, which forwards the result based on request id and invoke the code that is waiting for this output, running to the end. Notice that the code invoked here might schedule for more async operation.
If timeout happens, code scheduled to execute by setTimeout
would similarly be invoked and running to the end.
The event loop would continue to wait for output and run more code, until self.is_idle
becomes true
. This happens when there are no more pending tasks. Then, the event loop exits and Deno quits.
It is possible that an error might happen when executing code, and Deno would exit if such error exists. If it is a promise error (e.g. uncaught rejection), Deno would invoke self.check_promise_errors
to check.
Promise Table
In the previous section, we mentioned that req_id
is provided to self.complete_op
as a task id. Actually, it is used to retrieve the correct promise created by the past async operation from a promise table, which holds all the promises that are pending resolve.
The promise table is located in src/dispatch.ts
Promise table is populated by dispatch.sendAsync
, which is used to forward async requests to Rust.
The cmdId
here is essentially the req_id
on the Rust side. It is returned by sendInternal
, which calls into V8 Send binding and increments a global request id counter. For each async operation, we create a deferred/resolvable and place it into the promise table with cmdId
as the key.
From the denoMain
section, we saw that we attach handleAsyncMsgFromRust
to libdeno
as the receive callback whenever a new message arrives. Here is its source code:
Notice that it grabs the cmdId
from the response and find the corresponding Promise in the table. The promise is then resolved, allowing code awaiting the response to be executed as microtasks.
Lifecycle Example
For a file looks like the following:
The Deno process lifecycle control flow graph looks like this:
Last updated