[Day 24] Node Event loop 3

前言

今天继续看看 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 任务, 会在哪边被处理吧 !

明天见 !


<<:  【第9天】训练模型-迁移学习

>>:  #24 JS: HTML DOM Events - Part 2

Day 10 无限循环照片

无限循环照片 教学原文参考:无限循环照片 这篇文章会介绍在 GIMP 使用「选取」、「变换」工具,做...

GCP Cloud SQL

Cloud SQL 今天主题Mysql,所有的开发我想一定无法脱离资料库的使用,这点Google也帮...

AZ-304 Practice Exam - The key to Transform Failure into Success

Azure Solutions Architect Expert AZ-304 certificat...

【设计+切版30天实作】|Day3 - 参考Bootstrap画出理想的header(上集)

设计大纲 今天来设计Landing page的header。这次想要做的是一个满版的header,在...

Day8-D3 资料整理的API们:Array、Time Formats、Number Formats、Random

整理资料的API们:Array、Time Formats、Number Formats、Rando...