Programming/Node.js

Node.js C++ Module Thread Callback

acidpop 2012. 4. 5. 16:51
반응형

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/

 

반응형