Skip to content

Commit 7d7b821

Browse files
committed
switch to "basic" finalizer in docs
1 parent 6591a36 commit 7d7b821

File tree

1 file changed

+60
-76
lines changed

1 file changed

+60
-76
lines changed

doc/finalization.md

+60-76
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,22 @@
11
# Finalization
22

33
Various node-addon-api methods accept a templated `Finalizer finalizeCallback`
4-
parameter. This parameter represents a callback function that runs in a
5-
_synchronous_ or _asynchronous_ manner in response to a garbage collection
6-
event. The callback executes either during the garbage collection cycle
7-
(_synchronously_) or after the cycle has completed (_asynchronously_).
4+
parameter. This parameter represents a native callback function that runs in
5+
response to a garbage collection event. A finalizer is considered a _basic_
6+
finalizer if the callback only utilizes a certain subset of APIs, which may
7+
provide optimizations, improved execution, or other benefits.
88

9-
In general, it is best to use synchronous finalizers whenever a callback must
10-
free native data.
9+
In general, it is best to use basic finalizers whenever possible (eg. when
10+
access to JavaScript is _not_ needed).
1111

12-
## Synchronous Finalizers
12+
*NOTE*: Optimizations via basic finalizers will only occur if using
13+
`NAPI_EXPERIMENTAL` and the `NODE_API_EXPERIMENTAL_NOGC_ENV_OPT_OUT` define flag
14+
has _not_ been set. Otherwise, the engine will not differentiate between basic
15+
and (extended) finalizers.
1316

14-
Synchronous finalizers execute during the garbage collection cycle, and
15-
therefore must not manipulate the engine heap. The finalizer executes in the
16-
current tick, providing a chance to free native memory. The callback takes
17-
`Napi::BasicEnv` as its first argument.
17+
## Finalizers
1818

19-
### Example
20-
21-
```cpp
22-
Napi::ArrayBuffer::New(
23-
Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) {
24-
delete[] static_cast<uint8_t*>(finalizeData);
25-
});
26-
```
27-
28-
## Asynchronous Finalizers
29-
30-
Asynchronous finalizers execute outside the context of garbage collection later
31-
in the event loop. Care must be taken when creating several objects in
32-
succession if the finalizer must free native memory. The engine may garbage
33-
collect several objects created in one tick, but any asynchronous finalizers --
34-
and therefore the freeing of native memory -- will not execute in the same tick.
35-
The callback takes `Napi::Env` as its first argument.
19+
The callback takes `Napi::Env` as its first argument:
3620

3721
### Example
3822

