Skip to content

Commit 502aee5

Browse files
committed
wip on docs for general finalization
1 parent fc73ba9 commit 502aee5

File tree

1 file changed

+158
-0
lines changed

1 file changed

+158
-0
lines changed

doc/finalization.md

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Finalization
2+
3+
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_).
8+
9+
In general, it is best to use synchronous finalizers whenever a callback must
10+
free native data.
11+
12+
## Synchronous Finalizers
13+
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.
18+
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.
36+
37+
### Example
38+
39+
```cpp
40+
Napi::External<int>::New(Env(), new int(1), [](Napi::Env env, int* data) {
41+
env.RunScript("console.log('Finalizer called')");
42+
delete data;
43+
});
44+
```
45+
46+
#### Caveats
47+
48+
```js
49+
while (conditional()) {
50+
const external = createExternal();
51+
doSomethingWith(external);
52+
}
53+
```
54+
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.
59+
60+
## Scheduling Asynchronous Finalizers
61+
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.
66+
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.
72+
73+
### Example
74+
75+
```cpp
76+
// Native Add-on
77+
78+
#include <iostream>
79+
#include <memory>
80+
#include "napi.h"
81+
82+
using namespace Napi;
83+
84+
// A structure representing some data that uses a "large" amount of memory.
85+
class LargeData {
86+
public:
87+
LargeData() : id(instances++) {}
88+
size_t id;
89+
90+
static size_t instances;
91+
};
92+
93+
size_t LargeData::instances = 0;
94+
95+
// Synchronous finalizer to free `LargeData`. Takes ownership of the pointer and
96+
// frees its memory after use.
97+
void MySyncFinalizer(Napi::BasicEnv env, LargeData* data) {
98+
std::unique_ptr<LargeData> instance(data);
99+
std::cout << "Synchronous finalizer for instance " << instance->id
100+
<< " called\n";
101+
102+
// Register the asynchronous callback. Since the instance will be deleted by
103+
// the time this callback executes, pass the instance's `id` via lambda copy
104+
// capture and _not_ a reference capture that accesses `this`.
105+
env.PostFinalizer([instanceId = instance->id](Napi::Env env) {
106+
std::cout << "Asynchronous finalizer for instance " << instanceId
107+
<< " called\n";
108+
});
109+
110+
// Free the `LargeData` held in `data` once `instance` goes out of scope.
111+
}
112+
113+
Value CreateExternal(const CallbackInfo& info) {
114+
// Create a new instance of LargeData.
115+
auto instance = std::make_unique<LargeData>();
116+
117+
// Wrap the instance in an External object, registering a synchronous
118+
// finalizer that will delete the instance to free the "large" amount of
119+
// memory.
120+
auto ext =
121+
External<LargeData>::New(info.Env(), instance.release(), MySyncFinalizer);
122+
123+
return ext;
124+
}
125+
126+
Object Init(Napi::Env env, Object exports) {
127+
exports["createExternal"] = Function::New(env, CreateExternal);
128+
return exports;
129+
}
130+
131+
NODE_API_MODULE(addon, Init)
132+
```
133+
134+
```js
135+
// JavaScript
136+
137+
const { createExternal } = require('./addon.node');
138+
139+
for (let i = 0; i < 5; i++) {
140+
createExternal();
141+
}
142+
143+
console.log('Conditional done');
144+
```
145+
146+
Possible output:
147+
148+
```
149+
Synchronous finalizer for instance 0 called
150+
Synchronous finalizer for instance 1 called
151+
Synchronous finalizer for instance 2 called
152+
Synchronous finalizer for instance 3 called
153+
Conditional done
154+
Asynchronous finalizer for instance 3 called
155+
Asynchronous finalizer for instance 1 called
156+
Asynchronous finalizer for instance 2 called
157+
Asynchronous finalizer for instance 0 called
158+
```

0 commit comments

Comments
 (0)