How does single-threaded Node.js handles requests concurrently?











up vote
1
down vote

favorite
1












I am currently deeply learning Nodejs platform. As we know, Nodejs is single-threaded, and if it executes blocking operation (for example fs.readFileSync), a thread should wait to finish that operation. I decided to make an experiment: I created a server that responses with the huge amount of data from a file on each request






const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});

server.listen(8000);





Also, I launched 5 terminals in order to do parallel requests to a server. I waited to see that while one request is being handled, the others should wait for finishing blocking operation from the first request. However, the other 4 requests were responded concurrently. Why does this behavior occur?










share|improve this question
























  • Possible duplicate of How does concurrency work in nodejs?
    – Sebastian Sebald
    Nov 19 at 7:17










  • Can you show us what exactly you see?
    – Bergi
    Nov 19 at 7:44






  • 1




    I suppose that the file is cached the first time it's read. It's unknown what testing conditions are. As you already noticed, there cannot be concurrent requests with blocking operations.
    – estus
    Nov 19 at 7:53















up vote
1
down vote

favorite
1












I am currently deeply learning Nodejs platform. As we know, Nodejs is single-threaded, and if it executes blocking operation (for example fs.readFileSync), a thread should wait to finish that operation. I decided to make an experiment: I created a server that responses with the huge amount of data from a file on each request






const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});

server.listen(8000);





Also, I launched 5 terminals in order to do parallel requests to a server. I waited to see that while one request is being handled, the others should wait for finishing blocking operation from the first request. However, the other 4 requests were responded concurrently. Why does this behavior occur?










share|improve this question
























  • Possible duplicate of How does concurrency work in nodejs?
    – Sebastian Sebald
    Nov 19 at 7:17










  • Can you show us what exactly you see?
    – Bergi
    Nov 19 at 7:44






  • 1




    I suppose that the file is cached the first time it's read. It's unknown what testing conditions are. As you already noticed, there cannot be concurrent requests with blocking operations.
    – estus
    Nov 19 at 7:53













up vote
1
down vote

favorite
1









up vote
1
down vote

favorite
1






1





I am currently deeply learning Nodejs platform. As we know, Nodejs is single-threaded, and if it executes blocking operation (for example fs.readFileSync), a thread should wait to finish that operation. I decided to make an experiment: I created a server that responses with the huge amount of data from a file on each request






const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});

server.listen(8000);





Also, I launched 5 terminals in order to do parallel requests to a server. I waited to see that while one request is being handled, the others should wait for finishing blocking operation from the first request. However, the other 4 requests were responded concurrently. Why does this behavior occur?










share|improve this question















I am currently deeply learning Nodejs platform. As we know, Nodejs is single-threaded, and if it executes blocking operation (for example fs.readFileSync), a thread should wait to finish that operation. I decided to make an experiment: I created a server that responses with the huge amount of data from a file on each request






const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});

server.listen(8000);





Also, I launched 5 terminals in order to do parallel requests to a server. I waited to see that while one request is being handled, the others should wait for finishing blocking operation from the first request. However, the other 4 requests were responded concurrently. Why does this behavior occur?






const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});

server.listen(8000);





const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
let data;
data =fs.readFileSync('./big.file');
res.end(data);
});

server.listen(8000);






javascript node.js single-threaded






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 20 at 21:48









halfer

14.2k757106




14.2k757106










asked Nov 19 at 7:03









longroad

14519




14519












  • Possible duplicate of How does concurrency work in nodejs?
    – Sebastian Sebald
    Nov 19 at 7:17










  • Can you show us what exactly you see?
    – Bergi
    Nov 19 at 7:44






  • 1




    I suppose that the file is cached the first time it's read. It's unknown what testing conditions are. As you already noticed, there cannot be concurrent requests with blocking operations.
    – estus
    Nov 19 at 7:53


















  • Possible duplicate of How does concurrency work in nodejs?
    – Sebastian Sebald
    Nov 19 at 7:17










  • Can you show us what exactly you see?
    – Bergi
    Nov 19 at 7:44






  • 1




    I suppose that the file is cached the first time it's read. It's unknown what testing conditions are. As you already noticed, there cannot be concurrent requests with blocking operations.
    – estus
    Nov 19 at 7:53
















Possible duplicate of How does concurrency work in nodejs?
– Sebastian Sebald
Nov 19 at 7:17




