From 5c73b25d1fdf37d867e9fdf914a1a6dc1879392f Mon Sep 17 00:00:00 2001 From: John French Date: Tue, 27 Aug 2019 21:11:14 +0800 Subject: [PATCH] src: implement AsyncProgressWorker PR-URL: https://github.com/nodejs/node-addon-api/pull/529 Fixes: https://github.com/nodejs/node-addon-api/issues/473 Reviewed-By: Michael Dawson Reviewed-By: Gabriel Schulhof --- doc/async_progress_worker.md | 344 +++++++++++++++++++++++++++++++++++ napi-inl.h | 150 +++++++++++++++ napi.h | 60 ++++++ test/asyncprogressworker.cc | 72 ++++++++ test/asyncprogressworker.js | 42 +++++ test/binding.cc | 6 + test/binding.gyp | 1 + test/index.js | 2 + 8 files changed, 677 insertions(+) create mode 100644 doc/async_progress_worker.md create mode 100644 test/asyncprogressworker.cc create mode 100644 test/asyncprogressworker.js diff --git a/doc/async_progress_worker.md b/doc/async_progress_worker.md new file mode 100644 index 0000000..296b51b --- /dev/null +++ b/doc/async_progress_worker.md @@ -0,0 +1,344 @@ +# AsyncProgressWorker + +`Napi::AsyncProgressWorker` is an abstract class which implements `Napi::AsyncWorker` +while extending `Napi::AsyncWorker` internally with `Napi::ThreadSafeFunction` for +moving work progress reports from worker thread(s) to event loop threads. + +Like `Napi::AsyncWorker`, once created, execution is requested by calling +`Napi::AsyncProgressWorker::Queue`. When a thread is available for execution +the `Napi::AsyncProgressWorker::Execute` method will be invoked. During the +execution, `Napi::AsyncProgressWorker::ExecutionProgress::Send` can be used to +indicate execution process, which will eventually invoke `Napi::AsyncProgressWorker::OnProgress` +on the JavaScript thread to safely call into JavaScript. Once `Napi::AsyncProgressWorker::Execute` +completes either `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` +will be invoked. Once the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` +methods are complete the `Napi::AsyncProgressWorker` instance is destructed. + +For the most basic use, only the `Napi::AsyncProgressWorker::Execute` and +`Napi::AsyncProgressWorker::OnProgress` method must be implemented in a subclass. + +## Methods + +[`Napi::AsyncWorker`][] provides detailed descriptions for most methods. + +### Execute + +This method is used to execute some tasks outside of the **event loop** on a libuv +worker thread. Subclasses must implement this method and the method is run on +a thread other than that running the main event loop. As the method is not +running on the main event loop, it must avoid calling any methods from node-addon-api +or running any code that might invoke JavaScript. Instead, once this method is +complete any interaction through node-addon-api with JavaScript should be implemented +in the `Napi::AsyncProgressWorker::OnOK` method and/or `Napi::AsyncProgressWorker::OnError` +which run on the main thread and are invoked when the `Napi::AsyncProgressWorker::Execute` +method completes. + +```cpp +virtual void Napi::AsyncProgressWorker::Execute(const ExecutionProgress& progress) = 0; +``` + +### OnOK + +This method is invoked when the computation in the `Execute` method ends. +The default implementation runs the `Callback` optionally provided when the +`AsyncProgressWorker` class was created. The `Callback` will by default receive no +arguments. Arguments to the callback can be provided by overriding the `GetResult()` +method. + +```cpp +virtual void Napi::AsyncProgressWorker::OnOK(); +``` + +### OnProgress + +This method is invoked when the computation in the `Napi::AsyncProgressWorker::ExecutionProcess::Send` +method was called during worker thread execution. + +```cpp +virtual void Napi::AsyncProgressWorker::OnProgress(const T* data, size_t count) +``` + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Function& callback); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Function& callback, const char* resource_name); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Function& callback, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. +- `[in] resource`: Object associated with the asynchronous operation that +will be passed to possible async_hooks. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name); +``` + +- `[in] receiver`: The `this` object passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. + +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(const Napi::Object& receiver, const Napi::Function& callback, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] receiver`: The `this` object to be passed to the called function. +- `[in] callback`: The function which will be called when an asynchronous +operations ends. The given function is called from the main event loop thread. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. +- `[in] resource`: Object associated with the asynchronous operation that +will be passed to possible async_hooks. + +Returns a `Napi::AsyncWork` instance which can later be queued for execution by +calling `Napi::AsyncWork::Queue`. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(Napi::Env env); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`. + +Returns an `Napi::AsyncProgressWorker` instance which can later be queued for execution by calling +`Napi::AsyncProgressWorker::Queue`. + +Available with `NAPI_VERSION` equal to or greater than 5. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(Napi::Env env, const char* resource_name); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncProgressWorker::Queue`. + +Available with `NAPI_VERSION` equal to or greater than 5. + +### Constructor + +Creates a new `Napi::AsyncProgressWorker`. + +```cpp +explicit Napi::AsyncProgressWorker(Napi::Env env, const char* resource_name, const Napi::Object& resource); +``` + +- `[in] env`: The environment in which to create the `Napi::AsyncProgressWorker`. +- `[in] resource_name`: Null-terminated string that represents the +identifier for the kind of resource that is being provided for diagnostic +information exposed by the async_hooks API. +- `[in] resource`: Object associated with the asynchronous operation that +will be passed to possible async_hooks. + +Returns a `Napi::AsyncProgressWorker` instance which can later be queued for execution by +calling `Napi::AsyncProgressWorker::Queue`. + +Available with `NAPI_VERSION` equal to or greater than 5. + +### Destructor + +Deletes the created work object that is used to execute logic asynchronously and +release the internal `Napi::ThreadSafeFunction`, which will be aborted to prevent +unexpected upcoming thread safe calls. + +```cpp +virtual Napi::AsyncProgressWorker::~AsyncProgressWorker(); +``` + +# AsyncProgressWorker::ExecutionProcess + +A bridge class created before the worker thread execution of `Napi::AsyncProgressWorker::Execute`. + +## Methods + +### Send + +`Napi::AsyncProgressWorker::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::AsyncProgressWorker` so +after the call to `Napi::AsyncProgressWorker::ExecutionProcess::Send` the data can +be safely released. + +Note that `Napi::AsyncProgressWorker::ExecutionProcess::Send` merely guarantees +**eventual** invocation of `Napi::AsyncProgressWorker::OnProgress`, which means +multiple send might be coalesced into single invocation of `Napi::AsyncProgressWorker::OnProgress` +with latest data. + +```cpp +void Napi::AsyncProgressWorker::ExecutionProcess::Send(const T* data, size_t count) const; +``` + +## Example + +The first step to use the `Napi::AsyncProgressWorker` class is to create a new class that +inherits from it and implement the `Napi::AsyncProgressWorker::Execute` abstract method. +Typically input to the worker will be saved within the class' fields generally +passed in through its constructor. + +During the worker thread execution, the first argument of `Napi::AsyncProgressWorker::Execute` +can be used to report the progress of the execution. + +When the `Napi::AsyncProgressWorker::Execute` method completes without errors the +`Napi::AsyncProgressWorker::OnOK` function callback will be invoked. In this function the +results of the computation will be reassembled and returned back to the initial +JavaScript context. + +`Napi::AsyncProgressWorker` ensures that all the code in the `Napi::AsyncProgressWorker::Execute` +function runs in the background out of the **event loop** thread and at the end +the `Napi::AsyncProgressWorker::OnOK` or `Napi::AsyncProgressWorker::OnError` function will be +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 + +use namespace Napi; + +class EchoWorker : public AsyncProgressWorker { + public: + EchoWorker(Function& callback, std::string& echo) + : AsyncProgressWorker(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::AsyncProgressWorker` base class will store persistently. When +the work on the `Napi::AsyncProgressWorker::Execute` method is done the +`Napi::AsyncProgressWorker::OnOk` method is called and the results are return 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::AsyncProgressWorker` + +```cpp +#include + +// Include EchoWorker class +// .. + +use 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::AsyncProgressWorker` 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::AsyncProgressWorker::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 91837f7..0abfcba 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -9,7 +9,9 @@ // Note: Do not include this file directly! Include "napi.h" instead. +#include #include +#include #include namespace Napi { @@ -4179,6 +4181,154 @@ inline void ThreadSafeFunction::CallJS(napi_env env, Function(env, jsCallback).Call({}); } } + +//////////////////////////////////////////////////////////////////////////////// +// Async Progress Worker class +//////////////////////////////////////////////////////////////////////////////// + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback) + : AsyncProgressWorker(callback, "generic") { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name) + : AsyncProgressWorker(callback, resource_name, Object::New(callback.Env())) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncProgressWorker(Object::New(callback.Env()), + callback, + resource_name, + resource) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback) + : AsyncProgressWorker(receiver, callback, "generic") { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name) + : AsyncProgressWorker(receiver, + callback, + resource_name, + Object::New(callback.Env())) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name, + const Object& resource) + : AsyncWorker(receiver, callback, resource_name, resource), + _asyncdata(nullptr), + _asyncsize(0) { + _tsfn = ThreadSafeFunction::New(callback.Env(), callback, resource_name, 1, 1); +} + +#if NAPI_VERSION > 4 +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env) + : AsyncProgressWorker(env, "generic") { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name) + : AsyncProgressWorker(env, resource_name, Object::New(env)) { +} + +template +inline AsyncProgressWorker::AsyncProgressWorker(Napi::Env env, + const char* resource_name, + const Object& resource) + : AsyncWorker(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); + _asyncdata = nullptr; + _asyncsize = 0; + } + _tsfn.Release(); +} + +template +inline void AsyncProgressWorker::Execute() { + ExecutionProgress progress(this); + Execute(progress); +} + +template +inline void AsyncProgressWorker::WorkProgress_(Napi::Env /* env */, Napi::Function /* jsCallback */, void* _data) { + AsyncProgressWorker* self = static_cast(_data); + + T* data; + size_t size; + { + std::lock_guard lock(self->_mutex); + data = self->_asyncdata; + size = self->_asyncsize; + self->_asyncdata = nullptr; + self->_asyncsize = 0; + } + + self->OnProgress(data, size); + delete[] data; +} + +template +inline void AsyncProgressWorker::SendProgress_(const T* data, size_t count) { + T* new_data = new T[count]; + std::copy(data, data + count, new_data); + + T* old_data; + { + std::lock_guard lock(_mutex); + old_data = _asyncdata; + _asyncdata = new_data; + _asyncsize = count; + } + _tsfn.NonBlockingCall(this, WorkProgress_); + + delete[] old_data; +} + +template +inline void AsyncProgressWorker::Signal() const { + _tsfn.NonBlockingCall(this, WorkProgress_); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Signal() const { + _worker->Signal(); +} + +template +inline void AsyncProgressWorker::ExecutionProgress::Send(const T* data, size_t count) const { + _worker->SendProgress_(data, count); +} + #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/napi.h b/napi.h index d70270e..f4a1ecb 100644 --- a/napi.h +++ b/napi.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -2089,6 +2090,65 @@ namespace Napi { std::unique_ptr _tsfn; Deleter _d; }; + + template + class AsyncProgressWorker : public AsyncWorker { + public: + virtual ~AsyncProgressWorker(); + + class ExecutionProgress { + friend class AsyncProgressWorker; + public: + void Signal() const; + void Send(const T* data, size_t count) const; + private: + explicit ExecutionProgress(AsyncProgressWorker* worker) : _worker(worker) {} + AsyncProgressWorker* const _worker; + }; + + protected: + explicit AsyncProgressWorker(const Function& callback); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(const Function& callback, + const char* resource_name, + const Object& resource); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback); + explicit AsyncProgressWorker(const Object& receiver, + const Function& callback, + const char* resource_name); + explicit AsyncProgressWorker(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 AsyncProgressWorker(Napi::Env env); + explicit AsyncProgressWorker(Napi::Env env, + const char* resource_name); + explicit AsyncProgressWorker(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: + 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); + + std::mutex _mutex; + T* _asyncdata; + size_t _asyncsize; + ThreadSafeFunction _tsfn; + }; #endif // Memory management. diff --git a/test/asyncprogressworker.cc b/test/asyncprogressworker.cc new file mode 100644 index 0000000..8ac062f --- /dev/null +++ b/test/asyncprogressworker.cc @@ -0,0 +1,72 @@ +#include "napi.h" + +#include +#include +#include +#include + +#if (NAPI_VERSION > 3) + +using namespace Napi; + +namespace { + +struct ProgressData { + size_t progress; +}; + +class TestWorker : public AsyncProgressWorker { +public: + static void DoWork(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())); + worker->_times = times; + worker->Queue(); + } + +protected: + void Execute(const ExecutionProgress& progress) override { + if (_times < 0) { + SetError("test error"); + } + ProgressData data{0}; + std::unique_lock lock(_cvm); + for (int32_t idx = 0; idx < _times; idx++) { + data.progress = idx; + progress.Send(&data, 1); + _cv.wait(lock); + } + } + + void OnProgress(const ProgressData* data, size_t /* count */) override { + Napi::Env env = Env(); + if (!_progress.IsEmpty()) { + Number progress = Number::New(env, data->progress); + _progress.MakeCallback(Receiver().Value(), { progress }); + } + _cv.notify_one(); + } + +private: + TestWorker(Function cb, Function progress, const char* resource_name, const Object& resource) + : AsyncProgressWorker(cb, resource_name, resource) { + _progress.Reset(progress, 1); + } + std::condition_variable _cv; + std::mutex _cvm; + int32_t _times; + FunctionReference _progress; +}; + +} + +Object InitAsyncProgressWorker(Env env) { + Object exports = Object::New(env); + exports["doWork"] = Function::New(env, TestWorker::DoWork); + return exports; +} + +#endif diff --git a/test/asyncprogressworker.js b/test/asyncprogressworker.js new file mode 100644 index 0000000..0aee9b7 --- /dev/null +++ b/test/asyncprogressworker.js @@ -0,0 +1,42 @@ +'use strict'; +const buildType = process.config.target_defaults.default_configuration; +const common = require('./common') +const assert = require('assert'); + +test(require(`./build/${buildType}/binding.node`)); +test(require(`./build/${buildType}/binding_noexcept.node`)); + +function test({ asyncprogressworker }) { + success(asyncprogressworker); + fail(asyncprogressworker); + return; +} + +function success(binding) { + const expected = [0, 1, 2, 3]; + const actual = []; + binding.doWork(expected.length, + common.mustCall((err) => { + if (err) { + assert.fail(err); + } + }), + common.mustCall((_progress) => { + actual.push(_progress); + if (actual.length === expected.length) { + assert.deepEqual(actual, expected); + } + }, expected.length) + ); +} + +function fail(binding) { + binding.doWork(-1, + common.mustCall((err) => { + assert.throws(() => { throw err }, /test error/) + }), + () => { + assert.fail('unexpected progress report'); + } + ); +} diff --git a/test/binding.cc b/test/binding.cc index 9286a87..0490a85 100644 --- a/test/binding.cc +++ b/test/binding.cc @@ -5,6 +5,9 @@ using namespace Napi; Object InitArrayBuffer(Env env); Object InitAsyncContext(Env env); +#if (NAPI_VERSION > 3) +Object InitAsyncProgressWorker(Env env); +#endif Object InitAsyncWorker(Env env); Object InitPersistentAsyncWorker(Env env); Object InitBasicTypesArray(Env env); @@ -50,6 +53,9 @@ Object InitThunkingManual(Env env); Object Init(Env env, Object exports) { exports.Set("arraybuffer", InitArrayBuffer(env)); exports.Set("asynccontext", InitAsyncContext(env)); +#if (NAPI_VERSION > 3) + exports.Set("asyncprogressworker", InitAsyncProgressWorker(env)); +#endif exports.Set("asyncworker", InitAsyncWorker(env)); exports.Set("persistentasyncworker", InitPersistentAsyncWorker(env)); exports.Set("basic_types_array", InitBasicTypesArray(env)); diff --git a/test/binding.gyp b/test/binding.gyp index 29878c3..769175d 100644 --- a/test/binding.gyp +++ b/test/binding.gyp @@ -7,6 +7,7 @@ 'sources': [ 'arraybuffer.cc', 'asynccontext.cc', + 'asyncprogressworker.cc', 'asyncworker.cc', 'asyncworker-persistent.cc', 'basic_types/array.cc', diff --git a/test/index.js b/test/index.js index 33889d4..d685944 100644 --- a/test/index.js +++ b/test/index.js @@ -10,6 +10,7 @@ process.config.target_defaults.default_configuration = let testModules = [ 'arraybuffer', 'asynccontext', + 'asyncprogressworker', 'asyncworker', 'asyncworker-nocallback', 'asyncworker-persistent', @@ -68,6 +69,7 @@ if ((process.env.npm_config_NAPI_VERSION !== undefined) && if ((process.env.npm_config_NAPI_VERSION !== undefined) && (process.env.npm_config_NAPI_VERSION < 4)) { + testModules.splice(testModules.indexOf('asyncprogressworker'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_ptr'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function_unref'), 1); testModules.splice(testModules.indexOf('threadsafe_function/threadsafe_function'), 1);