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