Search code examples
c++node.jsv8libuvnode.js-nan

Invoking some callback function twice leads to Segmentation fault: Nan


I am writing C++ addon using nbind - GitHub link for most thing and Nan - GitHub link for calling callbacks asynchronous. When I invoke callback only once, it works perfect. But When I invoke callback twice it gives Segmentation fault (core dumped). Couldn't find error using gdb. Here is JS and C++ codes(compiling using node-gyp configure build):

//main.js code
var nbind = require('nbind');
var lib = nbind.init().lib;

lib.HeaderExample.callJS(function(a) {
console.log("result" + a);
});

lib.HeaderExample.startThread();
lib.HeaderExample.startThread(); 

C++ addon's code

//c++ code
class CallbackRunner : public Nan::AsyncWorker {
public:
    CallbackRunner(Nan::Callback *callback)
            : AsyncWorker(callback) {}
    void Execute () {}
    void HandleOKCallback () {
        std::cout << "running HandleOKCallback in thread " << std::this_thread::get_id() << std::endl;
        Nan::HandleScope scope;
        v8::Local<v8::Value> argv[] = {
                Nan::New<v8::Number>(10)
        };
        callback->Call(1, argv);
    }
};

class HeaderExample {
public:
    static void callJS(nbind::cbFunction &callback) {
        std::cout << "running callJS in thread " << std::this_thread::get_id() << std::endl;
        m_onInitialisationStarted = new nbind::cbFunction(callback);
        Nan::Callback *callbackNan = new Nan::Callback(m_onInitialisationStarted->getJsFunction());
        runner = new CallbackRunner(callbackNan);
    }
    static void startThread() {
        std::cout << "it is here";
        std::thread threadS(some);
        threadS.join();
    }
    static void some() {
        std::cout << "running some in thread: " << std::this_thread::get_id() << std::endl;
        if(runner){
        AsyncQueueWorker(runner);
        }
    }
    inline static nbind::cbFunction *m_onInitialisationStarted = 0;
    inline static CallbackRunner *runner;
};

Solution

  • Your class uses AsyncQueueWorker to invoke the CallbackRunner, but AsyncQueueWorker calls AsyncExecuteComplete after the callback is done, which in turn calls worker->Destroy(). See the AsyncQueueWorker code from nan.h:

    inline void AsyncExecute (uv_work_t* req) {
      AsyncWorker *worker = static_cast<AsyncWorker*>(req->data);
      worker->Execute();
    }
    
    inline void AsyncExecuteComplete (uv_work_t* req) {
      AsyncWorker* worker = static_cast<AsyncWorker*>(req->data);
      worker->WorkComplete();
      worker->Destroy();
    }
    
    inline void AsyncQueueWorker (AsyncWorker* worker) {
      uv_queue_work(
          uv_default_loop()
        , &worker->request
        , AsyncExecute
        , reinterpret_cast<uv_after_work_cb>(AsyncExecuteComplete)
      );
    }
    

    worker->Destroy() will delete the CallbackRunner class, along with the Nan::Callback that you fed to its constructor. That is the reason why you get a segmentation fault when attempting to call this callback a second time.

    You might be better off basing your class on Nan::AsyncProgressQueueWorker instead of Nan::AsyncWorker. AsyncProgressQueueWorker inherits AsyncWorker and it allows you to schedule work from the main thread just as AsyncWorker does, but it provides you with an ExecutionProgress class that allows you to use any thread to call back into the main thread any number of times while the original scheduled job is running.

    Nan::AsyncProgressQueueWorker was added to NAN in version 2.8.0