diff --git a/doc/async_progress_worker.md b/doc/async_worker_variants.md similarity index 76% rename from doc/async_progress_worker.md rename to doc/async_worker_variants.md index 296b51b7d..a1fb6787e 100644 --- a/doc/async_progress_worker.md +++ b/doc/async_worker_variants.md @@ -272,12 +272,12 @@ called and are executed as part of the event loop. The code below shows a basic example of the `Napi::AsyncProgressWorker` implementation: ```cpp -#include +#include #include #include -use namespace Napi; +using namespace Napi; class EchoWorker : public AsyncProgressWorker { public: @@ -323,7 +323,7 @@ The following code shows an example of how to create and use an `Napi::AsyncProg // Include EchoWorker class // .. -use namespace Napi; +using namespace Napi; Value Echo(const CallbackInfo& info) { // We need to validate the arguments here @@ -341,4 +341,116 @@ asynchronous task ends and other data needed for the computation. Once created, the only other action needed is to call the `Napi::AsyncProgressWorker::Queue` method that will queue the created worker for execution. +# AsyncProgressQueueWorker + +`Napi::AsyncProgressQueueWorker` acts exactly like `Napi::AsyncProgressWorker` +except that each progress committed by `Napi::AsyncProgressQueueWorker::ExecutionProgress::Send` +during `Napi::AsyncProgressQueueWorker::Execute` is guaranteed to be +processed by `Napi::AsyncProgressQueueWorker::OnProgress` on the JavaScript +thread in the order it was committed. + +For the most basic use, only the `Napi::AsyncProgressQueueWorker::Execute` and +`Napi::AsyncProgressQueueWorker::OnProgress` method must be implemented in a subclass. + +# AsyncProgressQueueWorker::ExecutionProcess + +A bridge class created before the worker thread execution of `Napi::AsyncProgressQueueWorker::Execute`. + +## Methods + +### Send + +`Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` takes two arguments, a pointer +to a generic type of data, and a `size_t` to indicate how many items the pointer is +pointing to. + +The data pointed to will be copied to internal slots of `Napi::AsyncProgressQueueWorker` so +after the call to `Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` the data can +be safely released. + +`Napi::AsyncProgressQueueWorker::ExecutionProcess::Send` guarantees invocation +of `Napi::AsyncProgressQueueWorker::OnProgress`, which means multiple `Send` +call will result in the in-order invocation of `Napi::AsyncProgressQueueWorker::OnProgress` +with each data item. + +```cpp +void Napi::AsyncProgressQueueWorker::ExecutionProcess::Send(const T* data, size_t count) const; +``` + +## Example + +The code below shows a basic example of the `Napi::AsyncProgressQueueWorker` implementation: + +```cpp +#include + +#include +#include + +using namespace Napi; + +class EchoWorker : public AsyncProgressQueueWorker { + public: + EchoWorker(Function& callback, std::string& echo) + : AsyncProgressQueueWorker(callback), echo(echo) {} + + ~EchoWorker() {} + // This code will be executed on the worker thread + void Execute(const ExecutionProgress& progress) { + // Need to simulate cpu heavy task + for (uint32_t i = 0; i < 100; ++i) { + progress.Send(&i, 1) + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + } + + void OnOK() { + HandleScope scope(Env()); + Callback().Call({Env().Null(), String::New(Env(), echo)}); + } + + void OnProgress(const uint32_t* data, size_t /* count */) { + HandleScope scope(Env()); + Callback().Call({Env().Null(), Env().Null(), Number::New(Env(), *data)}); + } + + private: + std::string echo; +}; +``` + +The `EchoWorker`'s constructor calls the base class' constructor to pass in the +callback that the `Napi::AsyncProgressQueueWorker` base class will store +persistently. When the work on the `Napi::AsyncProgressQueueWorker::Execute` +method is done the `Napi::AsyncProgressQueueWorker::OnOk` method is called and +the results are returned back to JavaScript when the stored callback is invoked +with its associated environment. + +The following code shows an example of how to create and use an +`Napi::AsyncProgressQueueWorker`. + +```cpp +#include + +// Include EchoWorker class +// .. + +using namespace Napi; + +Value Echo(const CallbackInfo& info) { + // We need to validate the arguments here. + Function cb = info[1].As(); + std::string in = info[0].As(); + EchoWorker* wk = new EchoWorker(cb, in); + wk->Queue(); + return info.Env().Undefined(); +} +``` + +The implementation of a `Napi::AsyncProgressQueueWorker` can be used by creating a +new instance and passing to its constructor the callback to execute when the +asynchronous task ends and other data needed for the computation. Once created, +the only other action needed is to call the `Napi::AsyncProgressQueueWorker::Queue` +method that will queue the created worker for execution. + [`Napi::AsyncWorker`]: ./async_worker.md diff --git a/napi-inl.h b/napi-inl.h index dca1c4e5a..82846c257 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -4590,9 +4590,89 @@ inline void ThreadSafeFunction::CallJS(napi_env env, } //////////////////////////////////////////////////////////////////////////////// -// Async Progress Worker class +// Async Progress Worker Base class //////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(receiver, callback, resource_name, resource) { + // Fill all possible arguments to work around ambiguous ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(callback.Env(), + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorkerBase::AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size) + : AsyncWorker(env, resource_name, resource) { + // TODO: Once the changes to make the callback optional for threadsafe + // functions are available on all versions we can remove the dummy Function here. + Function callback; + // Fill all possible arguments to work around ambiguous ThreadSafeFunction::New signatures. + _tsfn = ThreadSafeFunction::New(env, + callback, + resource, + resource_name, + queue_size, + /** initialThreadCount */ 1, + /** context */ this, + OnThreadSafeFunctionFinalize, + /** finalizeData */ this); +} +#endif + +template +inline AsyncProgressWorkerBase::~AsyncProgressWorkerBase() { + // Abort pending tsfn call. + // Don't send progress events after we've already completed. + // It's ok to call ThreadSafeFunction::Abort and ThreadSafeFunction::Release duplicated. + _tsfn.Abort(); +} +template +inline void AsyncProgressWorkerBase::OnAsyncWorkProgress(Napi::Env /* env */, + Napi::Function /* jsCallback */, + void* data) { + ThreadSafeData* tsd = static_cast(data); + tsd->asyncprogressworker()->OnWorkProgress(tsd->data()); +} + +template +inline napi_status AsyncProgressWorkerBase::NonBlockingCall(DataType* data) { + auto tsd = new AsyncProgressWorkerBase::ThreadSafeData(this, data); + return _tsfn.NonBlockingCall(tsd, OnAsyncWorkProgress); +} + +template +inline void AsyncProgressWorkerBase::OnWorkComplete(Napi::Env /* env */, napi_status status) { + _work_completed = true; + _complete_status = status; + _tsfn.Release(); +} + +template +inline void AsyncProgressWorkerBase::OnThreadSafeFunctionFinalize(Napi::Env env, void* /* data */, AsyncProgressWorkerBase* context) { + if (context->_work_completed) { + context->AsyncWorker::OnWorkComplete(env, context->_complete_status); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// template inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) : AsyncProgressWorker(callback, "generic") { @@ -4635,10 +4715,9 @@ inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, const Function& callback, const char* resource_name, const Object& resource) - : AsyncWorker(receiver, callback, resource_name, resource), + : AsyncProgressWorkerBase(receiver, callback, resource_name, resource), _asyncdata(nullptr), _asyncsize(0) { - _tsfn = ThreadSafeFunction::New(callback.Env(), callback, resource_name, 1, 1); } #if NAPI_VERSION > 4 @@ -4657,27 +4736,19 @@ template inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, const char* resource_name, const Object& resource) - : AsyncWorker(env, resource_name, resource), + : AsyncProgressWorkerBase(env, resource_name, resource), _asyncdata(nullptr), _asyncsize(0) { - // TODO: Once the changes to make the callback optional for threadsafe - // functions are no longer optional we can remove the dummy Function here. - Function callback; - _tsfn = ThreadSafeFunction::New(env, callback, resource_name, 1, 1); } #endif template inline AsyncProgressWorker::~AsyncProgressWorker() { - // Abort pending tsfn call. - // Don't send progress events after we've already completed. - _tsfn.Abort(); { - std::lock_guard lock(_mutex); + std::lock_guard lock(this->_mutex); _asyncdata = nullptr; _asyncsize = 0; } - _tsfn.Release(); } template @@ -4687,20 +4758,18 @@ inline void AsyncProgressWorker::Execute() { } template -inline void AsyncProgressWorker::WorkProgress_(Napi::Env /* env */, Napi::Function /* jsCallback */, void* _data) { - AsyncProgressWorker* self = static_cast(_data); - +inline void AsyncProgressWorker::OnWorkProgress(void*) { T* data; size_t size; { - std::lock_guard lock(self->_mutex); - data = self->_asyncdata; - size = self->_asyncsize; - self->_asyncdata = nullptr; - self->_asyncsize = 0; + std::lock_guard lock(this->_mutex); + data = this->_asyncdata; + size = this->_asyncsize; + this->_asyncdata = nullptr; + this->_asyncsize = 0; } - self->OnProgress(data, size); + this->OnProgress(data, size); delete[] data; } @@ -4711,19 +4780,19 @@ inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { T* old_data; { - std::lock_guard lock(_mutex); + std::lock_guard lock(this->_mutex); old_data = _asyncdata; _asyncdata = new_data; _asyncsize = count; } - _tsfn.NonBlockingCall(this, WorkProgress_); + this->NonBlockingCall(nullptr); delete[] old_data; } template inline void AsyncProgressWorker::Signal() const { - _tsfn.NonBlockingCall(this, WorkProgress_); + this->NonBlockingCall(nullptr); } template @@ -4735,6 +4804,124 @@ template inline void AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const { _worker->SendProgress_(data, count); } + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Queue Worker class +//////////////////////////////////////////////////////////////////////////////// +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback) + : AsyncProgressQueueWorker(callback, "generic") { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback, + const char* resource_name) + : AsyncProgressQueueWorker(callback, resource_name, Object::New(callback.Env())) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressQueueWorker(Object::New(callback.Env()), + callback, + resource_name, + resource) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, + const Function& callback) + : AsyncProgressQueueWorker(receiver, callback, "generic") { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressQueueWorker(receiver, + callback, + resource_name, + Object::New(callback.Env())) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>(receiver, callback, resource_name, resource, /** unlimited queue size */0) { +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env) + : AsyncProgressQueueWorker(env, "generic") { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressQueueWorker(env, resource_name, Object::New(env)) { +} + +template +inline AsyncProgressQueueWorker::AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncProgressWorkerBase>(env, resource_name, resource, /** unlimited queue size */0) { +} +#endif + +template +inline void AsyncProgressQueueWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressQueueWorker::OnWorkProgress(std::pair* datapair) { + if (datapair == nullptr) { + return; + } + + T *data = datapair->first; + size_t size = datapair->second; + + this->OnProgress(data, size); + delete datapair; + delete[] data; +} + +template +inline void AsyncProgressQueueWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + auto pair = new std::pair(new_data, count); + this->NonBlockingCall(pair); +} + +template +inline void AsyncProgressQueueWorker::Signal() const { + this->NonBlockingCall(nullptr); +} + +template +inline void AsyncProgressQueueWorker::OnWorkComplete(Napi::Env env, napi_status status) { + // Draining queued items in TSFN. + AsyncProgressWorkerBase>::OnWorkComplete(env, status); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Signal() const { + _worker->Signal(); +} + +template +inline void AsyncProgressQueueWorker::ExecutionProgress::Send(const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index ff7b82226..e4b964f87 100644 --- a/napi.h +++ b/napi.h @@ -2241,8 +2241,55 @@ namespace Napi { napi_threadsafe_function _tsfn; }; + template + class AsyncProgressWorkerBase : public AsyncWorker { + public: + virtual void OnWorkProgress(DataType* data) = 0; + class ThreadSafeData { + public: + ThreadSafeData(AsyncProgressWorkerBase* asyncprogressworker, DataType* data) + : _asyncprogressworker(asyncprogressworker), _data(data) {} + + AsyncProgressWorkerBase* asyncprogressworker() { return _asyncprogressworker; }; + DataType* data() { return _data; }; + + private: + AsyncProgressWorkerBase* _asyncprogressworker; + DataType* _data; + }; + void OnWorkComplete(Napi::Env env, napi_status status) override; + protected: + explicit AsyncProgressWorkerBase(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); + virtual ~AsyncProgressWorkerBase(); + +// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. +// Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressWorkerBase(Napi::Env env, + const char* resource_name, + const Object& resource, + size_t queue_size = 1); +#endif + + static inline void OnAsyncWorkProgress(Napi::Env env, + Napi::Function jsCallback, + void* data); + + napi_status NonBlockingCall(DataType* data); + + private: + ThreadSafeFunction _tsfn; + bool _work_completed = false; + napi_status _complete_status; + static inline void OnThreadSafeFunctionFinalize(Napi::Env env, void* data, AsyncProgressWorkerBase* context); + }; + template - class AsyncProgressWorker : public AsyncWorker { + class AsyncProgressWorker : public AsyncProgressWorkerBase { public: virtual ~AsyncProgressWorker(); @@ -2256,6 +2303,8 @@ namespace Napi { AsyncProgressWorker* const _worker; }; + void OnWorkProgress(void*) override; + protected: explicit AsyncProgressWorker(const Function& callback); explicit AsyncProgressWorker(const Function& callback, @@ -2287,8 +2336,6 @@ namespace Napi { virtual void OnProgress(const T* data, size_t count) = 0; private: - static void WorkProgress_(Napi::Env env, Napi::Function jsCallback, void* data); - void Execute() override; void Signal() const; void SendProgress_(const T* data, size_t count); @@ -2296,7 +2343,60 @@ namespace Napi { std::mutex _mutex; T* _asyncdata; size_t _asyncsize; - ThreadSafeFunction _tsfn; + }; + + template + class AsyncProgressQueueWorker : public AsyncProgressWorkerBase> { + public: + virtual ~AsyncProgressQueueWorker() {}; + + class ExecutionProgress { + friend class AsyncProgressQueueWorker; + public: + void Signal() const; + void Send(const T* data, size_t count) const; + private: + explicit ExecutionProgress(AsyncProgressQueueWorker* worker) : _worker(worker) {} + AsyncProgressQueueWorker* const _worker; + }; + + void OnWorkComplete(Napi::Env env, napi_status status) override; + void OnWorkProgress(std::pair*) override; + + protected: + explicit AsyncProgressQueueWorker(const Function& callback); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressQueueWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource); + +// Optional callback of Napi::ThreadSafeFunction only available after NAPI_VERSION 4. +// Refs: https://github.com/nodejs/node/pull/27791 +#if NAPI_VERSION > 4 + explicit AsyncProgressQueueWorker(Napi::Env env); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name); + explicit AsyncProgressQueueWorker(Napi::Env env, + const char* resource_name, + const Object& resource); +#endif + virtual void Execute(const ExecutionProgress& progress) = 0; + virtual void OnProgress(const T* data, size_t count) = 0; + + private: + void Execute() override; + void Signal() const; + void SendProgress_(const T* data, size_t count); }; #endif diff --git a/test/asyncprogressqueueworker.cc b/test/asyncprogressqueueworker.cc new file mode 100644 index 000000000..b30863301 --- /dev/null +++ b/test/asyncprogressqueueworker.cc @@ -0,0 +1,96 @@ +#include "napi.h" + +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct ProgressData { + int32_t progress; +}; + +class TestWorker : public AsyncProgressQueueWorker { +public: + static Napi::Value CreateWork(const CallbackInfo& info) { + int32_t times = info[0].As().Int32Value(); + Function cb = info[1].As(); + Function progress = info[2].As(); + + TestWorker* worker = new TestWorker(cb, + progress, + "TestResource", + Object::New(info.Env()), + times); + + return Napi::External::New(info.Env(), worker); + } + + static void QueueWork(const CallbackInfo& info) { + auto wrap = info[0].As>(); + auto worker = wrap.Data(); + worker->Queue(); + } + + static void CancelWork(const CallbackInfo& info) { + auto wrap = info[0].As>(); + auto worker = wrap.Data(); + // We cannot cancel a worker if it got started. So we have to do a quick cancel. + worker->Queue(); + worker->Cancel(); + } + +protected: + void Execute(const ExecutionProgress& progress) override { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); + + if (_times < 0) { + SetError("test error"); + } + ProgressData data{0}; + for (int32_t idx = 0; idx < _times; idx++) { + data.progress = idx; + progress.Send(&data, 1); + } + } + + void OnProgress(const ProgressData* data, size_t /* count */) override { + Napi::Env env = Env(); + if (!_js_progress_cb.IsEmpty()) { + Number progress = Number::New(env, data->progress); + _js_progress_cb.Call(Receiver().Value(), { progress }); + } + } + +private: + TestWorker(Function cb, + Function progress, + const char* resource_name, + const Object& resource, + int32_t times) + : AsyncProgressQueueWorker(cb, resource_name, resource), + _times(times) { + _js_progress_cb.Reset(progress, 1); + } + + int32_t _times; + FunctionReference _js_progress_cb; +}; + +} // namespace + +Object InitAsyncProgressQueueWorker(Env env) { + Object exports = Object::New(env); + exports["createWork"] = Function::New(env, TestWorker::CreateWork); + exports["queueWork"] = Function::New(env, TestWorker::QueueWork); + exports["cancelWork"] = Function::New(env, TestWorker::CancelWork); + return exports; +} + +#endif diff --git a/test/asyncprogressqueueworker.js b/test/asyncprogressqueueworker.js new file mode 100644 index 000000000..6fa65520e --- /dev/null +++ b/test/asyncprogressqueueworker.js @@ -0,0 +1,63 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const common = require('./common') +const assert = require('assert'); +const os = require('os'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test({ asyncprogressqueueworker }) { + success(asyncprogressqueueworker); + fail(asyncprogressqueueworker); + cancel(asyncprogressqueueworker); + return; +} + +function success(binding) { + const expected = [0, 1, 2, 3]; + const actual = []; + const worker = binding.createWork(expected.length, + common.mustCall((err) => { + if (err) { + assert.fail(err); + } + // All queued items shall be invoked before complete callback. + assert.deepEqual(actual, expected); + }), + common.mustCall((_progress) => { + actual.push(_progress); + }, expected.length) + ); + binding.queueWork(worker); +} + +function fail(binding) { + const worker = binding.createWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err }, /test error/) + }), + () => { + assert.fail('unexpected progress report'); + } + ); + binding.queueWork(worker); +} + +function cancel(binding) { + // make sure the work we are going to cancel will not be + // able to start by using all the threads in the pool. + for (let i = 0; i < os.cpus().length; ++i) { + const worker = binding.createWork(-1, () => {}, () => {}); + binding.queueWork(worker); + } + const worker = binding.createWork(-1, + () => { + assert.fail('unexpected callback'); + }, + () => { + assert.fail('unexpected progress report'); + } + ); + binding.cancelWork(worker); +} diff --git a/test/binding.cc b/test/binding.cc index aa9db6e41..111bcce01 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -5,6 +5,7 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); #if (NAPI_VERSION > 3) +Object InitAsyncProgressQueueWorker(Env env); Object InitAsyncProgressWorker(Env env); #endif Object InitAsyncWorker(Env env); @@ -60,6 +61,7 @@ Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asynccontext", InitAsyncContext(env)); #if (NAPI_VERSION > 3) + exports.Set("asyncprogressqueueworker", InitAsyncProgressQueueWorker(env)); exports.Set("asyncprogressworker", InitAsyncProgressWorker(env)); #endif exports.Set("asyncworker", InitAsyncWorker(env)); diff --git a/test/binding.gyp b/test/binding.gyp index b6777808d..2d6ac9549 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -4,6 +4,7 @@ 'sources': [ 'arraybuffer.cc', 'asynccontext.cc', + 'asyncprogressqueueworker.cc', 'asyncprogressworker.cc', 'asyncworker.cc', 'asyncworker-persistent.cc', diff --git a/test/index.js b/test/index.js index 1bd1d9144..e96ac5bf8 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ process.config.target_defaults.default_configuration = let testModules = [ 'arraybuffer', 'asynccontext', + 'asyncprogressqueueworker', 'asyncprogressworker', 'asyncworker', 'asyncworker-nocallback', @@ -72,6 +73,7 @@ if (napiVersion < 3) { } if (napiVersion < 4) { + testModules.splice(testModules.indexOf('asyncprogressqueueworker'), 1); testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ctx'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_existing_tsfn'), 1);