В JavaScript не хватает идентификаторов тайм-аута? - PullRequest
0 голосов
/ 01 ноября 2018

Удивительно, но я не могу найти ответ на этот вопрос в Интернете.

В документации указано , что setTimeout и setInterval совместно используют один и тот же пул идентификаторов, а также что идентификатор не будет никогда повторяться. Если это так, то они должны в конечном итоге закончиться, потому что существует максимальное число , которое может обработать компьютер? Что происходит, вы больше не можете использовать тайм-ауты?

1 Ответ

0 голосов
/ 03 ноября 2018

TL; DR;

Зависит от движка браузера.

В Blink и Webkit:

  • Максимальное количество одновременных таймеров составляет 2 31 -1.
  • Если вы попытаетесь использовать больше, ваш браузер, вероятно, зависнет из-за бесконечного цикла.

Официальная спецификация

Из документов W3C :

Метод setTimeout() должен выполнять следующие шаги:

  1. Пусть handle будет целым числом, определенным пользовательским агентом, которое больше нуля и будет определять время ожидания, которое будет установлено этим вызовом.

  2. Добавить запись в список активных тайм-аутов для handle .

  3. [...]

Также:

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

Примечание : хотя W3C упоминает два списка, WHATWG spec устанавливает, что setTimeout и setInterval совместно используют общие список активных таймеров . Это означает, что вы можете использовать clearInterval() для удаления таймера, созданного с помощью setTimeout(), и наоборот.

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

Давайте посмотрим, например, что делает Blink.

Blink реализация

Предыдущая заметка : не такая простая задача , чтобы найти реальный исходный код Blink. Он принадлежит кодовой базе Chromium , которая отражается в GitHub . Я буду ссылаться на GitHub (его текущий последний тег: 72.0.3598.1), потому что это лучшие инструменты для навигации по коду. Три года назад они давили коммиты на хром / моргание / . В настоящее время активная разработка ведется на chromium / third_party / WebKit , но обсуждается о новой миграции.

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

// Maintains a set of DOMTimers for a given page or
// worker. DOMTimerCoordinator assigns IDs to timers; these IDs are
// the ones returned to web authors from setTimeout or setInterval. It
// also tracks recursive creation or iterative scheduling of timers,
// which is used as a signal for throttling repetitive timers.
class DOMTimerCoordinator {

DOMTimerCoordinator сохраняет таймеры в коллекции blink::HeapHashMap (псевдоним TimeoutMap) timers_, который ключ (соответствует спецификации) int типа:

using TimeoutMap = HeapHashMap<int, Member<DOMTimer>>;
TimeoutMap timers_;

Это ответ на ваш первый вопрос (в контексте Blink): максимальное количество активных таймеров для каждого контекста составляет 2 31 -1 ; намного ниже, чем JavaScript MAX_SAFE_INTEGER (2 53 -1), который вы упомянули, но все же более чем достаточно для обычных случаев использования.

На ваш второй вопрос: « Что происходит, вы больше не можете использовать таймауты? », у меня пока только частичный ответ.

Новые таймеры создаются DOMTimerCoordinator::InstallNewTimeout(). Он вызывает закрытую функцию-член NextID() для получения доступного целочисленного ключа и DOMTimer::Create для фактического создания объекта таймера. Затем он вставляет новый таймер и соответствующий ключ в timers_.

int timeout_id = NextID();
timers_.insert(timeout_id, DOMTimer::Create(context, action, timeout,
                                            single_shot, timeout_id));

NextID() получает следующий идентификатор в круговой последовательности от 1 до 2 31 -1:

int DOMTimerCoordinator::NextID() {
  while (true) {
    ++circular_sequential_id_;

    if (circular_sequential_id_ <= 0)
      circular_sequential_id_ = 1;

    if (!timers_.Contains(circular_sequential_id_))
      return circular_sequential_id_;
  }
}

Увеличивает в 1 значение circular_sequential_id_ или устанавливает его в 1, если оно выходит за пределы верхнего предела (хотя INT_MAX + 1 вызывает UB, большинство реализаций C возвращают INT_MIN).

Итак, когда у DOMTimerCoordinator заканчиваются идентификаторы, повторяется попытка с 1 до тех пор, пока не будет найден один свободный.

Но что произойдет, если они все используются? Что мешает NextID() войти в бесконечный цикл? Это кажется, что ничего . Вероятно, разработчики Blink закодировали NextID() в предположении, что никогда не будет 2 31 -1 таймеров одновременно. Это имеет смысл; для каждого байта, возвращаемого DOMTimer::Create(), вам потребуется ГБ ОЗУ для хранения timers_, если он заполнен. Это может добавить к ТБ, если вы храните длинные обратные вызовы. Не говоря уже о времени, необходимом для их создания.

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

...