NodeJS IO голодал, но таймеры все еще работают? - PullRequest
0 голосов
/ 12 февраля 2019

У меня довольно сложное приложение NodeJS, которое управляет различными операциями на сервере.Он прослушивает сообщения на канале PubNub и делает несколько запросов к сторонним API.Он также имеет таймер (с помощью setTimeout), который запускается каждую секунду, чтобы проверить, что все соответствующие процессы все еще работают и работают.

Недавно я заметил странное поведение.При определенных обстоятельствах (определенных, но воспроизводимых на 100%) он переходит в странное состояние, когда все сетевые операции ввода-вывода перестают работать.Обновления PubNub перестают быть получены, и любые выполненные HTTP-запросы (с использованием библиотеки request-обещание-native) будут прерваны.Это состояние кажется постоянным после того, как приложение вошло в него, так как я пытался запускать запросы с очень большими таймаутами, и они просто никогда не разрешались.

Я читал в Интернете о том, как можно остановить IO, остановивцикл обработки событий, часто использующий длительные операции в главном потоке, process.nextTick или неправильное использование обещаний.У меня нет ничего из этого в моем приложении.

Но даже страннее, я читал, что если вы блокируете цикл обработки событий, все таймеры также перестают работать.Я использую setTimeout, setInterval и setIntermediate в приложении, и они все продолжают работать, даже когда приложение вошло в это состояние.Так что мой цикл событий, очевидно, все еще работает.

Я думаю, что проблема, скорее всего, вызвана моим использованием именованных каналов.Часть моего приложения создает именованный канал и затем читает его как поток:

let pipePath = "/tmp/myPipe";
await promisify(fs.unlink)(pipePath)
            .then(function() {}, function(err) {console.warn("Error removing pipe", err);}); // Ignore errors which may happen if the pipe doesn't exist
childProcess.spawnSync('mkfifo', [pipePath]);

// (not shown) here it starts a 3rd party application that writes to the pipe

let pipeHandle = await promisify(fs.open)(pipePath, O_RDWR); // https://stackoverflow.com/a/53492514/674675

let stream = fs.createReadStream(null, {fd: pipeHandle, autoClose: false});

// (not shown) here several event listeners are added to stream's data event

Я также определяю, завершено ли стороннее приложение по какой-либо причине, и когда это происходит, я очищаю канал:

// (not-shown) here all the listeners on the stream are removed using removeListener

stream.close(); // This also seems to close the handle to the file

Состояние с замороженным вводом-выводом возникает, если стороннее приложение завершается без записи в канал.Но действительно странно, что замороженное состояние ввода-вывода вводится только после четвертого раза , когда это происходит.Таким образом, я могу создать канал, запустить стороннее приложение, прочитать канал и убить приложение, прежде чем оно будет 3 раза записывать какие-либо данные, но 4-е каким-то образом сломает NodeJS.

Я использую Node v8.10,0.Кто-нибудь знает, что происходит?

Вещи, которые я пробовал: (без успеха)

Я не доверял steam.close() закрытию дескриптора файла, поэтому я попробовал эту альтернативу:

// (not-shown) here all the listeners on the stream are removed using removeListener

stream.pause();
await promisify(fs.close)(pipeHandle);

Я обновился до Node v11.8.0.

Я переключился с использования fs на graceful-fs.

Я понял, как подключить Chrome DevToolsк процессу и обнаружили, что точный момент ошибки, как представляется, при вызове fs.unlink перед созданием пятого канала.Обещание для fs.unlink никогда не разрешается и не отклоняется.До сих пор не уверен, почему это происходит, но по крайней мере это прогресс.

Я попытался заменить вызов fs.unlink на spawnSync("rm", [pipePath]).Это позволяет коду проходить после вызова, но операция fs.open не удалась.Я также попытался запустить fs.openSync, и это заморозило всю программу.Таким образом, кажется, что после того, как четвертый процесс завершен, приложение по какой-то причине не может делать больше ввода-вывода, а следующие fs вызывают блоки бесконечно.

Я прошел программу и использовал lsof вдругой экран для проверки файловых дескрипторов в каждой интересной части процесса.Обычно в моем временном файле есть дескриптор файла после создания канала, и я начинаю его читать, а затем, когда я закрываю поток, дескриптор исчезает.Но после закрытия потока в четвертый раз дескриптор все еще там.Это всегда четвертый раз.Каково значение 4?Я начинаю становиться тетрафобом.

Попытка изменить autoClose на true.

Попытка создать минимальный пример, который воспроизводит проблему.Не точно воспроизвести проблему, но, возможно, нашел что-то связанное? Вопрос здесь

Используется lslocks, чтобы увидеть, ожидает ли процесс блокировки.(Это не так)

Переключено на использование fifo-js для обработки труб.Это решило проблему!Но, к сожалению, он искажает двоичные данные, поступающие по каналу, поэтому его невозможно использовать.

...