Skip to content

Commit

Permalink
add hinted finalizer
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel Schulhof committed Apr 14, 2020
1 parent 131e252 commit ed7042c
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 22 deletions.
23 changes: 20 additions & 3 deletions napi-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,21 @@ inline Value Env::RunScript(String script) {
template <typename T, Env::Finalizer<T> fini>
inline void Env::SetInstanceData(T* data) {
napi_status status =
napi_set_instance_data(_env, data, [](napi_env env, void* data, void*) {
fini(env, static_cast<T*>(data));
}, nullptr);
napi_set_instance_data(_env, data, [](napi_env env, void* data, void*) {
fini(env, static_cast<T*>(data));
}, nullptr);
NAPI_THROW_IF_FAILED_VOID(_env, status);
}

template <typename DataType,
typename HintType,
Napi::Env::FinalizerWithHint<DataType, HintType> fini>
inline void Env::SetInstanceData(DataType* data, HintType* hint) {
napi_status status =
napi_set_instance_data(_env, data,
[](napi_env env, void* data, void* hint) {
fini(env, static_cast<DataType*>(data), static_cast<HintType*>(hint));
}, hint);
NAPI_THROW_IF_FAILED_VOID(_env, status);
}

Expand All @@ -345,6 +357,11 @@ inline T* Env::GetInstanceData() {
template <typename T> void Env::DefaultFini(Env, T* data) {
delete data;
}

template <typename DataType, typename HintType>
void Env::DefaultFiniWithHint(Env, DataType* data, HintType*) {
delete data;
}
#endif // NAPI_VERSION > 5

////////////////////////////////////////////////////////////////////////////////
Expand Down
8 changes: 8 additions & 0 deletions napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,14 @@ namespace Napi {
template <typename T> using Finalizer = void (*)(Env, T*);
template <typename T, Finalizer<T> fini = Env::DefaultFini<T>>
void SetInstanceData(T* data);

template <typename DataType, typename HintType>
using FinalizerWithHint = void (*)(Env, DataType*, HintType*);
template <typename DataType,
typename HintType,
FinalizerWithHint<DataType, HintType> fini =
Env::DefaultFiniWithHint<DataType, HintType>>
void SetInstanceData(DataType* data, HintType* hint);
#endif // NAPI_VERSION > 5

private:
Expand Down
26 changes: 23 additions & 3 deletions test/addon_data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,22 @@ class Addon {
}
}

static Napi::Object Init(Napi::Env env) {
env.SetInstanceData(new Addon(env));
static void DeleteAddon(Napi::Env, Addon* addon, uint32_t* hint) {
delete addon;
fprintf(stderr, "hint: %d\n", *hint);
delete hint;
}

static Napi::Object Init(Napi::Env env, Napi::Value jshint) {
if (!jshint.IsNumber()) {
NAPI_THROW(Napi::Error::New(env, "Expected number"), Napi::Object());
}
uint32_t hint = jshint.As<Napi::Number>();
if (hint == 0)
env.SetInstanceData(new Addon(env));
else
env.SetInstanceData<Addon, uint32_t, DeleteAddon>(new Addon(env),
new uint32_t(hint));
Napi::Object result = Napi::Object::New(env);
result.DefineProperties({
Napi::PropertyDescriptor::Accessor<Getter, Setter>("verbose"),
Expand All @@ -71,7 +85,13 @@ class Addon {
Napi::FunctionReference VerboseIndicator;
};

// We use an addon factory so we can cover both the case where there is an
// instance data hint and the case where there isn't.
static Napi::Value AddonFactory(const Napi::CallbackInfo& info) {
return Addon::Init(info.Env(), info[0]);
}

Napi::Object InitAddonData(Napi::Env env) {
return Addon::Init(env);
return Napi::Function::New(env, AddonFactory);
}
#endif // (NAPI_VERSION > 5)
38 changes: 22 additions & 16 deletions test/addon_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,34 @@ const path = require('path');
test(path.resolve(__dirname, `./build/${buildType}/binding.node`));
test(path.resolve(__dirname, `./build/${buildType}/binding_noexcept.node`));

function test(bindingName) {
const binding = require(bindingName);

// Make sure it is possible to get/set instance data.
assert.strictEqual(binding.addon_data.verbose.verbose, false);
binding.addon_data.verbose = true;
assert.strictEqual(binding.addon_data.verbose.verbose, true);
binding.addon_data.verbose = false;
assert.strictEqual(binding.addon_data.verbose.verbose, false);

// Make sure the instance data finalizer is called at process exit.
// Make sure the instance data finalizer is called at process exit. If the hint
// is non-zero, it will be printed out by the child process.
function testFinalizer(bindingName, hint, expected) {
const child = spawn(process.execPath, [
'-e',
`require('${bindingName}').addon_data.verbose = true;`
`require('${bindingName}').addon_data(${hint}).verbose = true;`
]);
let foundMessage = false;
const actual = [];
readline
.createInterface({ input: child.stderr })
.on('line', (line) => {
if (line.match('addon_data: Addon::~Addon')) {
foundMessage = true;
if (expected.indexOf(line) >= 0) {
actual.push(line);
}
})
.on('close', () => assert.strictEqual(foundMessage, true));
.on('close', () => assert.deepStrictEqual(expected, actual));
}

function test(bindingName) {
const binding = require(bindingName).addon_data(0);

// Make sure it is possible to get/set instance data.
assert.strictEqual(binding.verbose.verbose, false);
binding.verbose = true;
assert.strictEqual(binding.verbose.verbose, true);
binding.verbose = false;
assert.strictEqual(binding.verbose.verbose, false);

testFinalizer(bindingName, 0, ['addon_data: Addon::~Addon']);
testFinalizer(bindingName, 42, ['addon_data: Addon::~Addon', 'hint: 42']);
}

0 comments on commit ed7042c

Please sign in to comment.