• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "async_lock_manager.h"
17 #include "helper/hitrace_helper.h"
18 #include "js_concurrent_module/utils/locks/weak_wrap.h"
19 #include "tools/log.h"
20 
21 namespace Commonlibrary::Concurrent::LocksModule {
22 using namespace Commonlibrary::Concurrent::Common::Helper;
23 
LockRequest(AsyncLock * lock,tid_t tid,napi_env env,napi_ref cb,LockMode mode,const LockOptions & options,napi_deferred deferred)24 LockRequest::LockRequest(AsyncLock *lock, tid_t tid, napi_env env, napi_ref cb, LockMode mode,
25                          const LockOptions &options, napi_deferred deferred)
26     : lock_(lock),
27       tid_(tid),
28       engine_(reinterpret_cast<NativeEngine *>(env)),
29       env_(env),
30       callback_(cb),
31       mode_(mode),
32       options_(options),
33       deferred_(deferred),
34       work_(nullptr),
35       timeoutActive_(false),
36       engineId_(engine_->GetId())
37 {
38     // timeout timer initialization: just fill the data fields, do not arm it
39     timeoutTimer_ = new uv_timer_t();
40     uv_timer_init(NapiHelper::GetLibUV(env), timeoutTimer_);
41     RequestTimeoutData *data = new RequestTimeoutData(lock, this);
42     timeoutTimer_->data = data;
43 
44     // saving the creation point (file, function and line) for future use
45     NativeEngine *engine = reinterpret_cast<NativeEngine *>(env);
46     engine->BuildJsStackTrace(creationStacktrace_);
47 
48     NAPI_CALL_RETURN_VOID(env_, napi_add_env_cleanup_hook(env_, EnvCleanUp, this));
49     napi_value resourceName;
50     napi_create_string_utf8(env, "AsyncLock::AsyncCallback", NAPI_AUTO_LENGTH, &resourceName);
51     napi_status status = napi_create_async_work(env_, nullptr, resourceName, AsyncLockManager::EmptyExecuteCallback,
52                                                 AsyncAfterWorkCallback, this, &work_);
53     if (status != napi_ok) {
54         HILOG_FATAL("Internal error: cannot create async work");
55     }
56 }
57 
EnvCleanUp(void * arg)58 void LockRequest::EnvCleanUp(void *arg)
59 {
60     LockRequest *lockRequest = static_cast<LockRequest *>(arg);
61     std::unique_lock<std::mutex> guard(lockRequest->lockRequestMutex_);
62     napi_delete_reference(lockRequest->env_, lockRequest->callback_);
63     lockRequest->callback_ = nullptr;
64     lockRequest->env_ = nullptr;
65     lockRequest->CleanTimer();
66 }
67 
CleanTimer()68 void LockRequest::CleanTimer()
69 {
70     if (stopTimerTsfn_ != nullptr) {
71         NAPI_CALL_RETURN_VOID(env_, napi_release_threadsafe_function(stopTimerTsfn_, napi_tsfn_abort));
72         stopTimerTsfn_ = nullptr;
73     }
74     RequestTimeoutData *data = static_cast<RequestTimeoutData *>(timeoutTimer_->data);
75     delete data;
76     timeoutTimer_->data = nullptr;
77     uv_close(reinterpret_cast<uv_handle_t *>(timeoutTimer_), DeallocateTimeoutTimerCallback);
78 }
79 
DeallocateTimeoutTimerCallback(uv_handle_t * handle)80 void LockRequest::DeallocateTimeoutTimerCallback(uv_handle_t* handle)
81 {
82     delete handle;
83 }
84 
~LockRequest()85 LockRequest::~LockRequest()
86 {
87     std::unique_lock<std::mutex> guard(lockRequestMutex_);
88     if (env_ == nullptr) {
89         return;
90     }
91     CleanTimer();
92 }
93 
AsyncAfterWorkCallback(napi_env env,napi_status status,void * data)94 void LockRequest::AsyncAfterWorkCallback(napi_env env, [[maybe_unused]] napi_status status, void *data)
95 {
96     LockRequest* lockRequest = reinterpret_cast<LockRequest *>(data);
97     std::unique_lock<std::mutex> lock(lockRequest->lockRequestMutex_);
98     napi_delete_async_work(env, lockRequest->work_);
99     lockRequest->work_ = nullptr;
100     if (lockRequest->env_ == nullptr) {
101         lock.unlock();
102         HILOG_ERROR("AsyncCallback is called after env cleaned up");
103         lockRequest->lock_->CleanUpLockRequestOnCompletion(lockRequest);
104         return;
105     }
106     lock.unlock();
107     lockRequest->CallCallback();
108 }
109 
FinallyCallback(napi_env env,napi_callback_info info)110 napi_value LockRequest::FinallyCallback(napi_env env, napi_callback_info info)
111 {
112     LockRequest *lockRequest = nullptr;
113 
114     NAPI_CALL(env, napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&lockRequest)));
115     HITRACE_HELPER_METER_NAME("AsyncLock FinallyCallback, " + lockRequest->GetLockInfo());
116     lockRequest->lock_->CleanUpLockRequestOnCompletion(lockRequest);
117 
118     napi_value undefined;
119     napi_get_undefined(env, &undefined);
120     return undefined;
121 }
122 
CallCallbackAsync()123 void LockRequest::CallCallbackAsync()
124 {
125     lockRequestMutex_.lock();
126     if (env_ == nullptr || !NativeEngine::IsAlive(engine_) || engineId_ != engine_->GetId()) {
127         lockRequestMutex_.unlock();
128         HILOG_ERROR("Callback is called after env cleaned up");
129         lock_->CleanUpLockRequestOnCompletion(this);
130         return;
131     }
132     napi_status status = napi_queue_async_work(env_, work_);
133     lockRequestMutex_.unlock();
134     if (status != napi_ok) {
135         HILOG_FATAL("Internal error: cannot queue async work");
136     }
137 }
138 
CallCallback()139 void LockRequest::CallCallback()
140 {
141     HITRACE_HELPER_METER_NAME("AsyncLock Callback, " + GetLockInfo());
142     napi_remove_env_cleanup_hook(env_, EnvCleanUp, this);
143     if (AbortIfNeeded()) {
144         napi_delete_reference(env_, callback_);
145         callback_ = nullptr;
146         lock_->CleanUpLockRequestOnCompletion(this);
147         return;
148     }
149     napi_value cb = nullptr;
150     NAPI_CALL_RETURN_VOID(env_, napi_get_reference_value(env_, callback_, &cb));
151     napi_value result;
152     napi_status status = napi_call_function(env_, nullptr, cb, 0, nullptr, &result);
153     napi_delete_reference(env_, callback_);
154     callback_ = nullptr;
155     if (status == napi_ok) {
156         napi_resolve_deferred(env_, deferred_, result);
157 
158         bool isPromise = false;
159         napi_is_promise(env_, result, &isPromise);
160         if (isPromise) {
161             // Save lock_ and env_ into locals. If the callback returns fulfilled promise,
162             // the lock request will be destroyed during napi_call_function(finallyFn).
163             napi_env env = env_;
164             // Increament reference counter for the lock. Do it to prevent lock destruction.
165             napi_value finallyFn;
166             napi_get_named_property(env, result, "finally", &finallyFn);
167             napi_value finallyCallback;
168             napi_create_function(env, nullptr, 0, FinallyCallback, this, &finallyCallback);
169             napi_value finallyPromise;
170             napi_call_function(env, result, finallyFn, 1, &finallyCallback, &finallyPromise);
171             return;
172         }
173     } else {
174         napi_value err;
175         napi_get_and_clear_last_exception(env_, &err);
176         napi_reject_deferred(env_, deferred_, err);
177     }
178     lock_->CleanUpLockRequestOnCompletion(this);
179 }
180 
OnSatisfied(napi_env env)181 void LockRequest::OnSatisfied(napi_env env)
182 {
183     if (timeoutActive_) {
184         DisarmTimeoutTimer(env);
185     }
186 }
187 
OnQueued(napi_env env,uint32_t timeoutMillis)188 void LockRequest::OnQueued(napi_env env, uint32_t timeoutMillis)
189 {
190     if (timeoutMillis > 0) {
191         ArmTimeoutTimer(env, timeoutMillis);
192     }
193 }
194 
AbortIfNeeded()195 bool LockRequest::AbortIfNeeded()
196 {
197     if (options_.signal == nullptr) {
198         return false;
199     }
200     napi_value signal;
201     napi_get_reference_value(env_, options_.signal, &signal);
202     napi_value aborted = NapiHelper::GetNameProperty(env_, signal, "aborted");
203     bool isAborted = false;
204     napi_get_value_bool(env_, aborted, &isAborted);
205     if (!isAborted) {
206         return false;
207     }
208     napi_value reason = NapiHelper::GetNameProperty(env_, signal, "reason");
209     napi_reject_deferred(env_, deferred_, reason);
210     return true;
211 }
212 
ArmTimeoutTimer(napi_env env,uint32_t timeoutMillis)213 void LockRequest::ArmTimeoutTimer(napi_env env, uint32_t timeoutMillis)
214 {
215     timeoutActive_ = true;
216     uv_update_time(NapiHelper::GetLibUV(env));
217     uv_timer_start(timeoutTimer_, TimeoutCallback, timeoutMillis, 0);
218     napi_value resourceName = nullptr;
219     NAPI_CALL_RETURN_VOID(env, napi_create_string_utf8(env, "stopTimerTsfn", NAPI_AUTO_LENGTH, &resourceName));
220     NAPI_CALL_RETURN_VOID(env, napi_create_threadsafe_function(env, nullptr, nullptr, resourceName, 0, 1, nullptr,
221                                                                nullptr, timeoutTimer_, StopTimer, &stopTimerTsfn_));
222     // NOTE: handle the abortsignal functionality in the future
223     // NOTE: need to check call results for 0
224 }
225 
DisarmTimeoutTimer(napi_env env)226 void LockRequest::DisarmTimeoutTimer(napi_env env)
227 {
228     std::unique_lock<std::mutex> guard(lockRequestMutex_);
229     if (stopTimerTsfn_ == nullptr) {
230         return;
231     }
232     NAPI_CALL_RETURN_VOID(env, napi_call_threadsafe_function(stopTimerTsfn_, new WeakWrap<LockRequest>(GetWeakPtr()),
233                                                              napi_tsfn_blocking));
234     timeoutActive_ = false;
235 }
236 
StopTimer(napi_env env,napi_value jsCallback,void * context,void * data)237 void LockRequest::StopTimer(napi_env env, napi_value jsCallback, void *context, void *data)
238 {
239     WeakWrap<LockRequest> *weakRequest = static_cast<WeakWrap<LockRequest> *>(data);
240     if (weakRequest->GetWeakPtr().expired() || uv_is_closing(static_cast<uv_handle_t *>(context))) {
241         delete weakRequest;
242         return;
243     }
244     uv_timer_stop(static_cast<uv_timer_t *>(context));
245     delete weakRequest;
246 }
247 
TimeoutCallback(uv_timer_t * handle)248 void LockRequest::TimeoutCallback(uv_timer_t *handle)
249 {
250     RequestTimeoutData *data = static_cast<RequestTimeoutData*>(handle->data);
251     if (data == nullptr) {
252         // fail! something terribly bad happened!
253         HILOG_FATAL("Internal error: unable to handle the AsyncLock timeout");
254         return;
255     }
256     // Check deadlocks and form the rejector value with or w/o the warning. It is required to be done
257     // first in order to obtain the actual data.
258     std::string error;
259     AsyncLockManager::DumpLocksInfoForThread(AsyncLockManager::GetCurrentTid(data->request->env_), error);
260 
261     // NOTE: both AsyncLock and LockRequest might be deleted here, but at this point we imply that
262     // AsyncLock exists, later on we we will handle the case when it does not
263 
264     // NOTE:
265     // We might have the race with the lock acquirer function here and the request will be
266     // already deleted if the race is won by the acquirer. So we should firstly make sure that
267     // the race is won by us and then call the request's methods
268 
269     bool success = data->lock->CleanUpLockRequestOnTimeout(data->request);
270     if (!success) {
271         return;
272     }
273     data->request->HandleRequestTimeout(std::move(error));
274     AsyncLock *lock = data->lock;
275     napi_env env = data->request->env_;
276     napi_remove_env_cleanup_hook(env, EnvCleanUp, data->request);
277     // will delete 'data' too
278     delete data->request;
279     lock->ProcessPendingLockRequest(env);
280 }
281 
HandleRequestTimeout(std::string && errorMessage)282 void LockRequest::HandleRequestTimeout(std::string &&errorMessage)
283 {
284     HILOG_ERROR("AsyncLock lockAsync() timed out! Information: %s", errorMessage.c_str());
285     // here we imply that there are no races already and the timeout function should do its job
286     // by rejecting the associated promise with an BusinessError instance.
287     napi_delete_reference(env_, callback_);
288     callback_ = nullptr;
289 
290     napi_handle_scope scope = nullptr;
291     napi_open_handle_scope(env_, &scope);
292     if (scope == nullptr) {
293         return;
294     }
295 
296     HILOG_ERROR("AsyncLock lockAsync() timed out! Rejecting the promise.");
297     napi_value error = ErrorHelper::NewError(env_, ErrorHelper::ERR_ASYNCLOCK_TIMEOUT, errorMessage.c_str());
298     napi_reject_deferred(env_, deferred_, error);
299 
300     napi_close_handle_scope(env_, scope);
301 }
302 
GetLockInfo() const303 std::string LockRequest::GetLockInfo() const
304 {
305     std::string strTrace;
306     if (lock_->GetLockId() == 0) {
307         strTrace += "lockName: " + lock_->GetLockName();
308     } else {
309         strTrace += "lockId: " + std::to_string(lock_->GetLockId());
310     }
311     return strTrace;
312 }
313 
314 } // namespace Commonlibrary::Concurrent::LocksModule
315