hardillb
hardillb

Reputation: 59648

Native to NodeJS Callback

I'm trying to write a native NodeJS module that will make callbacks periodically from a loop. I've think I've got my head round the libuv bits needed to get back onto the main thread to make the callback, but it's still crashing with a sigsegv at the moment that it makes the calback.

test_sbio.cpp:

#include <node.h>
#include "SBIONative.h"

namespace sbio {

using v8::Local;
using v8::Object;

void InitAll(Local<Object> exports) {
  SBIONative::Init(exports);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, InitAll)

}

SBIONative.h:

#ifndef SBIONative_H
#define SBIONative_H

#include <iostream>
#include <unistd.h> 

#include <uv.h>
#include <node.h>
#include <node_object_wrap.h>

namespace sbio {
    class SBIONative : public node::ObjectWrap {
    public:
        static void Init(v8::Local<v8::Object> exports);

    private:
        explicit SBIONative();
        ~SBIONative();

        static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
        static v8::Persistent<v8::Function> constructor;
        static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
        static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args);

        static void loop(void *arg);
        static void asyncmsg(uv_async_t* handle);
        v8::Persistent<v8::Function> cb;
        bool run_;
        uv_async_t async;
        uv_thread_t event_loop;
    };
}
#endif

SBIONative.cpp:

#include <SBIONative.h>

namespace sbio {
    using v8::Context;
    using v8::Function;
    using v8::FunctionCallbackInfo;
    using v8::FunctionTemplate;
    using v8::Isolate;
    using v8::HandleScope;
    using v8::Local;
    using v8::Number;
    using v8::Object;
    using v8::Persistent;
    using v8::String;
    using v8::Value;

    Persistent<Function> SBIONative::constructor;

    SBIONative::SBIONative()  {
    }

    SBIONative::~SBIONative() {
    }

    void SBIONative::Init(Local<Object> exports) {
      Isolate* isolate = exports->GetIsolate();

      // Prepare constructor template
      Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
      tpl->SetClassName(String::NewFromUtf8(isolate, "SBIONative"));
      tpl->InstanceTemplate()->SetInternalFieldCount(2);

      // Prototype
      NODE_SET_PROTOTYPE_METHOD(tpl, "start", Start);
      NODE_SET_PROTOTYPE_METHOD(tpl, "stop", Stop);

      constructor.Reset(isolate, tpl->GetFunction());
      exports->Set(String::NewFromUtf8(isolate, "SBIONative"),
                   tpl->GetFunction());

    }

    void SBIONative::New(const FunctionCallbackInfo<Value>& args) {
      Isolate* isolate = args.GetIsolate();

      if (args.IsConstructCall()) {
        // Invoked as constructor: `new MyObject(...)`
        SBIONative* obj = new SBIONative();
        obj->Wrap(args.This());
        args.GetReturnValue().Set(args.This());

        uv_loop_t* loop = uv_default_loop();
        uv_async_init(loop, &obj->async, asyncmsg);
      } else {
        // Invoked as plain function `MyObject(...)`, turn into construct call.
        const int argc = 1;
        Local<Value> argv[argc] = { args[0] };
        Local<Context> context = isolate->GetCurrentContext();
        Local<Function> cons = Local<Function>::New(isolate, constructor);
        Local<Object> result =
            cons->NewInstance(context, argc, argv).ToLocalChecked();
        args.GetReturnValue().Set(result);
      }
    }

    void SBIONative::Start(const FunctionCallbackInfo<Value>& args) {
        std::cout << "Start\n";
        Isolate* isolate = args.GetIsolate();
        SBIONative* obj = ObjectWrap::Unwrap<SBIONative>(args.Holder());
        obj->run_ = true;

        Local<Function> callback = Local<v8::Function>::Cast(args[0]);
        obj->cb.Reset(isolate, callback);

        uv_thread_create(&obj->event_loop, SBIONative::loop, (void*)obj);
    }

    void SBIONative::Stop(const FunctionCallbackInfo<Value>& args) {
        std::cout << "Stop\n";
        SBIONative* obj = ObjectWrap::Unwrap<SBIONative>(args.Holder());
        obj->run_ = false;
        uv_close((uv_handle_t*) &obj->async, NULL);
    }

    void SBIONative::loop(void *arg) {
        SBIONative* obj = (SBIONative*)arg;

        while(obj->run_) {
            usleep(1 * 1000 * 1000);
            std::cout << "loop\n";
            obj->async.data = &obj->cb;
            uv_async_send(&obj->async);
        }

        std::cout << "after loop" << std::endl;
    }

