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