Skip to content

JavaScript 运行时架构:V8、Node.js 与 libuv 的交互以 setTimeout 为例展示完整调用链和事件循环机制

js
// ==========================================
// JavaScript 运行时架构:V8、Node.js 与 libuv 的交互
// 以 setTimeout 为例展示完整调用链和事件循环机制
// ==========================================

// --- 1. 用户 JavaScript 代码 ---
function myActualCallback() {
  console.log('定时器触发!');
}

// 调用 Node.js 环境提供的 setTimeout
const timerId = setTimeout(myActualCallback, 1000);
console.log('setTimeout 已调用,但回调尚未执行');
c++
// --- 2. V8 引擎对 JavaScript 代码的处理 ---
// 2.1. 解析与编译阶段
V8_Parse_And_Compile {
  // 将源代码转换为抽象语法树(AST)
  ast = V8_ParseJavaScript(source_code);
  // 将 AST 转换为字节码
  bytecode = V8_GenerateBytecode(ast);
  // 优化热点代码(根据使用频率)
  V8_OptimizeHotFunctions(bytecode);
}

// 2.2. 执行阶段
V8_Internal_Execution {
  // 查找全局对象中的 "setTimeout" 标识符
  target = V8_LookupGlobalIdentifier("setTimeout");

  // 发现它是一个绑定到 C++ 的函数(而非纯 JavaScript 函数)
  if (IsNativeBinding(target)) {
    // 获取对应的 C++ 函数指针
    cpp_function = GetNativeFunctionPointer(target);

    // 将 JavaScript 参数转换为 V8 C++ API 可理解的格式
    v8_callback_handle = V8_WrapJavaScriptFunction(myActualCallback);
    v8_delay_handle = V8_WrapJavaScriptNumber(1000);

    // 检查可选参数(如 ...args)
    v8_args_array = V8_CreateEmptyArray();  // 该例中无额外参数

    // *** 关键:调用 Node.js 提供的 C++ 函数 ***
    // 这里是直接的、进程内的函数调用,不是跨进程通讯
    timer_id = Node_setTimeout_Cpp_Binding(v8_callback_handle, v8_delay_handle, v8_args_array);

    // 将 C++ 返回的计时器 ID 转换为 JavaScript 值并返回
    return V8_WrapNumber(timer_id);
  }
}

// --- 3. Node.js 的 setTimeout C++ 实现 ---
function Node_setTimeout_Cpp_Binding(v8_callback, v8_delay, v8_args_array) {
  // 从 V8 对象中提取实际值
  if (!V8_IsFunction(v8_callback)) {
    throw V8_TypeError("Callback must be a function");
  }

  // 创建持久化引用,防止 V8 垃圾回收器回收 JavaScript 对象
  js_callback_persistent_ref = V8_CreatePersistentHandle(v8_callback);

  // 转换并验证参数
  delay_milliseconds = V8_ConvertToUint32(v8_delay);
  // 内部实现细节:Node.js 强制最小延迟为 1ms (历史原因)
  // 且最大延迟为 2^31-1 ms (约 24.8 天)
  delay_milliseconds = Math.max(1, Math.min(delay_milliseconds, 0x7FFFFFFF));

  // 提取可能的额外参数(apply 到回调)
  args_count = V8_GetArrayLength(v8_args_array);
  args_persistent_refs = [];
  for (i = 0; i < args_count; i++) {
    arg = V8_GetArrayElement(v8_args_array, i);
    args_persistent_refs.push(V8_CreatePersistentHandle(arg));
  }

  // 生成唯一的计时器 ID (用于 clearTimeout)
  timer_id = Nodejs_GenerateUniqueTimerId();

  // 创建计时器实例并存储到全局映射中(便于 clearTimeout 查找)
  timer_context = {
    id: timer_id,
    js_callback_ref: js_callback_persistent_ref,
    args_refs: args_persistent_refs,
    is_active: true,
    is_ref: true  // 默认影响进程生命周期 (ref'd)
  };
  Nodejs_Timers_Map.Set(timer_id, timer_context);

  // *** 关键:调用 libuv 来设置定时器 ***
  // 提供延迟时间和 *一个 C 回调函数* (Nodejs_Libuv_Timer_Callback)
  uv_timer_handle = Libuv_Timer_Create();
  timer_context.uv_handle = uv_timer_handle;  // 保存 libuv 句柄

  // 关联 timer_context 到 libuv 句柄的数据字段
  Libuv_Handle_SetData(uv_timer_handle, timer_context);

  // 启动单次定时器 (repeat = 0 表示只触发一次)
  Libuv_Timer_Start(uv_timer_handle, Nodejs_Libuv_Timer_Callback, delay_milliseconds, 0);

  // 返回计时器 ID (用于 clearTimeout)
  return timer_id;
}