    void SBIONative::asyncmsg(uv_async_t* handle) {
        v8::Isolate* isolate = v8::Isolate::GetCurrent();
        v8::HandleScope scope(isolate);
        Local<Value> argv[] = { v8::String::NewFromUtf8(isolate, "Hello world") };
        //TODO This breaks here
        ((v8::Function*)handle->data)->Call(isolate->GetCurrentContext()->Global(), 1, argv);
        std::cout << "callback" << std::endl;
    }
}

This fails in the same place if I build it on a Mac or on Linux. My best guess to date is to do with the storing of the callback in the object or casting it in the asyncmsg function

Upvotes: 0

Views: 484

Answers (1)

hardillb
hardillb

Reputation: 59648

After having another go at this today I've got a working solution. The trick was to use Nan rather than try and roll my own native interface.

addon.c:

#include <nan.h>
#include "test.h"

void InitAll(v8::Local<v8::Object> exports) {
  test::Test::Init(exports);
}

NODE_MODULE(addon, InitAll)

test.h:

#ifndef TEST_H
#define TEST_H

#include <nan.h>

#include <iostream>
#include <unistd.h> 

namespace test {
    class Test : public Nan::ObjectWrap {
    public:
        static void Init(v8::Local<v8::Object> exports);
    private:
        explicit Test();
        ~Test();

        static void New(const Nan::FunctionCallbackInfo<v8::Value>& info);
        static void Start(const Nan::FunctionCallbackInfo<v8::Value>& info);
        static void Stop(const Nan::FunctionCallbackInfo<v8::Value>& info);
        static void loop(void *arg);
        static void asyncmsg(uv_async_t* handle);
        static Nan::Persistent<v8::Function> constructor;

        bool run_;
        Nan::Callback* callback;

        uv_async_t async;
        uv_thread_t event_loop;
    };
}

#endif

test.cpp:

#include "test.h"

namespace test {

    Nan::Persistent<v8::Function> Test::constructor;

    Test::Test(){}

    Test::~Test(){}

    void Test::Init(v8::Local<v8::Object> exports) {
        Nan::HandleScope scope;

        v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
        tpl->SetClassName(Nan::New("Test").ToLocalChecked());
        tpl->InstanceTemplate()->SetInternalFieldCount(1);

        Nan::SetPrototypeMethod(tpl, "start", Start);
        Nan::SetPrototypeMethod(tpl, "stop", Stop);

        constructor.Reset(tpl->GetFunction());
        exports->Set(Nan::New("Test").ToLocalChecked(), tpl->GetFunction());
    }

    void Test::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
        if (info.IsConstructCall()) {
        // Invoked as constructor: `new Test(...)`
        test::Test* obj = new test::Test();
        obj->Wrap(info.This());
        info.GetReturnValue().Set(info.This());
        uv_loop_t* loop = uv_default_loop();
            uv_async_init(loop, &obj->async, asyncmsg);
      } else {
        // Invoked as plain function `Test(...)`, turn into construct call.
        const int argc = 1;
        v8::Local<v8::Value> argv[argc] = { info[0] };
        v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor);
        info.GetReturnValue().Set(cons->NewInstance(argc, argv));
      }
    }

    void Test::Start(const Nan::FunctionCallbackInfo<v8::Value>& info) {
        Test* obj = ObjectWrap::Unwrap<Test>(info.Holder());
        obj->callback = new Nan::Callback(info[0].As<v8::Function>());
        obj->run_ = true;

        uv_thread_create(&obj->event_loop, Test::loop, (void*)obj);
    }

    void Test::Stop(const Nan::FunctionCallbackInfo<v8::Value>& info) {
        Test* obj = ObjectWrap::Unwrap<Test>(info.Holder());
        obj->run_ = false;
    }

    void Test::loop(void *arg) {    
        Test* obj = (Test*)arg;

        while(obj->run_) {
            usleep(1 * 1000 * 1000);
            obj->async.data = obj->callback;
            uv_async_send(&obj->async);
        }

        uv_close((uv_handle_t*) &obj->async, NULL);
    }

    void Test::asyncmsg(uv_async_t* handle) {
        Nan::HandleScope scope;
        v8::Isolate* isolate = v8::Isolate::GetCurrent();
        v8::Local<v8::Value> argv[] = { v8::String::NewFromUtf8(isolate, "Loop Callback") };
        ((Nan::Callback*)handle->data)->Call(1,argv);
    }

}

Write up and link to full project code on github here

Upvotes: 1

Related Questions