diff --git a/js_concurrent_module/utils/locks/async_lock.cpp b/js_concurrent_module/utils/locks/async_lock.cpp index 61821fdeb6e31c636e2517be1ef841fb3446caee..1506157cb1d9fc727f5811519ece95a0a0c82862 100644 --- a/js_concurrent_module/utils/locks/async_lock.cpp +++ b/js_concurrent_module/utils/locks/async_lock.cpp @@ -37,6 +37,10 @@ napi_value AsyncLock::LockAsync(napi_env env, napi_ref cb, LockMode mode, const napi_value promise; napi_deferred deferred; napi_create_promise(env, &deferred, &promise); + if (!NativeEngine::IsAlive(reinterpret_cast(env))) { + ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "env is not alive"); + return nullptr; + } LockRequest *lockRequest = new LockRequest(this, AsyncLockManager::GetCurrentTid(env), env, cb, mode, options, deferred); std::unique_lock lock(asyncLockMutex_); @@ -45,7 +49,6 @@ napi_value AsyncLock::LockAsync(napi_env env, napi_ref cb, LockMode mode, const NAPI_CALL(env, napi_create_string_utf8(env, "The lock is acquired", NAPI_AUTO_LENGTH, &err)); napi_reject_deferred(env, deferred, err); } else { - lockRequest->OnQueued(env, options.timeoutMillis); pendingList_.push_back(lockRequest); ProcessPendingLockRequestUnsafe(env, lockRequest); } @@ -78,7 +81,7 @@ void AsyncLock::CleanUpLockRequestOnCompletion(LockRequest* lockRequest) ProcessPendingLockRequestUnsafe(env); } -bool AsyncLock::CleanUpLockRequestOnTimeout(LockRequest* lockRequest) +bool AsyncLock::CleanUpLockRequest(LockRequest *lockRequest) { std::unique_lock lock(asyncLockMutex_); auto it = std::find(pendingList_.begin(), pendingList_.end(), lockRequest); @@ -94,7 +97,6 @@ bool AsyncLock::CleanUpLockRequestOnTimeout(LockRequest* lockRequest) template void AsyncLock::ProcessLockRequest(napi_env env, LockRequest *lockRequest) { - lockRequest->OnSatisfied(env); heldList_.push_back(lockRequest); pendingList_.pop_front(); asyncLockMutex_.unlock(); diff --git a/js_concurrent_module/utils/locks/async_lock.h b/js_concurrent_module/utils/locks/async_lock.h index e6feddc6865b281263770b8cf63ed814aca7f30f..ad6793357c093feefd8172927b9f40f6420a6e13 100644 --- a/js_concurrent_module/utils/locks/async_lock.h +++ b/js_concurrent_module/utils/locks/async_lock.h @@ -39,7 +39,7 @@ public: napi_value LockAsync(napi_env env, napi_ref cb, LockMode mode, const LockOptions &options); void CleanUpLockRequestOnCompletion(LockRequest* lockRequest); - bool CleanUpLockRequestOnTimeout(LockRequest* lockRequest); + bool CleanUpLockRequest(LockRequest *lockRequest); napi_status FillLockState(napi_env env, napi_value held, napi_value pending); void ProcessPendingLockRequest(napi_env env, LockRequest* syncLockRequest = nullptr); diff --git a/js_concurrent_module/utils/locks/lock_request.cpp b/js_concurrent_module/utils/locks/lock_request.cpp index e4d871450c2e85a5beb13e268592313511174c98..5b2d5b9f4bea143060bf9e0f1535257226867a21 100644 --- a/js_concurrent_module/utils/locks/lock_request.cpp +++ b/js_concurrent_module/utils/locks/lock_request.cpp @@ -15,6 +15,7 @@ #include "async_lock_manager.h" #include "helper/hitrace_helper.h" +#include "js_concurrent_module/utils/locks/lock_request.h" #include "js_concurrent_module/utils/locks/weak_wrap.h" #include "tools/log.h" @@ -32,20 +33,15 @@ LockRequest::LockRequest(AsyncLock *lock, tid_t tid, napi_env env, napi_ref cb, options_(options), deferred_(deferred), work_(nullptr), - timeoutActive_(false), engineId_(engine_->GetId()) { - // timeout timer initialization: just fill the data fields, do not arm it - timeoutTimer_ = new uv_timer_t(); - uv_timer_init(NapiHelper::GetLibUV(env), timeoutTimer_); - RequestTimeoutData *data = new RequestTimeoutData(lock, this); - timeoutTimer_->data = data; - // saving the creation point (file, function and line) for future use NativeEngine *engine = reinterpret_cast(env); engine->BuildJsStackTrace(creationStacktrace_); - NAPI_CALL_RETURN_VOID(env_, napi_add_env_cleanup_hook(env_, EnvCleanUp, this)); + AddEnvCleanupHook(); + InitTimer(); + napi_value resourceName; napi_create_string_utf8(env, "AsyncLock::AsyncCallback", NAPI_AUTO_LENGTH, &resourceName); napi_status status = napi_create_async_work(env_, nullptr, resourceName, AsyncLockManager::EmptyExecuteCallback, @@ -55,55 +51,20 @@ LockRequest::LockRequest(AsyncLock *lock, tid_t tid, napi_env env, napi_ref cb, } } -void LockRequest::EnvCleanUp(void *arg) -{ - LockRequest *lockRequest = static_cast(arg); - std::unique_lock guard(lockRequest->lockRequestMutex_); - napi_delete_reference(lockRequest->env_, lockRequest->callback_); - lockRequest->callback_ = nullptr; - lockRequest->env_ = nullptr; - lockRequest->CleanTimer(); -} - -void LockRequest::CleanTimer() -{ - if (stopTimerTsfn_ != nullptr) { - NAPI_CALL_RETURN_VOID(env_, napi_release_threadsafe_function(stopTimerTsfn_, napi_tsfn_abort)); - stopTimerTsfn_ = nullptr; - } - RequestTimeoutData *data = static_cast(timeoutTimer_->data); - delete data; - timeoutTimer_->data = nullptr; - uv_close(reinterpret_cast(timeoutTimer_), DeallocateTimeoutTimerCallback); -} - void LockRequest::DeallocateTimeoutTimerCallback(uv_handle_t* handle) { delete handle; } -LockRequest::~LockRequest() -{ - std::unique_lock guard(lockRequestMutex_); - if (env_ == nullptr) { - return; - } - CleanTimer(); -} - void LockRequest::AsyncAfterWorkCallback(napi_env env, [[maybe_unused]] napi_status status, void *data) { LockRequest* lockRequest = reinterpret_cast(data); - std::unique_lock lock(lockRequest->lockRequestMutex_); - napi_delete_async_work(env, lockRequest->work_); - lockRequest->work_ = nullptr; - if (lockRequest->env_ == nullptr) { - lock.unlock(); + if (lockRequest->envIsInvalid_) { HILOG_ERROR("AsyncCallback is called after env cleaned up"); + lockRequest->Release(); lockRequest->lock_->CleanUpLockRequestOnCompletion(lockRequest); return; } - lock.unlock(); lockRequest->CallCallback(); } @@ -113,6 +74,7 @@ napi_value LockRequest::FinallyCallback(napi_env env, napi_callback_info info) NAPI_CALL(env, napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast(&lockRequest))); HITRACE_HELPER_METER_NAME("AsyncLock FinallyCallback, " + lockRequest->GetLockInfo()); + lockRequest->Release(); lockRequest->lock_->CleanUpLockRequestOnCompletion(lockRequest); napi_value undefined; @@ -122,15 +84,12 @@ napi_value LockRequest::FinallyCallback(napi_env env, napi_callback_info info) void LockRequest::CallCallbackAsync() { - lockRequestMutex_.lock(); - if (env_ == nullptr || !NativeEngine::IsAlive(engine_) || engineId_ != engine_->GetId()) { - lockRequestMutex_.unlock(); + if (!NativeEngine::IsAlive(engine_) || engineId_ != engine_->GetId()) { HILOG_ERROR("Callback is called after env cleaned up"); lock_->CleanUpLockRequestOnCompletion(this); return; } napi_status status = napi_queue_async_work(env_, work_); - lockRequestMutex_.unlock(); if (status != napi_ok) { HILOG_FATAL("Internal error: cannot queue async work"); } @@ -139,10 +98,9 @@ void LockRequest::CallCallbackAsync() void LockRequest::CallCallback() { HITRACE_HELPER_METER_NAME("AsyncLock Callback, " + GetLockInfo()); - napi_remove_env_cleanup_hook(env_, EnvCleanUp, this); + RemoveEnvCleanupHook(); if (AbortIfNeeded()) { - napi_delete_reference(env_, callback_); - callback_ = nullptr; + Release(); lock_->CleanUpLockRequestOnCompletion(this); return; } @@ -150,8 +108,6 @@ void LockRequest::CallCallback() NAPI_CALL_RETURN_VOID(env_, napi_get_reference_value(env_, callback_, &cb)); napi_value result; napi_status status = napi_call_function(env_, nullptr, cb, 0, nullptr, &result); - napi_delete_reference(env_, callback_); - callback_ = nullptr; if (status == napi_ok) { napi_resolve_deferred(env_, deferred_, result); @@ -175,23 +131,10 @@ void LockRequest::CallCallback() napi_get_and_clear_last_exception(env_, &err); napi_reject_deferred(env_, deferred_, err); } + Release(); lock_->CleanUpLockRequestOnCompletion(this); } -void LockRequest::OnSatisfied(napi_env env) -{ - if (timeoutActive_) { - DisarmTimeoutTimer(env); - } -} - -void LockRequest::OnQueued(napi_env env, uint32_t timeoutMillis) -{ - if (timeoutMillis > 0) { - ArmTimeoutTimer(env, timeoutMillis); - } -} - bool LockRequest::AbortIfNeeded() { if (options_.signal == nullptr) { @@ -210,53 +153,18 @@ bool LockRequest::AbortIfNeeded() return true; } -void LockRequest::ArmTimeoutTimer(napi_env env, uint32_t timeoutMillis) -{ - timeoutActive_ = true; - uv_update_time(NapiHelper::GetLibUV(env)); - uv_timer_start(timeoutTimer_, TimeoutCallback, timeoutMillis, 0); - napi_value resourceName = nullptr; - NAPI_CALL_RETURN_VOID(env, napi_create_string_utf8(env, "stopTimerTsfn", NAPI_AUTO_LENGTH, &resourceName)); - NAPI_CALL_RETURN_VOID(env, napi_create_threadsafe_function(env, nullptr, nullptr, resourceName, 0, 1, nullptr, - nullptr, timeoutTimer_, StopTimer, &stopTimerTsfn_)); - // NOTE: handle the abortsignal functionality in the future - // NOTE: need to check call results for 0 -} - -void LockRequest::DisarmTimeoutTimer(napi_env env) -{ - std::unique_lock guard(lockRequestMutex_); - if (stopTimerTsfn_ == nullptr) { - return; - } - NAPI_CALL_RETURN_VOID(env, napi_call_threadsafe_function(stopTimerTsfn_, new WeakWrap(GetWeakPtr()), - napi_tsfn_blocking)); - timeoutActive_ = false; -} - -void LockRequest::StopTimer(napi_env env, napi_value jsCallback, void *context, void *data) -{ - WeakWrap *weakRequest = static_cast *>(data); - if (weakRequest->GetWeakPtr().expired() || uv_is_closing(static_cast(context))) { - delete weakRequest; - return; - } - uv_timer_stop(static_cast(context)); - delete weakRequest; -} - void LockRequest::TimeoutCallback(uv_timer_t *handle) { - RequestTimeoutData *data = static_cast(handle->data); - if (data == nullptr) { - // fail! something terribly bad happened! - HILOG_FATAL("Internal error: unable to handle the AsyncLock timeout"); + LockRequest *lockRequest = static_cast(handle->data); + if (!lockRequest->lock_->CleanUpLockRequest(lockRequest)) { return; } + lockRequest->RemoveEnvCleanupHook(); + // Check deadlocks and form the rejector value with or w/o the warning. It is required to be done // first in order to obtain the actual data. std::string error; - AsyncLockManager::DumpLocksInfoForThread(AsyncLockManager::GetCurrentTid(data->request->env_), error); + AsyncLockManager::DumpLocksInfoForThread(AsyncLockManager::GetCurrentTid(lockRequest->env_), error); // NOTE: both AsyncLock and LockRequest might be deleted here, but at this point we imply that // AsyncLock exists, later on we we will handle the case when it does not @@ -265,17 +173,12 @@ void LockRequest::TimeoutCallback(uv_timer_t *handle) // We might have the race with the lock acquirer function here and the request will be // already deleted if the race is won by the acquirer. So we should firstly make sure that // the race is won by us and then call the request's methods + lockRequest->HandleRequestTimeout(std::move(error)); - bool success = data->lock->CleanUpLockRequestOnTimeout(data->request); - if (!success) { - return; - } - data->request->HandleRequestTimeout(std::move(error)); - AsyncLock *lock = data->lock; - napi_env env = data->request->env_; - napi_remove_env_cleanup_hook(env, EnvCleanUp, data->request); - // will delete 'data' too - delete data->request; + AsyncLock *lock = lockRequest->lock_; + napi_env env = lockRequest->env_; + lockRequest->Release(); + delete lockRequest; lock->ProcessPendingLockRequest(env); } @@ -284,8 +187,6 @@ void LockRequest::HandleRequestTimeout(std::string &&errorMessage) HILOG_ERROR("AsyncLock lockAsync() timed out! Information: %s", errorMessage.c_str()); // here we imply that there are no races already and the timeout function should do its job // by rejecting the associated promise with an BusinessError instance. - napi_delete_reference(env_, callback_); - callback_ = nullptr; napi_handle_scope scope = nullptr; napi_open_handle_scope(env_, &scope); @@ -311,4 +212,80 @@ std::string LockRequest::GetLockInfo() const return strTrace; } +void LockRequest::EnvCleanup(void *arg) +{ + LockRequest *lockRequest = static_cast(arg); + lockRequest->envIsInvalid_ = true; + if (!lockRequest->lock_->CleanUpLockRequest(lockRequest)) { + return; + } + + AsyncLock *lock = lockRequest->lock_; + napi_env env = lockRequest->env_; + lockRequest->Release(); + delete lockRequest; + lock->ProcessPendingLockRequest(env); +} + +void LockRequest::InitTimer() +{ + if (options_.timeoutMillis <= 0) { + return; + } + timeoutTimer_ = new uv_timer_t(); + uv_loop_t *loop = NapiHelper::GetLibUV(env_); + + int status = uv_timer_init(loop, timeoutTimer_); + if (status != 0) { + HILOG_FATAL("Internal error: unable to initialize the AsyncLock timeout timer %{public}d", status); + return; + } + + uv_update_time(loop); + timeoutTimer_->data = this; + status = uv_timer_start(timeoutTimer_, TimeoutCallback, options_.timeoutMillis, 0); + if (status != 0) { + HILOG_FATAL("Internal error: unable to start the AsyncLock timeout timer %{public}d", status); + } +} + +void LockRequest::StopTimer() +{ + if (timeoutTimer_ == nullptr) { + return; + } + int status = uv_timer_stop(static_cast(timeoutTimer_)); + if (status != 0) { + HILOG_FATAL("Internal error: unable to stop the AsyncLock timeout timer %{public}d", status); + } +} + +void LockRequest::CloseTimer() +{ + if (timeoutTimer_ == nullptr) { + return; + } + uv_close(reinterpret_cast(timeoutTimer_), [](uv_handle_t *handle) { delete handle; }); + timeoutTimer_ = nullptr; +} + +void LockRequest::AddEnvCleanupHook() +{ + NAPI_CALL_RETURN_VOID(env_, napi_add_env_cleanup_hook(env_, EnvCleanup, this)); +} + +void LockRequest::RemoveEnvCleanupHook() +{ + NAPI_CALL_RETURN_VOID(env_, napi_remove_env_cleanup_hook(env_, EnvCleanup, this)); +} + +void LockRequest::Release() +{ + CloseTimer(); + NAPI_CALL_RETURN_VOID(env_, napi_delete_reference(env_, callback_)); + callback_ = nullptr; + NAPI_CALL_RETURN_VOID(env_, napi_delete_async_work(env_, work_)); + work_ = nullptr; +} + } // namespace Commonlibrary::Concurrent::LocksModule diff --git a/js_concurrent_module/utils/locks/lock_request.h b/js_concurrent_module/utils/locks/lock_request.h index 2ffa7dde7bef6e1ad951730d19f991ca6529c894..84d986070e2dfe5047f66fcfec66012ff0c16ecb 100644 --- a/js_concurrent_module/utils/locks/lock_request.h +++ b/js_concurrent_module/utils/locks/lock_request.h @@ -16,6 +16,7 @@ #ifndef JS_CONCURRENT_MODULE_UTILS_LOCKS_LOCK_REQUEST_H #define JS_CONCURRENT_MODULE_UTILS_LOCKS_LOCK_REQUEST_H +#include #include #include #include @@ -45,7 +46,6 @@ class LockRequest : public std::enable_shared_from_this { public: LockRequest(AsyncLock* lock, tid_t tid, napi_env env, napi_ref cb, LockMode mode, const LockOptions &options, napi_deferred deferred); - ~LockRequest(); std::weak_ptr GetWeakPtr() { @@ -80,22 +80,26 @@ public: void CallCallbackAsync(); void CallCallback(); - void OnQueued(napi_env env, uint32_t timeoutMillis); - void OnSatisfied(napi_env env); - private: bool AbortIfNeeded(); void ArmTimeoutTimer(napi_env env, uint32_t timeoutMillis); void DisarmTimeoutTimer(napi_env env); void HandleRequestTimeout(std::string &&errorMessage); std::string GetLockInfo() const; - void CleanTimer(); static void AsyncAfterWorkCallback(napi_env env, napi_status status, void *data); static napi_value FinallyCallback(napi_env env, napi_callback_info info); static void TimeoutCallback(uv_timer_t *handle); static void DeallocateTimeoutTimerCallback(uv_handle_t* handle); - static void EnvCleanUp(void* arg); - static void StopTimer(napi_env env, napi_value jsCallback, void* context, void* data); + static void EnvCleanup(void *arg); + + void InitTimer(); + void StopTimer(); + void CloseTimer(); + + void AddEnvCleanupHook(); + void RemoveEnvCleanupHook(); + + void Release(); AsyncLock* lock_; tid_t tid_; @@ -107,17 +111,9 @@ private: LockOptions options_; napi_deferred deferred_; napi_async_work work_; - uv_timer_t *timeoutTimer_; - bool timeoutActive_; - napi_threadsafe_function stopTimerTsfn_{nullptr}; - std::mutex lockRequestMutex_; + uv_timer_t *timeoutTimer_ {nullptr}; uint64_t engineId_; -}; - -struct RequestTimeoutData { - RequestTimeoutData(AsyncLock* l, LockRequest* r): lock(l), request(r) {} - AsyncLock* lock; - LockRequest* request; + std::atomic_bool envIsInvalid_ {false}; }; } // namespace Commonlibrary::Concurrent::LocksModule diff --git a/js_concurrent_module/utils/test/test_locks.cpp b/js_concurrent_module/utils/test/test_locks.cpp index 51f0a282dfd3a4ff0202ec5d22b9d70cc5cea983..980cd3c91f368ec0ad8c8c0e0aac4fcd4c66b010 100644 --- a/js_concurrent_module/utils/test/test_locks.cpp +++ b/js_concurrent_module/utils/test/test_locks.cpp @@ -257,7 +257,6 @@ TEST_F(LocksTest, SharedLockSingle) LockOptions options; lock->LockAsync(env, callback_ref, LOCK_MODE_SHARED, options); - Loop(LOOP_ONCE); ASSERT_TRUE(isCalled); } @@ -324,7 +323,6 @@ TEST_F(LocksTest, SharedLockMulti) LockOptions options; barrier.arrive_and_wait(); lockPtr->LockAsync(env, callback_ref, LOCK_MODE_SHARED, options); - Loop(LOOP_ONCE); LocksTest::DestroyEngine(); }); napi_env env = GetEnv(); @@ -334,7 +332,6 @@ TEST_F(LocksTest, SharedLockMulti) LockOptions options; lock->LockAsync(env, callback_ref, LOCK_MODE_SHARED, options); - Loop(LOOP_ONCE); t.join(); ASSERT_FALSE(callbackData.fail); ASSERT_EQ(callbackData.callCount, 2U); @@ -539,12 +536,10 @@ TEST_F(LocksTest, SharedModeWithEnvDestroyed) bool isPromise {false}; ASSERT_CHECK_CALL(napi_is_promise(env, result, &isPromise)); ASSERT_TRUE(isPromise); - Loop(LOOP_ONCE); LocksTest::DestroyEngine(); }); - Loop(LOOP_ONCE); t.join(); ASSERT_EQ(callbackData.callCount, 2U); } @@ -615,7 +610,6 @@ TEST_F(LocksTest, TimeoutLockWithEnvDestroyedTest) napi_value result; ASSERT_CHECK_CALL(napi_call_function(env, lock, lockAsync, argc, args, &result)); - Loop(LOOP_ONCE); LocksTest::Sleep(); callbackData.end.arrive_and_wait(); LocksTest::DestroyEngine();