Hidden threads in Javascript/Node that never execute user code: is it possible, and if so could it lead to an arcane possibility for a race condition?
See bottom of question for an update, based on comments/answers: This question is really about the possibility of hidden threads that do not execute callbacks.
I have a question about a potential arcane scenario involving the Node Request module in which:
A complete HTTP request is constructed and executed over the network (taking however many ms or even seconds)
... before a single function is executed at runtime on the local machine (typically in the nanoseconds?) - see below for details
I am posting this mostly as a sanity check just to make sure I am not misunderstanding something about Node / JS / Request module code.
From the examples in the Request module (see the SECOND example in that section), is this:
// Copied-and-pasted from the second example in the
// Node Request library documentation, here:
// https://www.npmjs.com/package/request#examples
// ... My ARCANE SCENARIO is injected in the middle
var request = require('request')
request(
{ method: 'GET'
, uri: 'http://www.google.com'
, gzip: true
}
, function (error, response, body) {
// body is the decompressed response body
console.log('server encoded the data as: ' + (response.headers['content-encoding'] || 'identity'))
console.log('the decoded data is: ' + body)
}
)
// **************************************************** //
// Is the following scenario possible?
//
// <-- HANG HANG HANG HANG HANG HANG HANG HANG HANG -->
//
// Let us pretend that the current thread HANGS here,
// but that the request had time to be sent,
// and the response is pending being received by the thread
//
// <-- HANG HANG HANG HANG HANG HANG HANG HANG HANG -->
// **************************************************** //
.on('data', function(data) {
// decompressed data as it is received
console.log('decoded chunk: ' + data)
})
.on('response', function(response) {
// unmodified http.IncomingMessage object
response.on('data', function(data) {
// compressed data as it is received
console.log('received ' + data.length + ' bytes of compressed data')
})
})
I have indicated my arcane scenario in the code snippet.
Suppose the Node process hangs at the point indicated, but that Node internally (in a hidden thread, invisible to Javascript, and therefore not calling any callbacks) WAS able to construct the request, and send it over the network; suppose the hang continues until a response (in two chunks, say) is received and waiting to be processed by Node. (This is the scenario that is certainly arcane, and that I'm not sure is even theoretically possible.)
Then suppose that the hang ends, and the Node thread above wakes up. Further, suppose that (somehow) Node was able to process the response all the way to the point of executing the callback function in the code above (yet without moving past the 'hanged' point in the code in the original code path -- again, if this is even theoretically possible).
Is the above arcane scenario theoretically possible? If so, wouldn't the data packets be received over the network and combined, ready to be passed to the callback function, before the 'data'
event was scheduled on the object? In this case, if it's possible, I would imagine that the 'data'
event would be missed.
Again, I understand that this is an arcane scenario - perhaps it's not even theoretically possible, given the internal mechanisms and coding involved.
That is my question - is the above arcane scenario, with its extremely unlikely race condition, nonetheless theoretically possible?
I ask just to make sure I'm not missing some key point. Thanks.
UPDATE: From comments & answers: I now have clarified my question. The 'arcane scenario' would require that there is a HIDDEN thread (which therefore CANNOT execute any USER code, including CALLBACKS) that constructs the request, sends it over the network, and receives the response - WITHOUT having any callbacks to trigger, including the 'data'
callback - and stops short just at the point that the 'response'
callback is ready to be called, waiting for the (single) visible JS thread to wake up.
No, this cannot happen.
Yes, there are indeed "hidden" background threads that do the work for asychronous methods, but those don't call callbacks. All execution of javascript does happen on the same thread, synchronously, sequentially. That data
event callback will always be executed asynchronously, that is, after the current script/function ran to completion.
While there could indeed already arrive packets from the network before the callback is created and attached to the event emitter, the callback that listens for packets on the lowest level is always created before the request is sent - it is an argument to the native "makeRequest" method, and is available to be called right from the beginning. So when a packet does arrive before the current script (still being occupied by constructing event emitters and attaching handlers) has finished, this event is queued up, and the callback will only be executed after the event loop is ready - on the next turn. By then, the data
event callback is certainly created and attached.
nodejs Javsacript execution is single threaded and event driven. That means that everything runs through an event queue. A thread of Javascript execution runs until it's done and then the system checks the event queue to see if there's anything else to do (timers waiting to fire, async callbacks waiting to be called, etc...).
nodejs does use some internal threads in some of its implementation (such as file I/O), but it is my understanding that it does not use threads in networking. But, it's immaterial whether there are some internal threads or not because all communication between sub-systems like networking and the main nodejs JS thread is via the event queue.
A nodejs thread of execution is never interrupted to do something else. It finishes and runs to completion, then the JS engine checks the event queue to see if there's something else waiting to be executed.
When there's incoming data available on socket, an event is placed in the event queue. The current nodejs Javascript that is executing finishes doing what it's doing, then the JS engine sees there's an event in the event queue and fires that event. If there's a function callback or event handler associated with that event (there usually is), then that gets called to execute the event.
If there's a mishap in the internals of some infrastructure such as networking, then all that happens to the nodejs code is that some networking event just doesn't occur. The nodejs code has its event handlers in place and just doesn't receive the event they are waiting for until the infrastructure gets unwedged and creates the event. This doesn't create any sort of hang in the nodejs code.
So, in your update:
From comments & answers: I now have clarified my question. The 'arcane scenario' would require that there is a HIDDEN thread (which therefore CANNOT execute any USER code, including CALLBACKS) that constructs the request, sends it over the network, and receives the response - WITHOUT having any callbacks to trigger, including the 'data' callback - and stops short just at the point that the 'response' callback is ready to be called, waiting for the (single) visible JS thread to wake up.
The nodejs thread runs to completion, then the JS engine waits for a new event to occur (e.g. get put in the event queue). When that event occurs, the JS engine runs the code that corresponds to that event (event handlers, callbacks, etc...). You make it sound like the single visible JS thread is asleep waiting to wake up and it can get stuck there because some other sub-system gets hung. That is not the case. The only thing that can happen is that some event that the single JS thread has an event handler for just never occurs. This would be no different than a situation where you send a message to a server and you have an event handler to see a response, but the server never sends the response. Your nodejs code continues processing other events (timers, other networking, other I/O), but this particular event just never occurs because the other server just never sent the data that would trigger that event. Nothing hangs.
This is "evented I/O" which is how nodejs describes itself.