Изящно отключите сервер UNIX-сокетов на NodeJS, работающем под Forever

У меня есть приложение 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. Когда постоянно выполняется restartall, он использует SIGKILL для завершения всех дочерних процессов. SIGKILL не может быть обработан процессом, поэтому мое приложение умирает без каких-либо процедур завершения работы.

Проблема заключается в файле сокета, который не удаляется при использовании SIGKILL. После перезапуска дочернего процесса новый сервер не может быть запущен, потому что вызов listen вызовет ошибку EADDRINUSE.

Я не могу удалить существующий файл сокета во время запуска приложения, потому что я не знаю, настоящий ли это рабочий сокет или какие-то следы предыдущего нечистого завершения работы.

Итак, вопрос... Как лучше справиться с такой ситуацией (SIGKILL и сервер UNIX-сокетов)?


person Olegas    schedule 23.04.2013    source источник
comment
вы читали это nodejs.org/api/all.html#all_signal_events   -  person wayne    schedule 24.04.2013
comment
Да, и вы прочитали мой вопрос?   -  person Olegas    schedule 24.04.2013
comment
нет не читал. Легко сказать, чем сделать. Если вы не против навсегда изменить код, то forever/node_modules/forever-monitor/lib/forever-monitor/monitor.js, в fuunction Monitor.prototype.kill добавьте SIGINT, прежде чем навсегда отправить сигнал SIGKILL   -  person wayne    schedule 24.04.2013
comment
@wayne Это плохое решение. Я не хочу модифицировать стороннее программное обеспечение. Кроме того, мой процесс может быть убит с помощью SIGKILL без навсегда...   -  person Olegas    schedule 24.04.2013
comment
К сведению, node-dev отправляет SIGTERM, который можно перехватить.   -  person mpen    schedule 19.04.2017


Ответы (5)


Как упоминали другие люди, вы ничего не можете сделать в ответ на 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');
});
person Old Pro    schedule 11.05.2013
comment
Отличный ответ. Только одна ошибка: когда вы вызываете fs.unlink, вы должны передать обратный вызов и снова перезапустить server.listen для этого обратного вызова. Таким образом, вы никогда не пытаетесь подключиться до отключения (также узел предупреждает fs: missing callback, когда не используется обратный вызов). - person Salvatorelab; 26.03.2014
comment
@TheBronx, пожалуйста, отредактируйте ответ, включив в него свои рекомендации/улучшения. - person Old Pro; 28.03.2014
comment
Немного не по теме: меня поражает, как для этого было одобрено редактирование пробелов с 2 пробелов на 4 пробела. - person Claudiu; 28.03.2014
comment
@Claudius: Верно, я действительно пытался отвергнуть это, но было слишком поздно. - person Stijn de Witt; 28.03.2014
comment
Хорошо, что Old Pro пригласил на редактирование. Это многое компенсирует. - person Stijn de Witt; 28.03.2014
comment
Вот как я сюда попал, я опоздал на несколько секунд - person Claudiu; 28.03.2014
comment
Да, это был не просто пробел :) - person Claudiu; 28.03.2014
comment
@TheBronx, поскольку это проблема запуска сервера с практически неизбежным состоянием гонки, я хочу использовать функцию синхронной развязки, чтобы минимизировать время между удалением сокета и созданием нового. Я считаю, что fs.unlink был синхронным, когда я писал это (конечно, я не получил предупреждения, когда запускал его), но теперь, когда есть явно синхронная версия, я заменил ваше изменение этим. - person Old Pro; 30.03.2014
comment
Я написал простой пакет npm, который реализует это для простоты использования: npmjs.com/package/server-starter - person Cameron Tacklind; 28.05.2018
comment
Вот суть, которая изящно запускает сервер для вас и удаляет устаревшие файлы сокетов: /876e2df1103232f1fe6781c927e0eb63 - person Eric Vicenti; 06.12.2018

вы должны иметь возможность использовать SIGTERM, чтобы делать то, что вы хотите: process.on('SIGTERM', shutdown)

person Clintm    schedule 07.05.2013
comment
Я могу обработать SIGTERM, но forever перезапускает дочерние процессы SIGKILL - person Olegas; 08.05.2013
comment
@Olegas - forever имеет возможность изменить SIG, используемый при остановке процесса. Вы можете изменить его на SIGTERM. - person jfriend00; 16.09.2014

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

UPD

вы не можете обрабатывать SIGKILL, тогда вы должны очистить сокет вручную

этот пример отлично работает навсегда

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);
  }
});
person amirka    schedule 07.05.2013
comment
Но EADDRINUSE в случае сервера файловых сокетов означает, что файл сокета существует. Он никогда не закроется и не исчезнет, ​​потому что родительское приложение уже мертво, убито с помощью SIGKILL - person Olegas; 08.05.2013
comment
Программа закрытия SIGKILL немедленно использует fs.unlink вместо server.close() - person amirka; 08.05.2013
comment
... и я потенциально уничтожу сокет из другого работающего экземпляра. - person Olegas; 08.05.2013
comment
Сокеты UNIX — это объекты файловой системы, вам нужно очистить их после того, как вы закончите. Перед выходом убедитесь, что вы вызываете server.close(). если вы не можете этого сделать, то вы должны исправить это в Forever-Monitor для отправки другого сигнала или в вашем приложении через fs.unlink - person amirka; 09.05.2013

Поскольку вы не можете обрабатывать SIGKILL и хотите использовать навсегда (который использует SIGKILL), вам придется использовать обходной путь.

Например, сначала отправьте сигнал на выключение сервера, а затем перезапустите его навсегда:

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);
person laktak    schedule 08.05.2013

ты должен выбрать

  1. измените SIGKILL на другой сигнал в постоянном мониторе для обработки в приложении
  2. позаботьтесь о своем приложении в этом случае, используя fs.unlink
  3. прекратить использование навсегда
person amirka    schedule 09.05.2013