| # Finalization |
| |
| Various node-addon-api methods accept a templated `Finalizer finalizeCallback` |
| parameter. This parameter represents a native callback function that runs in |
| response to a garbage collection event. A finalizer is considered a _basic_ |
| finalizer if the callback only utilizes a certain subset of APIs, which may |
| provide more efficient memory management, optimizations, improved execution, or |
| other benefits. |
| |
| In general, it is best to use basic finalizers whenever possible (eg. when |
| access to JavaScript is _not_ needed). The |
| `NODE_ADDON_API_REQUIRE_BASIC_FINALIZERS` preprocessor directive can be defined |
| to ensure that all finalizers are basic. |
| |
| ## Finalizers |
| |
| The callback takes `Napi::Env` as its first argument: |
| |
| ### Example |
| |
| ```cpp |
| Napi::External<int>::New(Env(), new int(1), [](Napi::Env env, int* data) { |
| env.RunScript("console.log('Finalizer called')"); |
| delete data; |
| }); |
| ``` |
| |
| ## Basic Finalizers |
| |
| Use of basic finalizers may allow the engine to perform optimizations when |
| scheduling or executing the callback. For example, V8 does not allow access to |
| the engine heap during garbage collection. Restricting finalizers from accessing |
| the engine heap allows the callback to execute during garbage collection, |
| providing a chance to free native memory eagerly. |
| |
| In general, APIs that access engine heap are not allowed in basic finalizers. |
| |
| The callback takes `Napi::BasicEnv` as its first argument: |
| |
| ### Example |
| |
| ```cpp |
| Napi::ArrayBuffer::New( |
| Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) { |
| delete[] static_cast<uint8_t*>(finalizeData); |
| }); |
| ``` |
| |
| ## Scheduling Finalizers |
| |
| In addition to passing finalizers to `Napi::External`s and other Node-API |
| constructs, `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` can be |
| used to schedule a callback to run outside of the garbage collector |
| finalization. Since the associated native memory may already be freed by the |
| basic finalizer, any additional data may be passed eg. via the finalizer's |
| parameters (`T data*`, `Hint hint*`) or via lambda capture. This allows for |
| freeing native data in a basic finalizer, while executing any JavaScript code in |
| an additional finalizer. |
| |
| ### Example |
| |
| ```cpp |
| // Native Add-on |
| |
| #include <iostream> |
| #include <memory> |
| #include "napi.h" |
| |
| using namespace Napi; |
| |
| // A structure representing some data that uses a "large" amount of memory. |
| class LargeData { |
| public: |
| LargeData() : id(instances++) {} |
| size_t id; |
| |
| static size_t instances; |
| }; |
| |
| size_t LargeData::instances = 0; |
| |
| // Basic finalizer to free `LargeData`. Takes ownership of the pointer and |
| // frees its memory after use. |
| void MyBasicFinalizer(Napi::BasicEnv env, LargeData* data) { |
| std::unique_ptr<LargeData> instance(data); |
| std::cout << "Basic finalizer for instance " << instance->id |
| << " called\n"; |
| |
| // Register a finalizer. Since the instance will be deleted by |
| // the time this callback executes, pass the instance's `id` via lambda copy |
| // capture and _not_ a reference capture that accesses `this`. |
| env.PostFinalizer([instanceId = instance->id](Napi::Env env) { |
| env.RunScript("console.log('Finalizer for instance " + |
| std::to_string(instanceId) + " called');"); |
| }); |
| |
| // Free the `LargeData` held in `data` once `instance` goes out of scope. |
| } |
| |
| Value CreateExternal(const CallbackInfo& info) { |
| // Create a new instance of LargeData. |
| auto instance = std::make_unique<LargeData>(); |
| |
| // Wrap the instance in an External object, registering a basic |
| // finalizer that will delete the instance to free the "large" amount of |
| // memory. |
| return External<LargeData>::New(info.Env(), instance.release(), MyBasicFinalizer); |
| } |
| |
| Object Init(Napi::Env env, Object exports) { |
| exports["createExternal"] = Function::New(env, CreateExternal); |
| return exports; |
| } |
| |
| NODE_API_MODULE(addon, Init) |
| ``` |
| |
| ```js |
| // JavaScript |
| |
| const { createExternal } = require('./addon.node'); |
| |
| for (let i = 0; i < 5; i++) { |
| const ext = createExternal(); |
| // ... do something with `ext` .. |
| } |
| |
| console.log('Loop complete'); |
| await new Promise(resolve => setImmediate(resolve)); |
| console.log('Next event loop cycle'); |
| ``` |
| |
| Possible output: |
| |
| ``` |
| Basic finalizer for instance 0 called |
| Basic finalizer for instance 1 called |
| Basic finalizer for instance 2 called |
| Basic finalizer for instance 3 called |
| Basic finalizer for instance 4 called |
| Loop complete |
| Finalizer for instance 3 called |
| Finalizer for instance 4 called |
| Finalizer for instance 1 called |
| Finalizer for instance 2 called |
| Finalizer for instance 0 called |
| Next event loop cycle |
| ``` |
| |
| If the garbage collector runs during the loop, the basic finalizers execute and |
| display their logging message synchronously during the loop execution. The |
| additional finalizers execute at some later point after the garbage collection |
| cycle. |