• 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 
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