这篇文章主要从node源码的./lib/timers.js 入手,历经./lib/internal/timers.js、./src/timer_wrap.cc,并最终下沉到./deps/uv/src/unix/timer.c来叙述整体timers的实现流程。
前言
在阅读 ./lib/timers.js代码的时候,首先映入眼帘的便是如下这几行注释:
// ╔════ > Object Map
// ║
// ╠══
// ║ refedLists: { '40': { }, '320': { etc } } (keys of millisecond duration)
// ╚══ ┌─────────┘
// │
// ╔══ │
// ║ TimersList { _idleNext: { }, _idlePrev: (self), _timer: (TimerWrap) }
// ║ ┌────────────────┘
// ║ ╔══ │ ^
// ║ ║ { _idleNext: { }, _idlePrev: { }, _onTimeout: (callback) }
// ║ ║ ┌───────────┘
// ║ ║ │ ^
// ║ ║ { _idleNext: { etc }, _idlePrev: { }, _onTimeout: (callback) }
// ╠══ ╠══
// ║ ║
// ║ ╚════ > Actual JavaScript timeouts
// ║
// ╚════ > Linked List
我们按照注释的引导,一点一点把整体的timers.js结构扒开,看它究竟做了什么。
timers中的双向链表
按照注释中的引子,我们先来看一下TimersList究竟是什么样子的,目光直接移向下面这段代码:
function TimersList(msecs, unrefed) {
this._idleNext = this; // Create the list with the linkedlist properties to
this._idlePrev = this; // prevent any unnecessary hidden class changes.
this._unrefed = unrefed;
this.msecs = msecs;
this.nextTick = false;
const timer = this._timer = new TimerWrap();
timer._list = this;
if (unrefed === true)
timer.unref();
timer.start(msecs);
timer[kOnTimeout] = listOnTimeout;
}
这里是TimersList的实现代码,不管其他的,看到this.xxxNext
和this.xxxPrev
,第一时间就会想到双向链表。没错,TimersList就是一个双向链表,那么它的两端分别连接的是什么呢?
function insert(item, unrefed) {
const msecs = item._idleTimeout;
if (msecs < 0 || msecs === undefined) return;
item._idleStart = TimerWrap.now();
const lists = unrefed === true ? unrefedLists : refedLists;
var list = lists[msecs];
if (list === undefined) {
debug('no %d list was found in insert, creating a new one', msecs);
lists[msecs] = list = new TimersList(msecs, unrefed);
}
//other codes...
L.append(list, item);
assert(!L.isEmpty(list));
}
大家可以看一下这个insert函数,其中实现了TimersList的实例化,其中:
const lists = unrefed === true ? unrefedLists : refedLists;
var list = lists[msecs];
if (list === undefined) {
debug('no %d list was found in insert, creating a new one', msecs);
lists[msecs] = list = new TimersList(msecs, unrefed);
}
这几句话基本涵盖了刚才注释里面的refedLists
以及TimersList
,这里可以看出referedLists是一个对象,而对象的key是变量msecs, value则对应一个TimersList。
之后的L.append(list, item);
,L是来自于./lib/internal/linkedlist.js中操作双向链表的方法,append则是在链表后面插入一个元素,而list是实例化的TimersList,所以也就是在TimersList后插入新的item,到此为止,整体结构如下所示:
refedLists = {
[msecs0]: item<->item<->item<->item<->item,//<->代指双向链表TimersList
[msecs1]: item<->item<->item<->item<->item,
[msecs2]: item<->item<->item<->item<->item
......
}
msecs是什么
在理解了注释的大体结构之后,那么下一个疑问就来了,msecs到底指的是什么?
通过翻阅./lib/timers.js的代码可以找到几个和msecs强相关的地方:
function insert(item, unrefed) {
const msecs = item._idleTimeout;
if (msecs < 0 || msecs === undefined) return;
//...
if (list === undefined) {
debug('no %d list was found in insert, creating a new one', msecs);
lists[msecs] = list = new TimersList(msecs, unrefed);
}
//...
}
function TimersList(msecs, unrefed) {
//...
this._unrefed = unrefed;
this.msecs = msecs;
this.nextTick = false;
//...
}
从这些代码可以看出来msecs的起源实际是item._idleTimeout,而item根据如下几个地方溯源:
const timerInternals = require('internal/timers');
const Timeout = timerInternals.Timeout;
const timeout = new Timeout(callback, after, args, false);//实例化之后传入到item中了
const active = exports.active = function(item) {
insert(item, false);
};
active(timeout);
直接跟进到./lib/internal/timers.js, 其中有这样一段代码:
function Timeout(callback, after, args, isRepeat) {
//...
this._onTimeout = callback;
this._idleTimeout = after;
//...
}
所以,msesc其实是Timeout的第二个参数–after,即setTimeout API中的第二个参数,延迟几秒执行,_onTimeout就是最终要执行的callback
读完这个章节,我们的整体结构可以修改成:
// <->代指双向链表TimersList,
// 10/100/1000分别为延迟10/100/1000毫秒后执行
refedLists = {
10: item<->item<->item<->item<->item,
100: item<->item<->item<->item<->item,
1000: item<->item<->item<->item<->item
......
}
TimersList的TimerWrap
根据上面的描述,基本上整体时间链已经比较清晰了:setTimout(callback,time) 会根据time来向refedLists[time]中append一个新的item。那么这个TimersList的机制是什么样的呢?
这里我们要从一个细节入手,在定义TimersList的地方,有一行代码:
const {
Timer: TimerWrap,
setImmediateCallback,
} = process.binding('timer_wrap');
function TimersList(msecs, unrefed) {
//...
const timer = this._timer = new TimerWrap();
timer._list = this;
//...
timer.start(msecs);
timer[kOnTimeout] = listOnTimeout;
//...
}
从这里我们可以看到TimersList有一个属性_timer,而这个_timer的来源又很隐蔽,是通过binding方法挂载进来的内置api。在这种情况下,矛头直接指向./src/timer_wrap.cc中一探究竟。
class TimerWrap : public HandleWrap {
public:
static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context) {
// ...
env->SetTemplateMethod(constructor, "now", Now);
AsyncWrap::AddWrapMethods(env, constructor);
env->SetProtoMethod(constructor, "close", HandleWrap::Close);
env->SetProtoMethod(constructor, "ref", HandleWrap::Ref);
env->SetProtoMethod(constructor, "unref", HandleWrap::Unref);
env->SetProtoMethod(constructor, "hasRef", HandleWrap::HasRef);
env->SetProtoMethod(constructor, "start", Start);
env->SetProtoMethod(constructor, "stop", Stop);
//...
}
NODE_BUILTIN_MODULE_CONTEXT_AWARE(timer_wrap, node::TimerWrap::Initialize)
可以看到,在node中构造了timer_wrap的构造函数,其中包含了若干方法和属性,在这里我们先重点看一下start方法:
TimerWrap(Environment* env, Local<Object> object)
: HandleWrap(env,
object,
reinterpret_cast<uv_handle_t*>(&handle_),
AsyncWrap::PROVIDER_TIMERWRAP) {
int r = uv_timer_init(env->event_loop(), &handle_);
CHECK_EQ(r, 0);
}
static void Start(const FunctionCallbackInfo<Value>& args) {
TimerWrap* wrap = Unwrap<TimerWrap>(args.Holder());
CHECK(HandleWrap::IsAlive(wrap));
int64_t timeout = args[0]->IntegerValue();
int err = uv_timer_start(&wrap->handle_, OnTimeout, timeout, 0);
args.GetReturnValue().Set(err);
}
首先,TimerWrap会执行uv_timer_init(env->event_loop(), &handle_)
。uv_timer_init为libuv中定时器的初始化函数,它的参数有两个:第一个参数是定时器初始化时候的主event_loop,第二个参数为指向uv_timer_t的指针。一般以handle命名(因为uv_timer_t为uv_handle_t的子类,uv_handle_t的结构体声明中又包含了uv_loop_t),handle相当于uv_timer_start之后的唯一标识,如下我所了解到的一些用法:
- handle->flags 标识此timer是否已经结束
- handle->type 标识此timer的类型,可选类型有:UV_TCP、UV_NAMED_PIPE、UV_TTY
- handle->timer_cb libuv需要执行的回调函数
- handle->timeout 定时执行的绝对时间
- handle->repeat 是否重复执行
- handle->start_id 定时器启动的id,初始化在loop->timer_counter(uv_loop_t * loop)中,初始为0,逐渐递增
我对libuv也不是十分了解,如上是我大致了解的handle的用途,还希望大家给予补充。
uv_timer_start四个参数的意义可以在 ./deps/uv/src/unix/timer.c中找到,分别为:handle、回调、延迟执行的时间、是否重复执行。
问题马上迎刃而解了,下面我们只需要关注一下OnTimeout:
static void OnTimeout(uv_timer_t* handle) {
TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);
Environment* env = wrap->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
wrap->MakeCallback(kOnTimeout, 0, nullptr);
}
在这里有一个比较重要的过程,就是handle->data
,这里的handle指向的是uv_timer_t,而这个data则是通过node中的HandleWrap传递给libuv的handle_warp.cc:
class TimerWrap : public HandleWrap{}
HandleWrap::HandleWrap(Environment* env,
Local<Object> object,
uv_handle_t* handle,
AsyncWrap::ProviderType provider)
: AsyncWrap(env, object, provider),
state_(kInitialized),
handle_(handle) {
handle_->data = this;// 在这里把上下文传递给libuv
HandleScope scope(env->isolate());
Wrap(object, this);
env->handle_wrap_queue()->PushBack(this);
}
所以接下来就可以通过wrap->MakeCallback
调用MakeCallback实现对js函数的调用。
通过这一章节的叙述,可以基本缕清refedLists的整体结构:
// <->代指双向链表TimersList,
// 10/100/1000分别为延迟10/100/1000毫秒后执行
// TimerWrap中为10毫秒后待执行的链表
refedLists = {
10: TimerWrap._list (TimersList(item<->item<->item<->item<->item)),
100: TimerWrap._list (TimersList(item<->item<->item<->item<->item)),
1000: TimerWrap._list (TimersList(item<->item<->item<->item<->item))
......
}
执行阶段
沿用刚才的逻辑,其实暴露出来的timer.start
方法就会调用uv_timer_start
来启动定时循环处理,由此可见,在TimersList中:
function TimersList(msecs, unrefed) {
// ...
timer.start(msecs);
timer[kOnTimeout] = listOnTimeout;
}
随着每一个TimersList的注册,会启动uv_timer_start,来开始整体的一个事件循环,拿上面的总结举例子来说:
refedLists = {
10: TimerWrap._list (TimersList(item<->item<->item<->item<->item))
......
}
当refedLists中注册了10毫秒后需要执行的TimersList的时候,旋即启动uv_timer_start,每隔十秒执行timer[kOnTimeout] = listOnTimeout
即listOnTimeout,下面是整个完整的回调函数调用链:
function listOnTimeout() {
// ...
while (timer = L.peek(list)) {
// ...
}
// ...
tryOnTimeout(timer, list);
}
function tryOnTimeout(timer, list) {
// ...
try {
ontimeout(timer);
threw = false;
} finally {
// ...
}
// ...
}
function ontimeout(timer) {
// ...
if (!args)
timer._onTimeout();
else
Reflect.apply(timer._onTimeout, timer, args);
if (timer._repeat)
rearm(timer);
}
刚才经过分析,大家应该还对_onTimeout
有印象吧?没错,这就是最后要执行的callback,也就是listOnTimeout
最终执行了setTimeout
方法的callback。至此,整个timers的整体流程就分析结束了。
by 小菜
如果有什么疑问或者我的表述有不正确的地方欢迎回复和指正。
原文地址:https://github.com/xtx1130/blog/issues/15 欢迎watch和star。