300x250 AD TOP

Search This Blog

Pages

Paling Dilihat

Powered by Blogger.

Showing posts with label NAN. Show all posts
Showing posts with label NAN. Show all posts

Wednesday, November 30, 2016

Async in Node.js Addons with NAN

Async in node js is implemented as threadpool jobs. The way node js works is that V8 is executed in the main thread, when V8 needs to call a c++ function it can either execute it in the main thread or it can schedule it in the threadpool.

Node js uses libuv asynchronous I/O library. by default it uses 4 threads in the threadpool, but it can be changed with the UV_THREADPOOL_SIZE environment variable. (ref)

The way NAN async works is, first you define a c++ class (which inherits from AsyncWorker or that accepts a callback and c++ primitive data types as parameters. then you define the Execute function which does the actual work and lastly you define the HandleOKCallback function which will do the actual callback.

The first step is to create a Nan::Callback instance from the v8::Function parameter, which is a standard Javascript error-first callback, which later can be converted to Promises.


// Asynchronous access to the `Estimate()` function
NAN_METHOD(CalculateAsync) {
  int points = To<int>(info[0]).FromJust();
  Callback *callback = new Callback(info[1].As<Function>());

  AsyncQueueWorker(new PiWorker(callback, points));
}

If you need to keep data between the call and the callback you can use SaveToPersistent and GetFromPersistent, this data is not available inside the async execution but its only used between the Callback instantiation and the javascript callback after execution.

From v8 Embedder's Guide:

Persistent handles provide a reference to a heap-allocated JavaScript Object, just like a local handle. There are two flavors, which differ in the lifetime management of the reference they handle. Use a persistent handle when you need to keep a reference to an object for more than one function call, or when handle lifetimes do not correspond to C++ scopes. Google Chrome, for example, uses persistent handles to refer to Document Object Model (DOM) nodes. A persistent handle can be made weak, using PersistentBase::SetWeak, to trigger a callback from the garbage collector when the only references to an object are from weak persistent handles.
If you need to use data for the execution you must use native c/c++ types and you must do it while creating the class instance, either in the constructor or before calling AsyncQueueWorker (more on this later).

In our example, PiWorker needs the number of points to calculate an estimation of PI, since we don't have access to any of the V8 values that we got in the function arguments (e.g. info[0]), we'll need to convert the value to C type and then use it in the Async Worker Execute.

Now we have created an instance of PiWorker, which inherits from AsyncWorker, we passed the number of points AND the callback which was passed to our CalculateAsync function:


addon.calculateAsync(100, function(err,result){
  if (err){
    console.log("error executing pi calculations", err);
    return;
  }

  console.log("result", result);
});

We can schedule the PiWorker on the threadpool



// Asynchronous access to the `Estimate()` function
NAN_METHOD(CalculateAsync) {
  int points = To<int>(info[0]).FromJust();
  Callback *callback = new Callback(info[1].As<Function>());

  AsyncQueueWorker(new PiWorker(callback, points));
}

actually, when calling AsyncQueueWorker it schedules the Execute for execution and WorkComplete  to execute (on the main thread) when the execution is done.


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)
  );
}

The Execute method will be executed in one of the threads of the threadpool and when its done, it will execute either the HandleOKCallback or the HandleErrorCallback depending if the SetErrorMessage was called or not.


virtual void WorkComplete() {
  HandleScope scope;

  if (errmsg_ == NULL)
    HandleOKCallback();
  else
    HandleErrorCallback();
  delete callback;
  callback = NULL;
}

So writing Async Callbacks is pretty simple once you understand the program flow.

Source Code:
https://github.com/nodejs/nan/tree/master/examples/async_pi_estimate
Tags: , ,

Saturday, October 22, 2016

Node js C++ Addon Overload Resolution


Function overloads is not part of javascript as functions are objects inside object properties and object property names are unique.


I've attempted to add overload resolution on top of node js c++ addons for easily importing c++ libraries by having a middleware between v8 and the actual code executed.


Before you read on, make sure you understand Native Abstractions for Node.js (NAN) as it relies on it for some of its functionality and builds on top of it.

You can find the project and its tests at:
https://github.com/drorgl/node-overload-resolution

Structure

overload_resolution - the main class that registers and calls the overloaded functions, it provides a macro POLY_METHOD which is almost identical to NAN_METHOD for declaring functions and calling them after the correct overload has been determined.
by itself, the class does nothing, you will still have to register all the callbacks to overload_resolution::execute function for it to do anything:
Declare a callback:
namespace general_callback {
    std::shared_ptr<overload_resolution> overload;
    NAN_METHOD(tester_callback) {
        return overload->execute("current_namespace", info);
    }
}
in the init function, populate general_callback::overload with overload resolution instance:
general_callback::overload = overload;
and tell v8 you want to callback into tester_callback:
Nan::SetMethod(target, "overloaded_function", general_callback::tester_callback);
now the hook is ready to call overload_resolution::execute whenever overloaded_function is called, but we need to tell overload_resolution which function to call when an overload is matched:
constructor:
overload->addOverloadConstructor(
            "", 
                    "constructor_class_tester", 
                            { std::make_shared<overload_info>("a","Number",Nan::Undefined()) }, 
                                    New_number);
            ^ namespace or empty
                    ^ class name 
                            ^ array of parameter names, types and default values
                              note that Nan::Undefined is used as no default
                                    ^ callback POLY_METHOD
static:
overload->addStaticOverload(
            "", 
                    "constructor_class_tester", 
                            "static_function", 
                                    { std::make_shared<overload_info>("a","String",Nan::Undefined()) }, 
                                            static_function_static_string);
            ^ namespace or empty
                    ^ class name or function group name or empty
                            ^ function name
                                    ^ array of parameter names, types and default values
                                            ^ callback POLY_METHOD
member:
overload->addOverload(
            "", 
                    "constructor_class_tester", 
                            "member_function", 
                                    { std::make_shared<overload_info>("a","Map",Nan::Undefined()) }, 
                                            static_function_instance_map);
            ^ namespace or empty
                    ^ class name
                            ^ function name
                                    ^ array of parameter names, types and default values
                                            ^ callback POLY_METHOD

Issues

Currently the project is working but inefficient, it does a lot behind the scenes to determine which overload should be called and hopefully the performance issues will be resolved in the future, the slowest actions are around structs, which are very slow, they were intended as automatic object structure recognition and overload resolution accordingly.

Tests

Attempts were made to have as many tests as possible, at the moment of this update there are 2336 tests, all pass.

Future

If there will be future development, it should do the following:
  • performance analysis, solve the struct performance, check prototype inheritance performance and correctness, check big arrays performance.
  • implement macros
  • cleanup function registration
  • add logging and instrumentation
Tags: , , ,