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