// Node.js 清除计时器的实现
function Node_clearTimeout_Cpp_Binding(v8_timer_id) {
  timer_id = V8_ConvertToUint32(v8_timer_id);

  // 查找计时器上下文
  if (!Nodejs_Timers_Map.Has(timer_id)) {
    return; // 无效 ID 或已清除的计时器,静默失败
  }

  timer_context = Nodejs_Timers_Map.Get(timer_id);
  if (!timer_context.is_active) {
    return; // 已经被清除或触发
  }

  // 停止 libuv 计时器
  Libuv_Timer_Stop(timer_context.uv_handle);

  // 清理资源
  Libuv_Handle_Close(timer_context.uv_handle, Nodejs_Timer_Close_Callback);

  // 标记为非活动(避免再次清除)
  timer_context.is_active = false;
}

// 定时器关闭回调(清理资源)
function Nodejs_Timer_Close_Callback(uv_handle) {
  timer_context = Libuv_Handle_GetData(uv_handle);

  // 释放 V8 持久化引用(允许垃圾回收)
  V8_DisposePersistentHandle(timer_context.js_callback_ref);
  for (i = 0; i < timer_context.args_refs.length; i++) {
    V8_DisposePersistentHandle(timer_context.args_refs[i]);
  }

  // 从映射中移除
  Nodejs_Timers_Map.Delete(timer_context.id);

  // 释放 libuv 句柄
  Libuv_Handle_Free(uv_handle);
}

// --- 4. libuv 库 ---
// 4.1. 定时器数据结构和基本操作
TimerHeap = MinHeap(); // 用最小堆管理所有定时器(按到期时间排序)

function Libuv_Timer_Create() {
  // 分配内存并初始化句柄
  handle = Libuv_Allocate(sizeof(uv_timer_t));
  Libuv_Handle_Init(handle, UV_TIMER);
  return handle;
}

function Libuv_Timer_Start(handle, c_callback, timeout, repeat) {
  handle.c_callback = c_callback; // 存储 Node.js 提供的 C 回调
  handle.timeout = timeout;
  handle.repeat = repeat;

  // 计算绝对到期时间
  now = Libuv_GetHighResTime();
  handle.expiry_time = now + timeout;

  // 插入计时器堆(按到期时间排序)
  TimerHeap.Insert(handle, handle.expiry_time);

  // 如果这是最早的计时器,可能需要调整事件循环的唤醒时间
  Libuv_WakeUpEventLoopIfNeeded();

  return 0; // 成功代码
}

function Libuv_Timer_Stop(handle) {
  // 从计时器堆中移除
  TimerHeap.Remove(handle);
  return 0; // 成功代码
}

function Libuv_Handle_Close(handle, close_callback) {
  if (handle.is_closing) {
    return; // 已经在关闭中
  }

  handle.is_closing = true;
  handle.close_callback = close_callback;

  // 将句柄添加到关闭列表(在事件循环的 close 阶段处理)
  Libuv_CloseHandlesList.Add(handle);
}

// 4.2. 事件循环的核心实现
function Libuv_EventLoop_Run(loop_mode) {
  // 事件循环的主要状态
  alive = true;

  while (alive && !Libuv_IsLoopStopped()) {
    // 更新内部时间戳(避免频繁系统调用)
    Libuv_UpdateTime();

    // 1. 运行到期的定时器回调
    Libuv_ProcessTimers();

    // 2. 运行待处理的 I/O 回调
    Libuv_ProcessPendingCallbacks();

    // 3. Idle 回调阶段
    Libuv_ProcessIdleHandles();

    // 4. Prepare 回调阶段
    Libuv_ProcessPrepareHandles();

    // 5. 计算轮询超时时间
    timeout = Libuv_CalculatePollTimeout();

    // 6. 阻塞等待 I/O 事件(核心系统调用,如 epoll_wait/kqueue/IOCP)
    Libuv_PollForEvents(timeout);

    // 7. Check 回调阶段 (setImmediate 在此处理)
    Libuv_ProcessCheckHandles();

    // 8. 关闭句柄回调阶段
    Libuv_ProcessCloseHandles();

    // 检查是否还有活动的任务/句柄
    alive = Libuv_LoopAlive();

    // 根据运行模式决定是否继续
    if (loop_mode == UV_RUN_ONCE) {
      break;
    }
  }

  return alive ? 1 : 0;
}

