diff --git a/doc/finalization.md b/doc/finalization.md index 3b45e7a1e..f4e28e6f2 100644 --- a/doc/finalization.md +++ b/doc/finalization.md @@ -1,38 +1,22 @@ # Finalization Various node-addon-api methods accept a templated `Finalizer finalizeCallback` -parameter. This parameter represents a callback function that runs in a -_synchronous_ or _asynchronous_ manner in response to a garbage collection -event. The callback executes either during the garbage collection cycle -(_synchronously_) or after the cycle has completed (_asynchronously_). +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 optimizations, improved execution, or other benefits. -In general, it is best to use synchronous finalizers whenever a callback must -free native data. +In general, it is best to use basic finalizers whenever possible (eg. when +access to JavaScript is _not_ needed). -## Synchronous Finalizers +*NOTE*: Optimizations via basic finalizers will only occur if using +`NAPI_EXPERIMENTAL` and the `NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT` define flag +has _not_ been set. Otherwise, the engine will not differentiate between basic +and (extended) finalizers. -Synchronous finalizers execute during the garbage collection cycle, and -therefore must not manipulate the engine heap. The finalizer executes in the -current tick, providing a chance to free native memory. The callback takes -`Napi::BasicEnv` as its first argument. +## Finalizers -### Example - -```cpp -Napi::ArrayBuffer::New( - Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) { - delete[] static_cast(finalizeData); - }); -``` - -## Asynchronous Finalizers - -Asynchronous finalizers execute outside the context of garbage collection later -in the event loop. Care must be taken when creating several objects in -succession if the finalizer must free native memory. The engine may garbage -collect several objects created in one tick, but any asynchronous finalizers -- -and therefore the freeing of native memory -- will not execute in the same tick. -The callback takes `Napi::Env` as its first argument. +The callback takes `Napi::Env` as its first argument: ### Example @@ -43,32 +27,36 @@ Napi::External::New(Env(), new int(1), [](Napi::Env env, int* data) { }); ``` -#### Caveats +## Basic Finalizers -```js -while (conditional()) { - const external = createExternal(); - doSomethingWith(external); -} -``` +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 on the current tick. + +In general, APIs that access engine heap are not allowed in basic finalizers. + +The callback takes `Napi::BasicEnv` as its first argument: -The engine may determine to run the garbage collector within the loop, freeing -the JavaScript engine's resources for all objects created by `createExternal()`. -However, asynchronous finalizers will not execute during this loop, and the -native memory held in `data` will not be freed. +### Example -## Scheduling Asynchronous Finalizers +```cpp +Napi::ArrayBuffer::New( + Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) { + delete[] static_cast(finalizeData); + }); +``` -In addition to passing asynchronous finalizers to externals and other Node-API -constructs, use `Napi::BasicEnv::PostFinalize(Napi::Env, Finalizer)` to schedule -an asynchronous finalizer to run after the next garbage collection cycle -completes. +## Scheduling Finalizers -Free native data in a synchronous finalizer, while executing any JavaScript code -in an asynchronous finalizer attached via this method. Since the associated -native memory may already be freed by the synchronous finalizer, any additional -data may be passed eg. via the finalizer's parameters (`T data*`, `Hint hint*`) -or via lambda capture. +In addition to passing finalizers to `Napi::External`s and other Node-API +constructs, use `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` 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 @@ -92,19 +80,19 @@ class LargeData { size_t LargeData::instances = 0; -// Synchronous finalizer to free `LargeData`. Takes ownership of the pointer and +// Basic finalizer to free `LargeData`. Takes ownership of the pointer and // frees its memory after use. -void MySyncFinalizer(Napi::BasicEnv env, LargeData* data) { +void MyBasicFinalizer(Napi::BasicEnv env, LargeData* data) { std::unique_ptr instance(data); - std::cout << "Synchronous finalizer for instance " << instance->id + std::cout << "Basic finalizer for instance " << instance->id << " called\n"; - // Register the asynchronous callback. Since the instance will be deleted by + // 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) { - std::cout << "Asynchronous finalizer for instance " << instanceId - << " called\n"; + env.RunScript("console.log('Finalizer for instance " + + std::to_string(instanceId) + " called');"); }); // Free the `LargeData` held in `data` once `instance` goes out of scope. @@ -114,13 +102,10 @@ Value CreateExternal(const CallbackInfo& info) { // Create a new instance of LargeData. auto instance = std::make_unique(); - // Wrap the instance in an External object, registering a synchronous + // Wrap the instance in an External object, registering a basic // finalizer that will delete the instance to free the "large" amount of // memory. - auto ext = - External::New(info.Env(), instance.release(), MySyncFinalizer); - - return ext; + return External::New(info.Env(), instance.release(), MyBasicFinalizer); } Object Init(Napi::Env env, Object exports) { @@ -138,33 +123,32 @@ const { createExternal } = require('./addon.node'); for (let i = 0; i < 5; i++) { const ext = createExternal(); - doSomethingWith(ext); + // ... do something with `ext` .. } console.log('Loop complete'); await new Promise(resolve => setImmediate(resolve)); console.log('Next event loop cycle'); - ``` Possible output: ``` -Synchronous finalizer for instance 0 called -Synchronous finalizer for instance 1 called -Synchronous finalizer for instance 2 called -Synchronous finalizer for instance 3 called -Synchronous finalizer for instance 4 called +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 -Asynchronous finalizer for instance 3 called -Asynchronous finalizer for instance 4 called -Asynchronous finalizer for instance 1 called -Asynchronous finalizer for instance 2 called -Asynchronous finalizer for instance 0 called +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 synchronous finalizers -execute and display their logging message before the loop completes. The -asynchronous finalizers execute at some later point after the garbage collection -cycle. \ No newline at end of file +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.