Possible duplicate of How does concurrency work in nodejs?
– Sebastian Sebald
Nov 19 at 7:17












Can you show us what exactly you see?
– Bergi
Nov 19 at 7:44




Can you show us what exactly you see?
– Bergi
Nov 19 at 7:44




1




1




I suppose that the file is cached the first time it's read. It's unknown what testing conditions are. As you already noticed, there cannot be concurrent requests with blocking operations.
– estus
Nov 19 at 7:53




I suppose that the file is cached the first time it's read. It's unknown what testing conditions are. As you already noticed, there cannot be concurrent requests with blocking operations.
– estus
Nov 19 at 7:53












2 Answers
2






active

oldest

votes

















up vote
3
down vote



accepted










What you're likely seeing is either some asynchronous part of the implementation inside of res.end() to actually send your large amount of data or you are seeing all the data get sent very quickly and serially, but the clients can't process it fast enough to actually show it serially and because the clients are each in their own separate process, they "appear" to show it arriving concurrently just because they're too slow reacting to show the actually arrival sequence.



One would have to use a network sniffer to see which of these is actually occurring or run some different tests or put some logging inside the implementation of res.end() or tap into some logging inside the client's TCP stack to determine the actual order of packet arrival among the different requests.





If you have one server and it has one request handler that is doing synchronous I/O, then you will not get multiple requests processes concurrently. If you believe that is happening, then you will have to document exactly how you measured that or concluded that (so we can help you clear up your misunderstanding) because that is not how node.js works when using blocking, synchronous I/O such as fs.readFileSync().



node.js runs your JS as single threaded and when you use blocking, synchronous I/O, it blocks that one single thread of Javascript. That's why you should never use synchronous I/O in a server, except perhaps in startup code that only runs once during startup.



What is clear is that fs.readFileSync('./big.file') is synchronous so your second request will not get started processing until the first fs.readFileSync() is done. And, calling it on the same file over and over again will be very fast (OS disk caching).



But, res.end(data) is non-blocking, asynchronous. res is a stream and you're giving the stream some data to process. It will send out as much as it can over the socket, but if it gets flow controlled by TCP, it will pause until there's more room to send on the socket. How much that happens depends upon all sorts of things about your computer, it's configuration and the network link to the client.



So, what could be happening is this sequence of events:




  1. First request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  2. Second request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  3. At this point, the event loop might start processing the third or fourth requests or it might service some more events (from inside the implementation of res.end() or the writeStream from the first request to keep sending more data. If it does service those events, it could give the appearance (from the client point of view) of true concurrency of the different requests).



Also, the client could be causing it to appear sequenced. Each client is reading a different buffered socket and if they are all in different terminals, then they are multi-tasked. So, if there is more data on each client's socket than it can read and display immediately (which is probably the case), then each client will read some, display some, read some more, display some more, etc... If the delay between sending each client's response on your server is smaller than the delay in reading and displaying on the client, then the clients (which are each in their own separate processes) are able to run concurrently.





When you are using asynchronous I/O such as fs.readFile(), then properly written node.js Javascript code can have many requests "in flight" at the same time. They don't actually run concurrently at exactly the same time, but one can run, do some work, launch an asynchronous operation, then give way to let another request run. With properly written asynchronous I/O, there can be an appearance from the outside world of concurrent processing, even though it's more akin to sharing of the single thread whenever a request handler is waiting for an asynchronous I/O request to finish. But, the server code you show is not this cooperative, asynchronous I/O.






share|improve this answer























  • Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
    – longroad
    Nov 19 at 7:33










  • Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
    – Bergi
    Nov 19 at 7:46










  • @longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
    – jfriend00
    Nov 19 at 8:12










  • @longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
    – jfriend00
    Nov 19 at 8:16












  • @Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
    – jfriend00
    Nov 19 at 8:22




















up vote
3
down vote













Maybe is not related directly to your question but i think this is useful,



You can use a stream instead of reading the full file into memory, for example:



const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
const readStream = fs.createReadStream('./big.file'); // Here we create the stream.
readStream.pipe(res); // Here we pipe the readable stream to the res writeable stream.
});

server.listen(8000);


The point of doing this is:




  • Looks nicer.

  • You don't store the full file in RAM.


This works better because is non blocking, and the res object is already a stream, and this means the data will be transfered in chunks.



Ok so streams = chunked



Why not read chunks from the file and send them in real time instead of reading a really big file and divide that in chunks after?



