Skip to content

Commit 1504beb

Browse files
author
John French
committed
src: empty data OnProgress in AsyncProgressWorker
Fixes: nodejs/node-addon-api#821 PR-URL: nodejs/node-addon-api#853 Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Michael Dawson <midawson@redhat.com>
1 parent ac292d9 commit 1504beb

File tree

5 files changed

+90
-0
lines changed

5 files changed

+90
-0
lines changed

.github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on: [push, pull_request]
44

55
jobs:
66
test:
7+
timeout-minutes: 30
78
strategy:
89
matrix:
910
node-version:

napi-inl.h

+11
Original file line numberDiff line numberDiff line change
@@ -5390,6 +5390,17 @@ inline void AsyncProgressWorker<T>::OnWorkProgress(void*) {
53905390
this->_asyncsize = 0;
53915391
}
53925392

5393+
/**
5394+
* The callback of ThreadSafeFunction is not been invoked immediately on the
5395+
* callback of uv_async_t (uv io poll), rather the callback of TSFN is
5396+
* invoked on the right next uv idle callback. There are chances that during
5397+
* the deferring the signal of uv_async_t is been sent again, i.e. potential
5398+
* not coalesced two calls of the TSFN callback.
5399+
*/
5400+
if (data == nullptr) {
5401+
return;
5402+
}
5403+
53935404
this->OnProgress(data, size);
53945405
delete[] data;
53955406
}

test/asyncprogressworker.cc

+58
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,69 @@ class TestWorker : public AsyncProgressWorker<ProgressData> {
6161
FunctionReference _progress;
6262
};
6363

64+
class MalignWorker : public AsyncProgressWorker<ProgressData> {
65+
public:
66+
static void DoWork(const CallbackInfo& info) {
67+
Function cb = info[0].As<Function>();
68+
Function progress = info[1].As<Function>();
69+
70+
MalignWorker* worker =
71+
new MalignWorker(cb, progress, "TestResource", Object::New(info.Env()));
72+
worker->Queue();
73+
}
74+
75+
protected:
76+
void Execute(const ExecutionProgress& progress) override {
77+
std::unique_lock<std::mutex> lock(_cvm);
78+
// Testing a nullptr send is acceptable.
79+
progress.Send(nullptr, 0);
80+
_cv.wait(lock);
81+
// Testing busy looping on send doesn't trigger unexpected empty data
82+
// OnProgress call.
83+
for (size_t i = 0; i < 1000000; i++) {
84+
ProgressData data{0};
85+
progress.Send(&data, 1);
86+
}
87+
}
88+
89+
void OnProgress(const ProgressData* /* data */, size_t count) override {
90+
Napi::Env env = Env();
91+
_test_case_count++;
92+
bool error = false;
93+
Napi::String reason = Napi::String::New(env, "No error");
94+
if (_test_case_count == 1 && count != 0) {
95+
error = true;
96+
reason = Napi::String::New(env, "expect 0 count of data on 1st call");
97+
}
98+
if (_test_case_count > 1 && count != 1) {
99+
error = true;
100+
reason = Napi::String::New(env, "expect 1 count of data on non-1st call");
101+
}
102+
_progress.MakeCallback(Receiver().Value(),
103+
{Napi::Boolean::New(env, error), reason});
104+
_cv.notify_one();
105+
}
106+
107+
private:
108+
MalignWorker(Function cb,
109+
Function progress,
110+
const char* resource_name,
111+
const Object& resource)
112+
: AsyncProgressWorker(cb, resource_name, resource) {
113+
_progress.Reset(progress, 1);
114+
}
115+
116+
size_t _test_case_count = 0;
117+
std::condition_variable _cv;
118+
std::mutex _cvm;
119+
FunctionReference _progress;
120+
};
64121
}
65122

66123
Object InitAsyncProgressWorker(Env env) {
67124
Object exports = Object::New(env);
68125
exports["doWork"] = Function::New(env, TestWorker::DoWork);
126+
exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork);
69127
return exports;
70128
}
71129

test/asyncprogressworker.js

+17
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module.exports = test(require(`./build/${buildType}/binding.node`))
99
async function test({ asyncprogressworker }) {
1010
await success(asyncprogressworker);
1111
await fail(asyncprogressworker);
12+
await malignTest(asyncprogressworker);
1213
}
1314

1415
function success(binding) {
@@ -43,3 +44,19 @@ function fail(binding) {
4344
);
4445
});
4546
}
47+
48+
function malignTest(binding) {
49+
return new Promise((resolve, reject) => {
50+
binding.doMalignTest(
51+
common.mustCall((err) => {
52+
if (err) {
53+
return reject(err);
54+
}
55+
resolve();
56+
}),
57+
common.mustCallAtLeast((error, reason) => {
58+
assert(!error, reason);
59+
}, 1)
60+
);
61+
});
62+
}

test/common/index.js

+3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ function runCallChecks(exitCode) {
3333
exports.mustCall = function(fn, exact) {
3434
return _mustCallInner(fn, exact, 'exact');
3535
};
36+
exports.mustCallAtLeast = function(fn, minimum) {
37+
return _mustCallInner(fn, minimum, 'minimum');
38+
};
3639

3740
function _mustCallInner(fn, criteria, field) {
3841
if (typeof fn === 'number') {

0 commit comments

Comments
 (0)