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#include <unistd.h> 16#include <sys/syscall.h> 17 18#include <ctime> 19#include <latch> 20#include <thread> 21#include <gtest/gtest.h> 22 23#include "ark_native_engine.h" 24#include "locks/async_lock.h" 25 26using namespace Commonlibrary::Concurrent::LocksModule; 27 28class LocksTest : public testing::Test { 29public: 30 static void SetUpTestSuite() 31 { 32 InitializeEngine(); 33 } 34 35 static void TearDownTestSuite() 36 { 37 DestroyEngine(); 38 } 39 40 static void InitializeEngine() 41 { 42 panda::RuntimeOption option; 43 option.SetGcType(panda::RuntimeOption::GC_TYPE::GEN_GC); 44 45 const int64_t poolSize = 0x1000000; // 16M 46 option.SetGcPoolSize(poolSize); 47 48 option.SetLogLevel(panda::RuntimeOption::LOG_LEVEL::ERROR); 49 option.SetDebuggerLibraryPath(""); 50 vm_ = panda::JSNApi::CreateJSVM(option); 51 ASSERT_TRUE(vm_ != nullptr); 52 53 engine_ = new ArkNativeEngine(vm_, nullptr); 54 } 55 56 static void DestroyEngine() 57 { 58 delete engine_; 59 engine_ = nullptr; 60 panda::JSNApi::DestroyJSVM(vm_); 61 } 62 63 static napi_env GetEnv() 64 { 65 return reinterpret_cast<napi_env>(engine_); 66 } 67 68 static void Loop(LoopMode mode) 69 { 70 engine_->Loop(mode); 71 } 72 73 template <typename P> 74 static void LoopUntil(const P &pred) 75 { 76 static constexpr size_t timeoutNs = 10000000UL; 77 timespec timeout = {0, timeoutNs}; 78 while (!pred()) { 79 Loop(LOOP_NOWAIT); 80 nanosleep(&timeout, nullptr); 81 } 82 } 83 84 static napi_value CreateFunction(const char *name, napi_value (*callback)(napi_env, napi_callback_info), 85 void *data = nullptr) 86 { 87 napi_value result; 88 napi_status status = napi_create_function(GetEnv(), name, NAPI_AUTO_LENGTH, callback, data, &result); 89 EXPECT_EQ(status, napi_ok); 90 return result; 91 } 92 93 static void Sleep() 94 { 95 timespec ts{0, 100U * 1000U * 1000U}; // 100ms 96 nanosleep(&ts, nullptr); 97 } 98 99protected: 100 static thread_local NativeEngine *engine_; 101 static thread_local EcmaVM *vm_; 102}; 103 104thread_local NativeEngine *LocksTest::engine_ = nullptr; 105thread_local EcmaVM *LocksTest::vm_ = nullptr; 106 107static napi_value ExclusiveLockSingleCb(napi_env env, napi_callback_info info) 108{ 109 bool *isCalled = nullptr; 110 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&isCalled)); 111 *isCalled = true; 112 napi_value undefined; 113 napi_get_undefined(env, &undefined); 114 return undefined; 115} 116 117TEST_F(LocksTest, ExclusiveLockSingle) 118{ 119 napi_env env = GetEnv(); 120 std::unique_ptr<AsyncLock> lock = std::make_unique<AsyncLock>(1); 121 bool isCalled = false; 122 napi_value callback = CreateFunction("exclusivelocksingle", ExclusiveLockSingleCb, &isCalled); 123 napi_ref callback_ref; 124 napi_create_reference(env, callback, 1, &callback_ref); 125 126 LockOptions options; 127 napi_value result = lock->LockAsync(env, callback_ref, LOCK_MODE_EXCLUSIVE, options); 128 bool isPromise = false; 129 napi_is_promise(env, result, &isPromise); 130 ASSERT_TRUE(isPromise); 131 Loop(LOOP_ONCE); 132 ASSERT_TRUE(isCalled); 133} 134 135struct CallbackData { 136 std::atomic<bool> executing = false; 137 std::atomic<bool> fail = false; 138 std::atomic<uint32_t> callCount = 0; 139}; 140 141static napi_value ExclusiveLockMultiCb(napi_env env, napi_callback_info info) 142{ 143 napi_value undefined; 144 napi_get_undefined(env, &undefined); 145 CallbackData *data = nullptr; 146 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&data)); 147 data->callCount += 1; 148 bool prev = data->executing.exchange(true); 149 if (prev) { 150 // The callback is executing now by another thread. 151 // Fail the test 152 data->fail = true; 153 return undefined; 154 } 155 156 LocksTest::Sleep(); 157 158 data->executing = false; 159 return undefined; 160} 161 162TEST_F(LocksTest, ExclusiveLockMulti) 163{ 164 std::unique_ptr<AsyncLock> lock = std::make_unique<AsyncLock>(1); 165 AsyncLock *lockPtr = lock.get(); 166 CallbackData callbackData; 167 std::thread t([lockPtr, &callbackData] () { 168 LocksTest::InitializeEngine(); 169 napi_env env = GetEnv(); 170 napi_value callback = CreateFunction("exclusivelockmulti", ExclusiveLockMultiCb, &callbackData); 171 napi_ref callback_ref; 172 napi_create_reference(env, callback, 1, &callback_ref); 173 LockOptions options; 174 lockPtr->LockAsync(env, callback_ref, LOCK_MODE_EXCLUSIVE, options); 175 Loop(LOOP_ONCE); 176 LocksTest::DestroyEngine(); 177 }); 178 napi_env env = GetEnv(); 179 napi_value callback = CreateFunction("exclusivelockmulti", ExclusiveLockMultiCb, &callbackData); 180 napi_ref callback_ref; 181 napi_create_reference(env, callback, 1, &callback_ref); 182 183 LockOptions options; 184 lock->LockAsync(env, callback_ref, LOCK_MODE_EXCLUSIVE, options); 185 Loop(LOOP_ONCE); 186 t.join(); 187 ASSERT_EQ(callbackData.callCount, 2U); 188 ASSERT_FALSE(callbackData.fail); 189} 190 191TEST_F(LocksTest, SharedLockSingle) 192{ 193 napi_env env = GetEnv(); 194 std::unique_ptr<AsyncLock> lock = std::make_unique<AsyncLock>(1); 195 bool isCalled = false; 196 napi_value callback = CreateFunction("sharedlocksingle", ExclusiveLockSingleCb, &isCalled); 197 napi_ref callback_ref; 198 napi_create_reference(env, callback, 1, &callback_ref); 199 200 LockOptions options; 201 lock->LockAsync(env, callback_ref, LOCK_MODE_SHARED, options); 202 Loop(LOOP_ONCE); 203 ASSERT_TRUE(isCalled); 204} 205 206struct SharedMultiCallbackData: public CallbackData { 207 explicit SharedMultiCallbackData(std::latch &barrier): CallbackData(), barrier(barrier) 208 { 209 } 210 211 std::latch &barrier; 212}; 213 214static napi_value MainSharedLockMultiCb(napi_env env, napi_callback_info info) 215{ 216 napi_value undefined; 217 napi_get_undefined(env, &undefined); 218 SharedMultiCallbackData *data = nullptr; 219 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&data)); 220 data->barrier.arrive_and_wait(); 221 data->callCount += 1; 222 223 LocksTest::Sleep(); 224 data->executing.exchange(true); 225 226 if (data->callCount != 2) { 227 data->fail = true; 228 return undefined; 229 } 230 231 data->executing = false; 232 return undefined; 233} 234 235static napi_value SharedLockMultiCb(napi_env env, napi_callback_info info) 236{ 237 napi_value undefined; 238 napi_get_undefined(env, &undefined); 239 CallbackData *data = nullptr; 240 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&data)); 241 data->callCount += 1; 242 243 LocksTest::Sleep(); 244 245 if (data->callCount != 2) { 246 data->fail = true; 247 return undefined; 248 } 249 250 data->executing = false; 251 return undefined; 252} 253 254TEST_F(LocksTest, SharedLockMulti) 255{ 256 std::unique_ptr<AsyncLock> lock = std::make_unique<AsyncLock>(1); 257 AsyncLock *lockPtr = lock.get(); 258 std::latch barrier(2U); 259 SharedMultiCallbackData callbackData(barrier); 260 std::thread t([lockPtr, &callbackData, &barrier] () { 261 LocksTest::InitializeEngine(); 262 napi_env env = GetEnv(); 263 napi_value callback = CreateFunction("sharedlockmulti", SharedLockMultiCb, &callbackData); 264 napi_ref callback_ref; 265 napi_create_reference(env, callback, 1, &callback_ref); 266 LockOptions options; 267 barrier.arrive_and_wait(); 268 lockPtr->LockAsync(env, callback_ref, LOCK_MODE_SHARED, options); 269 Loop(LOOP_ONCE); 270 LocksTest::DestroyEngine(); 271 }); 272 napi_env env = GetEnv(); 273 napi_value callback = CreateFunction("sharedlockmulti", MainSharedLockMultiCb, &callbackData); 274 napi_ref callback_ref; 275 napi_create_reference(env, callback, 1, &callback_ref); 276 277 LockOptions options; 278 lock->LockAsync(env, callback_ref, LOCK_MODE_SHARED, options); 279 Loop(LOOP_ONCE); 280 t.join(); 281 ASSERT_FALSE(callbackData.fail); 282 ASSERT_EQ(callbackData.callCount, 2U); 283} 284 285struct IsAvailableCallbackData { 286 IsAvailableCallbackData(std::latch &b, std::latch &e): begin(b), end(e) 287 { 288 } 289 290 std::atomic<uint32_t> callCount = 0; 291 std::latch &begin; 292 std::latch &end; 293}; 294 295static napi_value IsAvailableCb(napi_env env, napi_callback_info info) 296{ 297 IsAvailableCallbackData *data = nullptr; 298 napi_get_cb_info(env, info, nullptr, nullptr, nullptr, reinterpret_cast<void **>(&data)); 299 data->callCount += 1; 300 data->begin.arrive_and_wait(); 301 data->end.arrive_and_wait(); 302 napi_value undefined; 303 napi_get_undefined(env, &undefined); 304 return undefined; 305} 306 307TEST_F(LocksTest, IsAvailable) 308{ 309 std::unique_ptr<AsyncLock> lock = std::make_unique<AsyncLock>(1); 310 AsyncLock *lockPtr = lock.get(); 311 std::latch begin(2U); 312 std::latch end(2U); 313 IsAvailableCallbackData data(begin, end); 314 std::thread t([lockPtr, &data] () { 315 LocksTest::InitializeEngine(); 316 data.begin.arrive_and_wait(); 317 napi_env env = GetEnv(); 318 napi_value callback = CreateFunction("isavailable", IsAvailableCb, &data); 319 napi_ref callback_ref; 320 napi_create_reference(env, callback, 1, &callback_ref); 321 LockOptions options; 322 options.isAvailable = true; 323 lockPtr->LockAsync(env, callback_ref, LOCK_MODE_EXCLUSIVE, options); 324 data.end.arrive_and_wait(); 325 LocksTest::DestroyEngine(); 326 }); 327 napi_env env = GetEnv(); 328 napi_value callback = CreateFunction("isavailable", IsAvailableCb, &data); 329 napi_ref callback_ref; 330 napi_create_reference(env, callback, 1, &callback_ref); 331 332 LockOptions options; 333 334 lock->LockAsync(env, callback_ref, LOCK_MODE_EXCLUSIVE, options); 335 LoopUntil([&data] () { return data.callCount > 0; }); 336 t.join(); 337 ASSERT_EQ(data.callCount, 1U); 338} 339