casynchronousnode.js-addonn-api

How to use napi_threadsafe_function for NodeJS Native Addon


I've been looking through the NAPI documentation to try and understand how it deals with multithreading. According to the documentation napi_create_threadsafe_function() and napi_call_threadsafe_function() are used to create and call js functions from multiple threads. The issue is that the documentation is not that straight forward, and there are no examples and I can't find any anywhere else.

If anyone has any experience using napi_create_threadsafe_function() and napi_call_threadsafe_function() or know where to find examples of them being used. I am seeking a basic example so I can understand how to use them correctly.

I'm writing a C addon not C++ and need to use these functions. I am not using the wrapper node-addon-api, but napi directly.


Solution

  • As a summery tag we may say, the N-API ThreadSafeFunctions acts as a safe tunnel between the asynchronous C/C++ code executing on a worker thread and the JavaScript layer for information exchange.

    Before going technical let us consider a scenario that we have a very long running process heavy task to be completed. We all know putting this task on node.js main thread is not a good choice, it will chock the event loop and block all other task in the queue. So a good choice could be to consider this task in a separate thread (let us call this thread as a worker thread). JavaScript asynchronous callback and Promise are doing exactly this approach.

    Let us say we have deployed the task on a worker thread and we are ready with a portion of result and we would like it to be send to JavaScript layer. Then the process involve are, converting the result into napi_value and then call the Callback JavaScript function from C/C++. Unfortunately neither of the operation can be performed from a worker thread; these operations should be exclusively done from the main thread. The JavaScript Promise and Callback, wait till the task completion and then switch over to main thread along with the task result in a normal C/C++ storage facility such as structure etc. Then do the napi_value conversion and call the JavaScript callback function from the main thread.

    Since our task is extremely long running probably we don't want to wait till the end of the task before exchanging the result with JavaScript layer. Let us consider a scenario where we are searching objects in a very large video where we prefer to get the detected objects send to JavaScript layer as on when it is found. In such a scenario we will have to start sending task result while the task is still in progress. This is the scenario where Asynchronous Thread-safe Function Calls come for our help. It acts as a safe tunnel between the worker thread and the JavaScript layer for information exchange. Let us consider the following function snippet

    napi_value CAsyncStreamSearch(napi_env env, napi_callback_info info)
    {
        // The native addon function exposed to JavaScript
        // This will be the funciton a node.js application calling.
    }
    
    void ExecuteWork(napi_env env, void* data)
    {
        // We will use this function to get the task done.
        // This code will be executed on a worker thread.
    }
    
    void OnWorkComplete(napi_env env, napi_status status, void* data)
    {
        // after the `ExecuteWork` function exits, this
        // callback function will be called on the main thread
    }
    
    void ThreadSafeCFunction4CallingJS(napi_env env, napi_value js_cb,
                     void* context, void* data)
    {
       // This funcion acts as a safe tunnel between the asynchronous C/C++ code 
       // executing the worker thread and the JavaScript layer for information exchange.
    }
    

    In this first three functions are nearly same as JavaScript Promise and Callback that we are familiar with. The fourth one is specifically for the Asynchronous Thread-safe Function Calls. In this, our long running task is being executed by ExecuteWork() function on a worker thread. Let us say it has instructed us not to call JavaScript (and also any napi_value conversion of result) from ExecuteWork() but permitted to do so from ThreadSafeCFunction4CallingJS as long as we are calling ThreadSafeCFunction4CallingJS with an napi equivalent of C/C++ function pointer. Then we could pack the JavaScript calls inside this ThreadSafeCFunction4CallingJS() function. Then when ExecuteWork() function could pass the result to ThreadSafeCFunction4CallingJS() while it is being invoked in a plain C/C++ storage units such as structure etc. The ThreadSafeCFunction4CallingJS() convert this result to napi_value and call JavaScript function. Under the cover the ThreadSafeCFunction4CallingJS() function is being queue to the event loop, and eventually it get executed by main thread.

    The following code snippet packed inside CAsyncStreamSearch() is responsible for creating a C/C++ function pointer equivalent of N-API by usng napi_create_threadsafe_function() and it is being done from the native addon's main thread itself. Similarly the request for creation of worker thread by using napi_create_async_work() function then placing the work int the event queue by using napi_queue_async_work() so that a worker thread will pickup this item in the future.

    napi_value CAsyncStreamSearch(napi_env env, napi_callback_info info)
    {
    -- -- -- --
    -- -- -- --
      // Create a thread-safe N-API callback function correspond to the C/C++ callback function
      napi_create_threadsafe_function(env,
          js_cb, NULL, work_name, 0, 1, NULL, NULL, NULL,
          ThreadSafeCFunction4CallingJS, // the C/C++ callback function
          // out: the asynchronous thread-safe JavaScript function
          &(async_stream_data_ex->tsfn_StreamSearch));
    
      // Create an async work item, that can be deployed in the node.js event queue
      napi_create_async_work( env, NULL,
           work_name,
           ExecuteWork,
           OnWorkComplete,
           async_stream_data_ex,
           // OUT: THE handle to the async work item
           &(async_stream_data_ex->work_StreamSearch);)
    
      // Queue the work item for execution.
      napi_queue_async_work(env, async_stream_data_ex->work_StreamSearch);
    
      return NULL;
    }
    

    Then during the asynchronous execution of task (ExecuteWork() function) invokes ThreadSafeCFunction4CallingJS() by calling napi_call_threadsafe_function() function as shown bellow.

    static void ExecuteWork(napi_env env, void *data)
    {
      // tsfn is napi equivalent of point to ThreadSafeCFunction4CallingJS
      // function that we created at CAsyncStreamSearch function
      napi_acquire_threadsafe_function( tsfn )
      Loop
      {
        // this will eventually invoke ThreadSafeCFunction4CallingJS()
       // we may call any number of time (in fact it can be called from any thread)
        napi_call_threadsafe_function( tsfn, WorkResult, );
      }
      napi_release_threadsafe_function( tsfn,);
    }
    

    The example you pointed out is one of the best source of information and it is directly form node.js team itself. When I was learning this concept I too was referring the same example, during my study the example has been recreated by extracting original idea from it, hope you may find this much simplified. and it is available at

    https://github.com/msatyan/MyNodeC/blob/master/src/mync1/ThreadSafeAsyncStream.cpp https://github.com/msatyan/MyNodeC/blob/master/test/ThreadSafeAsyncStream.js