Node.js has historically run as a single-threaded process. This all changed with the introduction of Worker Threads in Node 10. Worker Threads add a JavaScript-friendly concurrency abstraction that native add-on developers need to be aware of. What this means practically is that your native add-on may be loaded and unloaded more than once and its code may be executed concurrently in multiple threads. There are specific steps you must take to insure your native add-on code runs correctly.
The Worker Thread model specifies that each Worker runs completely independently of each other and communicate to the parent Worker using a MessagePort object supplied by the parent. This makes the Worker Threads essentially isolated from one another. The same is true for your native add-on.
Each Worker Thread operates within its own environment which is also referred to as a context. The context is available to each N-API function as an napi_env
value.
If your native add-on requires persistent memory, allocating this memory in static global space is a recipe for disaster. Instead, it is essential that this memory is allocated each time within the context in which the native add-on is initialized. This memory is typically allocated in your native add-on’s Init
method. But in some cases it can also be allocated as your native add-on is running.
In addition to the multiple loading described above, your native add-on is also subject to automatic unloading by the JavaScript runtime engine’s garbage collector when your native add-on is no longer in use. To prevent memory leaks, any memory your native add-on has allocated must be freed when you native add-on is unloaded.
The next sections describe two different techniques you can use to allocate and free persistent memory associated with your native add-on. The techniques may be used individually or together in your native add-on.
Note that the feature described here is currently experimental in Node 12.8.0 and later.
N-API gives you the ability to associate a single piece of memory your native-add allocates with the context under which it is running. This technique is called “instance data” and is useful when your native add-on allocates a single piece of data when its loaded.
The napi_set_instance_data
allows your native add-on to associate a single piece of allocated memory with the context under which you native add-on is loaded. The napi_get_instance_data
can then be called anywhere in you native add-on to retrieve the location of the memory that was allocated.
You specify a finalizer callback in your napi_set_instance_data
call. The finalizer callback gets called when your native add-on is released from memory and is where you should release the memory associated with this context.
Environment Life Cycle APIs Node.js documentation for napi_set_instance_data
and napi_get_instance_data
.
In this example, a number of Worker Threads are created. Each Worker Thread creates an AddonData
struct that is tied to the Worker Thread’s context using a call to napi_set_instance_data
. Over time, the value held in the struct is incremented and decremented using a computationally expensive operation.
In time, the Worker Threads complete their operations at which time the allocated struct is freed in the DeleteAddonData
function.
1 |
|
1 | // Example illustrating the case where a native addon is loaded multiple times. |
Note that the feature described here is currently available in N-API version 3 and later.
Your native add-on can receive one or more notifications from the Node.js runtime engine when the context in which your native-add-on has been running is being destroyed. This gives your native add-on the opportunity to release any allocated memory before the context is destroyed by the Node.js runtime engine.
The advantage of this technique is that your native add-on can allocate multiple pieces of memory to be associated with the context under which your native add-on is running. This can be useful if you need to allocate multiple memory buffers from different pieces of code as your native add-on is running.
The drawback is that if you need to access these allocated buffer you are responsible for keeping track of the pointers yourself within the context your native add-on is running. Depending upon the architecture of your native add-on, this may or may not be an issue.
Cleanup on exit of the current Node.js instance Node.js documentation for napi_add_env_cleanup_hook
and napi_remove_env_cleanup_hook
.
Because keeping track of the allocated buffers is dependent upon the architecture of the native add-on, this is a trivial example showing how the buffers can be allocated and released.
1 |
|
1 | ; |