Socket.io: no way to shutdown a server cleanly

Created on 7 Jun 2014  路  25Comments  路  Source: socketio/socket.io

Hi, I'm seeking for a way to shutdown a server. The current behavior is, when I call close() on engine, clients disconnect, but connect again immediately. Even if I call socket.io.close() on disconnect, the server is still listening.

And, can I suppose if socket.io.close() is called,(and references from my program is all deleted), the socket will be subject to garbage collection?

"use strict"

var svr = require('socket.io')(12332);
var clt1 = require('socket.io-client')('ws://127.0.0.1:12332');
var clt2 = require('socket.io-client')('ws://127.0.0.1:12332');
svr.on('connection', function(socket) {
console.log("svr connected");
svr.engine.close();
});

clt1.on('connect', function() {
console.log('clt connected');
});
clt1.on('disconnect', function() {
console.log('clt disconnected');
// clt1.io.close();
});
clt1.on('error', function() {
console.log('clt error');
});

Most helpful comment

Just spent way too much time struggling with this, but I had success with the following:

function wireUpServer(/*httpServer*/ server) {
    var connections = {};
    server.on('connection', function(conn) {
        var key = conn.remoteAddress + ':' + conn.remotePort;
        connections[key] = conn;
        conn.on('close', function() {
            delete connections[key];
        });
    });

    server.destroy = function(cb) {
        server.close(cb);
        for (var key in connections)
            connections[key].destroy();
    };
}

Then during your init process you can call:

wireUpServer(server)

and when you're ready to destroy

io.close();
server.destroy();

The connection tracking/destruction taken from here: https://github.com/isaacs/server-destroy/blob/master/index.js

All 25 comments

ok, I've read some code and found socket.destroy() for the client.

@msmouse is socket.destroy() working well for you? Hoping to close this issue

I'm experiencing this problem as well. Calling close on the http server does make it stop accepting new connections, but the http server never fires its close event, so the close callback never gets evoked. If I shutdown without anyone connecting, then the server closes fine, but once a single person has connected the server refuses to shutdown cleanly.

I've tried io.close(), io.engine.close(). I'm sending a shutdown command to all clients, which causes the frontend to close on its end, and then I see the socket disconnecting, so I know the sockets are all closed.

For whatever reason, the http server never realizes that all connections are complete. This is a pretty big issue for me, since I can't safely close my database links until I know all requests have finished and http is clear.

:+1:

+1

I think a problem is, that the httpServer.close() is async here.
So the server should bind to the httpServer close event.

Workaround:

ioClosed = new Promise (resolve) ->
  if io.httpServer
    io.httpServer.on "close", resolve
  else
    resolve()

+1

+1

Busy server has always active websocket connections, there must be a way to shutdown the server cleanly without expecting any co-operation from the clients? I mean assuming I have:

let io = socketIo(server);

