Как я могу написать блокировку в stdout с помощью node.js?

Я пишу приложение node.js, которое stdout передается в файл. Пишу все с console.log. Через некоторое время мое приложение достигает предела 1 ГБ и останавливается. Интересно, что если я использую console.error вместо console.log, использование памяти остается низким, и программа работает нормально. Итак, похоже, что node.js не может сбросить поток stdout, и все хранится в памяти. Я хочу, чтобы stderr не содержал ошибок.

Мой вопрос:

Есть ли способ написать блокировку в стандартный вывод? Или, по крайней мере, могу ли я написать с обратным вызовом на стандартный вывод, чтобы убедиться, что я пишу не слишком много?

спасибо!


person user698601    schedule 24.06.2011    source источник


Ответы (5)


Если вам действительно нужна синхронная запись в стандартный вывод, вы можете сделать:

var fs = require('fs');
fs.writeSync(1, "Foo\n");
fs.fsyncSync(1);
person Matt Sergeant    schedule 24.06.2011
comment
Это отличное решение! Но для чего нужен fs.fsyncSync(1)? - person user698601; 24.06.2011
comment
user698601: Убедитесь, что все записано в дескриптор файла один. Один — stdout, два — stderr, три — stdin. - person thejh; 24.06.2011
comment
Похоже, этот подход не работает при передаче вывода на стандартный вывод/stderr stackoverflow.com/questions/50574243/. - person Gajus; 29.05.2018
comment
stdin — нулевой файловый дескриптор, а не три - person Lucas Werkmeister; 01.06.2018

Напишите, используя process.stdout.write. получил буферизацию. Если это правда, продолжайте писать, когда process.stdout выдает событие drain.

Если вы хотите, чтобы ваш код выглядел синхронно, используйте streamlinejs, как описано здесь: Node.js stdout flush

person thejh    schedule 24.06.2011

Не.

То, что вы хотите сделать, это pause() ваш ввод, когда вывод заполнен, как это делает метод pump(), а затем resume() его, когда у вас есть место для записи. Если нет, ваш процесс раздуется до гигантских размеров.

Однако вы, вероятно, захотите использовать для этого более прямой материал outputStream или вызов write(), а не console.log().

person aredridel    schedule 24.06.2011
comment
Хотя конкретная проблема с производительностью здесь не является большой мотивацией для этого, есть абсолютно ситуации, когда вы делаете использование блокирующей записи на консоль. Например, если вы работаете с мониторами async_hooks, вы не можете использовать асинхронные методы внутри обратных вызовов асинхронного прослушивателя (поскольку запись сама вызовет прослушиватель и рекурсию навсегда). - person Chris Williamson; 12.09.2019

Изменить: как указано комментатором, у этого решения есть проблема. printResolver можно превратить в массив, но использовать лучшее решение намного проще

Функция синхронной печати, которая также работает с конвейерами, также известными как FIFO, с использованием Async/await. Убедитесь, что вы всегда вызываете печать с ожиданием печати

let printResolver;
process.stdout.on('drain', function () {
    if (printResolver) printResolver();
});

async function print(str) {
    var done = process.stdout.write(str);
    if (!done) {
        await new Promise(function (resolve) {
            printResolver = resolve;
        });
    }
}
person user3042469    schedule 15.11.2019
comment
Если print вызывается дважды до того, как произойдет drain, глобальное printResolve будет переопределено, и более раннее обещание не будет разрешено. - person Craig Hicks; 04.11.2020

Проблема (взрывное использование памяти), вероятно, возникает из-за того, что ваша программа создает вывод быстрее, чем он может быть отображен. Поэтому вы хотите задушить его. Ваш вопрос требует синхронного вывода, но на самом деле проблема может быть решена с помощью чисто асинхронного (*) кода.

(* ПРИМЕЧАНИЕ. В этом посте термин асинхронный используется в смысле javascript-однопоточность. Это отличается от обычного многопоточного смысла, который представляет собой совершенно другую рыбку).< /эм>

Этот ответ показывает, как асинхронный код можно использовать с промисами, чтобы предотвратить взрывное использование памяти путем приостановки (в отличие от блокировки) выполнения до тех пор, пока вывод записи не будет успешно очищен. В этом ответе также объясняется, чем решение с асинхронным кодом может быть выгоднее по сравнению с решением с синхронным кодом.

В: Пауза звучит как блокировка, и как асинхронный код может блокировать? Это оксюморон!

О: Это работает, потому что движок javascript v8 приостанавливает (блокирует) выполнение только одного фрагмента кода в ожидании завершения асинхронного обещания, в то же время позволяя другим фрагментам кода выполняться.

Вот функция асинхронной записи (адаптировано из здесь ).

async function streamWriteAsync(
  stream,
  chunk,
  encoding='utf8') {
  return await new Promise((resolve, reject) => {
    const errListener = (err) => {
      stream.removeListener('error', errListener);
      reject(err);
    };
    stream.addListener('error', errListener);
    const callback = () => {
      stream.removeListener('error', errListener);
      resolve(undefined);
    };
    stream.write(chunk, encoding, callback);
  });
}

Его можно вызвать из асинхронной функции в исходном коде, например,
случай 1.

async function main() {
  while (true)
    await streamWriteAsync(process.stdout, 'hello world\n')
}
main();

Где main() — единственная функция, вызываемая с верхнего уровня. Использование памяти не резко увеличится, как при вызове console.log('hello world');.

Требуется больше контекста, чтобы ясно увидеть преимущество перед настоящей синхронной записью:
случай 2.

async function logger() {
  while (true)
    await streamWriteAsync(process.stdout, 'hello world\n')
}
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
function allowOtherThreadsToRun(){
  return Promise(resolve => setTimeout(resolve, 0));
}
async function essentialWorker(){
  let a=0,b=1;
  while (true) {
    let tmp=a; a=b; b=tmp;
    allowOtherThreadsToRun();
  }
}
async function main(){
  Promise.all([logger(), essentialWorker()])  
}
main();

Выполнение приведенного выше кода (случай 2) покажет, что использование памяти по-прежнему не резко возрастает (так же, как случай 1), поскольку срез, связанный с logger, был приостановлен, но использование ЦП был по-прежнему, потому что фрагмент essentialWorker не был приостановлен — и это хорошо (вспомните COVID).

Для сравнения, синхронное решение также блокирует essentialWorker.

Что происходит, когда несколько фрагментов вызывают streamWrite?
случай 3

async function loggerHi() {
  while (true)
    await streamWriteAsync(process.stdout, 'hello world\n')
}
async function loggerBye() {
  while (true)
    await streamWriteAsync(process.stdout, 'goodbye world\n')
}
function allowOtherThreadsToRun(){
  return Promise(resolve => setTimeout(resolve, 0));
}
async function essentialWorker(){
  let a=0,b=1;
  while (true) {
    let tmp=a; a=b; b=tmp;
    allowOtherThreadsToRun();
  }
}
async function main(){
  Promise.all([loggerHi(), loggerBye(), essentialWorker()])  
}
main();

В этом случае (случай 3) использование памяти ограничено, а загрузка ЦП essentialWorker высока, как и в случай 2. Отдельные строки hello world и goodbye world останутся атомарными, но строки не будут чередоваться четко, например,

...
hello world 
hello world 
goodbye world 
hello world 
hello world 
...

могло появиться.

person Craig Hicks    schedule 04.11.2020