iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
自我挑戰組

從C到JS的同步非同步探索系列 第 24

[Day 24] Node Event loop 3

  • 分享至 

  • xImage
  •  

前言

今天繼續看看 event loop 的核心循環, uv_run() , 可以查看以下網址

https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/deps/uv/src/win/core.c

主要循環

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);
    uv__run_timers(loop);

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

    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);

    if (pGetQueuedCompletionStatusEx)
      uv__poll(loop, timeout);
    else
      uv__poll_wine(loop, timeout);

    /* Run one final update on the provider_idle_time in case uv__poll*
     * returned because the timeout expired, but no events were received. This
     * call will be ignored if the provider_entry_time was either never set (if
     * the timeout == 0) or was already updated b/c an event was received.
     */
    uv__metrics_update_idle_time(loop);

    uv_check_invoke(loop);
    uv_process_endgames(loop);

    if (mode == UV_RUN_ONCE) {
      /* UV_RUN_ONCE implies forward progress: at least one callback must have
       * been invoked when it returns. uv__io_poll() can return without doing
       * I/O (meaning: no callbacks) when its timeout expires - which means we
       * have pending timers that satisfy the forward progress constraint.
       *
       * UV_RUN_NOWAIT makes no guarantees about progress so it's omitted from
       * the check.
       */
      uv__run_timers(loop);
    }

    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }

  /* The if statement lets the compiler compile it to a conditional store.
   * Avoids dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

  return r;
}

今天繼續看主要循環, 我們要來看
uv__poll(loop, timeout);
uv__poll_wine(loop, timeout);

這兩句, 但這兩句基本只是 case 的差別, 所以我們僅看 uv__poll 即可

uv__poll(loop, timeout);

static void uv__poll(uv_loop_t* loop, DWORD timeout) {
  BOOL success;
  uv_req_t* req;
  OVERLAPPED_ENTRY overlappeds[128];
  ULONG count;
  ULONG i;
  int repeat;
  uint64_t timeout_time;
  uint64_t user_timeout;
  int reset_timeout;
	// 訂下此階段結束時間, 時間到就要結束, 不能在一個階段待太久
  timeout_time = loop->time + timeout;

  if (uv__get_internal_fields(loop)->flags & UV_METRICS_IDLE_TIME) {
    reset_timeout = 1;
    user_timeout = timeout;
    timeout = 0;
  } else {
    reset_timeout = 0;
  }

  for (repeat = 0; ; repeat++) {
    /* Only need to set the provider_entry_time if timeout != 0. The function
     * will return early if the loop isn't configured with UV_METRICS_IDLE_TIME.
     */
		// 期望 timeout 是 0 , 因為此處使用 IOCP , 實際上有一個任務 queue 裡面可以放任務,
		// 理想上只要看一眼 queue 裡有沒有任務, 有取出即可。不要等在那。
    if (timeout != 0)
      uv__metrics_set_provider_entry_time(loop);
		// IOCP 取出 C++ 層註冊的事件所觸發的任務
    success = pGetQueuedCompletionStatusEx(loop->iocp,
                                           overlappeds,
                                           ARRAY_SIZE(overlappeds),
                                           &count,
                                           timeout,
                                           FALSE);

    if (reset_timeout != 0) {
      timeout = user_timeout;
      reset_timeout = 0;
    }

    /* Placed here because on success the loop will break whether there is an
     * empty package or not, or if GetQueuedCompletionStatus returned early then
     * the timeout will be updated and the loop will run again. In either case
     * the idle time will need to be updated.
     */
    uv__metrics_update_idle_time(loop);

    if (success) {
      for (i = 0; i < count; i++) {
        /* Package was dequeued, but see if it is not a empty package
         * meant only to wake us up.
         */
        if (overlappeds[i].lpOverlapped) {
					// 轉換成可以推進 pending queue 的型態
					// overlapped 是微軟家的折疊變數
          req = uv_overlapped_to_req(overlappeds[i].lpOverlapped);
					// 把 IOCP 取出的任務放入 pending queue 中, 這是一個由 libuv 維護的資料結構
          uv_insert_pending_req(loop, req);
        }
      }

      /* Some time might have passed waiting for I/O,
       * so update the loop time here.
       */
      uv_update_time(loop);
    } else if (GetLastError() != WAIT_TIMEOUT) {
      /* Serious error */
      uv_fatal_error(GetLastError(), "GetQueuedCompletionStatusEx");
    } else if (timeout > 0) {
      /* GetQueuedCompletionStatus can occasionally return a little early.
       * Make sure that the desired timeout target time is reached.
       */
      uv_update_time(loop);
      if (timeout_time > loop->time) {
        timeout = (DWORD)(timeout_time - loop->time);
        /* The first call to GetQueuedCompletionStatus should return very
         * close to the target time and the second should reach it, but
         * this is not stated in the documentation. To make sure a busy
         * loop cannot happen, the timeout is increased exponentially
         * starting on the third round.
         */
        timeout += repeat ? (1 << (repeat - 1)) : 0;
        continue;
      }
    }
    break;
  }
}

poll 階段整理

以下簡述觸發流程

  1. JS 層註冊 IO 事件 ( ex : http request)
  2. C++ 層利用 IOCP 註冊 IO 事件的 socket
  3. uv_run 在運行到 uv__poll 也就是所謂的 poll 階段時抓取 IOCP 任務 queue 中的任務
  4. 如果有抓到新任務就放入由 libuv 維護的 pending queue 中

明天進度

明天我們來看看 pending queue 中的 IO 任務, 會在哪邊被處理吧 !

明天見 !


上一篇
[Day 23] Node Event loop 2
下一篇
[Day 25] Node Event loop 4
系列文
從C到JS的同步非同步探索30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言