Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no way to shutdown a server cleanly #1602

Closed
msmouse opened this issue Jun 7, 2014 · 29 comments
Closed

no way to shutdown a server cleanly #1602

msmouse opened this issue Jun 7, 2014 · 29 comments

Comments

@msmouse
Copy link

msmouse commented Jun 7, 2014

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');
});

@msmouse
Copy link
Author

msmouse commented Jun 8, 2014

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

@kevin-roark
Copy link
Contributor

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

@Twipped
Copy link

Twipped commented Mar 29, 2015

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.

@amitport
Copy link
Contributor

👍

@paulpflug
Copy link

+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()

@PritamUpadhyay
Copy link

+1

1 similar comment
@llaakso
Copy link

llaakso commented May 30, 2015

+1

@ilkkao
Copy link

ilkkao commented Jul 8, 2015

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.

@ilkovich
Copy link

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

@timdp
Copy link

timdp commented Sep 19, 2015

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.

@jamesshore
Copy link

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 #1602 (comment), 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.

@lgg
Copy link

lgg commented May 6, 2016

Also have this bug.

My code is similar to @jamesshore

@shakyShane
Copy link

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.

@enrichz
Copy link

enrichz commented Apr 11, 2017

Any update on this issue?

@paulpflug
Copy link

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..

@nguiard
Copy link

nguiard commented Nov 7, 2017

Any news on this issue?

@nguiard
Copy link

nguiard commented Nov 8, 2017

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.

@chenxiaochun
Copy link

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

@s-a
Copy link

s-a commented Dec 22, 2017

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. 😿

@joeytwiddle
Copy link

joeytwiddle commented May 3, 2018

For anyone wanting their server to clean up active connections, I made a package, based on ilkovich's snippet:

https://www.npmjs.com/package/socket.io-fix-close

@mandaputtra
Copy link

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

@allenchuang
Copy link

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...

jeremija added a commit to peer-calls/peer-calls that referenced this issue Nov 19, 2019
New version of socket.io cannot terminate cleanly when there are active
connections:

socketio/socket.io#1602
@n1ru4l
Copy link

n1ru4l commented May 1, 2020

#1602 (comment)

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);

@TheLudd
Copy link

TheLudd commented May 26, 2020

@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.

@n1ru4l
Copy link

n1ru4l commented May 26, 2020

@TheLudd https://github.com/dungeon-revealer/dungeon-revealer/blob/f5dcce720cee0e0d41e012cfb5c5bd5af3e77687/server/index.js#L50-L72

It works (for us).

@msmouse msmouse closed this as completed Jul 22, 2020
@mfahadshah
Copy link

This might help.. added in socket v4
// make all Socket instances disconnect
io.disconnectSockets();

https://socket.io/docs/v4/server-api/#server

@jokester
Copy link
Contributor

jokester commented Feb 2, 2024

I still experience server hang-when-close with socket.io v4.7.4 . Cutting underlying TCP connection still work, calling server.disconnectSockets(true) did not.

This is a renewed snippet of the TCP connection trick, thanks for information in this thread.

import type net from 'node:net';
import type http from 'node:http';

/**
 * record alive TCP connections, to force disconnect them during shutdown
 * (socket.io may have problem close all connections.)
 * @return a function to close all TCP sockets
 */
export function prepareTcpConnect(server: http.Server): () => void {
  const sockets = new Set<net.Socket>();
  server.on('connection', conn => {
    sockets.add(conn);
    conn.on('close', () => {
      sockets.delete(conn);
    });
  });

  return () => sockets.forEach(s => s.destroy());
}

@darrachequesne
Copy link
Member

@jokester I don't think this should be needed. Isn't io.close() sufficient?

socket.io/lib/index.ts

Lines 745 to 760 in e36062c

public close(fn?: (err?: Error) => void): void {
for (const socket of this.sockets.sockets.values()) {
socket._onclose("server shutting down");
}
this.engine.close();
// restore the Adapter prototype
restoreAdapter();
if (this.httpServer) {
this.httpServer.close(fn);
} else {
fn && fn();
}
}

It should close both:

Also, I was not able to reproduce the issue:

// server.js
import { createServer } from "node:http";
import { Server } from "socket.io";

const httpServer = createServer();
const io = new Server(httpServer, {});

const port = process.env.PORT || 3000;

io.on("connection", (socket) => {
  console.log(`connect ${socket.id}`);

  socket.on("disconnect", (reason) => {
    console.log(`disconnect ${socket.id} due to ${reason}`);
  });
});

io.of("/chat").on("connection", (socket) => {
  console.log(`[chat] connect ${socket.id}`);

  socket.on("disconnect", (reason) => {
    console.log(`[chat] disconnect ${socket.id} due to ${reason}`);
  });
});

httpServer.listen(port, () => {
  console.log(`server listening at http://localhost:${port}`);
});

setTimeout(() => {
  io.close();
  console.log("PROCESS SHOULD NOW EXIT");
}, 10_000);
// client.js
import { io } from "socket.io-client";

const port = process.env.PORT || 3000;

const socket = io(`http://localhost:${port}`);

socket.on("connect", () => {
  console.log(`connect ${socket.id}`);
});

socket.on("connect_error", (err) => {
  console.log(`connect_error due to ${err.message}`);
});

socket.on("disconnect", (reason) => {
  console.log(`disconnect due to ${reason}`);
});

const socket2 = io(`http://localhost:${port}`, {
  forceNew: true
});

const socket3 = io(`http://localhost:${port}/chat`, {
  forceNew: true
});

const socket4 = io(`http://localhost:${port}`, {
  forceNew: true,
  transports: ["polling"]
});

const socket5 = io(`http://localhost:${port}`, {
  forceNew: true,
  transports: ["websocket"]
});

That being said, I only found one change impacting the close() method since the opening of the issue, so it might not be completely fixed...

@jokester
Copy link
Contributor

@darrachequesne sorry I don't have a good reproduction or insight in code.

When developing my app using socket.io I think my server stuck at 5% or 10% of all runs, until I cut the TCP sockets.

I'm currently reading socket.io code to write an experimental adapter, maybe I can be lucky enough to find a hint 🤔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests