I am trying to 'gracefully' close a net.Server instance (created with app.listen()) if an un-handled error is thrown. Server creation occurs in my bin/www script. All error handling and routing middleware configuration is defined in index.js.
In my application configuration module (index.js) I have error handling middleware that checks that each error is handled. If the error is not handled then a 'close' event is emitted.
Note: Each req and res is wrapped in a domain. I am using express-domain-middleware middleware module to listen for error events on each req domain and route the error to my error handling. I only mention this in case it might be the culprit.
The 'close_server' event handler should:
Close the server so new connections are not accepted.
Close the current process once all open connections have completed.
If after 10 seconds the server has not closed, force the process to close.
The optional callback provided to server.close() never seems to be invoked and I'm not sure why. To test this I am making a single request which throws an error. The process is only closed after the timer expires (10 seconds has elapsed).
Could there be something holding open a connection in the server? Why is the server.close() callback never called?
Thanks!
Update
I was using Chrome to make a request to the server. It appears that the browser is holding open a connection. If I make the request using curl it works as expected.
See this issue
index.js
app.use(function (err, req, res, next) {
if (typeof err.statusCode !== 'undefined') {
if (err.statusCode >= 500) {
Logger.error({error: err});
return next(err);
} else {
Logger.warn({warn: err});
return next(err);
}
} else {
//The error is un-handled and the server needs to go bye, bye
var unhandledError = new UnhandledError(util.format('%s:%s', req.method, req.originalUrl), 'Server shutting down!', err.stack, 500);
Logger.fatal({fatal: unhandledError});
res.status(500).send('Server Error');
app.emit('close_server', unhandledError);
}
});
bin/www
#!/usr/bin/env node
var app = require('../index');
var port = config.applicationPort;
app.set('port', port);
var server = app.listen(app.get('port'));
/*
* Wait for open connections to complete and shut server down.
* After 10 seconds force process to close.
* */
app.on('close_server', function () {
server.close(function () {
console.log('Server Closed.');
process.exit()
});
setTimeout(function () {
console.log('Force Close.');
process.exit()
}, 10 * 1000);
});
server.close() does not close open client connections, it only stops accepting new connections.
So most likely Chrome is sending a request with Connection: keep-alive which means the connection stays open for some time for efficiency reasons (to be able to make multiple requests on the same connection), whereas curl is probably using Connection: close where the connection is severed immediately after the server's response.
As #mscdex mentioned server.close never runs its callback when the browser sends the request Connection: keep-alive, because server.close only stops the server from accepting new connections.
Node v18.2.0 introduced server.closeAllConnections() and server.closeIdleConnections()
server.closeAllConnections() closes all connections connected to the server, and server.closeIdleConnections() closes all connections connected to the server but only the ones which are not sending a request or waiting for a response.
Before Node v18.2.0 I tackled this problem by waiting 5 seconds for the server to shutdown, after which it would force exit.
The following code contemplates both situations
process.on('SIGINT', gracefulShutdown)
process.on('SIGTERM', gracefulShutdown)
function gracefulShutdown (signal) {
if (signal) {
console.log(`\nReceived signal ${signal}`)
}
console.log('Gracefully closing http server')
// closeAllConnections() is only available after Node v18.02
if (server.closeAllConnections) server.closeAllConnections()
else setTimeout(() => process.exit(0), 5000)
try {
server.close(function (err) {
if (err) {
console.error(err)
process.exit(1)
} else {
console.log('http server closed successfully. Exiting!')
process.exit(0)
}
})
} catch (err) {
console.error('There was an error', err)
setTimeout(() => process.exit(1), 500)
}
}
Related
Im having this alot of http petitions (6k INSIDE LAGGING) in 1-3 minutes in the console when i receive or send data to a socketio connection.
Im using node+express in the backend and vue on the front
Backend:
app.js
mongoose.connect('mongodb://localhost/app',{useNewUrlParser:true,useFindAndModify:false})
.then(result =>{
const server = app.listen(3000)
const io = require('./sockets/socket').init(server)
io.on('connection', socket =>{
// console.log('client connected')
})
if(result){console.log('express & mongo running');
}
})
.catch(error => console.log(error))
I created a io instance to use it on the routes
let io
module.exports = {
init: httpServer => {
io = require('socket.io')(httpServer)
return io;
},
getIo:()=>{
if(!io){
throw new Error('socket io not initialized')
}
return io;
}
}
Then, on the route, depending of the logic, the if,else choose what type socket response do
router.post('/post/voteup',checkAuthentication, async (req,res)=>{
//some logic
if(a.length <= 0){
io.getIo().emit('xxx', {action:'cleanAll'})
}
else if(b.length <= 0){
io.getIo().emit('xxx', {action:'cleanT',datoOne})
}
else{
io.getIo().emit('xxx', {action:'cleanX',dataTwo,dataOne,selected})
}
res.json({ serverResponse:'success'})
})
In the front (component) (activated with beforeUpdate life cycle hook)
getData(){
let socket = openSocket('http://localhost:3000')
socket.on('xxx', data => {
if(data.action === 'cleanX'){
if(this.selected === data.selected){
this.ddd = data.dataTwo
}
else if(!this.userTeamNickname){
this.qqq= data.dataOne
}
}
else if(data.action === 'cleanAll'){
this.ddd= []
this.qqq= []
}
else if(data.action === 'cleanT'){
this.ddd= data.dataOne
}
})
},
1. What kind of behavior can produce this such error?
2. Is any other most efficient way to do this?
It looks like socket.io is failing to establish a webSocket connection and has never advanced out of polling. By default, a socket.io connection starts with http polling and after a bit of negotiation with the server, it attempts to establish a webSocket connection. If that succeeds, it stops doing the polling and uses only the webSocket connection. If the the webSocket connection fails, it just keeps doing the polling.
Here are some reasons that can happen:
You have a mismatched version of socket.io in client and server.
You have some piece of infrastructure (proxy, firewall, load balancer, etc...) in between client and server that is not letting webSocket connections through.
You've attached more than one socket.io server handler to the same web server. You can't do that as the communication will get really messed up as multiple server handlers try to respond to the same client.
As a test, you could force the client to connect only with webSocket (no polling at all to start) and see if the connection fails:
let socket = io(yourURL, {transports: ["websocket"]})'
socket.on('connect', () => {console.log("connected"});
socket.on('connect_error', (e) => {console.log("connect error: ", e});
socket.on('connect_timeout', (e) => {console.log("connect timeout: ", e});
I have written sample node application to handle error data such as database connection error, port conflict error, process uncaught exception. When error occurrs an http request is made to process the error. In such case when node process exist abnormally, I am able to handle exist in process.on('exit') function but I am not able to send http request, the process is exiting quickly.
Can any one suggest how to send http request and get the response on Node.js before process exit. Below is the sample code for sending http request on process exists
var http = require('http');
var errorData=null;
var sendErrorReport = function(data,callback){
var options = {
host : connection.host,
path : "/api/errorReport",
port : connection.port,
method : 'POST',
timeout: connection.timeInterval,
headers:{
'Content-Type':'application/json',
'Content-Length': Buffer.byteLength(data)}
}
var request = http.request(options,function(response){
callback(response);
});
request.on('error',function(err){
console.log("On Error");
callback(err);
});
request.on('timeout', function(err){console.log("On Timeout");
callback(err);});
request.write(data);
request.end();
}
process.on('uncaughtException', function ( err ) {
errorData = err;
});
process.on('exit',function(code){
sendErrorReport(errorData,function(err,res){
console.log(res);
});
})
In process.on('exit', [fn]) you cannot do any asynchronous action as stated in the docs. However, this is an anti-pattern also discovered in many libraries.
You need to rely on process.on('uncaughtException', [fn]) or any signal handlers like SIGTERM.
Hit on same problem,
according to the doc https://nodejs.org/api/process.html#process_event_exit
"Event: 'exit' Listener functions must only perform synchronous operations." make it hard to send request to other system.
A possible workaround method is to to execute another script/cmd, eg.
import exec from 'child_process'
mocha.run(failures => {
process.on('exit', () => {
exec.execSync(some cmd, function (error, stderr) {
if (error) {
throw (error);
}
}
}
What i'm trying to do from my tests is to simulate ETIMEDOUT that should be caught by socket.on('error', () => {...}). In real word with 3rd party TCP server that im using, ETIMEDOUT is always caught by error event. Would like to mimic this situation in my tests also. Going through the tls docs, only candidate that could be used for this purpose is socket.setTimeout but it does not work how i would expect it:
describe('TCP timeout', () => {
const TIMEOUT_AFTER_IN_MILLISECONDS = 1
const socket = getActiveSocketFromSomewhere()
it('should simulate timeout', () => {
socket.setTimeout(TIMEOUT_AFTER_IN_MILLISECONDS, () => {
console.log('are we here')
})
/**
* This will trigger socket communication
* with dummy TCP server where socket from
* above will be used
*/
return something()
...
})
})
From console i can see that i was waiting for answer for 14ms and that callback provided to setTimeout was executed, but after i can see that i received response from TCP server:
are we here
{ result: 'success', ... }
Yes, That's how the behavior is.
When the timeout is reached, the socket is not ended explicitly. It is clearly mentioned in the document and you'll receive a response whenever it is destined
https://nodejs.org/dist/latest-v6.x/docs/api/net.html#net_socket_settimeout_timeout_callback
If you want to end the socket, you need to manually call socket.end() or socket.destroy() after timeout event is triggered
Code:
socket.on('timeout',function(){
socket.end();
})
Using node.js http.createServer to listen POST requests. If request completes fast, all works good. If request complete time > 5 seconds i get no response returned to client.
Added event listeners on created sockets:
server.on('connection', function(socket) {
log.info('SOCKET OPENED' + JSON.stringify(socket.address()));
socket.on('end', function() {
log.info('SOCKET END: other end of the socket sends a FIN packet');
});
socket.on('timeout', function() {
log.info('SOCKET TIMEOUT');
});
socket.on('error', function(error) {
log.info('SOCKET ERROR: ' + JSON.stringify(error));
});
socket.on('close', function(had_error) {
log.info('SOCKET CLOSED. IT WAS ERROR: ' + had_error);
});
});
Got those messages on middle of request (after about 10 sec after start):
info: SOCKET TIMEOUT
info: SOCKET CLOSED. IT WAS ERROR: false
But on client socket do not get closed, so client wait for response. End of request completed with success, response sent (on closed socket!), but client still wait.
No idea how to block those timeouts. Removed all timeouts from code. Tried to add KeepAlive, no result.
socket.setKeepAlive(true);
How to prevent socket bultin timeout?
From node's setTimeout documentation the connection will not be severed when a timeout occurs. That's why the browser's still waiting. You're still required to end() or destroy() the socket. You can increase the timeout by calling setTimeout on the socket.
socket.setTimeout(1000 * 60 * 300); // 5 hours
You can do a couple of things:
socket.setTimeout(/* number of milliseconds */);
If you do this, then the server can get a timeout event:
server.on('timeout', function(timedOutSocket) {
timedOutSocket.write('socket timed out!');
timedOutSocket.end();
});
I built a simple TCP server and a simple TCP client in Node.js
Now, when the client sends "exit" to the server, the connection is successfully closed. The server deletes the socket from its sockets list and sends "Bye bye!" to the client.
The connection on the client is closed as well but the app is still waiting for other inputs, so it doesn't die and I'm forced to type CTRL+C.
I tried adding process.exit() after connection closes but it doesn't work:
CLIENT CODE:
var net = require('net'),
config = require(__dirname + '/config.json'),
connection = net.createConnection(config.port, config.host);
connection.setEncoding('utf8');
connection.on('connect', function () {
console.log('Connected');
});
connection.on('error', function (err) {
console.error(err);
});
connection.on('data', function (data) {
console.log('ยป ' + data);
});
connection.on('close', function() {
console.log('Connection closed');
});
process.stdin.on('data', function (data) {
if ((new String(data)).toLowerCase() === 'exit') {
connection.end();
process.exit();
}
else {
connection.write(data);
}
});
process.stdin.resume();
SERVER CODE:
var server = require('net').createServer(),
config = require(__dirname + '/config.json'),
sockets = [];
server.on('connection', function (socket) {
socket.setEncoding('UTF-8');
socket.on('data', function (data) {
console.log('Received data: ' + data);
if (data.trim().toLowerCase() === 'exit') {
socket.write("Bye bye!\n");
socket.end();
}
else {
sockets.forEach(function (client) {
if (client && client != socket) {
client.write(data);
}
});
}
});
socket.on('close', function () {
console.log('Connection closed');
sockets.splice(sockets.indexOf(socket), 1);
console.info('Sockets connected: ' + sockets.length);
});
sockets.push(socket);
});
server.on('listening', function () {
console.log('Server listening');
});
server.on('close', function () {
console.log('Server is now closed');
});
server.on('error', function (err) {
console.log('error:', err);
});
server.listen(config.port);
EDIT:
I added a client connection "on close" event handler. So, the string "Connection closed" is now printed by the server and by the client too.
I think you're looking for this: socket.unref().
From Node.js documentation (https://nodejs.org/api/net.html#net_socket_unref):
socket.unref()#
Calling unref on a socket will allow the program to exit if this is the only active socket in the event system. If the socket is already unrefd calling unref again will have no effect.
Some time ago when improving the tests suite for node-cubrid module, I had encountered the same problem. After all tests have passed, nodeunit process didn't quit because node-cubrid was using connection.end() to close the client socket when timeout occurs, just like you did.
Then I replaced connection.end() with connection.destroy(), a cleaner way to ensure the socket is really closed without actually terminating the running process, which, I think, is a better solution than the above suggested process.exit(). So, in your client code context, I would do:
process.stdin.on('data', function (data) {
if ((new String(data)).toLowerCase() === 'exit') {
connection.destroy();
}
else {
connection.write(data);
}
});
According to Node.js documentation:
socket.destroy()
Ensures that no more I/O activity happens on this socket. Only necessary in case of errors (parse error or so).
I doubt that if ((new String(data)).toLowerCase() === 'exit') is succeeding because data most likely has a trailing newline (in your server, you trim() before doing the comparison, but not in the client).
If that's fixed, you've got a logic problem: when getting "exit" you close the connection without sending "exit" to the server, so the server code that looks for "exit" will never execute.
You have to put the process.exit() instruction only on the last event handler. So, in this case you have to put it inside the client connection "on close" event handler:
CLIENT:
connection.on('close', function() {
console.log('Connection closed');
process.exit();
});
Try with Event: 'close' in the server:
http://nodejs.org/api/net.html#net_event_close