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 "ecmascript/ecma_vm.h"
17#include "ecmascript/global_env.h"
18#include "ecmascript/js_handle.h"
19#include "ecmascript/js_runtime_options.h"
20#include "ecmascript/js_thread.h"
21#include "ecmascript/log.h"
22#include "ecmascript/tests/test_helper.h"
23#include "ecmascript/checkpoint/thread_state_transition.h"
24
25#include <csetjmp>
26#include <csignal>
27using namespace panda::ecmascript;
28
29namespace panda::test {
30
31class StateTransitioningTest : public BaseTestWithScope<false> {
32public:
33    void InitializeLogger()
34    {
35        panda::ecmascript::JSRuntimeOptions runtimeOptions;
36        runtimeOptions.SetLogLevel("error");
37        ecmascript::Log::Initialize(runtimeOptions);
38    }
39
40    static void NewVMThreadEntry(EcmaVM *newVm,
41                                 bool nativeState,
42                                 std::atomic<bool> *changeToRunning,
43                                 std::atomic<bool> *isTestEnded,
44                                 std::atomic<size_t> *activeThreadCount)
45    {
46        panda::RuntimeOption postOption;
47        JSNApi::PostFork(newVm, postOption);
48        {
49            JSThread *thread = JSThread::GetCurrent();
50            ThreadManagedScope managedScope(thread);
51            if (nativeState) {
52                ThreadNativeScope nativeScope(thread);
53                activeThreadCount->fetch_add(1);
54                while (!isTestEnded->load() && !changeToRunning->load()) {}
55                ThreadManagedScope secondManagedScope(thread);
56                while (!isTestEnded->load()) {}
57            } else {
58                activeThreadCount->fetch_add(1);
59                while (!isTestEnded->load()) {
60                    JSThread::GetCurrent()->CheckSafepoint();
61                }
62            }
63        }
64        JSNApi::DestroyJSVM(newVm);
65        activeThreadCount->fetch_sub(1);
66    }
67
68    void CreateNewVMInSeparateThread(bool nativeState)
69    {
70        std::thread t1([&]() {
71            RuntimeOption options;
72            EcmaVM *newVm = JSNApi::CreateJSVM(options);
73            vms.push_back(newVm);
74            {
75                ThreadManagedScope managedScope(newVm->GetJSThread());
76                JSNApi::PreFork(newVm);
77            }
78            size_t oldCount = activeThreadCount;
79            // This case isn't a really fork which causes JSThread::GetCurrentThread() equals nullptr in worker_thread.
80            // So reset the threadState as CREATED to skip the check.
81            newVm->GetAssociatedJSThread()->UpdateState(ThreadState::CREATED);
82            std::thread *worker_thread = new std::thread(StateTransitioningTest::NewVMThreadEntry, newVm, nativeState,
83                                                         &changeToRunning, &isTestEnded, &activeThreadCount);
84            threads.push_back(worker_thread);
85            while (activeThreadCount == oldCount) {
86            }
87        });
88        {
89            ThreadSuspensionScope suspensionScope(thread);
90            t1.join();
91        }
92    }
93
94    void ChangeAllThreadsToRunning()
95    {
96        changeToRunning.store(true);
97    }
98
99    bool CheckAllThreadsSuspended()
100    {
101        bool result = true;
102        for (auto i: vms) {
103            result &= i->GetAssociatedJSThread()->HasSuspendRequest();
104        }
105        return result;
106    }
107
108    bool CheckAllThreadsState(ThreadState expectedState)
109    {
110        bool result = true;
111        for (auto i: vms) {
112            result &= (i->GetAssociatedJSThread()->GetState() == expectedState);
113        }
114        return result;
115    }
116
117    void SuspendOrResumeAllThreads(bool toSuspend)
118    {
119        for (auto i: vms) {
120            if (toSuspend) {
121                i->GetAssociatedJSThread()->SuspendThread(false);
122            } else {
123                i->GetAssociatedJSThread()->ResumeThread(false);
124            }
125        }
126    }
127
128    void DestroyAllVMs()
129    {
130        isTestEnded = true;
131        while (activeThreadCount != 0) {}
132        for (auto i: threads) {
133            i->join();
134            delete(i);
135        }
136    }
137
138    std::list<EcmaVM *> vms;
139    std::list<std::thread *> threads;
140    std::atomic<size_t> activeThreadCount {0};
141    std::atomic<bool> isTestEnded {false};
142    std::atomic<bool> changeToRunning {false};
143};
144
145HWTEST_F_L0(StateTransitioningTest, ThreadStateTransitionScopeTest)
146{
147    ThreadState mainState = thread->GetState();
148    {
149        ThreadStateTransitionScope<JSThread, ThreadState::CREATED> scope(thread);
150        EXPECT_TRUE(thread->GetState() == ThreadState::CREATED);
151    }
152    EXPECT_TRUE(thread->GetState() == mainState);
153    {
154        ThreadStateTransitionScope<JSThread, ThreadState::RUNNING> scope(thread);
155        EXPECT_TRUE(thread->GetState() == ThreadState::RUNNING);
156    }
157    EXPECT_TRUE(thread->GetState() == mainState);
158    {
159        ThreadStateTransitionScope<JSThread, ThreadState::NATIVE> scope(thread);
160        EXPECT_TRUE(thread->GetState() == ThreadState::NATIVE);
161    }
162    EXPECT_TRUE(thread->GetState() == mainState);
163    {
164        ThreadStateTransitionScope<JSThread, ThreadState::WAIT> scope(thread);
165        EXPECT_TRUE(thread->GetState() == ThreadState::WAIT);
166    }
167    EXPECT_TRUE(thread->GetState() == mainState);
168    {
169        ThreadStateTransitionScope<JSThread, ThreadState::IS_SUSPENDED> scope(thread);
170        EXPECT_TRUE(thread->GetState() == ThreadState::IS_SUSPENDED);
171    }
172    EXPECT_TRUE(thread->GetState() == mainState);
173    {
174        ThreadStateTransitionScope<JSThread, ThreadState::TERMINATED> scope(thread);
175        EXPECT_TRUE(thread->GetState() == ThreadState::TERMINATED);
176    }
177    EXPECT_TRUE(thread->GetState() == mainState);
178}
179
180HWTEST_F_L0(StateTransitioningTest, ThreadManagedScopeTest)
181{
182    ThreadState mainState = thread->GetState();
183    {
184        ThreadManagedScope scope(thread);
185        EXPECT_TRUE(thread->GetState() == ThreadState::RUNNING);
186    }
187    if (mainState == ThreadState::RUNNING) {
188        ThreadStateTransitionScope<JSThread, ThreadState::WAIT> tempScope(thread);
189        {
190            ThreadManagedScope scope(thread);
191            EXPECT_TRUE(thread->GetState() == ThreadState::RUNNING);
192        }
193        EXPECT_TRUE(thread->GetState() == ThreadState::WAIT);
194    }
195    EXPECT_TRUE(thread->GetState() == mainState);
196}
197
198HWTEST_F_L0(StateTransitioningTest, ThreadNativeScopeTest)
199{
200    ThreadState mainState = thread->GetState();
201    {
202        ThreadNativeScope scope(thread);
203        EXPECT_TRUE(thread->GetState() == ThreadState::NATIVE);
204    }
205    if (mainState == ThreadState::NATIVE) {
206        ThreadStateTransitionScope<JSThread, ThreadState::WAIT> tempScope(thread);
207        {
208            ThreadNativeScope scope(thread);
209            EXPECT_TRUE(thread->GetState() == ThreadState::NATIVE);
210        }
211        EXPECT_TRUE(thread->GetState() == ThreadState::WAIT);
212    }
213    EXPECT_TRUE(thread->GetState() == mainState);
214}
215
216HWTEST_F_L0(StateTransitioningTest, ThreadSuspensionScopeTest)
217{
218    ThreadState mainState = thread->GetState();
219    {
220        ThreadSuspensionScope scope(thread);
221        EXPECT_TRUE(thread->GetState() == ThreadState::IS_SUSPENDED);
222    }
223    if (mainState == ThreadState::IS_SUSPENDED) {
224        ThreadStateTransitionScope<JSThread, ThreadState::WAIT> tempScope(thread);
225        {
226            ThreadSuspensionScope scope(thread);
227            EXPECT_TRUE(thread->GetState() == ThreadState::IS_SUSPENDED);
228        }
229        EXPECT_TRUE(thread->GetState() == ThreadState::WAIT);
230    }
231    EXPECT_TRUE(thread->GetState() == mainState);
232}
233
234HWTEST_F_L0(StateTransitioningTest, IsInRunningStateTest)
235{
236    {
237        ThreadNativeScope scope(thread);
238        EXPECT_TRUE(!thread->IsInRunningState());
239    }
240    {
241        ThreadManagedScope scope(thread);
242        EXPECT_TRUE(thread->IsInRunningState());
243    }
244}
245
246HWTEST_F_L0(StateTransitioningTest, ChangeStateTest)
247{
248    {
249        ThreadNativeScope nativeScope(thread);
250    }
251    {
252        ThreadNativeScope nativeScope(thread);
253        {
254            ThreadManagedScope managedScope(thread);
255        }
256    }
257}
258
259HWTEST_F_L0(StateTransitioningTest, SuspendResumeRunningThreadVMTest)
260{
261    CreateNewVMInSeparateThread(false);
262    EXPECT_FALSE(CheckAllThreadsSuspended());
263    {
264        SuspendOrResumeAllThreads(true);
265        while (!CheckAllThreadsState(ThreadState::IS_SUSPENDED)) {}
266        EXPECT_TRUE(CheckAllThreadsSuspended());
267        EXPECT_TRUE(CheckAllThreadsState(ThreadState::IS_SUSPENDED));
268    }
269    SuspendOrResumeAllThreads(false);
270    while (CheckAllThreadsState(ThreadState::IS_SUSPENDED)) {}
271    EXPECT_FALSE(CheckAllThreadsSuspended());
272    EXPECT_FALSE(CheckAllThreadsState(ThreadState::IS_SUSPENDED));
273    DestroyAllVMs();
274}
275
276HWTEST_F_L0(StateTransitioningTest, SuspendAllManagedTest)
277{
278    CreateNewVMInSeparateThread(false);
279    EXPECT_TRUE(CheckAllThreadsState(ThreadState::RUNNING));
280    {
281        SuspendAllScope suspendScope(JSThread::GetCurrent());
282        EXPECT_TRUE(CheckAllThreadsSuspended());
283    }
284    while (CheckAllThreadsState(ThreadState::IS_SUSPENDED)) {}
285    EXPECT_TRUE(CheckAllThreadsState(ThreadState::RUNNING));
286    DestroyAllVMs();
287}
288
289HWTEST_F_L0(StateTransitioningTest, SuspendAllNativeTest)
290{
291    CreateNewVMInSeparateThread(true);
292    EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
293    {
294        SuspendAllScope suspendScope(JSThread::GetCurrent());
295        EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
296    }
297    EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
298    DestroyAllVMs();
299}
300
301HWTEST_F_L0(StateTransitioningTest, SuspendAllNativeTransferToRunningTest)
302{
303    CreateNewVMInSeparateThread(true);
304    EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
305    {
306        SuspendAllScope suspendScope(JSThread::GetCurrent());
307        EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
308        ChangeAllThreadsToRunning();
309        while (!CheckAllThreadsState(ThreadState::NATIVE)) {}
310        EXPECT_TRUE(CheckAllThreadsState(ThreadState::NATIVE));
311    }
312    while (CheckAllThreadsState(ThreadState::IS_SUSPENDED)) {}
313    while (CheckAllThreadsState(ThreadState::NATIVE)) {}
314    EXPECT_TRUE(CheckAllThreadsState(ThreadState::RUNNING));
315    DestroyAllVMs();
316}
317}  // namespace panda::test
318