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/