Also why is really important on a real production server?



Because every time a request is received, your code is going to add that big file into ram, to that add this is concurrent so you are expecting to serve multiple files at the same time, so let's do the most advanced math my poor education allows:



1 request for a 1gb file = 1gb in ram



2 requests for a 1gb file = 2gb in ram



etc



That clearly doesn't scale nicely right?



Streams allows to decouple that data from the current state of the function (inside that scope), so in simple terms its going to be (with the default chunk size of 16kb):



1 request for 1gb file = 16kb in ram



2 requests for 1gb file = 32kb in ram



etc



And also, the OS its already passing a stream to node (fs) so it works with streams end to end.



Hope it helps :D.



PD: Never use sync operations (blocking) inside async operations (non blocking).






share|improve this answer



















  • 1




    Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
    – longroad
    Nov 19 at 9:37











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














 

draft saved


draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53369763%2fhow-does-single-threaded-node-js-handles-requests-concurrently%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
3
down vote



accepted










What you're likely seeing is either some asynchronous part of the implementation inside of res.end() to actually send your large amount of data or you are seeing all the data get sent very quickly and serially, but the clients can't process it fast enough to actually show it serially and because the clients are each in their own separate process, they "appear" to show it arriving concurrently just because they're too slow reacting to show the actually arrival sequence.



One would have to use a network sniffer to see which of these is actually occurring or run some different tests or put some logging inside the implementation of res.end() or tap into some logging inside the client's TCP stack to determine the actual order of packet arrival among the different requests.





If you have one server and it has one request handler that is doing synchronous I/O, then you will not get multiple requests processes concurrently. If you believe that is happening, then you will have to document exactly how you measured that or concluded that (so we can help you clear up your misunderstanding) because that is not how node.js works when using blocking, synchronous I/O such as fs.readFileSync().



node.js runs your JS as single threaded and when you use blocking, synchronous I/O, it blocks that one single thread of Javascript. That's why you should never use synchronous I/O in a server, except perhaps in startup code that only runs once during startup.



What is clear is that fs.readFileSync('./big.file') is synchronous so your second request will not get started processing until the first fs.readFileSync() is done. And, calling it on the same file over and over again will be very fast (OS disk caching).



But, res.end(data) is non-blocking, asynchronous. res is a stream and you're giving the stream some data to process. It will send out as much as it can over the socket, but if it gets flow controlled by TCP, it will pause until there's more room to send on the socket. How much that happens depends upon all sorts of things about your computer, it's configuration and the network link to the client.



So, what could be happening is this sequence of events:




  1. First request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  2. Second request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  3. At this point, the event loop might start processing the third or fourth requests or it might service some more events (from inside the implementation of res.end() or the writeStream from the first request to keep sending more data. If it does service those events, it could give the appearance (from the client point of view) of true concurrency of the different requests).



Also, the client could be causing it to appear sequenced. Each client is reading a different buffered socket and if they are all in different terminals, then they are multi-tasked. So, if there is more data on each client's socket than it can read and display immediately (which is probably the case), then each client will read some, display some, read some more, display some more, etc... If the delay between sending each client's response on your server is smaller than the delay in reading and displaying on the client, then the clients (which are each in their own separate processes) are able to run concurrently.





When you are using asynchronous I/O such as fs.readFile(), then properly written node.js Javascript code can have many requests "in flight" at the same time. They don't actually run concurrently at exactly the same time, but one can run, do some work, launch an asynchronous operation, then give way to let another request run. With properly written asynchronous I/O, there can be an appearance from the outside world of concurrent processing, even though it's more akin to sharing of the single thread whenever a request handler is waiting for an asynchronous I/O request to finish. But, the server code you show is not this cooperative, asynchronous I/O.






share|improve this answer























  • Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
    – longroad
    Nov 19 at 7:33










  • Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
    – Bergi
    Nov 19 at 7:46










  • @longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
    – jfriend00
    Nov 19 at 8:12










  • @longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
    – jfriend00
    Nov 19 at 8:16












  • @Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
    – jfriend00
    Nov 19 at 8:22

















up vote
3
down vote



accepted










What you're likely seeing is either some asynchronous part of the implementation inside of res.end() to actually send your large amount of data or you are seeing all the data get sent very quickly and serially, but the clients can't process it fast enough to actually show it serially and because the clients are each in their own separate process, they "appear" to show it arriving concurrently just because they're too slow reacting to show the actually arrival sequence.



