| #include <chrono> |
| #include <condition_variable> |
| #include <mutex> |
| #include <thread> |
| #include "assert.h" |
| #include "napi.h" |
| |
| using namespace Napi; |
| |
| class TestWorkerWithUserDefRecv : public AsyncWorker { |
| public: |
| static void DoWork(const CallbackInfo& info) { |
| Object recv = info[0].As<Object>(); |
| Function cb = info[1].As<Function>(); |
| |
| TestWorkerWithUserDefRecv* worker = new TestWorkerWithUserDefRecv(recv, cb); |
| worker->Queue(); |
| } |
| |
| static void DoWorkWithAsyncRes(const CallbackInfo& info) { |
| Object recv = info[0].As<Object>(); |
| Function cb = info[1].As<Function>(); |
| Value resource = info[2]; |
| |
| TestWorkerWithUserDefRecv* worker = nullptr; |
| if (resource == info.Env().Null()) { |
| worker = new TestWorkerWithUserDefRecv(recv, cb, "TestResource"); |
| } else { |
| worker = new TestWorkerWithUserDefRecv( |
| recv, cb, "TestResource", resource.As<Object>()); |
| } |
| |
| worker->Queue(); |
| } |
| |
| protected: |
| void Execute() override {} |
| |
| private: |
| TestWorkerWithUserDefRecv(const Object& recv, const Function& cb) |
| : AsyncWorker(recv, cb) {} |
| TestWorkerWithUserDefRecv(const Object& recv, |
| const Function& cb, |
| const char* resource_name) |
| : AsyncWorker(recv, cb, resource_name) {} |
| TestWorkerWithUserDefRecv(const Object& recv, |
| const Function& cb, |
| const char* resource_name, |
| const Object& resource) |
| : AsyncWorker(recv, cb, resource_name, resource) {} |
| }; |
| |
| // Using default std::allocator impl, but assuming user can define their own |
| // allocate/deallocate methods |
| class CustomAllocWorker : public AsyncWorker { |
| using Allocator = std::allocator<CustomAllocWorker>; |
| |
| public: |
| CustomAllocWorker(Function& cb) : AsyncWorker(cb){}; |
| static void DoWork(const CallbackInfo& info) { |
| Function cb = info[0].As<Function>(); |
| Allocator allocator; |
| CustomAllocWorker* newWorker = allocator.allocate(1); |
| std::allocator_traits<Allocator>::construct(allocator, newWorker, cb); |
| newWorker->Queue(); |
| } |
| |
| protected: |
| void Execute() override {} |
| void Destroy() override { |
| assert(this->_secretVal == 24); |
| Allocator allocator; |
| std::allocator_traits<Allocator>::destroy(allocator, this); |
| allocator.deallocate(this, 1); |
| } |
| |
| private: |
| int _secretVal = 24; |
| }; |
| |
| class TestWorker : public AsyncWorker { |
| public: |
| static void DoWork(const CallbackInfo& info) { |
| bool succeed = info[0].As<Boolean>(); |
| Object resource = info[1].As<Object>(); |
| Function cb = info[2].As<Function>(); |
| Value data = info[3]; |
| |
| TestWorker* worker = nullptr; |
| if (resource == info.Env().Null()) { |
| worker = new TestWorker(cb, "TestResource"); |
| } else { |
| worker = new TestWorker(cb, "TestResource", resource); |
| } |
| |
| worker->Receiver().Set("data", data); |
| worker->_succeed = succeed; |
| worker->Queue(); |
| } |
| |
| protected: |
| void Execute() override { |
| if (!_succeed) { |
| SetError("test error"); |
| } |
| } |
| |
| private: |
| TestWorker(Function cb, const char* resource_name, const Object& resource) |
| : AsyncWorker(cb, resource_name, resource) {} |
| TestWorker(Function cb, const char* resource_name) |
| : AsyncWorker(cb, resource_name) {} |
| bool _succeed{}; |
| }; |
| |
| class TestWorkerWithResult : public AsyncWorker { |
| public: |
| static void DoWork(const CallbackInfo& info) { |
| bool succeed = info[0].As<Boolean>(); |
| Object resource = info[1].As<Object>(); |
| Function cb = info[2].As<Function>(); |
| Value data = info[3]; |
| |
| TestWorkerWithResult* worker = |
| new TestWorkerWithResult(cb, "TestResource", resource); |
| worker->Receiver().Set("data", data); |
| worker->_succeed = succeed; |
| worker->Queue(); |
| } |
| |
| protected: |
| void Execute() override { |
| if (!_succeed) { |
| SetError("test error"); |
| } |
| } |
| |
| std::vector<napi_value> GetResult(Napi::Env env) override { |
| return {Boolean::New(env, _succeed), |
| String::New(env, _succeed ? "ok" : "error")}; |
| } |
| |
| private: |
| TestWorkerWithResult(Function cb, |
| const char* resource_name, |
| const Object& resource) |
| : AsyncWorker(cb, resource_name, resource) {} |
| bool _succeed{}; |
| }; |
| |
| class TestWorkerNoCallback : public AsyncWorker { |
| public: |
| static Value DoWork(const CallbackInfo& info) { |
| bool succeed = info[0].As<Boolean>(); |
| |
| TestWorkerNoCallback* worker = new TestWorkerNoCallback(info.Env()); |
| worker->_succeed = succeed; |
| worker->Queue(); |
| return worker->_deferred.Promise(); |
| } |
| |
| static Value DoWorkWithAsyncRes(const CallbackInfo& info) { |
| napi_env env = info.Env(); |
| bool succeed = info[0].As<Boolean>(); |
| Object resource = info[1].As<Object>(); |
| |
| TestWorkerNoCallback* worker = nullptr; |
| if (resource == info.Env().Null()) { |
| worker = new TestWorkerNoCallback(env, "TestResource"); |
| } else { |
| worker = new TestWorkerNoCallback(env, "TestResource", resource); |
| } |
| worker->_succeed = succeed; |
| worker->Queue(); |
| return worker->_deferred.Promise(); |
| } |
| |
| protected: |
| void Execute() override {} |
| virtual void OnOK() override { _deferred.Resolve(Env().Undefined()); } |
| virtual void OnError(const Napi::Error& /* e */) override { |
| _deferred.Reject(Env().Undefined()); |
| } |
| |
| private: |
| TestWorkerNoCallback(Napi::Env env) |
| : AsyncWorker(env), _deferred(Napi::Promise::Deferred::New(env)) {} |
| |
| TestWorkerNoCallback(napi_env env, const char* resource_name) |
| : AsyncWorker(env, resource_name), |
| _deferred(Napi::Promise::Deferred::New(env)) {} |
| |
| TestWorkerNoCallback(napi_env env, |
| const char* resource_name, |
| const Object& resource) |
| : AsyncWorker(env, resource_name, resource), |
| _deferred(Napi::Promise::Deferred::New(env)) {} |
| Promise::Deferred _deferred; |
| bool _succeed{}; |
| }; |
| |
| class EchoWorker : public AsyncWorker { |
| public: |
| EchoWorker(Function& cb, std::string& echo) : AsyncWorker(cb), echo(echo) {} |
| ~EchoWorker() {} |
| |
| void Execute() override { |
| // Simulate cpu heavy task |
| std::this_thread::sleep_for(std::chrono::milliseconds(30)); |
| } |
| |
| void OnOK() override { |
| HandleScope scope(Env()); |
| Callback().Call({Env().Null(), String::New(Env(), echo)}); |
| } |
| |
| private: |
| std::string echo; |
| }; |
| |
| class FailCancelWorker : public AsyncWorker { |
| private: |
| bool taskIsRunning = false; |
| std::mutex mu; |
| std::condition_variable taskStartingCv; |
| void NotifyJSThreadTaskHasStarted() { |
| { |
| std::lock_guard<std::mutex> lk(mu); |
| taskIsRunning = true; |
| taskStartingCv.notify_one(); |
| } |
| } |
| |
| public: |
| FailCancelWorker(Function& cb) : AsyncWorker(cb) {} |
| ~FailCancelWorker() {} |
| |
| void WaitForWorkerTaskToStart() { |
| std::unique_lock<std::mutex> lk(mu); |
| taskStartingCv.wait(lk, [this] { return taskIsRunning; }); |
| taskIsRunning = false; |
| } |
| |
| static void DoCancel(const CallbackInfo& info) { |
| Function cb = info[0].As<Function>(); |
| |
| FailCancelWorker* cancelWorker = new FailCancelWorker(cb); |
| cancelWorker->Queue(); |
| cancelWorker->WaitForWorkerTaskToStart(); |
| |
| #ifdef NAPI_CPP_EXCEPTIONS |
| try { |
| cancelWorker->Cancel(); |
| } catch (Napi::Error&) { |
| Napi::Error::New(info.Env(), "Unable to cancel async worker tasks") |
| .ThrowAsJavaScriptException(); |
| } |
| #else |
| cancelWorker->Cancel(); |
| #endif |
| } |
| |
| void Execute() override { |
| NotifyJSThreadTaskHasStarted(); |
| std::this_thread::sleep_for(std::chrono::seconds(1)); |
| } |
| |
| void OnOK() override {} |
| |
| void OnError(const Error&) override {} |
| }; |
| |
| class CancelWorker : public AsyncWorker { |
| public: |
| CancelWorker(Function& cb) : AsyncWorker(cb) {} |
| ~CancelWorker() {} |
| |
| static void DoWork(const CallbackInfo& info) { |
| Function cb = info[0].As<Function>(); |
| std::string echo = info[1].As<String>(); |
| int threadNum = info[2].As<Number>().Uint32Value(); |
| |
| for (int i = 0; i < threadNum; i++) { |
| AsyncWorker* worker = new EchoWorker(cb, echo); |
| worker->Queue(); |
| assert(worker->Env() == info.Env()); |
| } |
| |
| AsyncWorker* cancelWorker = new CancelWorker(cb); |
| cancelWorker->Queue(); |
| |
| #ifdef NAPI_CPP_EXCEPTIONS |
| try { |
| cancelWorker->Cancel(); |
| } catch (Napi::Error&) { |
| Napi::Error::New(info.Env(), "Unable to cancel async worker tasks") |
| .ThrowAsJavaScriptException(); |
| } |
| #else |
| cancelWorker->Cancel(); |
| #endif |
| } |
| |
| void Execute() override { |
| // Simulate cpu heavy task |
| std::this_thread::sleep_for(std::chrono::milliseconds(100)); |
| } |
| |
| void OnOK() override { |
| Napi::Error::New(this->Env(), |
| "OnOk should not be invoked on successful cancellation") |
| .ThrowAsJavaScriptException(); |
| } |
| |
| void OnError(const Error&) override { |
| Napi::Error::New(this->Env(), |
| "OnError should not be invoked on successful cancellation") |
| .ThrowAsJavaScriptException(); |
| } |
| }; |
| |
| Object InitAsyncWorker(Env env) { |
| Object exports = Object::New(env); |
| exports["doWorkRecv"] = Function::New(env, TestWorkerWithUserDefRecv::DoWork); |
| exports["doWithRecvAsyncRes"] = |
| Function::New(env, TestWorkerWithUserDefRecv::DoWorkWithAsyncRes); |
| exports["doWork"] = Function::New(env, TestWorker::DoWork); |
| exports["doWorkAsyncResNoCallback"] = |
| Function::New(env, TestWorkerNoCallback::DoWorkWithAsyncRes); |
| exports["doWorkNoCallback"] = |
| Function::New(env, TestWorkerNoCallback::DoWork); |
| exports["doWorkWithResult"] = |
| Function::New(env, TestWorkerWithResult::DoWork); |
| exports["tryCancelQueuedWork"] = Function::New(env, CancelWorker::DoWork); |
| |
| exports["expectCancelToFail"] = |
| Function::New(env, FailCancelWorker::DoCancel); |
| exports["expectCustomAllocWorkerToDealloc"] = |
| Function::New(env, CustomAllocWorker::DoWork); |
| return exports; |
| } |