Skip to content

Commit

Permalink
src: add AsyncWorker destruction suppression
Browse files Browse the repository at this point in the history
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: #231
Re: nodejs/abi-stable-node#353
  • Loading branch information
Gabriel Schulhof committed Feb 13, 2019
1 parent d8e9c22 commit 1cdfc87
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 2 deletions.
9 changes: 9 additions & 0 deletions doc/async_worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ the computation that happened in the `Napi::AsyncWorker::Execute` method, unless
the default implementation of `Napi::AsyncWorker::OnOK` or
`Napi::AsyncWorker::OnError` is overridden.

### SuppressDestruct

```cpp
void Napi::AsyncWorker::SuppressDestruct();
```

Prevents the destruction of the `Napi::AsyncWorker` instance upon completion of
the `Napi::AsyncWorker::OnOK` callback.

### SetError

Sets the error message for the error that happened during the execution. Setting
Expand Down
13 changes: 11 additions & 2 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3524,7 +3524,8 @@ inline AsyncWorker::AsyncWorker(const Object& receiver,
const Object& resource)
: _env(callback.Env()),
_receiver(Napi::Persistent(receiver)),
_callback(Napi::Persistent(callback)) {
_callback(Napi::Persistent(callback)),
_suppress_destruct(false) {
napi_value resource_id;
napi_status status = napi_create_string_latin1(
_env, resource_name, NAPI_AUTO_LENGTH, &resource_id);
Expand All @@ -3550,6 +3551,7 @@ inline AsyncWorker::AsyncWorker(AsyncWorker&& other) {
_receiver = std::move(other._receiver);
_callback = std::move(other._callback);
_error = std::move(other._error);
_suppress_destruct = other._suppress_destruct;
}

inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) {
Expand All @@ -3560,6 +3562,7 @@ inline AsyncWorker& AsyncWorker::operator =(AsyncWorker&& other) {
_receiver = std::move(other._receiver);
_callback = std::move(other._callback);
_error = std::move(other._error);
_suppress_destruct = other._suppress_destruct;
return *this;
}

Expand Down Expand Up @@ -3589,6 +3592,10 @@ inline FunctionReference& AsyncWorker::Callback() {
return _callback;
}

inline void AsyncWorker::SuppressDestruct() {
_suppress_destruct = true;
}

inline void AsyncWorker::OnOK() {
_callback.Call(_receiver.Value(), std::initializer_list<napi_value>{});
}
Expand Down Expand Up @@ -3629,7 +3636,9 @@ inline void AsyncWorker::OnWorkComplete(
return nullptr;
});
}
delete self;
if (!self->_suppress_destruct) {
delete self;
}
}