One would have to use a network sniffer to see which of these is actually occurring or run some different tests or put some logging inside the implementation of res.end() or tap into some logging inside the client's TCP stack to determine the actual order of packet arrival among the different requests.





If you have one server and it has one request handler that is doing synchronous I/O, then you will not get multiple requests processes concurrently. If you believe that is happening, then you will have to document exactly how you measured that or concluded that (so we can help you clear up your misunderstanding) because that is not how node.js works when using blocking, synchronous I/O such as fs.readFileSync().



node.js runs your JS as single threaded and when you use blocking, synchronous I/O, it blocks that one single thread of Javascript. That's why you should never use synchronous I/O in a server, except perhaps in startup code that only runs once during startup.



What is clear is that fs.readFileSync('./big.file') is synchronous so your second request will not get started processing until the first fs.readFileSync() is done. And, calling it on the same file over and over again will be very fast (OS disk caching).



But, res.end(data) is non-blocking, asynchronous. res is a stream and you're giving the stream some data to process. It will send out as much as it can over the socket, but if it gets flow controlled by TCP, it will pause until there's more room to send on the socket. How much that happens depends upon all sorts of things about your computer, it's configuration and the network link to the client.



So, what could be happening is this sequence of events:




  1. First request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  2. Second request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  3. At this point, the event loop might start processing the third or fourth requests or it might service some more events (from inside the implementation of res.end() or the writeStream from the first request to keep sending more data. If it does service those events, it could give the appearance (from the client point of view) of true concurrency of the different requests).



Also, the client could be causing it to appear sequenced. Each client is reading a different buffered socket and if they are all in different terminals, then they are multi-tasked. So, if there is more data on each client's socket than it can read and display immediately (which is probably the case), then each client will read some, display some, read some more, display some more, etc... If the delay between sending each client's response on your server is smaller than the delay in reading and displaying on the client, then the clients (which are each in their own separate processes) are able to run concurrently.





When you are using asynchronous I/O such as fs.readFile(), then properly written node.js Javascript code can have many requests "in flight" at the same time. They don't actually run concurrently at exactly the same time, but one can run, do some work, launch an asynchronous operation, then give way to let another request run. With properly written asynchronous I/O, there can be an appearance from the outside world of concurrent processing, even though it's more akin to sharing of the single thread whenever a request handler is waiting for an asynchronous I/O request to finish. But, the server code you show is not this cooperative, asynchronous I/O.






share|improve this answer























  • Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
    – longroad
    Nov 19 at 7:33










  • Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
    – Bergi
    Nov 19 at 7:46










  • @longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
    – jfriend00
    Nov 19 at 8:12










  • @longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
    – jfriend00
    Nov 19 at 8:16












  • @Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
    – jfriend00
    Nov 19 at 8:22















up vote
3
down vote



accepted







up vote
3
down vote



accepted






What you're likely seeing is either some asynchronous part of the implementation inside of res.end() to actually send your large amount of data or you are seeing all the data get sent very quickly and serially, but the clients can't process it fast enough to actually show it serially and because the clients are each in their own separate process, they "appear" to show it arriving concurrently just because they're too slow reacting to show the actually arrival sequence.



One would have to use a network sniffer to see which of these is actually occurring or run some different tests or put some logging inside the implementation of res.end() or tap into some logging inside the client's TCP stack to determine the actual order of packet arrival among the different requests.





If you have one server and it has one request handler that is doing synchronous I/O, then you will not get multiple requests processes concurrently. If you believe that is happening, then you will have to document exactly how you measured that or concluded that (so we can help you clear up your misunderstanding) because that is not how node.js works when using blocking, synchronous I/O such as fs.readFileSync().



node.js runs your JS as single threaded and when you use blocking, synchronous I/O, it blocks that one single thread of Javascript. That's why you should never use synchronous I/O in a server, except perhaps in startup code that only runs once during startup.



What is clear is that fs.readFileSync('./big.file') is synchronous so your second request will not get started processing until the first fs.readFileSync() is done. And, calling it on the same file over and over again will be very fast (OS disk caching).



But, res.end(data) is non-blocking, asynchronous. res is a stream and you're giving the stream some data to process. It will send out as much as it can over the socket, but if it gets flow controlled by TCP, it will pause until there's more room to send on the socket. How much that happens depends upon all sorts of things about your computer, it's configuration and the network link to the client.



