Skip to content

Commit 0beb8a6

Browse files
addaleaxMylesBorins
authored andcommitted
src: deprecate two- and one-argument AtExit()
Using `AtExit()` without an `Environment*` pointer or providing an argument is almost always a sign of improperly relying on global state and/or using `AtExit()` as an addon when the addon-targeting `AddEnvironmentCleanupHook()` would be the better choice. Deprecate those variants. This also updates the addon docs to refer to `AddEnvironmentCleanupHook()` rather than `AtExit()`. PR-URL: #30227 Reviewed-By: David Carlier <[email protected]> Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: Daniel Bevenius <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent bf4c39d commit 0beb8a6

File tree

3 files changed

+81
-89
lines changed

3 files changed

+81
-89
lines changed

doc/api/addons.md

+60-84
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,12 @@ NODE_MODULE_INIT(/* exports, module, context */) {
241241

242242
#### Worker support
243243

244+
In order to be loaded from multiple Node.js environments,
245+
such as a main thread and a Worker thread, an add-on needs to either:
246+
247+
* Be an N-API addon, or
248+
* Be declared as context-aware using `NODE_MODULE_INIT()` as described above
249+
244250
In order to support [`Worker`][] threads, addons need to clean up any resources
245251
they may have allocated when such a thread exists. This can be achieved through
246252
the usage of the `AddEnvironmentCleanupHook()` function:
@@ -254,13 +260,62 @@ void AddEnvironmentCleanupHook(v8::Isolate* isolate,
254260
This function adds a hook that will run before a given Node.js instance shuts
255261
down. If necessary, such hooks can be removed using
256262
`RemoveEnvironmentCleanupHook()` before they are run, which has the same
257-
signature.
263+
signature. Callbacks are run in last-in first-out order.
258264
259-
In order to be loaded from multiple Node.js environments,
260-
such as a main thread and a Worker thread, an add-on needs to either:
265+
The following `addon.cc` uses `AddEnvironmentCleanupHook`:
261266
262-
* Be an N-API addon, or
263-
* Be declared as context-aware using `NODE_MODULE_INIT()` as described above
267+
```cpp
268+
// addon.cc
269+
#include <assert.h>
270+
#include <stdlib.h>
271+
#include <node.h>
272+
273+
using node::AddEnvironmentCleanupHook;
274+
using v8::HandleScope;
275+
using v8::Isolate;
276+
using v8::Local;
277+
using v8::Object;
278+
279+
// Note: In a real-world application, do not rely on static/global data.
280+
static char cookie[] = "yum yum";
281+
static int cleanup_cb1_called = 0;
282+
static int cleanup_cb2_called = 0;
283+
284+
static void cleanup_cb1(void* arg) {
285+
Isolate* isolate = static_cast<Isolate*>(arg);
286+
HandleScope scope(isolate);
287+
Local<Object> obj = Object::New(isolate);
288+
assert(!obj.IsEmpty()); // assert VM is still alive
289+
assert(obj->IsObject());
290+
cleanup_cb1_called++;
291+
}
292+
293+
static void cleanup_cb2(void* arg) {
294+
assert(arg == static_cast<void*>(cookie));
295+
cleanup_cb2_called++;
296+
}
297+
298+
static void sanity_check(void*) {
299+
assert(cleanup_cb1_called == 1);
300+
assert(cleanup_cb2_called == 1);
301+
}
302+
303+
// Initialize this addon to be context-aware.
304+
NODE_MODULE_INIT(/* exports, module, context */) {
305+
Isolate* isolate = context->GetIsolate();
306+
307+
AddEnvironmentCleanupHook(isolate, sanity_check, nullptr);
308+
AddEnvironmentCleanupHook(isolate, cleanup_cb2, cookie);
309+
AddEnvironmentCleanupHook(isolate, cleanup_cb1, isolate);
310+
}
311+
```
312+
313+
Test in JavaScript by running:
314+
315+
```js
316+
// test.js
317+
require('./build/Release/addon');
318+
```
264319

265320
### Building
266321

@@ -1293,85 +1348,6 @@ console.log(result);
12931348
// Prints: 30
12941349
```
12951350

1296-
### AtExit hooks
1297-
1298-
An `AtExit` hook is a function that is invoked after the Node.js event loop
1299-
has ended but before the JavaScript VM is terminated and Node.js shuts down.
1300-
`AtExit` hooks are registered using the `node::AtExit` API.
1301-
1302-
#### void AtExit(callback, args)
1303-
1304-
* `callback` <span class="type">&lt;void (\*)(void\*)&gt;</span>
1305-
A pointer to the function to call at exit.
1306-
* `args` <span class="type">&lt;void\*&gt;</span>
1307-
A pointer to pass to the callback at exit.
1308-
1309-
Registers exit hooks that run after the event loop has ended but before the VM
1310-
is killed.
1311-
1312-
`AtExit` takes two parameters: a pointer to a callback function to run at exit,
1313-
and a pointer to untyped context data to be passed to that callback.
1314-
1315-
Callbacks are run in last-in first-out order.
1316-
1317-
The following `addon.cc` implements `AtExit`:
1318-
1319-
```cpp
1320-
// addon.cc
1321-
#include <assert.h>
1322-
#include <stdlib.h>
1323-
#include <node.h>
1324-
1325-
namespace demo {
1326-
1327-
using node::AtExit;
1328-
using v8::HandleScope;
1329-
using v8::Isolate;
1330-
using v8::Local;
1331-
using v8::Object;
1332-
1333-
static char cookie[] = "yum yum";
1334-
static int at_exit_cb1_called = 0;
1335-
static int at_exit_cb2_called = 0;
1336-
1337-
static void at_exit_cb1(void* arg) {
1338-
Isolate* isolate = static_cast<Isolate*>(arg);
1339-
HandleScope scope(isolate);
1340-
Local<Object> obj = Object::New(isolate);
1341-
assert(!obj.IsEmpty()); // assert VM is still alive
1342-
assert(obj->IsObject());
1343-
at_exit_cb1_called++;
1344-
}
1345-
1346-
static void at_exit_cb2(void* arg) {
1347-
assert(arg == static_cast<void*>(cookie));
1348-
at_exit_cb2_called++;
1349-
}
1350-
1351-
static void sanity_check(void*) {
1352-
assert(at_exit_cb1_called == 1);
1353-
assert(at_exit_cb2_called == 2);
1354-
}
1355-
1356-
void init(Local<Object> exports) {
1357-
AtExit(sanity_check);
1358-
AtExit(at_exit_cb2, cookie);
1359-
AtExit(at_exit_cb2, cookie);
1360-
AtExit(at_exit_cb1, exports->GetIsolate());
1361-
}
1362-
1363-
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
1364-
1365-
} // namespace demo
1366-
```
1367-
1368-
Test in JavaScript by running:
1369-
1370-
```js
1371-
// test.js
1372-
require('./build/Release/addon');
1373-
```
1374-
13751351
[`Worker`]: worker_threads.html#worker_threads_class_worker
13761352
[Electron]: https://electronjs.org/
13771353
[Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide

src/node.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -664,16 +664,27 @@ extern "C" NODE_EXTERN void node_module_register(void* mod);
664664

665665
/* Called after the event loop exits but before the VM is disposed.
666666
* Callbacks are run in reverse order of registration, i.e. newest first.
667+
*
668+
* You should always use the three-argument variant (or, for addons,
669+
* AddEnvironmentCleanupHook) in order to avoid relying on global state.
667670
*/
668-
NODE_EXTERN void AtExit(void (*cb)(void* arg), void* arg = nullptr);
671+
NODE_DEPRECATED(
672+
"Use the three-argument variant of AtExit() or AddEnvironmentCleanupHook()",
673+
NODE_EXTERN void AtExit(void (*cb)(void* arg), void* arg = nullptr));
669674

670675
/* Registers a callback with the passed-in Environment instance. The callback
671676
* is called after the event loop exits, but before the VM is disposed.
672677
* Callbacks are run in reverse order of registration, i.e. newest first.
673678
*/
674679
NODE_EXTERN void AtExit(Environment* env,
675680
void (*cb)(void* arg),
676-
void* arg = nullptr);
681+
void* arg);
682+
NODE_DEPRECATED(
683+
"Use the three-argument variant of AtExit() or AddEnvironmentCleanupHook()",
684+
inline void AtExit(Environment* env,
685+
void (*cb)(void* arg)) {
686+
AtExit(env, cb, nullptr);
687+
})
677688

678689
typedef double async_id;
679690
struct async_context {

test/addons/at-exit/binding.cc

+8-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
#include <node.h>
44
#include <v8.h>
55

6+
// TODO(addaleax): Maybe merge this file with the cctest for AtExit()?
7+
68
using node::AtExit;
9+
using node::Environment;
10+
using node::GetCurrentEnvironment;
711
using v8::HandleScope;
812
using v8::Isolate;
913
using v8::Local;
@@ -46,9 +50,10 @@ NODE_C_DTOR(sanity_check) {
4650
}
4751

4852
void init(Local<Object> exports) {
49-
AtExit(at_exit_cb1, exports->GetIsolate());
50-
AtExit(at_exit_cb2, cookie);
51-
AtExit(at_exit_cb2, cookie);
53+
Environment* env = GetCurrentEnvironment(exports->CreationContext());
54+
AtExit(env, at_exit_cb1, exports->GetIsolate());
55+
AtExit(env, at_exit_cb2, cookie);
56+
AtExit(env, at_exit_cb2, cookie);
5257
}
5358

5459
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

0 commit comments

Comments
 (0)