@@ -43,32 +27,36 @@ Napi::External<int>::New(Env(), new int(1), [](Napi::Env env, int* data) {
4327
});
4428
```
4529

46-
#### Caveats
30+
## Basic Finalizers
4731

48-
```js
49-
while (conditional()) {
50-
const external = createExternal();
51-
doSomethingWith(external);
52-
}
53-
```
32+
Use of basic finalizers may allow the engine to perform optimizations when
33+
scheduling or executing the callback. For example, V8 does not allow access to
34+
the engine heap during garbage collection. Restricting finalizers from accessing
35+
the engine heap allows the callback to execute during garbage collection,
36+
providing a chance to free native memory on the current tick.
37+
38+
In general, APIs that access engine heap are not allowed in basic finalizers.
39+
40+
The callback takes `Napi::BasicEnv` as its first argument:
5441

55-
The engine may determine to run the garbage collector within the loop, freeing
56-
the JavaScript engine's resources for all objects created by `createExternal()`.
57-
However, asynchronous finalizers will not execute during this loop, and the
58-
native memory held in `data` will not be freed.
42+
### Example
5943

60-
## Scheduling Asynchronous Finalizers
44+
```cpp
45+
Napi::ArrayBuffer::New(
46+
Env(), data, length, [](Napi::BasicEnv /*env*/, void* finalizeData) {
47+
delete[] static_cast<uint8_t*>(finalizeData);
48+
});
49+
```
6150
62-
In addition to passing asynchronous finalizers to externals and other Node-API
63-
constructs, use `Napi::BasicEnv::PostFinalize(Napi::Env, Finalizer)` to schedule
64-
an asynchronous finalizer to run after the next garbage collection cycle
65-
completes.
51+
## Scheduling Finalizers
6652
67-
Free native data in a synchronous finalizer, while executing any JavaScript code
68-
in an asynchronous finalizer attached via this method. Since the associated
69-
native memory may already be freed by the synchronous finalizer, any additional
70-
data may be passed eg. via the finalizer's parameters (`T data*`, `Hint hint*`)
71-
or via lambda capture.
53+
In addition to passing finalizers to `Napi::External`s and other Node-API
54+
constructs, use `Napi::BasicEnv::PostFinalize(Napi::BasicEnv, Finalizer)` to
55+
schedule a callback to run outside of the garbage collector finalization. Since
56+
the associated native memory may already be freed by the basic finalizer, any
57+
additional data may be passed eg. via the finalizer's parameters (`T data*`,
58+
`Hint hint*`) or via lambda capture. This allows for freeing native data in a
59+
basic finalizer, while executing any JavaScript code in an additional finalizer.
7260
7361
### Example
7462
@@ -92,19 +80,19 @@ class LargeData {
9280
9381
size_t LargeData::instances = 0;
9482
95-
// Synchronous finalizer to free `LargeData`. Takes ownership of the pointer and
83+
// Basic finalizer to free `LargeData`. Takes ownership of the pointer and
9684
// frees its memory after use.
97-
void MySyncFinalizer(Napi::BasicEnv env, LargeData* data) {
85+
void MyBasicFinalizer(Napi::BasicEnv env, LargeData* data) {
9886
std::unique_ptr<LargeData> instance(data);
99-
std::cout << "Synchronous finalizer for instance " << instance->id
87+
std::cout << "Basic finalizer for instance " << instance->id
10088
<< " called\n";
10189
102-
// Register the asynchronous callback. Since the instance will be deleted by
90+
// Register a finalizer. Since the instance will be deleted by
10391
// the time this callback executes, pass the instance's `id` via lambda copy
10492
// capture and _not_ a reference capture that accesses `this`.
10593
env.PostFinalizer([instanceId = instance->id](Napi::Env env) {
106-
std::cout << "Asynchronous finalizer for instance " << instanceId
107-
<< " called\n";
94+
env.RunScript("console.log('Finalizer for instance " +
95+
std::to_string(instanceId) + " called');");
10896
});
10997
11098
// Free the `LargeData` held in `data` once `instance` goes out of scope.
@@ -114,13 +102,10 @@ Value CreateExternal(const CallbackInfo& info) {
114102
// Create a new instance of LargeData.
115103
auto instance = std::make_unique<LargeData>();
116104
117-
// Wrap the instance in an External object, registering a synchronous
105+
// Wrap the instance in an External object, registering a basic
118106
// finalizer that will delete the instance to free the "large" amount of
119107
// memory.
120-
auto ext =
121-
External<LargeData>::New(info.Env(), instance.release(), MySyncFinalizer);
122-
123-
return ext;
108+
return External<LargeData>::New(info.Env(), instance.release(), MyBasicFinalizer);
124109
}
125110
126111
Object Init(Napi::Env env, Object exports) {
@@ -138,33 +123,32 @@ const { createExternal } = require('./addon.node');
138123

139124
for (let i = 0; i < 5; i++) {
140125
const ext = createExternal();
141-
doSomethingWith(ext);
126+
// ... do something with `ext` ..
142127
}
143128

144129
console.log('Loop complete');
145130
await new Promise(resolve => setImmediate(resolve));
146131
console.log('Next event loop cycle');
147-
148132
```
149133

150134
Possible output:
151135

152136
```
153-
Synchronous finalizer for instance 0 called
154-
Synchronous finalizer for instance 1 called
155-
Synchronous finalizer for instance 2 called
156-
Synchronous finalizer for instance 3 called
157-
Synchronous finalizer for instance 4 called
137+
Basic finalizer for instance 0 called
138+
Basic finalizer for instance 1 called
139+
Basic finalizer for instance 2 called
140+
Basic finalizer for instance 3 called
141+
Basic finalizer for instance 4 called
158142
Loop complete
159-
Asynchronous finalizer for instance 3 called
160-
Asynchronous finalizer for instance 4 called
161-
Asynchronous finalizer for instance 1 called
162-
Asynchronous finalizer for instance 2 called
163-
Asynchronous finalizer for instance 0 called
143+
Finalizer for instance 3 called
144+
Finalizer for instance 4 called
145+
Finalizer for instance 1 called
146+
Finalizer for instance 2 called
147+
Finalizer for instance 0 called
164148
Next event loop cycle
165149
```
166150

167-
If the garbage collector runs during the loop, the synchronous finalizers
168-
execute and display their logging message before the loop completes. The
169-
asynchronous finalizers execute at some later point after the garbage collection
170-
cycle.
151+
If the garbage collector runs during the loop, the basic finalizers execute and
152+
display their logging message synchronously during the loop execution. The
153+
additional finalizers execute at some later point after the garbage collection
154+
cycle.

0 commit comments

Comments
 (0)