////////////////////////////////////////////////////////////////////////////////
Expand Down
2 changes: 2 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1722,6 +1722,7 @@ namespace Napi {

void Queue();
void Cancel();
void SuppressDestruct();

ObjectReference& Receiver();
FunctionReference& Callback();
Expand Down Expand Up @@ -1760,6 +1761,7 @@ namespace Napi {
ObjectReference _receiver;
FunctionReference _callback;
std::string _error;
bool _suppress_destruct;
};

// Memory management.
Expand Down
65 changes: 65 additions & 0 deletions test/asyncworker-persistent.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include "napi.h"

// A variant of TestWorker wherein destruction is suppressed. That is, instances
// are not destroyed during the `OnOK` callback. They must be explicitly
// destroyed.

using namespace Napi;

namespace {

class PersistentTestWorker : public AsyncWorker {
public:
static PersistentTestWorker* current_worker;
static void DoWork(const CallbackInfo& info) {
bool succeed = info[0].As<Boolean>();
Function cb = info[1].As<Function>();

PersistentTestWorker* worker = new PersistentTestWorker(cb, "TestResource");
current_worker = worker;

worker->SuppressDestruct();
worker->_succeed = succeed;
worker->Queue();
}

static Value WorkerGone(const CallbackInfo& info) {
return Boolean::New(info.Env(), current_worker == nullptr);
}

static void DeleteWorker(const CallbackInfo& info) {
(void) info;
delete current_worker;
}

~PersistentTestWorker() {
current_worker = nullptr;
}

protected:
void Execute() override {
if (!_succeed) {
SetError("test error");
}
}

private:
PersistentTestWorker(Function cb,
const char* resource_name)
: AsyncWorker(cb, resource_name) {}

bool _succeed;
};

PersistentTestWorker* PersistentTestWorker::current_worker = nullptr;

} // end of anonymous namespace

Object InitPersistentAsyncWorker(Env env) {
Object exports = Object::New(env);
exports["doWork"] = Function::New(env, PersistentTestWorker::DoWork);
exports["workerGone"] = Function::New(env, PersistentTestWorker::WorkerGone);
exports["deleteWorker"] =
Function::New(env, PersistentTestWorker::DeleteWorker);
return exports;
}
27 changes: 27 additions & 0 deletions test/asyncworker-persistent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use strict';
const buildType = process.config.target_defaults.default_configuration;
const assert = require('assert');
const common = require('./common');
const binding = require(`./build/${buildType}/binding.node`);
const noexceptBinding = require(`./build/${buildType}/binding_noexcept.node`);

function test(binding, succeed) {
return new Promise((resolve) =>
// Can't pass an arrow function to doWork because that results in an
// undefined context inside its body when the function gets called.
binding.doWork(succeed, function(e) {
setImmediate(() => {
// If the work is supposed to fail, make sure there's an error.
assert.strictEqual(succeed || e.message === 'test error', true);
assert.strictEqual(binding.workerGone(this), false);
binding.deleteWorker(this);
assert.strictEqual(binding.workerGone(this), true);
resolve();
});
}));
}

test(binding.persistentasyncworker, false, 'binding')
.then(() => test(binding.persistentasyncworker, true, 'binding'))
.then(() => test(noexceptBinding.persistentasyncworker, false, 'noxbinding'))
.then(() => test(noexceptBinding.persistentasyncworker, true, 'noxbinding'));
2 changes: 2 additions & 0 deletions test/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using namespace Napi;
Object InitArrayBuffer(Env env);
Object InitAsyncContext(Env env);
Object InitAsyncWorker(Env env);
Object InitPersistentAsyncWorker(Env env);
Object InitBasicTypesArray(Env env);
Object InitBasicTypesBoolean(Env env);
Object InitBasicTypesNumber(Env env);
Expand Down Expand Up @@ -42,6 +43,7 @@ Object Init(Env env, Object exports) {
exports.Set("arraybuffer", InitArrayBuffer(env));
exports.Set("asynccontext", InitAsyncContext(env));
exports.Set("asyncworker", InitAsyncWorker(env));
exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env));
exports.Set("basic_types_array", InitBasicTypesArray(env));
exports.Set("basic_types_boolean", InitBasicTypesBoolean(env));
exports.Set("basic_types_number", InitBasicTypesNumber(env));
Expand Down
7 changes: 7 additions & 0 deletions test/binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'arraybuffer.cc',
'asynccontext.cc',
'asyncworker.cc',
'asyncworker-persistent.cc',
'basic_types/array.cc',
'basic_types/boolean.cc',
'basic_types/number.cc',
Expand Down Expand Up @@ -43,6 +44,12 @@
'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED']
}, {
'sources': ['object/object_deprecated.cc']
}],
['OS=="mac"', {
'cflags+': ['-fvisibility=hidden'],
'xcode_settings': {
'OTHER_CFLAGS': ['-fvisibility=hidden']
}
}]
],
'include_dirs': ["<!@(node -p \"require('../').include\")"],
Expand Down
1 change: 1 addition & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let testModules = [
'arraybuffer',
'asynccontext',
'asyncworker',
'asyncworker-persistent',
'basic_types/array',
'basic_types/boolean',
'basic_types/number',
Expand Down

0 comments on commit 1cdfc87

Please sign in to comment.