How does single-threaded Node.js handles requests concurrently?
up vote
1
down vote
favorite
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?
javascript node.js single-threaded
add a comment |
up vote
1
down vote
favorite
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?
javascript node.js single-threaded
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
add a comment |
up vote
1
down vote
favorite
up vote
1
down vote
favorite
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?
javascript node.js single-threaded
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
javascript node.js single-threaded
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
add a comment |
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
add a comment |
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:
First request arrives and does
fs.readFileSync()
and callsres.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.Second request arrives and does
fs.readFileSync()
and callsres.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.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.
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, couldres.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 theres.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
|
show 1 more comment
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).
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
add a comment |
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:
First request arrives and does
fs.readFileSync()
and callsres.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.Second request arrives and does
fs.readFileSync()
and callsres.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.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.
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, couldres.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 theres.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
|
show 1 more comment
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:
First request arrives and does
fs.readFileSync()
and callsres.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.Second request arrives and does
fs.readFileSync()
and callsres.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.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.
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, couldres.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 theres.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
|
show 1 more comment
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:
First request arrives and does
fs.readFileSync()
and callsres.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.Second request arrives and does
fs.readFileSync()
and callsres.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.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.
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:
First request arrives and does
fs.readFileSync()
and callsres.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.Second request arrives and does
fs.readFileSync()
and callsres.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.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.
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, couldres.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 theres.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
|
show 1 more comment
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, couldres.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 theres.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
|
show 1 more comment
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).
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
add a comment |
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).
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
add a comment |
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).
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).
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
add a comment |
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
add a comment |
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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