1
1
# Finalization
2
2
3
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 _ ) .
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 .
8
8
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) .
11
11
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.
13
16
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
18
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.
19
+ The callback takes ` Napi::Env ` as its first argument:
36
20
37
21
### Example
38
22
@@ -43,32 +27,36 @@ Napi::External<int>::New(Env(), new int(1), [](Napi::Env env, int* data) {
43
27
});
44
28
```
45
29
46
- #### Caveats
30
+ ## Basic Finalizers
47
31
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:
54
41
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
59
43
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
+ ```
61
50
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
66
52
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.
72
60
73
61
### Example
74
62
@@ -92,19 +80,19 @@ class LargeData {
92
80
93
81
size_t LargeData::instances = 0;
94
82
95
- // Synchronous finalizer to free ` LargeData ` . Takes ownership of the pointer and
83
+ // Basic finalizer to free `LargeData`. Takes ownership of the pointer and
96
84
// frees its memory after use.
97
- void MySyncFinalizer (Napi::BasicEnv env, LargeData* data) {
85
+ void MyBasicFinalizer (Napi::BasicEnv env, LargeData* data) {
98
86
std::unique_ptr<LargeData> instance(data);
99
- std::cout << "Synchronous finalizer for instance " << instance->id
87
+ std::cout << "Basic finalizer for instance " << instance->id
100
88
<< " called\n";
101
89
102
- // Register the asynchronous callback . Since the instance will be deleted by
90
+ // Register a finalizer . Since the instance will be deleted by
103
91
// the time this callback executes, pass the instance's `id` via lambda copy
104
92
// capture and _not_ a reference capture that accesses `this`.
105
93
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');") ;
108
96
});
109
97
110
98
// Free the `LargeData` held in `data` once `instance` goes out of scope.
@@ -114,13 +102,10 @@ Value CreateExternal(const CallbackInfo& info) {
114
102
// Create a new instance of LargeData.
115
103
auto instance = std::make_unique<LargeData>();
116
104
117
- // Wrap the instance in an External object, registering a synchronous
105
+ // Wrap the instance in an External object, registering a basic
118
106
// finalizer that will delete the instance to free the "large" amount of
119
107
// 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);
124
109
}
125
110
126
111
Object Init(Napi::Env env, Object exports) {
@@ -138,33 +123,32 @@ const { createExternal } = require('./addon.node');
138
123
139
124
for (let i = 0 ; i < 5 ; i++ ) {
140
125
const ext = createExternal ();
141
- doSomethingWith( ext);
126
+ // ... do something with ` ext` ..
142
127
}
143
128
144
129
console .log (' Loop complete' );
145
130
await new Promise (resolve => setImmediate (resolve));
146
131
console .log (' Next event loop cycle' );
147
-
148
132
```
149
133
150
134
Possible output:
151
135
152
136
```
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
158
142
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
164
148
Next event loop cycle
165
149
```
166
150
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