Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

node源码粗读(8):setImmediate注册+触发全流程解析 #19

Open
xtx1130 opened this issue Mar 5, 2018 · 0 comments
Open

Comments

@xtx1130
Copy link
Owner

xtx1130 commented Mar 5, 2018

本篇文章主要介绍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 processingsetTimeout的触发函数也修改为了使用setupTimers注册)。

node中对setImmediate的处理

processImmediate函数在node中的注册

接上文,我们视线转移到./src/timer_wrap.ccSetupTimers函数中:

// 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 小菜

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant