Skip to content

Commit e50e3c8

Browse files
author
John French
committed
src: add AsyncWorker destruction suppression
Add method `SuppressDestruct()` to `AsyncWorker`, which will cause an instance of the class to remain allocated even after the `OnOK` callback fires. Such an instance must be explicitly `delete`-ed from user code. Re: nodejs/node-addon-api#231 Re: nodejs/abi-stable-node#353 PR-URL: nodejs/node-addon-api#407 Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: NickNaso <[email protected]>
1 parent d52fafb commit e50e3c8

8 files changed

+126
-2
lines changed

doc/async_worker.md

+9
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ the computation that happened in the `Napi::AsyncWorker::Execute` method, unless
6666
the default implementation of `Napi::AsyncWorker::OnOK` or
6767
`Napi::AsyncWorker::OnError` is overridden.
6868

69+
### SuppressDestruct
70+
71+
```cpp
72+
void Napi::AsyncWorker::SuppressDestruct();
73+
```
74+
75+
Prevents the destruction of the `Napi::AsyncWorker` instance upon completion of
76+
the `Napi::AsyncWorker::OnOK` callback.
77+
6978
### SetError
7079

7180
Sets the error message for the error that happened during the execution. Setting

napi-inl.h

+11-2
Original file line numberDiff line numberDiff line change
@@ -3510,7 +3510,8 @@ inline AsyncWorker::AsyncWorker(const Object& receiver,
35103510
const Object& resource)
35113511
: _env(callback.Env()),
35123512
_receiver(Napi::Persistent(receiver)),
3513-
_callback(Napi::Persistent(callback)) {
3513+
_callback(Napi::Persistent(callback)),
3514+
_suppress_destruct(false) {
35143515
napi_value resource_id;
35153516
napi_status status = napi_create_string_latin1(
35163517
_env, resource_name, NAPI_AUTO_LENGTH, &resource_id);
@@ -3536,6 +3537,7 @@ inline AsyncWorker::AsyncWorker(AsyncWorker&& other) {
35363537
_receiver = std::move(other._receiver);
35373538
_callback = std::move(other._callback);
35383539
_error = std::move(other._error);
3540+
_suppress_destruct = other._suppress_destruct;
35393541
}
35403542

35413543
inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) {
@@ -3546,6 +3548,7 @@ inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) {
35463548
_receiver = std::move(other._receiver);
35473549
_callback = std::move(other._callback);
35483550
_error = std::move(other._error);
3551+
_suppress_destruct = other._suppress_destruct;
35493552
return *this;
35503553
}
35513554

@@ -3575,6 +3578,10 @@ inline FunctionReference& AsyncWorker::Callback() {
35753578
return _callback;
35763579
}
35773580

3581+
inline void AsyncWorker::SuppressDestruct() {
3582+
_suppress_destruct = true;
3583+
}
3584+
35783585
inline void AsyncWorker::OnOK() {
35793586
_callback.Call(_receiver.Value(), std::initializer_list<napi_value>{});
35803587
}
@@ -3615,7 +3622,9 @@ inline void AsyncWorker::OnWorkComplete(
36153622
return nullptr;
36163623
});
36173624
}
3618-
delete self;
3625+
if (!self->_suppress_destruct) {
3626+
delete self;
3627+
}
36193628
}
36203629

36213630
////////////////////////////////////////////////////////////////////////////////

napi.h