// 定时器处理的具体实现
function Libuv_ProcessTimers() {
  // 获取当前高精度时间戳
  now = Libuv_GetHighResTime();

  // 处理所有已到期的计时器
  while (TimerHeap.IsNotEmpty() && TimerHeap.GetMinExpiryTime() <= now) {
    expired_handle = TimerHeap.ExtractMin();

    // 如果句柄正在关闭,忽略它
    if (expired_handle.is_closing) {
      continue;
    }

    // *** 关键:执行 Node.js 在 C++ 绑定中提供的回调函数 ***
    expired_handle.c_callback(expired_handle);

    // 对于重复计时器,重新计算到期时间并重新插入
    if (expired_handle.repeat > 0 && !expired_handle.is_closing) {
      // 使用 repeat 间隔计算下一个到期时间
      // 注意:libuv 会尝试保持准确的间隔,而不是简单地添加 repeat 值
      // 这里使用特殊算法减少定时器漂移
      next_expiry = Libuv_CalculateNextExpiryTime(expired_handle, now);
      expired_handle.expiry_time = next_expiry;

      // 重新插入计时器堆
      TimerHeap.Insert(expired_handle, next_expiry);
    }
  }
}

// 轮询超时计算(决定 I/O 阻塞时间)
function Libuv_CalculatePollTimeout() {
  // 默认无限等待
  timeout = -1;

  // 如果有立即回调,不阻塞
  if (HasPendingImmediateCallbacks()) {
    return 0;
  }

  // 如果有到期的定时器,不阻塞
  now = Libuv_GetHighResTime();
  if (TimerHeap.IsNotEmpty() && TimerHeap.GetMinExpiryTime() <= now) {
    return 0;
  }

  // 如果有定时器,等待到最近的定时器到期
  if (TimerHeap.IsNotEmpty()) {
    next_timer_ms = TimerHeap.GetMinExpiryTime() - now;
    timeout = next_timer_ms;
  }

  // 如果有活动句柄但没有待处理事件,使用默认超时
  if (timeout < 0 && Libuv_LoopAlive()) {
    timeout = UV_DEFAULT_LOOP_IDLE_TIMEOUT; // 通常为几秒
  }

  return timeout;
}

// --- 5. Node.js 的 libuv 回调和事件循环集成 ---
// 这个函数是前面传递给 libuv 的 C 回调
function Nodejs_Libuv_Timer_Callback(libuv_timer_handle) {
  // 从句柄取回之前存储的上下文
  timer_context = Libuv_Handle_GetData(libuv_timer_handle);

  // 如果计时器已被清除或无效,不执行操作
  if (!timer_context || !timer_context.is_active) {
    return;
  }

  // 获取 JavaScript 回调引用和参数
  js_callback_ref = timer_context.js_callback_ref;
  js_args_refs = timer_context.args_refs;

  // 对于单次计时器,标记为非活动(防止重复触发)
  if (libuv_timer_handle.repeat == 0) {
    timer_context.is_active = false;
  }

  // *** 关键:将 JavaScript 回调放入 Node.js 的计时器队列 ***
  Nodejs_Timers_Queue.Enqueue({
    callback_ref: js_callback_ref,
    args_refs: js_args_refs,
    timer_context: timer_context
  });

  // 如果是单次计时器,准备清理资源
  if (libuv_timer_handle.repeat == 0) {
    // 不在回调中直接关闭,而是将关闭操作推迟到下一个关闭阶段
    // 这避免了在回调期间修改数据结构的潜在问题
    Libuv_Handle_Close(libuv_timer_handle, Nodejs_Timer_Close_Callback);
  }
}

// Node.js 事件循环的实现(封装和扩展 libuv 事件循环)
Nodejs_Timers_Queue = new Queue();
Nodejs_IO_Callbacks_Queue = new Queue();
Nodejs_Immediate_Queue = new Queue();
Nodejs_NextTick_Queue = new Queue();
Nodejs_Promise_Microtasks_Queue = new Queue();

function Nodejs_EventLoop_Run() {
  // 持续运行直到没有更多工作要做
  while (Nodejs_IsLoopAlive()) {
    Nodejs_EventLoop_Tick();
  }
}

function Nodejs_EventLoop_Tick() {
  // 阶段 1: 处理到期的计时器回调
  Nodejs_ProcessTimers();

  // 执行所有累积的微任务(nextTick 和 Promise 回调)
  Nodejs_ProcessMicrotasks();

  // 阶段 2: 处理待处理的 I/O 回调
  Nodejs_ProcessIOCallbacks();
  Nodejs_ProcessMicrotasks();

  // 阶段 3 & 4: Idle & Prepare (主要在 libuv 内部使用)

  // 阶段 5: Poll - 等待 I/O 事件,可能阻塞
  // (在内部调用 libuv 的主事件循环进行一次迭代)
  Libuv_EventLoop_Run(UV_RUN_ONCE);
  Nodejs_ProcessMicrotasks();

  // 阶段 6: 处理 setImmediate 回调
  Nodejs_ProcessImmediateCallbacks();
  Nodejs_ProcessMicrotasks();

  // 阶段 7: Close callbacks - 在 libuv 中已处理

  // 检查是否还有工作要做(决定是否继续事件循环)
  Nodejs_UpdateLoopAliveState();
}

