Node.js C++ Module Thread Callback
Node.js 를 공부 하던 중 내가 사용중이던 Library 를 붙여볼 일이 생겼다.
라이브러리는 내부적으로 Callback Function Pointer 를 등록을 시키면 다른 쓰레드에서 Callback Function Pointer 를 호출하는 구조이다.
위와 같이 등록한곳과 다른 Thread 에서 Callback Function 을 호출하게 되면 MFC 같은 메시지 펌프 구조로 된 프레임에서는
ASSERT 오류가 발생한다.
마찬가지로 Node.js 도 역시 Segmentation Fault 가 떨어진다.
Callback 을 등록하고 시험적으로 만든 모듈은 정상 작동하는데
라이브러리만 붙이면 알수 없는 위치에서 Segmentation Fault 가 떨어져서 한참 구글링을 하던 중
해결방법이 있는 영문 블로그 글이 있어 이곳에 다시 한번 정리 한다.
#include <queue>
// node headers
#include <v8.h>
#include <node.h>
#include <ev.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
using namespace node;
using namespace v8;
// handles required for callback messages
static pthread_t texample_thread;
static ev_async eio_texample_notifier;
Persistent<String> callback_symbol;
Persistent<Object> module_handle;
// message queue
std::queue<int> cb_msg_queue = std::queue<int>();
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
// The background thread
static void* TheThread(void *)
{
int i = 0;
while(true) {
// fire event every 5 seconds
sleep(5);
pthread_mutex_lock(&queue_mutex);
cb_msg_queue.push(i);
pthread_mutex_unlock(&queue_mutex);
i++;
// wake up callback
ev_async_send(EV_DEFAULT_UC_ &eio_texample_notifier);
}
return NULL;
}
// callback that runs the javascript in main thread
static void Callback(EV_P_ ev_async *watcher, int revents)
{
HandleScope scope;
assert(watcher == &eio_texample_notifier);
assert(revents == EV_ASYNC);
// locate callback from the module context if defined by script
// texample = require('texample')
// texample.callback = function( ... ) { ..
Local<Value> callback_v = module_handle->Get(callback_symbol);
if (!callback_v->IsFunction()) {
// callback not defined, ignore
return;
}
Local<Function> callback = Local<Function>::Cast(callback_v);
// dequeue callback message
pthread_mutex_lock(&queue_mutex);
int number = cb_msg_queue.front();
cb_msg_queue.pop();
pthread_mutex_unlock(&queue_mutex);
TryCatch try_catch;
// prepare arguments for the callback
Local<Value> argv[1];
argv[0] = Local<Value>::New(Integer::New(number));
// call the callback and handle possible exception
callback->Call(module_handle, 1, argv);
if (try_catch.HasCaught()) {
FatalException(try_catch);
}
}
// Start the background thread
Handle<Value> Start(const Arguments &args)
{
HandleScope scope;
// start background thread and event handler for callback
ev_async_init(&eio_texample_notifier, Callback);
//ev_set_priority(&eio_texample_notifier, EV_MAXPRI);
ev_async_start(EV_DEFAULT_UC_ &eio_texample_notifier);
ev_unref(EV_DEFAULT_UC);
pthread_create(&texample_thread, NULL, TheThread, 0);
return True();
}
void Initialize(Handle<Object> target)
{
HandleScope scope;
NODE_SET_METHOD(target, "start", Start);
callback_symbol = NODE_PSYMBOL("callback");
// store handle for callback context
module_handle = Persistent<Object>::New(target);
}
extern "C" {
static void Init(Handle<Object> target)
{
Initialize(target);
}
NODE_MODULE(texample, Init);
}
Building
wscript File
def set_options(opt):
opt.tool_options("compiler_cxx")
def configure(conf):
conf.check_tool("compiler_cxx")
conf.check_tool("node_addon")
def build(bld):
obj = bld.new_task_gen("cxx", "shlib", "node_addon")
obj.cxxflags = ["-g", "-D_FILE_OFFSET_BITS=64",
"-D_LARGEFILE_SOURCE", "-Wall"]
obj.target = "texample"
obj.source = "texample.cc"
Module Build 는 node-waf configure build 명령을 이용해서 Build 한다.
app.js
var example = require('./build/default/texample');
example.callback = function(i) {
console.log('Bang: ' + i);
};
example.start();
여기에서 중점적으로 봐야 할것이 callback_symbol 이란 변수와 module_handle 이란 변수
그리고 Start 함수에서 Event 를 등록하는 부분 ev_async_init, ev_async_start, ev_unref 함수이다.
메인 쓰레드 에서 발생하는 데이터를 전역 queue 에 push 한 다음
Callback 하는 쓰레드에서는 ev_async_send 함수를 이용해서 이벤트를 전송한다.
MFC 의 SendMessage 와 비슷한 역할을 하는것으로 보임.
C/C++ 개발자라면 충분히 이해 할 만한 코드라고 생각되며
Node.js 모듈을 개발할때 Thread 에서 Callback 을 구현하는 사용자라면 유용하게 사용할 수 있는 코드일듯 하다.
출처 : http://bravenewmethod.wordpress.com/2011/03/30/callbacks-from-threaded-node-js-c-extension/