main
function in src/main.rs
: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
is located in js/main.ts
.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.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.event_loop
is located in src/isolate.rs
: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.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.setTimeout
would similarly be invoked and running to the end.self.is_idle
becomes true
. This happens when there are no more pending tasks. Then, the event loop exits and Deno quits.self.check_promise_errors
to check.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.src/dispatch.ts
dispatch.sendAsync
, which is used to forward async requests to Rust.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.denoMain
section, we saw that we attach handleAsyncMsgFromRust
to libdeno
as the receive callback whenever a new message arrives. Here is its source code: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.