Выполняет ли событие nodejs (libuv) l oop все обратные вызовы в одной фазе (очереди), прежде чем перейти к следующей или выполнить циклически? - PullRequest
3 голосов
/ 05 марта 2020

Я изучаю событие l oop, предоставленное libuv в Узле. Я наткнулся на следующий блог Deepal Jayasekara , а также увидел объяснения Берта Белдера и Даниэля Хана на YouTube.

Есть один момент, с которым мне неясно: согласно моему пониманию, событие l oop обрабатывает все элементы одной фазы, прежде чем перейти к другой. Так что, если это так, я смогу заблокировать событие l oop, если к фазе setTimeout постоянно добавляются обратные вызовы.

Однако, когда я попытался повторить это - этого не произошло. Ниже приведен код:

var http = require('http');

http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.write('Hello World!');
  console.log("Response sent");
  res.end();
}).listen(8081);


setInterval(() => {
  console.log("Entering for loop");
// Long running loop that allows more callbacks to get added to the setTimeout phase before this callback's processing completes
 for (let i = 0; i < 7777777777; i++); 
 console.log("Exiting for loop");
}, 0);

Кажется, что событие l oop проходит в циклическом режиме. Сначала он выполняет обратные вызовы, которые были добавлены до того, как я отправлю запрос на сервер, затем обрабатывает запрос и затем продолжает обратные вызовы. Такое ощущение, что работает одна очередь. Из того, что я понял, нет ни одной очереди, и все обратные вызовы таймера с истекшим сроком действия должны выполняться в первую очередь перед переходом к следующей фазе. Следовательно, приведенный выше фрагмент не должен быть в состоянии вернуть ответ Hello World.

Что может быть возможным объяснением этого? Спасибо.

Ответы [ 2 ]

3 голосов
/ 06 марта 2020

Если вы загляните в сам libuv, вы обнаружите, что оперативная часть запуска таймеров в событии l oop является функцией uv_run_timers().

void uv__run_timers(uv_loop_t* loop) {
  struct heap_node* heap_node;
  uv_timer_t* handle;

  for (;;) {
    heap_node = heap_min(timer_heap(loop));
    if (heap_node == NULL)
      break;

    handle = container_of(heap_node, uv_timer_t, heap_node);
    if (handle->timeout > loop->time)
      break;

    uv_timer_stop(handle);
    uv_timer_again(handle);
    handle->timer_cb(handle);
  }
}

То, как оно работает, это событие l oop устанавливает отметку времени в текущем времени, а затем обрабатывает все таймеры, которые должны быть к этому времени по порядку, без обновления l oop время. Таким образом, это запустит все таймеры, которые уже истекли, но не запустит новые таймеры, которые должны быть выполнены, пока он обрабатывает те, которые уже должны были быть выполнены.

Это приводит к более справедливому планированию, так как он запускает все таймеры, которые должны, затем идет и запускает остальные типы событий в событии l oop, а затем возвращается, чтобы сделать больше таймеров, которые должны быть снова. Это НЕ будет обрабатывать любые таймеры, которые не должны быть запущены в начале цикла этого события l oop, но наступают во время обработки других таймеров. Таким образом, вы видите поведение, о котором вы спрашивали.

Вышеуказанная функция вызывается из основной части события l oop с этим кодом:

int uv_run(uv_loop_t *loop, uv_run_mode mode) {
  DWORD timeout;
  int r;
  int ran_pending;

  r = uv__loop_alive(loop);
  if (!r)
    uv_update_time(loop);

  while (r != 0 && loop->stop_flag == 0) {
    uv_update_time(loop);                    <==  establish loop time
    uv__run_timers(loop);                    <==  process only timers due by that loop time

    ran_pending = uv_process_reqs(loop);
    uv_idle_invoke(loop);
    uv_prepare_invoke(loop);

 .... more code here

}

Запишите вызов uv_update_time(loop) прямо перед вызовом uv__run_timers(). Это устанавливает таймер, на который ссылается uv__run_timers(). Вот код для uv_update_time():

void uv_update_time(uv_loop_t* loop) {
  uint64_t new_time = uv__hrtime(1000);
  assert(new_time >= loop->time);
  loop->time = new_time;
}
0 голосов
/ 06 марта 2020

из документов ,

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

Также из документов ,

Если задержка больше 2147483647 или меньше 1, задержка будет установлена ​​на 1

Теперь, когда вы запускаете свой фрагмент, происходят следующие вещи,

  1. начинается выполнение сценария и регистрируются обратные вызовы для определенных c фаз. Кроме того, поскольку документы предполагают, что задержка setInterval неявно преобразуется в 1 se c.
  2. После 1 se c будет выполнен ваш обратный вызов setInterval, он заблокирует событие l oop, пока все итерации и завершены. Между тем, eventl oop не будет уведомлен ни о каком входящем запросе, по крайней мере, до тех пор, пока l oop не прекратится.
  3. После того, как все итерации будут завершены, и истечет время ожидания 1 se c, фаза опроса выполнит ваш обратный вызов HTTP-запроса, если таковой имеется.
  4. к шагу 2.
...