I have a really, really simple app, but when it experiences a high load of POST requests, it resets the connection for a few of them. I have tried replacing BPromise (my variable holding Bluebird's promise class) with Promise (i.e. native JavaScript promises) and the problem goes away. This leads me to suspect that Bluebird is involved in causing the error.
Here is the app code:
var http = require('http');
var BPromise = require("bluebird");
function my_route(req, res) {
var array = [];
for (var j = 0; j < 1000; ++j)
array.push(Math.random());
array.sort();
BPromise.resolve(true)
.then(function(){
res.writeHead(200);
res.end("true", 'utf-8');
});
}
http.createServer(function (req, res) {
my_route(req, res);
}).listen(1390);
console.log('Server running at http://127.0.0.1:1390/');
I have this code running in cluster mode (via PM2) behind a very run-of-the-mill NGINX reverse proxy. To test the configuration, I am hammering it with enough POSTs to fill CPU utilization from a separate server running apache bench:
ab -n 600000 -p body.json -c 32 [my public ip address]
Out of the 600000 requests sent, maybe 1-4 come back with non-2xx responses every couple of trials. The Nginx logs have the following error:
recv() failed (104: Connection reset by peer) while reading response header from upstream
The error that Nginx is providing tells me that the problem lies not with Nginx, but rather with Node or Bluebird. As I stated above, changing BPromise to Promise eliminates the problem.
Can anyone help me figure out why the HTTP module is hanging up the phone when Bluebird is used? Thank you so much for your help!
Here is my precise configuration:
Nginx Files
/etc/nginx/nginx.conf:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
keepalive_timeout 0;
underscores_in_headers on;
include /etc/nginx/conf.d/*.conf;
}
/etc/nginx/conf.d/[my public ip address].conf:
upstream api_upstream
{
server localhost:1390;
keepalive 64;
}
server
{
listen 80 default_server;
server_name [my public ip address];
location /
{
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://api_upstream/;
}
}
Thank you again!
Hey, very interesting!
For isolating the issue - can you try running:
NODE_ENV=production?Promise.setScheduler(fn => process.nextTick(fn))? Thanks for the report!
Hi @benjamingr,
Thank you so much for responding!!! I tried all combinations of the two settings that you asked about. BPromise.setScheduler() fixed the problem; changing NODE_ENV had no effect either way.
Specifically, I added the following two lines to my program:
process.env.NODE_ENV = 'production';
BPromise.setScheduler(fn => process.nextTick(fn));
And I ran six trials:
Thank you again for your help! Please let me know what next steps I should take.
@DanielRLevine this is because bluebird is scheduling tasks _behind_ the event queue and native promises do it before the event queue.
So the native implementation starves new requests while serving whereas the bluebird one doesn't.
It's a tradeoff, and you should be free to use .setScheduler as the above to switch to microtick semantics which should be working fine.
That is a great explanation of what is going on and is incredibly helpful! Just to clarify, does the .setScheduler() call cause Bluebird to schedule tasks before the event cue?
@DanielRLevine yes, that's exactly what it does!
I was able to answer my own question: using the .setScheduler() call you presented will move Bluebird promises from the immediate queue to the next tick queue, which will make them a higher priority than the IO events queue. For anyone else reading this in the future, there was a very helpful series of articles here: https://jsblog.insiderattack.net/promises-next-ticks-and-immediates-nodejs-event-loop-part-3-9226cbe7a6aa
Thank you, @benjamingr again for your help. You aid went a long way in solving a question that had been puzzling me for a few weeks.
I have a question about this issue , Should I use the setScheduler function once on my entire code like on main file or it should be used whenever I require bluebird ?
The latter.
Most helpful comment
Hey, very interesting!
For isolating the issue - can you try running:
NODE_ENV=production?Promise.setScheduler(fn => process.nextTick(fn))?Thanks for the report!