I have read that fs.readFile
is an asynchronous operation and the reading takes place in a separate thread other than the main thread and hence the main thread execution is not blocked. So I wanted to try out something like below
// reads take almost 12-15ms
fs.readFile('./file.txt', (err, data) => {
console.log('FIRST READ', Date.now() - start)
const now = Date.now()
fs.readFile('./file.txt', (err, data) => {
// logs how much time it took from the beginning
console.log('NESTED READ CALLBACK', Date.now() - start)
})
// blocks untill 20ms more, untill the above read operation is done
// so that the above read operation is done and another callback is queued in the poll phase
while (Date.now() - now < 20) {}
console.log('AFTER BLOCKING', Date.now() - start)
})
I am making another fs.readFile
call inside the callback of parent fs.readFile
call. What I am expecting is that the log NESTED READ CALLBACK
must arrive immediately after AFTER BLOCKING
since, the reading must be completed asynchronously in a separate thread
Turns out the log NESTED READ CALLBACK
comes 15ms after the AFTER BLOCKING
call, indicating that while I was blocking in the while loop, the async read operation somehow never took place. By the way the while loop is there to model some task that takes 20ms to complete
What exactly is happening here? Or am I missing some information here? Btw I am using windows
CodePudding user response:
During your while()
loop, no events in Javascript will be processed and none of your Javascript code will run (because you are blocking the event loop from processing by just looping).
The disk operations can make some progress (as they do some work in a system thread), but their results will not be processed until your while
loop is done. But, because the fs.readFile()
actually consists of three or more operations, fs.open()
, fs.read()
and fs.close()
, it probably won't get very far while the event loop is blocked because it needs events to be processed to advance through the different stages of its work.
Turns out the log NESTED READ CALLBACK comes 15ms after the AFTER BLOCKING call, indicating that while I was blocking in the while loop, the async read operation somehow never took place. By the way the while loop is there to model some task that takes 20ms to complete
What exactly is happening here?
fs.readFile()
is not a single monolithic operation. Instead, it consists of fs.open()
, fs.read()
and fs.close()
and the sequencing of those is run in user-land Javascript in the main thread. So, while you are blocking the main thread with your while()
loop, the fs.readFile()
can't make a lot of progress. Probably what happens is you initiate the second fs.readFile()
operation and that kicks off the fs.open()
operation. That gets sent off to an OS thread in the libuv thread pool. Then, you block the event loop with your while()
loop. While that loop is blocking the event loop, the fs.open()
completes and (internal to the libuv event loop) an event is placed into the event queue to call the completion callback for the fs.open()
call. But, the event loop is blocked by your loop so that callback can't get called immediately. Thus, any further progress on completing the fs.readFile()
operation is blocked until the event loop frees up and can process waiting events again.
When your while()
loop finishes, then control returns to the event loop and the completion callback for the fs.open()
call gets called while will then initiate the reading of the actual data from the file.
FYI, you can actually inspect the code yourself for fs.readFile()
right here in the Github repository. If you follow its flow, you will see that, from its own Javascript, it first calls binding.open()
which is a native code operation and then, when that completes and Javascript is able to process the completion event through the event queue, it will then run the fucntion readFileAfterOpen(...)
which will call bind.fstat()
(another native code operation) and then, when that completes and Javascript is able to process the completion event
through the event queue, it will then call `readFileAfterStat(...) which will allocate a buffer and initiate the read operation.
Here, the code gets momentarily harder to follow as flow skips over to a read_file_context
object here where eventually it calls read()
and again when that completes and Javascript is able to process the completion event via the event loop, it can advance the process some more, eventually reading all the bytes from the file into a buffer, closing the file and then calling the final callback.
The point of all this detail is to illustrate how fs.readFile()
is written itself in Javascript and consists of multiple steps (some of which call code which will use some native code in a different thread), but can only advance from one step to the next when the event loop is able to process new events. So, if you are blocking the event loop with a while
loop, then fs.readFile()
will get stuck between steps and not be able to advance. It will only be able to advance and eventually finish when the event loop is able to process events.
An analogy
Here's a simple analogy. You ask your brother to do you a favor and go to three stores for you to pick up things for dinner. You give him the list and destination for the first store and then you ask him to call you on your cell phone after he's done at the first store and you'll give him the second destination and the list for that store. He heads off to the first store.
Meanwhile you call your girlfriend on your cell phone and start having a long conversation with her. Your brother finishes at the first store and calls you, but you're ignoring his call because you're still talking to your girlfriend. Your brother gets stuck on his mission of running errands because he needs to talk to you to find out what the next step is.
In this analogy, the cell phone is kind of like the event loop's ability to process the next event. If you are blocking his ability to call you on the phone, then he can't advance to his next step (the event loop is blocked). His visits to the three stores are like the individual steps involved in carrying out the fs.readfile()
operation.