So, what could be happening is this sequence of events:




  1. First request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  2. Second request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  3. At this point, the event loop might start processing the third or fourth requests or it might service some more events (from inside the implementation of res.end() or the writeStream from the first request to keep sending more data. If it does service those events, it could give the appearance (from the client point of view) of true concurrency of the different requests).



Also, the client could be causing it to appear sequenced. Each client is reading a different buffered socket and if they are all in different terminals, then they are multi-tasked. So, if there is more data on each client's socket than it can read and display immediately (which is probably the case), then each client will read some, display some, read some more, display some more, etc... If the delay between sending each client's response on your server is smaller than the delay in reading and displaying on the client, then the clients (which are each in their own separate processes) are able to run concurrently.





When you are using asynchronous I/O such as fs.readFile(), then properly written node.js Javascript code can have many requests "in flight" at the same time. They don't actually run concurrently at exactly the same time, but one can run, do some work, launch an asynchronous operation, then give way to let another request run. With properly written asynchronous I/O, there can be an appearance from the outside world of concurrent processing, even though it's more akin to sharing of the single thread whenever a request handler is waiting for an asynchronous I/O request to finish. But, the server code you show is not this cooperative, asynchronous I/O.






share|improve this answer














What you're likely seeing is either some asynchronous part of the implementation inside of res.end() to actually send your large amount of data or you are seeing all the data get sent very quickly and serially, but the clients can't process it fast enough to actually show it serially and because the clients are each in their own separate process, they "appear" to show it arriving concurrently just because they're too slow reacting to show the actually arrival sequence.



One would have to use a network sniffer to see which of these is actually occurring or run some different tests or put some logging inside the implementation of res.end() or tap into some logging inside the client's TCP stack to determine the actual order of packet arrival among the different requests.





If you have one server and it has one request handler that is doing synchronous I/O, then you will not get multiple requests processes concurrently. If you believe that is happening, then you will have to document exactly how you measured that or concluded that (so we can help you clear up your misunderstanding) because that is not how node.js works when using blocking, synchronous I/O such as fs.readFileSync().



node.js runs your JS as single threaded and when you use blocking, synchronous I/O, it blocks that one single thread of Javascript. That's why you should never use synchronous I/O in a server, except perhaps in startup code that only runs once during startup.



What is clear is that fs.readFileSync('./big.file') is synchronous so your second request will not get started processing until the first fs.readFileSync() is done. And, calling it on the same file over and over again will be very fast (OS disk caching).



But, res.end(data) is non-blocking, asynchronous. res is a stream and you're giving the stream some data to process. It will send out as much as it can over the socket, but if it gets flow controlled by TCP, it will pause until there's more room to send on the socket. How much that happens depends upon all sorts of things about your computer, it's configuration and the network link to the client.



So, what could be happening is this sequence of events:




  1. First request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  2. Second request arrives and does fs.readFileSync() and calls res.end(data). That starts sending data to the client, but returns before it is done because of TCP flow control. This sends node.js back to its event loop.


  3. At this point, the event loop might start processing the third or fourth requests or it might service some more events (from inside the implementation of res.end() or the writeStream from the first request to keep sending more data. If it does service those events, it could give the appearance (from the client point of view) of true concurrency of the different requests).



Also, the client could be causing it to appear sequenced. Each client is reading a different buffered socket and if they are all in different terminals, then they are multi-tasked. So, if there is more data on each client's socket than it can read and display immediately (which is probably the case), then each client will read some, display some, read some more, display some more, etc... If the delay between sending each client's response on your server is smaller than the delay in reading and displaying on the client, then the clients (which are each in their own separate processes) are able to run concurrently.





When you are using asynchronous I/O such as fs.readFile(), then properly written node.js Javascript code can have many requests "in flight" at the same time. They don't actually run concurrently at exactly the same time, but one can run, do some work, launch an asynchronous operation, then give way to let another request run. With properly written asynchronous I/O, there can be an appearance from the outside world of concurrent processing, even though it's more akin to sharing of the single thread whenever a request handler is waiting for an asynchronous I/O request to finish. But, the server code you show is not this cooperative, asynchronous I/O.







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 19 at 8:45

























answered Nov 19 at 7:17









jfriend00

423k51535583




