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