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
26 using namespace Commonlibrary::Concurrent::LocksModule;
27
28 class LocksTest : public testing::Test {
29 public:
SetUpTestSuite()30 static void SetUpTestSuite()
31 {
32 InitializeEngine();
33 }
34
TearDownTestSuite()35 static void TearDownTestSuite()
36 {
37 DestroyEngine();
38 }
39
InitializeEngine()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
DestroyEngine()56 static void DestroyEngine()
57 {
58 delete engine_;
59 engine_ = nullptr;
60 panda::JSNApi::DestroyJSVM(vm_);
61 }
62
GetEnv()63 static napi_env GetEnv()
64 {
65 return reinterpret_cast<napi_env>(engine_);
66 }
67
Loop(LoopMode mode)68 static void Loop(LoopMode mode)
69 {
70 engine_->Loop(mode);
71 }
72
73 template <typename P>
LoopUntil(const P & pred)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
CreateFunction(const char * name,napi_value (* callback)(napi_env,napi_callback_info),void * data=nullptr)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
Sleep()93 static void Sleep()
94 {
95 timespec ts{0, 100U * 1000U * 1000U}; // 100ms
96 nanosleep(&ts, nullptr);
97 }
98
99 protected:
100 static thread_local NativeEngine *engine_;
101 static thread_local EcmaVM *vm_;
102 };
103
104 thread_local NativeEngine *LocksTest::engine_ = nullptr;
105 thread_local EcmaVM *LocksTest::vm_ = nullptr;
106
ExclusiveLockSingleCb(napi_env env,napi_callback_info info)107 static 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
TEST_F(LocksTest,ExclusiveLockSingle)117 TEST_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
135 struct CallbackData {
136 std::atomic<bool> executing = false;
137 std::atomic<bool> fail = false;
138 std::atomic<uint32_t> callCount = 0;
139 };
140
ExclusiveLockMultiCb(napi_env env,napi_callback_info info)141 static 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
TEST_F(LocksTest,ExclusiveLockMulti)162 TEST_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
TEST_F(LocksTest,SharedLockSingle)191 TEST_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
206 struct SharedMultiCallbackData: public CallbackData {
SharedMultiCallbackDataSharedMultiCallbackData207 explicit SharedMultiCallbackData(std::latch &barrier): CallbackData(), barrier(barrier)
208 {
209 }
210
211 std::latch &barrier;
212 };
213
MainSharedLockMultiCb(napi_env env,napi_callback_info info)214 static 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
SharedLockMultiCb(napi_env env,napi_callback_info info)235 static 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
TEST_F(LocksTest,SharedLockMulti)254 TEST_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
285 struct IsAvailableCallbackData {
IsAvailableCallbackDataIsAvailableCallbackData286 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
IsAvailableCb(napi_env env,napi_callback_info info)295 static 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
TEST_F(LocksTest,IsAvailable)307 TEST_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