在Forever下运行的NodeJS UNIX套接字服务器如何优雅地关闭?

19

我有一个NodeJS应用程序,它设置了一个UNIX套接字以暴露一些进程间通信通道(某种监视功能)。UNIX套接字文件放置在os.tmpdir()文件夹中(即/tmp/app-monitor.sock)。

var net = require('net');
var server = net.createServer(...);
server.listen('/tmp/app-monitor.sock', ...);

我使用信号处理(SIGINT、SITERM等)来优雅地关闭我的服务器并删除套接字文件。

function shutdown() {
    server.close(); // socket file is automatically removed here
    process.exit();
}

process.on('SIGINT', shutdown);
// and so on

我的应用程序正在使用 forever start ... 命令来监控其生命周期。

我有一个问题需要解决,就是当我执行 forever restartall 命令时。由于该命令使用 SIGKILL 信号终止所有子进程,因此无法通过任何关闭程序的过程来关闭我的应用程序。

问题在于,当使用 SIGKILL 信号时,某些套接字文件并未删除。在子进程重新启动后,新服务器无法启动,因为 listen 调用会导致 EADDRINUSE 错误。

我无法在应用程序启动期间删除现有的套接字文件,因为我不知道它是否是实际工作的套接字,还是之前未清理干净的痕迹。

那么,如何更好地处理这种情况(SIGKILL 和 UNIX 套接字服务器)呢?


你看过这个吗?http://nodejs.org/api/all.html#all_signal_events - wayne
5
是的,你有读懂我的问题吗? - Olegas
不,我没有读过。说起来容易做起来难。如果您不介意永久修改代码,则在 forever/node_modules/forever-monitor/lib/forever-monitor/monitor.js 中的函数 Monitor.prototype.kill 中,在 forever 发送 SIGKILL 信号之前添加 SIGINT。 - wayne
3
@wayne 这是一个糟糕的解决方案。我不想修改第三方软件。而且,我的进程可能被 SIGKILL 杀死,没有永久性的解决方案... - Olegas
FYI node-dev 发送了一个可捕获的 SIGTERM 信号。 - mpen
5个回答

48

像其他人提到的一样,你无法对SIGKILL做出任何响应,这通常是为什么forever(以及所有其他人)不应该在非极端情况下使用SIGKILL的原因。所以你能做的最好的事情就是在另一个进程中清理。

我建议你在启动时进行清理。当你遇到EADDRINUSE错误时,尝试连接套接字。如果套接字连接成功,则另一个服务器正在运行,因此此实例应该退出。如果连接失败,则可以安全地取消链接套接字文件并创建新文件。

var fs = require('fs');
var net = require('net');
var server = net.createServer(function(c) { //'connection' listener
    console.log('server connected');
    c.on('end', function() {
        console.log('server disconnected');
    });
    c.write('hello\r\n');
    c.pipe(c);
});

server.on('error', function (e) {
    if (e.code == 'EADDRINUSE') {
        var clientSocket = new net.Socket();
        clientSocket.on('error', function(e) { // handle error trying to talk to server
            if (e.code == 'ECONNREFUSED') {  // No other server listening
                fs.unlinkSync('/tmp/app-monitor.sock');
                server.listen('/tmp/app-monitor.sock', function() { //'listening' listener
                    console.log('server recovered');
                });
            }
        });
        clientSocket.connect({path: '/tmp/app-monitor.sock'}, function() { 
            console.log('Server running, giving up...');
            process.exit();
        });
    }
});

server.listen('/tmp/app-monitor.sock', function() { //'listening' listener
    console.log('server bound');
});

1
很好的答案。只有一个“错误”:当你调用 fs.unlink 时,应该传递一个回调函数,并在该回调函数上重新启动 server.listen。这样你就不会在解除链接之前尝试连接(同时当不使用回调函数时,node 会警告 fs: missing callback)。 - Salvatorelab
2
@TheBronx请编辑答案以包含您的建议/改进。 - Old Pro
1
有些偏离主题:我无法想象这个由2个空格到4个空格的空白编辑是如何得到批准的。 - Claudiu
1
@Claudius:非常正确,我实际上试图拒绝它,但为时已晚。 - Stijn de Witt
1
@TheBronx,由于这是一个几乎无法避免的竞态条件的服务器启动问题,我想使用同步unlink函数来最小化删除套接字和创建新套接字之间的时间。我相信当我编写此代码时,fs.unlink是同步的(当我运行它时肯定没有警告),但现在有了明确的同步版本,我用它替换了你的更改。 - Old Pro
显示剩余5条评论

2

你应该能够使用SIGTERM来实现你想要的功能: process.on('SIGTERM', shutdown)


我能够处理 SIGTERM 信号,但是 forever 通过 SIGKILL 信号重新启动子进程。 - Olegas
3
“forever”有一个选项可以在停止进程时更改使用的SIG信号。您可以将其更改为SIGTERM。 - jfriend00

1
server.on('error', function (e) {
  if (e.code == 'EADDRINUSE') {
    console.log('Address in use, retrying...');
    setTimeout(function () {
      server.close();
      server.listen(PORT, HOST);
    }, 1000);
  }
});

http://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback

更新

如果你无法处理SIGKILL信号,那么你必须手动清理套接字。

这个例子在使用forever时可以正常工作。

var fs = require('fs');
var net = require('net');
var server = net.createServer(function(c) {});
server.listen('./app-monitor.sock', function() {
  console.log('server bound');
});

server.on('error', function (e) {
  if (e.code == 'EADDRINUSE') {
    console.log('Address in use, retrying...');
    setTimeout(function () {
      fs.unlink('./app-monitor.sock');
    }, 1000);
  }
});

但是,对于文件套接字服务器而言,EADDRINUSE 表示套接字文件已经存在。它不会关闭或消失,因为父应用程序已经死亡,被 SIGKILL 杀死了。 - Olegas
SIGKILL 立即关闭程序,请使用 fs.unlink 而不是 server.close()。 - amirka
...而我可能会关闭另一个正在运行实例的套接字。 - Olegas
1
UNIX套接字是文件系统实体,使用完毕后需要清理它们。确保在退出之前调用server.close()。如果无法这样做,则必须通过forever-monitor发送另一个信号或通过fs.unlink在应用程序中进行修复。 - amirka

1

由于您无法处理 SIGKILL 并且想要使用 forever(它使用 SIGKILL),因此您需要使用一种解决方法。

例如,首先发送一个信号来关闭您的服务器,然后执行 forever 重启:

kill -s SIGUSR1 $pid
# $pid contains the pid of your node process, or use
# killall -SIGUSR1 node

sleep 2
forever restart test.js

在你的js中处理SIGUSR1信号:

process.on('SIGUSR1', gracefullyShutdownMyServer);

0

你必须选择

  1. 在forever-monitor中将SIGKILL更改为另一个信号以处理应用程序
  2. 在这种情况下,使用fs.unlink来保护您的应用程序
  3. 停止使用forever

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接