// 处理 Timer 回调的实现
function Nodejs_ProcessTimers() {
  while (Nodejs_Timers_Queue.IsNotEmpty()) {
    // 获取下一个计时器项
    timer_item = Nodejs_Timers_Queue.Dequeue();
    callback_ref = timer_item.callback_ref;
    args_refs = timer_item.args_refs;

    // 创建一个执行上下文(用于错误处理等)
    execution_context = {
      domain: null,  // Node.js 域(已弃用但仍支持)
      async_id: Nodejs_GenerateAsyncId(),  // 用于异步钩子
      trigger_id: Nodejs_GetCurrentAsyncId()
    };

    try {
      // *** 关键:告诉 V8 执行此 JavaScript 回调 ***
      V8_Execute_Function_With_Args(callback_ref, global_this, args_refs);
    } catch (error) {
      // 处理未捕获的异常
      if (HasUncaughtExceptionHandler()) {
        Nodejs_EmitUncaughtException(error, execution_context);
      } else {
        // 没有处理程序,终止进程
        Nodejs_FatalException(error);
      }
    }

    // 注意:我们不在这里释放持久化引用
    // 对于已完成的单次定时器,这会在关闭回调中完成
    // 对于重复计时器,引用在计时器被清除时释放
  }
}

// 处理微任务队列(nextTick 和 Promise 回调)
function Nodejs_ProcessMicrotasks() {
  // 首先处理 nextTick 队列(优先级高于 Promise)
  while (Nodejs_NextTick_Queue.IsNotEmpty()) {
    nextTick_item = Nodejs_NextTick_Queue.Dequeue();
    try {
      V8_Execute_Function_With_Args(
        nextTick_item.callback_ref,
        global_this,
        nextTick_item.args_refs
      );
    } catch (error) {
      // 处理未捕获的异常...
      Nodejs_HandleUncaughtException(error);
    }
  }

  // 然后处理 Promise 微任务队列(由 V8 直接管理)
  V8_RunMicrotasks();
}

// --- 6. V8 执行 JavaScript 回调 ---
function V8_Execute_Function_With_Args(fn_persistent_ref, this_context, args_refs) {
  // 从持久化引用获取实际 V8 函数对象
  v8_function = V8_GetLocalFromPersistentHandle(fn_persistent_ref);

  // 准备参数数组
  v8_args = [];
  for (i = 0; i < args_refs.length; i++) {
    v8_arg = V8_GetLocalFromPersistentHandle(args_refs[i]);
    v8_args.push(v8_arg);
  }

  // 在指定上下文中调用函数
  result = V8_FunctionCall(v8_function, this_context, v8_args);

  // 处理可能的 Promise 结果(如果回调返回 Promise)
  if (V8_IsPromise(result)) {
    V8_RegisterPromiseHooks(result);
  }

  return result;
}

// V8 微任务处理
function V8_RunMicrotasks() {
  // 执行所有待处理的微任务,直到队列为空
  // 这包括 Promise 回调和通过 queueMicrotask() 注册的任务
  V8_IsolateMicrotaskQueue.DrainMicrotaskQueue();
}

// --- 7. 完整的计时器生命周期 ---
// 7.1. 创建:JavaScript setTimeout -> Node.js 绑定 -> libuv 计时器
// 7.2. 等待:libuv 事件循环与操作系统计时器交互
// 7.3. 触发:OS信号 -> libuv回调 -> Node.js 队列 -> V8 执行
// 7.4. 清理:持久化引用释放 -> 计时器资源清理

// Node.js 主函数(初始化并启动事件循环)
function Node_Main(argc, argv) {
  // 初始化 V8 引擎
  V8_Initialize();

  // 初始化 Node.js 核心模块
  Nodejs_Initialize_Platform();
  Nodejs_RegisterBuiltInModules();

  // 初始化 libuv 事件循环
  Libuv_Loop_Init(uv_default_loop);

  // 执行主脚本(用户代码入口点)
  Nodejs_ExecuteEntryPoint();

  // 运行事件循环直到完成所有工作
  Nodejs_EventLoop_Run();

  // 清理资源并退出
  Nodejs_Cleanup();
  V8_Dispose();

  return exit_code;
}

Contributors

No contributors

Changelog

No recent changes

Discuss

Released under the CC BY-SA 4.0 License. (1ab673e)