+2
Original file line numberDiff line numberDiff line change
@@ -1778,6 +1778,7 @@ namespace Napi {
17781778

17791779
void Queue();
17801780
void Cancel();
1781+
void SuppressDestruct();
17811782

17821783
ObjectReference& Receiver();
17831784
FunctionReference& Callback();
@@ -1816,6 +1817,7 @@ namespace Napi {
18161817
ObjectReference _receiver;
18171818
FunctionReference _callback;
18181819
std::string _error;
1820+
bool _suppress_destruct;
18191821
};
18201822

18211823
// Memory management.

test/asyncworker-persistent.cc

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include "napi.h"
2+
3+
// A variant of TestWorker wherein destruction is suppressed. That is, instances
4+
// are not destroyed during the `OnOK` callback. They must be explicitly
5+
// destroyed.
6+
7+
using namespace Napi;
8+
9+
namespace {
10+
11+
class PersistentTestWorker : public AsyncWorker {
12+
public:
13+
static PersistentTestWorker* current_worker;
14+
static void DoWork(const CallbackInfo& info) {
15+
bool succeed = info[0].As<Boolean>();
16+
Function cb = info[1].As<Function>();
17+
18+
PersistentTestWorker* worker = new PersistentTestWorker(cb, "TestResource");
19+
current_worker = worker;
20+
21+
worker->SuppressDestruct();
22+
worker->_succeed = succeed;
23+
worker->Queue();
24+
}
25+
26+
static Value GetWorkerGone(const CallbackInfo& info) {
27+
return Boolean::New(info.Env(), current_worker == nullptr);
28+
}
29+
30+
static void DeleteWorker(const CallbackInfo& info) {
31+
(void) info;
32+
delete current_worker;
33+
}
34+
35+
~PersistentTestWorker() {
36+
current_worker = nullptr;
37+
}
38+
39+
protected:
40+
void Execute() override {
41+
if (!_succeed) {
42+
SetError("test error");
43+
}
44+
}
45+
46+
private:
47+
PersistentTestWorker(Function cb,
48+
const char* resource_name)
49+
: AsyncWorker(cb, resource_name) {}
50+
51+
bool _succeed;
52+
};
53+
54+
PersistentTestWorker* PersistentTestWorker::current_worker = nullptr;
55+
56+
} // end of anonymous namespace
57+
58+
Object InitPersistentAsyncWorker(Env env) {
59+
Object exports = Object::New(env);
60+
exports["doWork"] = Function::New(env, PersistentTestWorker::DoWork);
61+
exports.DefineProperty(
62+
PropertyDescriptor::Accessor(env, exports, "workerGone",
63+
PersistentTestWorker::GetWorkerGone));
64+
exports["deleteWorker"] =
65+
Function::New(env, PersistentTestWorker::DeleteWorker);
66+
return exports;
67+
}

test/asyncworker-persistent.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
const buildType = process.config.target_defaults.default_configuration;
3+
const assert = require('assert');
4+
const common = require('./common');
5+
const binding = require(`./build/${buildType}/binding.node`);
6+
const noexceptBinding = require(`./build/${buildType}/binding_noexcept.node`);
7+
8+
function test(binding, succeed) {
9+
return new Promise((resolve) =>
10+
// Can't pass an arrow function to doWork because that results in an
11+
// undefined context inside its body when the function gets called.
12+
binding.doWork(succeed, function(e) {
13+
setImmediate(() => {
14+
// If the work is supposed to fail, make sure there's an error.
15+
assert.strictEqual(succeed || e.message === 'test error', true);
16+
assert.strictEqual(binding.workerGone, false);
17+
binding.deleteWorker();
18+
assert.strictEqual(binding.workerGone, true);
19+
resolve();
20+
});
21+
}));
22+
}
23+
24+
test(binding.persistentasyncworker, false)
25+
.then(() => test(binding.persistentasyncworker, true))
26+
.then(() => test(noexceptBinding.persistentasyncworker, false))
27+
.then(() => test(noexceptBinding.persistentasyncworker, true));

test/binding.cc

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ using namespace Napi;
66
Object InitArrayBuffer(Env env);
77
Object InitAsyncContext(Env env);
88
Object InitAsyncWorker(Env env);
9+
Object InitPersistentAsyncWorker(Env env);
910
Object InitBasicTypesArray(Env env);
1011
Object InitBasicTypesBoolean(Env env);
1112
Object InitBasicTypesNumber(Env env);
@@ -42,6 +43,7 @@ Object Init(Env env, Object exports) {
4243
exports.Set("arraybuffer", InitArrayBuffer(env));
4344
exports.Set("asynccontext", InitAsyncContext(env));
4445
exports.Set("asyncworker", InitAsyncWorker(env));
46+
exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env));
4547
exports.Set("basic_types_array", InitBasicTypesArray(env));
4648
exports.Set("basic_types_boolean", InitBasicTypesBoolean(env));
4749
exports.Set("basic_types_number", InitBasicTypesNumber(env));

test/binding.gyp

+7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
'arraybuffer.cc',
99
'asynccontext.cc',
1010
'asyncworker.cc',
11+
'asyncworker-persistent.cc',
1112
'basic_types/array.cc',
1213
'basic_types/boolean.cc',
1314
'basic_types/number.cc',
@@ -43,6 +44,12 @@
4344
'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED']
4445
}, {
4546
'sources': ['object/object_deprecated.cc']
47+
}],
48+
['OS=="mac"', {
49+
'cflags+': ['-fvisibility=hidden'],
50+
'xcode_settings': {
51+
'OTHER_CFLAGS': ['-fvisibility=hidden']
52+
}
4653
}]
4754
],
4855
'include_dirs': ["<!@(node -p \"require('../').include\")"],

test/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ let testModules = [
1111
'arraybuffer',
1212
'asynccontext',
1313
'asyncworker',
14+
'asyncworker-persistent',
1415
'basic_types/array',
1516
'basic_types/boolean',
1617
'basic_types/number',

0 commit comments

Comments
 (0)