423k51535583












  • Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
    – longroad
    Nov 19 at 7:33










  • Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
    – Bergi
    Nov 19 at 7:46










  • @longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
    – jfriend00
    Nov 19 at 8:12










  • @longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
    – jfriend00
    Nov 19 at 8:16












  • @Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
    – jfriend00
    Nov 19 at 8:22




















  • Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
    – longroad
    Nov 19 at 7:33










  • Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
    – Bergi
    Nov 19 at 7:46










  • @longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
    – jfriend00
    Nov 19 at 8:12










  • @longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
    – jfriend00
    Nov 19 at 8:16












  • @Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
    – jfriend00
    Nov 19 at 8:22


















Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
– longroad
Nov 19 at 7:33




Hello. Thank you for your answer. I am using CURL in order to make requests. When I do requests in terminals, file's content appears immediately there.
– longroad
Nov 19 at 7:33












Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
– Bergi
Nov 19 at 7:46




Not sure, could res.end(data); write a huge chunk to a stream that is then read asynchronously in many small chunks, making it appear on the client that all responses are served in parallel?
– Bergi
Nov 19 at 7:46












@longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
– jfriend00
Nov 19 at 8:12




@longroad - OK, as Bergi mentioned, res.end() is asynchronous so there may be some asynchronous behavior involved in sending larger amounts of data that cause the remaining amount of data (after the first chunk is sent for each request) to be interleaved. This would all be internal to the res.end() implementation, not interleaving your actual Javascript. See what I added to the end of my answer.
– jfriend00
Nov 19 at 8:12












@longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
– jfriend00
Nov 19 at 8:16






@longroad - Also, the clients are in separate processes and probably can't display the incoming data as fast as it arrives. So, their internal buffers back up and they are all trying to display it concurrently. So, even if the server sent them in series, the clients may not be able to display them fast enough to actually keep up so the clients end up looking like their data arrived concurrently, but really it arrived serially and they just displayed it concurrently.
– jfriend00
Nov 19 at 8:16














@Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
– jfriend00
Nov 19 at 8:22






@Bergi - Yes, that is a possibility. res.end() could get flow controlled and could take a little while to actually send all the data (while other incoming requests are getting cycles to run). Or, it could all be because the clients which are in separate processes aren't fast enough processing and displaying the incoming data to actually visually show the data arriving serially. One could figure out which it is (or some combination of both) with a network sniffer (if you could avoid disturbing the network by looking at it - Heisenberg).
– jfriend00
Nov 19 at 8:22














up vote
3
down vote













Maybe is not related directly to your question but i think this is useful,



You can use a stream instead of reading the full file into memory, for example:



const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
const readStream = fs.createReadStream('./big.file'); // Here we create the stream.
readStream.pipe(res); // Here we pipe the readable stream to the res writeable stream.
});

server.listen(8000);


The point of doing this is:




  • Looks nicer.

  • You don't store the full file in RAM.


This works better because is non blocking, and the res object is already a stream, and this means the data will be transfered in chunks.



Ok so streams = chunked



Why not read chunks from the file and send them in real time instead of reading a really big file and divide that in chunks after?



Also why is really important on a real production server?



Because every time a request is received, your code is going to add that big file into ram, to that add this is concurrent so you are expecting to serve multiple files at the same time, so let's do the most advanced math my poor education allows:



1 request for a 1gb file = 1gb in ram



2 requests for a 1gb file = 2gb in ram



etc



That clearly doesn't scale nicely right?



Streams allows to decouple that data from the current state of the function (inside that scope), so in simple terms its going to be (with the default chunk size of 16kb):



1 request for 1gb file = 16kb in ram



2 requests for 1gb file = 32kb in ram



etc



And also, the OS its already passing a stream to node (fs) so it works with streams end to end.



Hope it helps :D.



PD: Never use sync operations (blocking) inside async operations (non blocking).






share|improve this answer



















  • 1




    Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
    – longroad
    Nov 19 at 9:37















up vote
3
down vote













Maybe is not related directly to your question but i think this is useful,



You can use a stream instead of reading the full file into memory, for example:



const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
const readStream = fs.createReadStream('./big.file'); // Here we create the stream.
readStream.pipe(res); // Here we pipe the readable stream to the res writeable stream.
});

server.listen(8000);


The point of doing this is:




  • Looks nicer.

  • You don't store the full file in RAM.


This works better because is non blocking, and the res object is already a stream, and this means the data will be transfered in chunks.



Ok so streams = chunked



Why not read chunks from the file and send them in real time instead of reading a really big file and divide that in chunks after?



