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