Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eb7b4e2

Browse files
committed
fix: empty data OnProgress by race conditions of AsyncProgressWorker
1 parent b4a3364 commit eb7b4e2

File tree

4 files changed

+88
-0
lines changed

4 files changed

+88
-0
lines changed

napi-inl.h

Lines changed: 11 additions & 0 deletions
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

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,70 @@ 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+
_cv.wait(lock);
88+
}
89+
90+
void OnProgress(const ProgressData* data, size_t count) override {
91+
Napi::Env env = Env();
92+
_test_case_count++;
93+
bool error = false;
94+
Napi::String reason = Napi::String::New(env, "No error");
95+
if (_test_case_count == 1 && count != 0) {
96+
error = true;
97+
reason = Napi::String::New(env, "expect 0 count of data on 1st call");
98+
}
99+
if (_test_case_count > 1 && count != 1) {
100+
error = true;
101+
reason = Napi::String::New(env, "expect 1 count of data on non-1st call");
102+
}
103+
_progress.MakeCallback(Receiver().Value(),
104+
{Napi::Boolean::New(env, error), reason});
105+
_cv.notify_one();
106+
}
107+
108+
private:
109+
MalignWorker(Function cb,
110+
Function progress,
111+
const char* resource_name,
112+
const Object& resource)
113+
: AsyncProgressWorker(cb, resource_name, resource) {
114+
_progress.Reset(progress, 1);
115+
}
116+
117+
size_t _test_case_count = 0;
118+
std::condition_variable _cv;
119+
std::mutex _cvm;
120+
FunctionReference _progress;
121+
};
64122
}
65123

66124
Object InitAsyncProgressWorker(Env env) {
67125
Object exports = Object::New(env);
68126
exports["doWork"] = Function::New(env, TestWorker::DoWork);
127+
exports["doMalignTest"] = Function::New(env, MalignWorker::DoWork);
69128
return exports;
70129
}
71130

test/asyncprogressworker.js

Lines changed: 15 additions & 0 deletions
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,17 @@ 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+
assert.throws(() => { throw err }, /test error/);
53+
resolve();
54+
}),
55+
common.mustCallAtLeast((error, reason) => {
56+
assert(!error, reason);
57+
}, 1)
58+
);
59+
});
60+
}

test/common/index.js

Lines changed: 3 additions & 0 deletions
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)