io.on('connection', function(socket) {
  [add socket to active sockets list] 

  [all other logic]

  socket.on('disconnect', function() {
    [remove socket from active sockets list]
  });
}

Then in shutdown this should work?

// 1.
io.close() // Stop accepting new connections

// 2.
for [all items in active sockets list] {
    socket.disconnect() // <- This doesn't close the underlying TCP socket.
}

But it doesn't.

Just spent way too much time struggling with this, but I had success with the following:

function wireUpServer(/*httpServer*/ server) {
    var connections = {};
    server.on('connection', function(conn) {
        var key = conn.remoteAddress + ':' + conn.remotePort;
        connections[key] = conn;
        conn.on('close', function() {
            delete connections[key];
        });
    });

    server.destroy = function(cb) {
        server.close(cb);
        for (var key in connections)
            connections[key].destroy();
    };
}

Then during your init process you can call:

wireUpServer(server)

and when you're ready to destroy

io.close();
server.destroy();

The connection tracking/destruction taken from here: https://github.com/isaacs/server-destroy/blob/master/index.js

Similar logic here for a plain old net.Server.

Given that socket.io tries to abstract away a lot of the underlying network tomfoolery, it'd be great if it came with this feature baked in.

I'm also experiencing this issue. socket.io v1.4.5. Here's code to reproduce it:

server-hang.js:

(function() {
  "use strict";

  var PORT = 5020;
  var TIMEOUT = 10000;

  var io = require('socket.io')(PORT);

  console.log("Waiting " + (TIMEOUT / 1000) + " seconds...");
  setTimeout(function() {
    io.close();
    console.log("PROCESS SHOULD NOW EXIT");
  }, TIMEOUT);

}());

server-hang.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Server won't exit</title>
  <meta charset="utf-8">

  <script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
</head>

<body>

<p>Look at the console!</p>

<script>
  console.log("Client started");

  console.log("Connecting...");
  var origin = "http://localhost:5020";
  var socket = io(origin);

  socket.on("connect", function() {
    console.log("Server connected");
    socket.disconnect();
    console.log("disconnect() called");
  });
</script>

</body>
</html>

To reproduce:

  1. Run node server-hang.js
  2. Within 10 seconds, drag server-hang.html into a browser window. It will load as a file resource and trigger the bug.
  3. Observe that the server doesn't exit after printing "PROCESS SHOULD NOW EXIT."

Expected behavior: The server should exit after 10 seconds.

Update: the server _does_ exit if the browser is closed, but not if the tab is closed. (Confirmed on Mac OS using Firefox, Chrome, and Safari.)

Update 2: Destroying the connections server-side, as suggested in https://github.com/socketio/socket.io/issues/1602#issuecomment-120561951, _does_ work. But it won't work when using the bare io(origin) constructor like my example does. You have to create an Node HTTP server and listen for connections to the Node server.

Also have this bug.

My code is similar to @jamesshore

I have tracked down the cause of this, it is indeed a bug.

The ws package that engine.io uses has the following block inside of a close method

screen shot 2016-06-07 at 13 19 16

src: https://github.com/websockets/ws/blob/master/lib/WebSocket.js#L125-L131

As you can see from the overlay, the _closeReceived property never gets set to true at any point by socket.io or engine.io, this means the terminate() method is never called and instead we enter into a 30 second wait

// socket.io/engine.io never sets `self._closeReceived` to true, so we can never get into this block
// to call the terminate method
if (self._closeReceived && self._isServer) {
    self.terminate();
} else {

    // WE ALWAYS REACH HERE
    // and closeTimeout = 30 seconds
    clearTimeout(self._closeTimer);
    self._closeTimer = setTimeout(cleanupWebsocketResources.bind(self, true), closeTimeout);
}

The chain of events that lead to this bug begin here https://github.com/socketio/engine.io/blob/master/lib/server.js#L180-L188

Here's a screencast showing the stack https://www.youtube.com/watch?v=H0ngC6TDePE&feature=youtu.be

@rauchg @3rd-Eden @einaros is there any extra information you would need?

This is a big problem for tools such as Browsersync as we want to allow users to gracefully boot up/shut down servers at will, and currently if any socket is connected, they will be doomes to the 30000 wait timeout as shown above.

Many thanks for your hard on this lib, please inform me if you need any further information.

Any update on this issue?

A part of the problem is, that a httpserver will not close as long as there are connections alive:
https://nodejs.org/api/http.html#http_server_close_callback

A proper closing mechanism would need to keep track of all connections - which is probably out of scope of socketio.

// will close the underlying httpserver - that means it wont accept new connections
// but as long as there are open connections, it won't actually close
io.httpServer.close() 

// will close all active websocket sessions - but not the connections
io.close()

Just use the solution presented above..

Any news on this issue?

Nevermind, sorry for the above question, I realise the solution presented above actually works. I was just having issues with closing socket.io server when clients are still connected and thought the problem was still here in engine.io.

@nguiard ,i aslo found the issue is still existed.

Is this module still maintained? We facing the same error. None of the suggested codes work around the problem. In fact in my case there is no client connected. 馃樋

For anyone wanting their server to clean up active connections, I made a package: https://www.npmjs.com/package/socket.io-fix-close

Hi quick question here @s-a do you experienced EARDDRESINUSE? or the port is still in use ? When use nodemon or others?

This issue still exists.... Ctrl-C killing the server would still persist the socket connections, and upon refresh of client, it will ask for another new socket + reconnecting the old socket... the server is not disconnected from the first connection, another refresh will trigger an extra socket...

https://github.com/socketio/socket.io/issues/1602#issuecomment-120561951

I am using the following with Set instead of an object (and delete):

const connections = new Set();

server.on("connection", connection => {
  connections.add(connection);
  connection.once("close", () => {
    connections.delete(connection);
  });
});

const shutdownHandler = once(() => {
  console.log("Shutting down");
  httpServer.close(err => {
    if (err) {
      console.error(err);
      process.exitCode = 1;
    }
  });

  for (const connection of connections) {
    connection.destroy();
  }
});

process.on("SIGINT", shutdownHandler);

@n1ru4l does this work for you?
I have done a similar solution but I get destroy is not a function.
There is a disconnect property on the socket but calling that does not make the server shut down.

Was this page helpful?
0 / 5 - 0 ratings