NodeJS: гонка по обещанию с тайм-аутом сохраняет данные до истечения тайм-аута, что приводит к утечке памяти - PullRequest
0 голосов
/ 12 октября 2018

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

Код:

const util = require('util');
const setTimeoutPromise = util.promisify(setTimeout);
const redis = ...;

function get(key) {
    return timeout(redis.get(key).then(JSON.parse), 900000 /*fifteenMinutesInMs*/);
}

function timeout(promise, timeoutMs) {
    return Promise.race([
        promise
        , setTimeoutPromise(timeoutMs) // If I comment this line out, the memory use will not grow
    ]);
}

С кодом, который находится под нагрузкой, сервер быстро потребляет все 3 ГБ памяти и зависает/ сбой.Если я закомментирую строку setTimeoutPromise, она будет работать нормально (оставаясь ниже 1 / 2GB памяти).Что не так с кодом?Согласно https://stackoverflow.com/a/32461436/204205 все в порядке.

Подробнее см. https://gist.github.com/jakubholynet/6c8f615c195ebb7b3ef4d9a0a0ee4362.

Спасибо!

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

1 Ответ

0 голосов
/ 14 октября 2018

Promise.race может разрешиться, как только ваше redis.get будет завершено, но обещания не могут быть отменены, поэтому обещание тайм-аута продолжает существовать до тех пор, пока не будет решено или отклонено.

Возможно, вы могли бы сделать что-то вродеthis?

const util = require('util');
const setTimeoutPromise = util.promisify(setTimeout);
const redis = ...;

function get(key) {
  return timeout(redis.get(key).then(JSON.parse), 900000);
}

function timeout(promise, timeoutMs) {
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      reject('GET took too long');
    }, timeoutMs);

    promise.then((...args) => {
      resolve(...args);
    }).catch((err) => {
      reject(err);
    }).then(() => {
      clearTimeout(timeout);
    });
  });
}

Вы могли бы получить ошибку о resolve введении или reject выполнении обещания дважды в случае, если get действительно истекает, но это несколько неизбежно, потому что обещания могут 'не подлежит отмене.

...