本篇文章主要介绍setImmediate底层的实现,主要涉及部分node和libuv源码。
前言
相信稍微对node感兴趣的同学都知道setImmediate触发是在event-loop的check阶段,那么这个setImmediate到底是在哪里实现的注册到uv_check中以及如何触发其中的回调呢?
js入口
如果要想撕开setImmediate的口子,那么最简单粗暴的方式就是直接从./lib/timers这里入手。闲话少说,直接撸代码:
// lib/timers.js setImmediate回调追踪
// ...
const Immediate = class Immediate {
constructor(callback, args) {
this._idleNext = null;
this._idlePrev = null;
// this must be set to null first to avoid function tracking
// on the hidden class, revisit in V8 versions after 6.2
this._onImmediate = null;
this._onImmediate = callback; // 注意这里
this._argv = args;
this._destroyed = false;
this[kRefed] = false;
initAsyncResource(this, 'Immediate');
this.ref();
immediateInfo[kCount]++;
immediateQueue.append(this);
}
// ...
}
// ...
这是setImmediate所调用的构造函数的一部分内容,从这里可以很明确的看出来我们注册的回调传递给了this._onImmediate
。
接下来我们看一下最终触发回调的代码:
// lib/timers.js setImmediate回调触发
function runCallback(timer) {
const argv = timer._argv;
if (typeof timer._onImmediate !== 'function')
return promiseResolve(timer._onImmediate, argv[0]);
if (!argv)
return timer._onImmediate();
Reflect.apply(timer._onImmediate, timer, argv); // timer._onImmediate通过apply触发
}
找起来也是轻松加愉快,通过_onImmediate
我们很快便找到了触发函数(或者直接找apply
)。这样我们可以通过函数名runCallback
一层一层向上溯源了。
最终溯源的结果在这里:
// lib/timers.js setImmediate溯源
const {
Timer: TimerWrap,
setupTimers,
} = process.binding('timer_wrap');
const [immediateInfo, toggleImmediateRef] =
setupTimers(processImmediate, processTimers);
向上溯源的函数是通过setupTimers
进行注册的,而setupTimers
定睛一看:process.binding
,内置模块timer_wrap
浮出了水面(在这里简单提一下,在最近的pr: timers: refactor timer list processing 中setTimeout
的触发函数也修改为了使用setupTimers
注册)。
node中对setImmediate的处理
processImmediate函数在node中的注册
接上文,我们视线转移到./src/timer_wrap.cc的SetupTimers
函数中:
// src/timer_wrap.cc
// ...
static void SetupTimers(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsFunction());
CHECK(args[1]->IsFunction());
auto env = Environment::GetCurrent(args);
env->set_immediate_callback_function(args[0].As<Function>());//注意这里
env->set_timers_callback_function(args[1].As<Function>());
auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
};
auto result = Array::New(env->isolate(), 2);
result->Set(env->context(), 0,
env->immediate_info()->fields().GetJSArray()).FromJust();
result->Set(env->context(), 1, toggle_ref_function).FromJust();
args.GetReturnValue().Set(result);
}
详细介绍一下env->set_immediate_callback_function(args[0].As<Function>())
。这句话的来源其实在env.h
中:
// src/env.h
// ...
V(immediate_callback_function, v8::Function)
// ...
#define V(PropertyName, TypeName) \
inline v8::Local<TypeName> PropertyName() const; \
inline void set_ ## PropertyName(v8::Local<TypeName> value);
ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V)
#undef V
整体意思是:定义了一个带参宏V
,而这个宏在调用的时候会定义一个属性,所以在通过V(immediate_callback_function, v8::Function)
调用后,可以实现env->set_immediate_callback_function
的调用了,同时还会生成一个成员函数PropertyName()
,所以在调用后同时也会使得env->immediate_callback_function
成为可调用函数。
args[0]
在这里指的便是processImmediate
,所以通过SetupTimers
可以使processImmediate
这个函数最终注册到node中。
libuv中对setImmediate的处理
processImmediate函数在libuv中的注册
视线转移到src/env.cc中,不知道大家是否还记得在第一章一个简单的nodejs文件从运行到结束都发生了什么中曾经一笔带过Environment::Start
。没错,setImmediate就是在Environment::Start
中注册到libuv的,接下来看代码:
// src/env.cc
// ...
void Environment::Start(int argc,
const char* const* argv,
int exec_argc,
const char* const* exec_argv,
bool start_profiler_idle_notifier) {
HandleScope handle_scope(isolate());
Context::Scope context_scope(context());
uv_check_init(event_loop(), immediate_check_handle());
uv_unref(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));
uv_idle_init(event_loop(), immediate_idle_handle());
uv_check_start(immediate_check_handle(), CheckImmediate);
// ...
}
有关于immediate
的总共有四句,接下来我们逐个详细介绍一下:
uv_check_init(event_loop(), immediate_check_handle());
这一句主要是初始化uv_check的handle;uv_unref(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));
这一句主要是解除uv_check的handle在event-loop中的引用,因为我们希望在event-loop中没有活跃handle的时候自动退出;uv_idle_init(event_loop(), immediate_idle_handle());
这里涉及到uv_idle的概念,uv_idle总是在uv_prepare阶段之前运行,在这里是用uv_idle_init
方法对uv_idle的handle进行初始化;uv_check_start(immediate_check_handle(), CheckImmediate);
在这里,真正的把上文提到的processImmediate
通过函数CheckImmediate
注册到了uv_check中。
uv_idle简介
在这里涉及到了uv_idle系列api,那么就给大家稍作介绍:
相信很多人都看过node官方那张经典的event-loop的图,在idle、prepare
阶段的下一阶段是poll
。而poll
阶段是不断轮训执行callback,所以是会阻塞的。具体的调用代码是uv__io_poll(loop, timeout);
,这里的timeout就是超时时间,具体设置timeout的代码可以看这里:
int uv_backend_timeout(const uv_loop_t* loop) {
if (loop->stop_flag != 0)
return 0;
if (!uv__has_active_handles(loop) && !uv__has_active_reqs(loop))
return 0;
if (!QUEUE_EMPTY(&loop->idle_handles))
return 0;
if (!QUEUE_EMPTY(&loop->pending_queue))
return 0;
if (loop->closing_handles)
return 0;
return uv__next_timeout(loop);
}
可以看到在一些情况下超时时间可以是0——即可以直接跨过poll阶段到达下一个check阶段,而check阶段就是setImmediate执行的阶段。这些可以跨过poll阶段的情况有:
- 使用
stop_flag
直接强制跨过; - event-loop中没有活跃的handle且没有活跃的请求时;
- idle不为空的时候;
- pending_queue不为空的时候(
uv__io_init
会初始化pending_queue); - 关闭handle的时候;
setImmediate正是利用了idle,实现了对poll阶段的跨越。
setImmediate与uv_idle
在src/env.cc中有一个比较不起眼的api–ToggleImmediateRef
:
void Environment::ToggleImmediateRef(bool ref) {
if (ref) {
// Idle handle is needed only to stop the event loop from blocking in poll.
uv_idle_start(immediate_idle_handle(), [](uv_idle_t*){ });
} else {
uv_idle_stop(immediate_idle_handle());
}
}
不知道大家还记得上文提到过的SetupTimers
吗,里面有一行代码:
static void SetupTimers(const FunctionCallbackInfo<Value>& args) {
// ...
auto toggle_ref_cb = [] (const FunctionCallbackInfo<Value>& args) {
Environment::GetCurrent(args)->ToggleImmediateRef(args[0]->IsTrue());
};
//...
}
结合这两个函数,很容易得出结论:setImmediate
通过函数ToggleImmediateRef
对uv_idle进行开关的控制,开的时候可以直接越过poll阶段,关的时候则执行poll阶段。
setImmediate的执行
刚才聊到了,通过uv_check_start(immediate_check_handle(), CheckImmediate);
把setImmediate
的上层函数processImmediate
通过CheckImmediate
注册到了uv_check中。接下来我们看下CheckImmediate
:
// src/env.cc
void Environment::CheckImmediate(uv_check_t* handle) {
Environment* env = Environment::from_immediate_check_handle(handle);
// ...
do {
MakeCallback(env->isolate(),
env->process_object(),
env->immediate_callback_function(),
0,
nullptr,
{0, 0}).ToLocalChecked();
} while (env->immediate_info()->has_outstanding());
if (env->immediate_info()->ref_count() == 0)
env->ToggleImmediateRef(false);
}
相信如果读者从头到尾贯穿下来的话,这里已经很明了了,通过一个do...while...
实现了对immediate_callback_function
的调用,即调用了js中的processImmediate
进而实现了setImmediate
的运行。在运行完后,通过env->ToggleImmediateRef(false);
实现对uv_idle
的停止,进而使得poll能阻塞处理回调。
结语
通过上面的分析,读者基本可以清晰了解到setImmediate
的整体注册和触发流程。而真正触发evnet-loop的,则是在src/node.cc中:
// src/node.cc
// ...
inline int Start(Isolate* isolate, IsolateData* isolate_data,
int argc, const char* const* argv,
int exec_argc, const char* const* exec_argv) {
// ...
Environment env(isolate_data, context);
env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling);// 触发Environment::Start
// ...
{
// ..
do {
uv_run(env.event_loop(), UV_RUN_DEFAULT); // 触发event-loop
} while (more == true);
}
// ...
}
by 小菜
原文地址:https://github.com/xtx1130/blog/issues/19,欢迎star和watch。如果叙述或者逻辑有问题,还请大家斧正。