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
18 #include <sys/types.h>
19 #include <unistd.h>
20
21 #include "async_lock.h"
22 #include "deadlock_helpers.h"
23 #include "helper/error_helper.h"
24 #include "helper/hitrace_helper.h"
25 #include "helper/napi_helper.h"
26 #include "helper/object_helper.h"
27 #include "tools/log.h"
28
29 namespace Commonlibrary::Concurrent::LocksModule {
30 using namespace Commonlibrary::Concurrent::Common::Helper;
31
32 static thread_local napi_ref asyncLockClassRef = nullptr;
33
34 std::mutex AsyncLockManager::lockMutex;
35 std::unordered_map<std::string, AsyncLock *> AsyncLockManager::lockMap = {};
36 std::unordered_map<uint32_t, AsyncLock *> AsyncLockManager::anonymousLockMap = {};
37 std::atomic<uint32_t> AsyncLockManager::nextId = 1;
38
AsyncLockOptionsCtor(napi_env env,napi_callback_info cbinfo)39 static napi_value AsyncLockOptionsCtor(napi_env env, napi_callback_info cbinfo)
40 {
41 napi_value thisVar;
42 NAPI_CALL(env, napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, nullptr));
43
44 napi_value isAvailable;
45 napi_get_boolean(env, false, &isAvailable);
46 napi_value signal;
47 napi_get_null(env, &signal);
48 napi_value timeout;
49 napi_create_uint32(env, 0, &timeout);
50
51 napi_property_descriptor properties[] = {
52 DECLARE_NAPI_DEFAULT_PROPERTY("isAvailable", isAvailable),
53 DECLARE_NAPI_DEFAULT_PROPERTY("signal", signal),
54 DECLARE_NAPI_DEFAULT_PROPERTY("timeout", timeout),
55 };
56 NAPI_CALL(env, napi_define_properties(env, thisVar, sizeof(properties) / sizeof(properties[0]), properties));
57
58 return thisVar;
59 }
60
CollectLockDependencies(std::vector<AsyncLockDependency> & dependencies)61 void AsyncLockManager::CollectLockDependencies(std::vector<AsyncLockDependency> &dependencies)
62 {
63 auto lockProcessor = [&dependencies](std::string lockName, AsyncLock *lock) {
64 auto holderInfos = lock->GetSatisfiedRequestInfos();
65 if (holderInfos.empty()) {
66 // lock should have holders to be waited, skip
67 return;
68 }
69 auto holderTid = holderInfos[0].tid;
70 dependencies.push_back(
71 AsyncLockDependency {INVALID_TID, holderTid, lockName, holderInfos[0].creationStacktrace});
72 for (auto &waiterInfo : lock->GetPendingRequestInfos()) {
73 dependencies.push_back(
74 AsyncLockDependency {waiterInfo.tid, holderTid, lockName, waiterInfo.creationStacktrace});
75 }
76 };
77 std::unique_lock<std::mutex> guard(lockMutex);
78 for (auto [name, lock] : lockMap) {
79 lockProcessor(name, lock);
80 }
81 for (auto [id, lock] : anonymousLockMap) {
82 std::string lockName = "anonymous #" + std::to_string(id);
83 lockProcessor(lockName, lock);
84 }
85 }
86
DumpLocksInfoForThread(tid_t targetTid,std::string & result)87 void AsyncLockManager::DumpLocksInfoForThread(tid_t targetTid, std::string &result)
88 {
89 std::vector<AsyncLockDependency> deps;
90 CollectLockDependencies(deps);
91 auto deadlock = CheckDeadlocks(deps);
92 result = CreateFullLockInfosMessage(targetTid, std::move(deps), std::move(deadlock));
93 }
94
CheckDeadlocksAndLogWarning()95 void AsyncLockManager::CheckDeadlocksAndLogWarning()
96 {
97 std::vector<AsyncLockDependency> deps;
98 CollectLockDependencies(deps);
99 auto deadlock = CheckDeadlocks(deps);
100 if (!deadlock.IsEmpty()) {
101 std::string warning = CreateDeadlockWarningMessage(std::move(deadlock));
102 HILOG_WARN("DeadlockDetector: %{public}s", warning.c_str());
103 }
104 }
105
Init(napi_env env,napi_value exports)106 napi_value AsyncLockManager::Init(napi_env env, napi_value exports)
107 {
108 // instance properties
109 napi_value name;
110 NAPI_CALL(env, napi_create_string_utf8(env, "", 0, &name));
111
112 napi_property_descriptor props[] = {
113 DECLARE_NAPI_STATIC_FUNCTION("request", Request),
114 DECLARE_NAPI_STATIC_FUNCTION("query", Query),
115 DECLARE_NAPI_STATIC_FUNCTION("queryAll", QueryAll),
116 DECLARE_NAPI_INSTANCE_PROPERTY("name", name),
117 DECLARE_NAPI_INSTANCE_OBJECT_PROPERTY("lockAsync"),
118 };
119
120 napi_value asyncLockManagerClass = nullptr;
121 napi_define_sendable_class(env, "AsyncLock", NAPI_AUTO_LENGTH, Constructor, nullptr,
122 sizeof(props) / sizeof(props[0]), props, nullptr, &asyncLockManagerClass);
123 NAPI_CALL(env, napi_create_reference(env, asyncLockManagerClass, 1, &asyncLockClassRef));
124
125 // AsyncLockMode enum
126 napi_value asyncLockMode = NapiHelper::CreateObject(env);
127 napi_value sharedMode = NapiHelper::CreateUint32(env, LOCK_MODE_SHARED);
128 napi_value exclusiveMode = NapiHelper::CreateUint32(env, LOCK_MODE_EXCLUSIVE);
129 napi_property_descriptor exportMode[] = {
130 DECLARE_NAPI_PROPERTY("SHARED", sharedMode),
131 DECLARE_NAPI_PROPERTY("EXCLUSIVE", exclusiveMode),
132 };
133 napi_define_properties(env, asyncLockMode, sizeof(exportMode) / sizeof(exportMode[0]), exportMode);
134
135 // AsyncLockOptions
136 napi_value asyncLockOptionsClass = nullptr;
137 napi_define_class(env, "AsyncLockOptions", NAPI_AUTO_LENGTH, AsyncLockOptionsCtor, nullptr, 0, nullptr,
138 &asyncLockOptionsClass);
139
140 napi_value locks;
141 bool locksExist = false;
142 NAPI_CALL(env, napi_has_named_property(env, exports, "locks", &locksExist));
143 if (locksExist) {
144 NAPI_CALL(env, napi_get_named_property(env, exports, "locks", &locks));
145 } else {
146 NAPI_CALL(env, napi_create_object(env, &locks));
147 NAPI_CALL(env, napi_set_named_property(env, exports, "locks", locks));
148 }
149 napi_property_descriptor locksProperties[] = {
150 DECLARE_NAPI_PROPERTY("AsyncLock", asyncLockManagerClass),
151 DECLARE_NAPI_PROPERTY("AsyncLockMode", asyncLockMode),
152 DECLARE_NAPI_PROPERTY("AsyncLockOptions", asyncLockOptionsClass),
153 };
154 napi_define_properties(env, locks, sizeof(locksProperties) / sizeof(locksProperties[0]), locksProperties);
155
156 return exports;
157 }
158
Constructor(napi_env env,napi_callback_info cbinfo)159 napi_value AsyncLockManager::Constructor(napi_env env, napi_callback_info cbinfo)
160 {
161 size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
162 NAPI_ASSERT(env, argc == 0 || argc == 1, "AsyncLock::Constructor: the number of params must be zero or one");
163
164 auto args = std::make_unique<napi_value[]>(argc);
165 napi_value thisVar;
166 NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, args.get(), &thisVar, nullptr));
167
168 AsyncLockIdentity *data;
169 napi_value name;
170 if (argc == 1) {
171 napi_valuetype type;
172 NAPI_CALL(env, napi_typeof(env, args[0], &type));
173 if (type != napi_string) {
174 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Request:: param must be string");
175 return nullptr;
176 }
177
178 std::string lockName = NapiHelper::GetString(env, args[0]);
179 Request(lockName);
180 name = args[0];
181
182 data = new AsyncLockIdentity{false, 0, lockName};
183 } else {
184 uint32_t lockId = nextId++;
185 std::ostringstream out;
186 out << "anonymousLock" << lockId;
187 std::string lockName = out.str();
188 Request(lockId);
189 NAPI_CALL(env, napi_create_string_utf8(env, lockName.c_str(), NAPI_AUTO_LENGTH, &name));
190
191 data = new AsyncLockIdentity{true, lockId};
192 }
193
194 napi_property_descriptor properties[] = {
195 DECLARE_NAPI_PROPERTY("name", name),
196 DECLARE_NAPI_FUNCTION_WITH_DATA("lockAsync", LockAsync, thisVar),
197 };
198 NAPI_CALL(env, napi_define_properties(env, thisVar, sizeof(properties) / sizeof(properties[0]), properties));
199 NAPI_CALL(env, napi_wrap_sendable(env, thisVar, data, Destructor, nullptr));
200
201 return thisVar;
202 }
203
Request(napi_env env,napi_callback_info cbinfo)204 napi_value AsyncLockManager::Request(napi_env env, napi_callback_info cbinfo)
205 {
206 size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
207 NAPI_ASSERT(env, argc == 1, "Request:: the number of params must be one");
208
209 auto args = std::make_unique<napi_value[]>(argc);
210 NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, args.get(), nullptr, nullptr));
211 napi_value asyncLockClass;
212 NAPI_CALL(env, napi_get_reference_value(env, asyncLockClassRef, &asyncLockClass));
213 napi_value instance;
214 NAPI_CALL(env, napi_new_instance(env, asyncLockClass, argc, args.get(), &instance));
215
216 return instance;
217 }
218
Destructor(napi_env env,void * data,void * hint)219 void AsyncLockManager::Destructor(napi_env env, void *data, [[maybe_unused]] void *hint)
220 {
221 std::unique_ptr<AsyncLockIdentity> identity(reinterpret_cast<AsyncLockIdentity *>(data));
222 std::unique_lock<std::mutex> guard(lockMutex);
223 if (identity->isAnonymous) {
224 // no way to have >1 reference to an anonymous lock
225 auto it = anonymousLockMap.find(identity->id);
226 if ((it == anonymousLockMap.end())) {
227 return;
228 }
229 AsyncLock *asyncLock = it->second;
230 if (!asyncLock->IsReadyForDeletion()) {
231 return;
232 }
233 anonymousLockMap.erase(it);
234 delete asyncLock;
235 } else {
236 auto it = lockMap.find(identity->name);
237 if ((it == lockMap.end())) {
238 return;
239 }
240 AsyncLock *asyncLock = it->second;
241 if (!asyncLock->IsReadyForDeletion()) {
242 return;
243 }
244 lockMap.erase(it);
245 delete asyncLock;
246 }
247 }
248
CheckAndRemoveLock(AsyncLock * lock)249 void AsyncLockManager::CheckAndRemoveLock(AsyncLock *lock)
250 {
251 std::unique_lock<std::mutex> guard(lockMutex);
252 if (!lock->IsReadyForDeletion()) {
253 return;
254 }
255 uint32_t id = lock->GetLockId();
256 if (id == 0) {
257 std::string name = lock->GetLockName();
258 auto it = lockMap.find(name);
259 if (it != lockMap.end()) {
260 lockMap.erase(it);
261 }
262 } else {
263 auto it = anonymousLockMap.find(id);
264 if (it != anonymousLockMap.end()) {
265 anonymousLockMap.erase(it);
266 }
267 }
268 delete lock;
269 }
270
LockAsync(napi_env env,napi_callback_info cbinfo)271 napi_value AsyncLockManager::LockAsync(napi_env env, napi_callback_info cbinfo)
272 {
273 HITRACE_HELPER_METER_NAME("Async lockAsync");
274 size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
275 NAPI_ASSERT(env, 0 < argc && argc < 4U, "Invalid number of arguments");
276
277 auto argv = std::make_unique<napi_value[]>(argc);
278 napi_value thisVar;
279 NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, argv.get(), &thisVar, nullptr));
280
281 AsyncLockIdentity *id;
282 NAPI_CALL(env, napi_unwrap_sendable(env, thisVar, reinterpret_cast<void **>(&id)));
283
284 AsyncLock *asyncLock = nullptr;
285 {
286 std::unique_lock<std::mutex> guard(lockMutex);
287 asyncLock = FindAsyncLockUnsafe(id);
288 }
289 if (asyncLock == nullptr) {
290 ErrorHelper::ThrowError(env, ErrorHelper::ERR_NO_SUCH_ASYNCLOCK);
291 napi_value undefined;
292 napi_get_undefined(env, &undefined);
293 return undefined;
294 }
295 std::string strTrace = "lockAsync: ";
296 if (id->isAnonymous) {
297 strTrace += "lockId: " + std::to_string(id->id);
298 } else {
299 strTrace += "lockName: " + id->name;
300 }
301 HITRACE_HELPER_METER_NAME(strTrace);
302 LockMode mode = LOCK_MODE_EXCLUSIVE;
303 LockOptions options;
304 if (argc > 1 && !GetLockMode(env, argv[1], mode)) {
305 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid lock mode.");
306 return nullptr;
307 }
308 if (argc > 2U && !GetLockOptions(env, argv[2U], options)) {
309 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid options.");
310 return nullptr;
311 }
312 napi_ref callback;
313 napi_create_reference(env, argv[0], 1, &callback);
314 return asyncLock->LockAsync(env, callback, mode, options);
315 }
316
Query(napi_env env,napi_callback_info cbinfo)317 napi_value AsyncLockManager::Query(napi_env env, napi_callback_info cbinfo)
318 {
319 size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
320 if (argc != 1) {
321 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid number of arguments");
322 return nullptr;
323 }
324
325 // later on we can decide to cache the check result if needed
326 CheckDeadlocksAndLogWarning();
327
328 napi_value undefined;
329 napi_get_undefined(env, &undefined);
330 napi_value arg;
331 NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, &arg, nullptr, nullptr));
332 napi_valuetype type;
333 napi_typeof(env, arg, &type);
334 if (type != napi_string) {
335 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid argument type");
336 return undefined;
337 }
338
339 std::string nameStr = NapiHelper::GetString(env, arg);
340 AsyncLockIdentity identity{false, 0, nameStr};
341 AsyncLock *lock = nullptr;
342 {
343 std::unique_lock<std::mutex> guard(lockMutex);
344 lock = FindAsyncLockUnsafe(&identity);
345 }
346 if (lock == nullptr) {
347 ErrorHelper::ThrowError(env, ErrorHelper::ERR_NO_SUCH_ASYNCLOCK);
348 return undefined;
349 }
350
351 return CreateLockState(env, lock);
352 }
353
QueryAll(napi_env env,napi_callback_info cbinfo)354 napi_value AsyncLockManager::QueryAll(napi_env env, napi_callback_info cbinfo)
355 {
356 size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
357 if (argc != 0) {
358 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid number of arguments");
359 return nullptr;
360 }
361
362 // later on we can decide to cache the check result if needed
363 CheckDeadlocksAndLogWarning();
364 return CreateLockStates(env, [] ([[maybe_unused]] const AsyncLockIdentity &identity) {
365 return true;
366 });
367 }
368
CreateLockState(napi_env env,AsyncLock * asyncLock)369 napi_value AsyncLockManager::CreateLockState(napi_env env, AsyncLock *asyncLock)
370 {
371 napi_value undefined;
372 napi_get_undefined(env, &undefined);
373 napi_value result;
374 if (napi_create_object(env, &result) != napi_ok) {
375 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
376 return undefined;
377 }
378 napi_value held;
379 napi_value pending;
380 if (napi_create_array(env, &held) != napi_ok) {
381 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
382 return undefined;
383 }
384 if (napi_create_array(env, &pending) != napi_ok) {
385 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
386 return undefined;
387 }
388 napi_property_descriptor properties[] = {
389 DECLARE_NAPI_PROPERTY("held", held),
390 DECLARE_NAPI_PROPERTY("pending", pending),
391 };
392 NAPI_CALL(env, napi_define_properties(env, result, sizeof(properties) / sizeof(properties[0]), properties));
393
394 if (asyncLock->FillLockState(env, held, pending) != napi_ok) {
395 ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
396 return undefined;
397 }
398
399 return result;
400 }
401
CreateLockStates(napi_env env,const std::function<bool (const AsyncLockIdentity & identity)> & pred)402 napi_value AsyncLockManager::CreateLockStates(napi_env env,
403 const std::function<bool(const AsyncLockIdentity& identity)> &pred)
404 {
405 bool pendingException = false;
406 napi_value undefined;
407 napi_get_undefined(env, &undefined);
408 napi_value array;
409 NAPI_CALL(env, napi_create_array(env, &array));
410
411 std::unique_lock<std::mutex> guard(lockMutex);
412 uint32_t idx = 0;
413 for (auto &entry : anonymousLockMap) {
414 AsyncLockIdentity identity = {true, entry.first, ""};
415 if (pred(identity)) {
416 napi_value v = CreateLockState(env, entry.second);
417 napi_is_exception_pending(env, &pendingException);
418 if (pendingException) {
419 return undefined;
420 }
421 napi_value index;
422 NAPI_CALL(env, napi_create_uint32(env, idx, &index));
423 NAPI_CALL(env, napi_set_property(env, array, index, v));
424 ++idx;
425 }
426 }
427 for (auto &entry : lockMap) {
428 AsyncLockIdentity identity = {false, 0, entry.first};
429 if (pred(identity)) {
430 napi_value v = CreateLockState(env, entry.second);
431 napi_is_exception_pending(env, &pendingException);
432 if (pendingException) {
433 return undefined;
434 }
435 napi_value index;
436 NAPI_CALL(env, napi_create_uint32(env, idx, &index));
437 NAPI_CALL(env, napi_set_property(env, array, index, v));
438 ++idx;
439 }
440 }
441 return array;
442 }
443
Request(uint32_t id)444 void AsyncLockManager::Request(uint32_t id)
445 {
446 std::unique_lock<std::mutex> guard(lockMutex);
447 AsyncLockIdentity identity{true, id, ""};
448 AsyncLock *lock = FindAsyncLockUnsafe(&identity);
449 if (lock == nullptr) {
450 anonymousLockMap.emplace(id, new AsyncLock(id));
451 }
452 }
453
Request(const std::string & name)454 void AsyncLockManager::Request(const std::string &name)
455 {
456 std::unique_lock<std::mutex> guard(lockMutex);
457 AsyncLockIdentity identity{false, 0, name};
458 AsyncLock *lock = FindAsyncLockUnsafe(&identity);
459 if (lock == nullptr) {
460 lockMap.emplace(name, new AsyncLock(name));
461 } else {
462 lock->IncRefCount();
463 }
464 }
465
FindAsyncLockUnsafe(AsyncLockIdentity * id)466 AsyncLock* AsyncLockManager::FindAsyncLockUnsafe(AsyncLockIdentity *id)
467 {
468 if (id->isAnonymous) {
469 auto it = anonymousLockMap.find(id->id);
470 if (it == anonymousLockMap.end()) {
471 return nullptr;
472 }
473 return it->second;
474 } else {
475 auto it = lockMap.find(id->name);
476 if (it == lockMap.end()) {
477 return nullptr;
478 }
479 return it->second;
480 }
481 }
482
GetLockMode(napi_env env,napi_value val,LockMode & mode)483 bool AsyncLockManager::GetLockMode(napi_env env, napi_value val, LockMode &mode)
484 {
485 uint32_t modeNative = NapiHelper::GetUint32Value(env, val);
486 if (modeNative < LockMode::LOCK_MODE_SHARED || modeNative > LOCK_MODE_EXCLUSIVE) {
487 return false;
488 }
489 mode = static_cast<LockMode>(modeNative);
490 return true;
491 }
492
GetLockOptions(napi_env env,napi_value val,LockOptions & options)493 bool AsyncLockManager::GetLockOptions(napi_env env, napi_value val, LockOptions &options)
494 {
495 napi_value isAvailable = NapiHelper::GetNameProperty(env, val, "isAvailable");
496 napi_value signal = NapiHelper::GetNameProperty(env, val, "signal");
497 napi_value timeout = NapiHelper::GetNameProperty(env, val, "timeout");
498 if (isAvailable != nullptr) {
499 options.isAvailable = NapiHelper::GetBooleanValue(env, isAvailable);
500 }
501 if (signal != nullptr) {
502 napi_create_reference(env, signal, 1, &options.signal);
503 }
504 if (timeout != nullptr) {
505 options.timeoutMillis = NapiHelper::GetUint32Value(env, timeout);
506 }
507 return true;
508 }
509
GetCurrentTid(napi_env env)510 tid_t AsyncLockManager::GetCurrentTid(napi_env env)
511 {
512 NativeEngine *engine = reinterpret_cast<NativeEngine *>(env);
513 return engine->GetCurSysTid();
514 }
515 } // namespace Commonlibrary::Concurrent::LocksModule
516