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