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     NAPI_CALL(env, napi_create_object(env, &locks));
142     napi_property_descriptor locksProperties[] = {
143         DECLARE_NAPI_PROPERTY("AsyncLock", asyncLockManagerClass),
144         DECLARE_NAPI_PROPERTY("AsyncLockMode", asyncLockMode),
145         DECLARE_NAPI_PROPERTY("AsyncLockOptions", asyncLockOptionsClass),
146     };
147     napi_define_properties(env, locks, sizeof(locksProperties) / sizeof(locksProperties[0]), locksProperties);
148 
149     napi_set_named_property(env, exports, "locks", locks);
150 
151     return exports;
152 }
153 
Constructor(napi_env env,napi_callback_info cbinfo)154 napi_value AsyncLockManager::Constructor(napi_env env, napi_callback_info cbinfo)
155 {
156     size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
157     NAPI_ASSERT(env, argc == 0 || argc == 1, "AsyncLock::Constructor: the number of params must be zero or one");
158 
159     auto args = std::make_unique<napi_value[]>(argc);
160     napi_value thisVar;
161     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, args.get(), &thisVar, nullptr));
162 
163     AsyncLockIdentity *data;
164     napi_value name;
165     if (argc == 1) {
166         napi_valuetype type;
167         NAPI_CALL(env, napi_typeof(env, args[0], &type));
168         if (type != napi_string) {
169             ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Request:: param must be string");
170             return nullptr;
171         }
172 
173         std::string lockName = NapiHelper::GetString(env, args[0]);
174         Request(lockName);
175         name = args[0];
176 
177         data = new AsyncLockIdentity{false, 0, lockName};
178     } else {
179         uint32_t lockId = nextId++;
180         std::ostringstream out;
181         out << "anonymousLock" << lockId;
182         std::string lockName = out.str();
183         Request(lockId);
184         NAPI_CALL(env, napi_create_string_utf8(env, lockName.c_str(), NAPI_AUTO_LENGTH, &name));
185 
186         data = new AsyncLockIdentity{true, lockId};
187     }
188 
189     napi_property_descriptor properties[] = {
190         DECLARE_NAPI_PROPERTY("name", name),
191         DECLARE_NAPI_FUNCTION_WITH_DATA("lockAsync", LockAsync, thisVar),
192     };
193     NAPI_CALL(env, napi_define_properties(env, thisVar, sizeof(properties) / sizeof(properties[0]), properties));
194     NAPI_CALL(env, napi_wrap_sendable(env, thisVar, data, Destructor, nullptr));
195 
196     return thisVar;
197 }
198 
Request(napi_env env,napi_callback_info cbinfo)199 napi_value AsyncLockManager::Request(napi_env env, napi_callback_info cbinfo)
200 {
201     size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
202     NAPI_ASSERT(env, argc == 1, "Request:: the number of params must be one");
203 
204     auto args = std::make_unique<napi_value[]>(argc);
205     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, args.get(), nullptr, nullptr));
206     napi_value asyncLockClass;
207     NAPI_CALL(env, napi_get_reference_value(env, asyncLockClassRef, &asyncLockClass));
208     napi_value instance;
209     NAPI_CALL(env, napi_new_instance(env, asyncLockClass, argc, args.get(), &instance));
210 
211     return instance;
212 }
213 
214 void AsyncLockManager::Destructor(napi_env env, void *data, [[maybe_unused]] void *hint)
215 {
216     AsyncLockIdentity *identity = reinterpret_cast<AsyncLockIdentity *>(data);
217     std::unique_lock<std::mutex> guard(lockMutex);
218     if (identity->isAnonymous) {
219         // no way to have >1 reference to an anonymous lock
220         auto it = anonymousLockMap.find(identity->id);
221         if ((it != anonymousLockMap.end()) && (it->second->DecRefCount() == 0)) {
222             anonymousLockMap.erase(it);
223         }
224     } else {
225         auto it = lockMap.find(identity->name);
226         if ((it != lockMap.end()) && (it->second->DecRefCount() == 0)) {
227             lockMap.erase(it);
228         }
229     }
230     delete identity;
231 }
232 
LockAsync(napi_env env,napi_callback_info cbinfo)233 napi_value AsyncLockManager::LockAsync(napi_env env, napi_callback_info cbinfo)
234 {
235     HITRACE_HELPER_METER_NAME("Async lockAsync");
236     size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
237     NAPI_ASSERT(env, 0 < argc && argc < 4U, "Invalid number of arguments");
238 
239     auto argv = std::make_unique<napi_value[]>(argc);
240     napi_value thisVar;
241     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, argv.get(), &thisVar, nullptr));
242 
243     AsyncLockIdentity *id;
244     NAPI_CALL(env, napi_unwrap_sendable(env, thisVar, reinterpret_cast<void **>(&id)));
245 
246     AsyncLock *asyncLock = nullptr;
247     {
248         std::unique_lock<std::mutex> guard(lockMutex);
249         asyncLock = FindAsyncLockUnsafe(id);
250     }
251     if (asyncLock == nullptr) {
252         ErrorHelper::ThrowError(env, ErrorHelper::ERR_NO_SUCH_ASYNCLOCK);
253         napi_value undefined;
254         napi_get_undefined(env, &undefined);
255         return undefined;
256     }
257     std::string strTrace = "lockAsync: ";
258     if (id->isAnonymous) {
259         strTrace += "lockId: " + std::to_string(id->id);
260     } else {
261         strTrace += "lockName: " + id->name;
262     }
263     HITRACE_HELPER_METER_NAME(strTrace);
264     LockMode mode = LOCK_MODE_EXCLUSIVE;
265     LockOptions options;
266     if (argc > 1 && !GetLockMode(env, argv[1], mode)) {
267         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid lock mode.");
268         return nullptr;
269     }
270     if (argc > 2U && !GetLockOptions(env, argv[2U], options)) {
271         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid options.");
272         return nullptr;
273     }
274     napi_ref callback;
275     napi_create_reference(env, argv[0], 1, &callback);
276     return asyncLock->LockAsync(env, callback, mode, options);
277 }
278 
Query(napi_env env,napi_callback_info cbinfo)279 napi_value AsyncLockManager::Query(napi_env env, napi_callback_info cbinfo)
280 {
281     size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
282     if (argc != 1) {
283         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid number of arguments");
284         return nullptr;
285     }
286 
287     // later on we can decide to cache the check result if needed
288     CheckDeadlocksAndLogWarning();
289 
290     napi_value undefined;
291     napi_get_undefined(env, &undefined);
292     napi_value arg;
293     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, &argc, &arg, nullptr, nullptr));
294     napi_valuetype type;
295     napi_typeof(env, arg, &type);
296     if (type != napi_string) {
297         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid argument type");
298         return undefined;
299     }
300 
301     std::string nameStr = NapiHelper::GetString(env, arg);
302     AsyncLockIdentity identity{false, 0, nameStr};
303     AsyncLock *lock = nullptr;
304     {
305         std::unique_lock<std::mutex> guard(lockMutex);
306         lock = FindAsyncLockUnsafe(&identity);
307     }
308     if (lock == nullptr) {
309         ErrorHelper::ThrowError(env, ErrorHelper::ERR_NO_SUCH_ASYNCLOCK);
310         return undefined;
311     }
312 
313     return CreateLockState(env, lock);
314 }
315 
QueryAll(napi_env env,napi_callback_info cbinfo)316 napi_value AsyncLockManager::QueryAll(napi_env env, napi_callback_info cbinfo)
317 {
318     size_t argc = NapiHelper::GetCallbackInfoArgc(env, cbinfo);
319     if (argc != 0) {
320         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Invalid number of arguments");
321         return nullptr;
322     }
323 
324     // later on we can decide to cache the check result if needed
325     CheckDeadlocksAndLogWarning();
326     return CreateLockStates(env, [] ([[maybe_unused]] const AsyncLockIdentity &identity) {
327         return true;
328     });
329 }
330 
CreateLockState(napi_env env,AsyncLock * asyncLock)331 napi_value AsyncLockManager::CreateLockState(napi_env env, AsyncLock *asyncLock)
332 {
333     napi_value undefined;
334     napi_get_undefined(env, &undefined);
335     napi_value result;
336     if (napi_create_object(env, &result) != napi_ok) {
337         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
338         return undefined;
339     }
340     napi_value held;
341     napi_value pending;
342     if (napi_create_array(env, &held) != napi_ok) {
343         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
344         return undefined;
345     }
346     if (napi_create_array(env, &pending) != napi_ok) {
347         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
348         return undefined;
349     }
350     napi_property_descriptor properties[] = {
351         DECLARE_NAPI_PROPERTY("held", held),
352         DECLARE_NAPI_PROPERTY("pending", pending),
353     };
354     NAPI_CALL(env, napi_define_properties(env, result, sizeof(properties) / sizeof(properties[0]), properties));
355 
356     if (asyncLock->FillLockState(env, held, pending) != napi_ok) {
357         ErrorHelper::ThrowError(env, ErrorHelper::TYPE_ERROR, "Cannot create an object");
358         return undefined;
359     }
360 
361     return result;
362 }
363 
CreateLockStates(napi_env env,const std::function<bool (const AsyncLockIdentity & identity)> & pred)364 napi_value AsyncLockManager::CreateLockStates(napi_env env,
365     const std::function<bool(const AsyncLockIdentity& identity)> &pred)
366 {
367     bool pendingException = false;
368     napi_value undefined;
369     napi_get_undefined(env, &undefined);
370     napi_value array;
371     NAPI_CALL(env, napi_create_array(env, &array));
372 
373     std::unique_lock<std::mutex> guard(lockMutex);
374     uint32_t idx = 0;
375     for (auto &entry : anonymousLockMap) {
376         AsyncLockIdentity identity = {true, entry.first, ""};
377         if (pred(identity)) {
378             napi_value v = CreateLockState(env, entry.second);
379             napi_is_exception_pending(env, &pendingException);
380             if (pendingException) {
381                 return undefined;
382             }
383             napi_value index;
384             NAPI_CALL(env, napi_create_uint32(env, idx, &index));
385             NAPI_CALL(env, napi_set_property(env, array, index, v));
386             ++idx;
387         }
388     }
389     for (auto &entry : lockMap) {
390         AsyncLockIdentity identity = {false, 0, entry.first};
391         if (pred(identity)) {
392             napi_value v = CreateLockState(env, entry.second);
393             napi_is_exception_pending(env, &pendingException);
394             if (pendingException) {
395                 return undefined;
396             }
397             napi_value index;
398             NAPI_CALL(env, napi_create_uint32(env, idx, &index));
399             NAPI_CALL(env, napi_set_property(env, array, index, v));
400             ++idx;
401         }
402     }
403     return array;
404 }
405 
Request(uint32_t id)406 void AsyncLockManager::Request(uint32_t id)
407 {
408     std::unique_lock<std::mutex> guard(lockMutex);
409     AsyncLockIdentity identity{true, id, ""};
410     AsyncLock *lock = FindAsyncLockUnsafe(&identity);
411     if (lock == nullptr) {
412         anonymousLockMap.emplace(id, new AsyncLock(id));
413     }
414 }
415 
Request(const std::string & name)416 void AsyncLockManager::Request(const std::string &name)
417 {
418     std::unique_lock<std::mutex> guard(lockMutex);
419     AsyncLockIdentity identity{false, 0, name};
420     AsyncLock *lock = FindAsyncLockUnsafe(&identity);
421     if (lock == nullptr) {
422         lockMap.emplace(name, new AsyncLock(name));
423     } else {
424         lock->IncRefCount();
425     }
426 }
427 
FindAsyncLockUnsafe(AsyncLockIdentity * id)428 AsyncLock* AsyncLockManager::FindAsyncLockUnsafe(AsyncLockIdentity *id)
429 {
430     if (id->isAnonymous) {
431         auto it = anonymousLockMap.find(id->id);
432         if (it == anonymousLockMap.end()) {
433             return nullptr;
434         }
435         return it->second;
436     } else {
437         auto it = lockMap.find(id->name);
438         if (it == lockMap.end()) {
439             return nullptr;
440         }
441         return it->second;
442     }
443 }
444 
GetLockMode(napi_env env,napi_value val,LockMode & mode)445 bool AsyncLockManager::GetLockMode(napi_env env, napi_value val, LockMode &mode)
446 {
447     uint32_t modeNative = NapiHelper::GetUint32Value(env, val);
448     if (modeNative  < LockMode::LOCK_MODE_SHARED || modeNative > LOCK_MODE_EXCLUSIVE) {
449         return false;
450     }
451     mode = static_cast<LockMode>(modeNative);
452     return true;
453 }
454 
GetLockOptions(napi_env env,napi_value val,LockOptions & options)455 bool AsyncLockManager::GetLockOptions(napi_env env, napi_value val, LockOptions &options)
456 {
457     napi_value isAvailable = NapiHelper::GetNameProperty(env, val, "isAvailable");
458     napi_value signal = NapiHelper::GetNameProperty(env, val, "signal");
459     napi_value timeout = NapiHelper::GetNameProperty(env, val, "timeout");
460     if (isAvailable != nullptr) {
461         options.isAvailable = NapiHelper::GetBooleanValue(env, isAvailable);
462     }
463     if (signal != nullptr) {
464         napi_create_reference(env, signal, 1, &options.signal);
465     }
466     if (timeout != nullptr) {
467         options.timeoutMillis = NapiHelper::GetUint32Value(env, timeout);
468     }
469     return true;
470 }
471 
GetCurrentTid(napi_env env)472 tid_t AsyncLockManager::GetCurrentTid(napi_env env)
473 {
474     NativeEngine *engine = reinterpret_cast<NativeEngine *>(env);
475     return engine->GetCurSysTid();
476 }
477 }  // namespace Commonlibrary::Concurrent::LocksModule
478