Also why is really important on a real production server?



Because every time a request is received, your code is going to add that big file into ram, to that add this is concurrent so you are expecting to serve multiple files at the same time, so let's do the most advanced math my poor education allows:



1 request for a 1gb file = 1gb in ram



2 requests for a 1gb file = 2gb in ram



etc



That clearly doesn't scale nicely right?



Streams allows to decouple that data from the current state of the function (inside that scope), so in simple terms its going to be (with the default chunk size of 16kb):



1 request for 1gb file = 16kb in ram



2 requests for 1gb file = 32kb in ram



etc



And also, the OS its already passing a stream to node (fs) so it works with streams end to end.



Hope it helps :D.



PD: Never use sync operations (blocking) inside async operations (non blocking).






share|improve this answer



















  • 1




    Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
    – longroad
    Nov 19 at 9:37













up vote
3
down vote










up vote
3
down vote









Maybe is not related directly to your question but i think this is useful,



You can use a stream instead of reading the full file into memory, for example:



const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
const readStream = fs.createReadStream('./big.file'); // Here we create the stream.
readStream.pipe(res); // Here we pipe the readable stream to the res writeable stream.
});

server.listen(8000);


The point of doing this is:




  • Looks nicer.

  • You don't store the full file in RAM.


This works better because is non blocking, and the res object is already a stream, and this means the data will be transfered in chunks.



Ok so streams = chunked



Why not read chunks from the file and send them in real time instead of reading a really big file and divide that in chunks after?



Also why is really important on a real production server?



Because every time a request is received, your code is going to add that big file into ram, to that add this is concurrent so you are expecting to serve multiple files at the same time, so let's do the most advanced math my poor education allows:



1 request for a 1gb file = 1gb in ram



2 requests for a 1gb file = 2gb in ram



etc



That clearly doesn't scale nicely right?



Streams allows to decouple that data from the current state of the function (inside that scope), so in simple terms its going to be (with the default chunk size of 16kb):



1 request for 1gb file = 16kb in ram



2 requests for 1gb file = 32kb in ram



etc



And also, the OS its already passing a stream to node (fs) so it works with streams end to end.



Hope it helps :D.



PD: Never use sync operations (blocking) inside async operations (non blocking).






share|improve this answer














Maybe is not related directly to your question but i think this is useful,



You can use a stream instead of reading the full file into memory, for example:



const { createServer } = require('http');
const fs = require('fs');

const server = createServer();

server.on('request', (req, res) => {
const readStream = fs.createReadStream('./big.file'); // Here we create the stream.
readStream.pipe(res); // Here we pipe the readable stream to the res writeable stream.
});

server.listen(8000);


The point of doing this is:




  • Looks nicer.

  • You don't store the full file in RAM.


This works better because is non blocking, and the res object is already a stream, and this means the data will be transfered in chunks.



Ok so streams = chunked



Why not read chunks from the file and send them in real time instead of reading a really big file and divide that in chunks after?



Also why is really important on a real production server?



Because every time a request is received, your code is going to add that big file into ram, to that add this is concurrent so you are expecting to serve multiple files at the same time, so let's do the most advanced math my poor education allows:



1 request for a 1gb file = 1gb in ram



2 requests for a 1gb file = 2gb in ram



etc



That clearly doesn't scale nicely right?



Streams allows to decouple that data from the current state of the function (inside that scope), so in simple terms its going to be (with the default chunk size of 16kb):



1 request for 1gb file = 16kb in ram



2 requests for 1gb file = 32kb in ram



etc



And also, the OS its already passing a stream to node (fs) so it works with streams end to end.



Hope it helps :D.



PD: Never use sync operations (blocking) inside async operations (non blocking).







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 19 at 9:08

























answered Nov 19 at 9:02









Sebastián Espinosa

1,339816




1,339816








  • 1




    Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
    – longroad
    Nov 19 at 9:37














  • 1




    Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
    – longroad
    Nov 19 at 9:37








1




1




Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
– longroad
Nov 19 at 9:37




Thank you for comment! I have already known about streams in nodejs. My example was just for presentation purposes, that's why I didn't use them.
– longroad
Nov 19 at 9:37


















 

draft saved


draft discarded



















































 


draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53369763%2fhow-does-single-threaded-node-js-handles-requests-concurrently%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Costa Masnaga

Fotorealismo

Sidney Franklin