• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2024-2025 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 <unistd.h>
17 #include <string_view>
18 #include "libpandabase/utils/logger.h"
19 #include "plugins/ets/runtime/ani/ani.h"
20 #include "plugins/ets/runtime/interop_js/logger.h"
21 #include "plugins/ets/runtime/interop_js/timer_module.h"
22 #include "plugins/ets/runtime/interop_js/code_scopes.h"
23 #include "plugins/ets/runtime/interop_js/interop_context.h"
24 #include "plugins/ets/runtime/interop_js/event_loop_module.h"
25 #include "plugins/ets/runtime/types/ets_type.h"
26 
27 // NOTE(konstanting, #23205): in case of interop with Node, libuv will be available automatically via
28 // the load of the ets_vm_plugin module. But since the libuv users are now in the libarkruntime, we should
29 // define some weak stubs for libuv functions. Actually it's worth to create a separate cpp file for them,
30 // like it is done for napi.
31 #if (!defined(PANDA_TARGET_OHOS) && !defined(PANDA_JS_ETS_HYBRID_MODE)) || \
32     defined(PANDA_JS_ETS_HYBRID_MODE_NEED_WEAK_SYMBOLS)
33 // NOLINTBEGIN(readability-identifier-naming)
34 // CC-OFFNXT(G.FMT.10-CPP) project code style
35 extern "C" {
uv_async_send(uv_async_t * async)36 __attribute__((weak)) int uv_async_send([[maybe_unused]] uv_async_t *async)
37 {
38     return -1;
39 }
uv_async_init(uv_loop_t * loop,uv_async_t * async,uv_async_cb async_cb)40 __attribute__((weak)) int uv_async_init([[maybe_unused]] uv_loop_t *loop, [[maybe_unused]] uv_async_t *async,
41                                         [[maybe_unused]] uv_async_cb async_cb)
42 {
43     return -1;
44 }
45 
uv_update_time(uv_loop_t * loop)46 __attribute__((weak)) void uv_update_time([[maybe_unused]] uv_loop_t *loop) {}
47 
uv_timer_init(uv_loop_t * loop,uv_timer_t * handle)48 __attribute__((weak)) int uv_timer_init([[maybe_unused]] uv_loop_t *loop, [[maybe_unused]] uv_timer_t *handle)
49 {
50     return -1;
51 }
uv_timer_start(uv_timer_t * handle,uv_timer_cb cb,uint64_t timeout,uint64_t repeat)52 __attribute__((weak)) int uv_timer_start([[maybe_unused]] uv_timer_t *handle, [[maybe_unused]] uv_timer_cb cb,
53                                          [[maybe_unused]] uint64_t timeout, [[maybe_unused]] uint64_t repeat)
54 {
55     return -1;
56 }
uv_timer_stop(uv_timer_t * handle)57 __attribute__((weak)) int uv_timer_stop([[maybe_unused]] uv_timer_t *handle)
58 {
59     return -1;
60 }
uv_timer_again(uv_timer_t * handle)61 __attribute__((weak)) int uv_timer_again([[maybe_unused]] uv_timer_t *handle)
62 {
63     return -1;
64 }
65 
uv_close(uv_handle_t * handle,uv_close_cb close_cb)66 __attribute__((weak)) void uv_close([[maybe_unused]] uv_handle_t *handle, [[maybe_unused]] uv_close_cb close_cb) {}
67 
uv_default_loop()68 __attribute__((weak)) uv_loop_t *uv_default_loop()
69 {
70     return nullptr;
71 }
uv_run(uv_loop_t * loop,uv_run_mode mode)72 __attribute__((weak)) int uv_run([[maybe_unused]] uv_loop_t *loop, [[maybe_unused]] uv_run_mode mode)
73 {
74     return -1;
75 }
uv_walk(uv_loop_t * loop,uv_walk_cb walk_cb,void * arg)76 __attribute__((weak)) void uv_walk([[maybe_unused]] uv_loop_t *loop, [[maybe_unused]] uv_walk_cb walk_cb,
77                                    [[maybe_unused]] void *arg)
78 {
79 }
80 }
81 // NOLINTEND(readability-identifier-naming)
82 #endif /* !defined(PANDA_TARGET_OHOS) && !defined(PANDA_JS_ETS_HYBRID_MODE) */
83 
Init(ani_env * env)84 bool TimerModule::Init(ani_env *env)
85 {
86     ani_module module {};
87     auto status = env->FindModule("Lescompat;", &module);
88     if (status != ANI_OK) {
89         INTEROP_LOG(ERROR) << "Cannot find ESCOMPAT module";
90         return false;
91     }
92 
93     std::array methods = {
94         ani_native_function {"startTimerImpl", "Lstd/core/Object;IZ:I",
95                              reinterpret_cast<void *>(TimerModule::StartTimer)},
96         ani_native_function {"stopTimerImpl", "I:V", reinterpret_cast<void *>(TimerModule::StopTimer)},
97     };
98     return env->Module_BindNativeFunctions(module, methods.data(), methods.size()) == ANI_OK;
99 }
100 
StartTimer(ani_env * env,ani_object funcObject,ani_int delayMs,ani_boolean repeat)101 ets_int TimerModule::StartTimer(ani_env *env, ani_object funcObject, ani_int delayMs, ani_boolean repeat)
102 {
103     ASSERT(ark::ets::interop::js::InteropCtx::Current() != nullptr);
104 
105     auto *timer = new uv_timer_t();
106     auto timerTable = GetTimerTable(env);
107     ani_ref timerInfoRef {};
108 
109     static constexpr auto SIGNATURE = "Lstd/core/Object;IZJ:Lstd/concurrency/Timer;";
110     [[maybe_unused]] auto status =
111         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
112         env->Object_CallMethodByName_Ref(timerTable, "createTimer", SIGNATURE, &timerInfoRef, funcObject, delayMs,
113                                          repeat, reinterpret_cast<ani_long>(timer));
114     ASSERT(status == ANI_OK);
115     auto timerInfo = static_cast<ani_object>(timerInfoRef);
116 
117     ani_int timerId {};
118     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
119     status = env->Object_CallMethodByName_Int(timerInfo, "getId", ":I", &timerId);
120     ASSERT(status == ANI_OK);
121 
122     timer->data = reinterpret_cast<void *>(timerId);
123 
124     uv_loop_t *loop = ark::ets::interop::js::EventLoop::GetEventLoop();
125     uv_timer_init(loop, timer);
126     uv_update_time(loop);
127     ani_int intervalMs = static_cast<bool>(repeat)
128                              ? (delayMs > TimerModule::MIN_INTERVAL_MS ? delayMs : TimerModule::MIN_INTERVAL_MS)
129                              : 0;
130     uv_timer_start(timer, TimerCallback, delayMs, intervalMs);
131     uv_async_send(&loop->wq_async);
132     return timerId;
133 }
134 
StopTimer(ani_env * env,ani_int timerId)135 void TimerModule::StopTimer(ani_env *env, ani_int timerId)
136 {
137     ASSERT(ark::ets::interop::js::InteropCtx::Current() != nullptr);
138     auto [info, exists] = FindTimerInfo(env, timerId);
139     if (exists) {
140         TimerInfo timerInfo(info);
141         auto *timer = timerInfo.GetLinkedTimer(env);
142         if (timer != nullptr) {
143             DisarmTimer(timer);
144         }
145         ClearTimerInfo(env, timerId);
146     }
147 }
148 
TimerCallback(uv_timer_t * timer)149 void TimerModule::TimerCallback(uv_timer_t *timer)
150 {
151     auto timerFunc = [](void *param) {
152         auto *timerHandle = static_cast<uv_timer_t *>(param);
153         auto *env = ark::ets::EtsCoroutine::GetCurrent()->GetEtsNapiEnv();
154         auto timerId = reinterpret_cast<uint64_t>(timerHandle->data);
155         auto [info, exists] = FindTimerInfo(env, timerId);
156         if (!exists) {
157             return;
158         }
159 
160         TimerInfo timerInfo(info);
161         auto oneShotTimer = timerInfo.IsOneShotTimer(env);
162         if (oneShotTimer) {
163             DisarmTimer(timerHandle);
164             ClearTimerInfo(env, timerId);
165         }
166         timerInfo.InvokeCallback(env);
167         if (!oneShotTimer) {
168             RepeatTimer(timerHandle, timerId);
169         }
170     };
171     auto timerId = reinterpret_cast<uint64_t>(timer->data);
172     auto coroName = "Timer callback: " + ark::ToPandaString(timerId);
173     {
174         auto *coro = ark::ets::EtsCoroutine::GetCurrent();
175         ASSERT(coro != nullptr);
176         ark::ScopedManagedCodeThread managedScope(coro);
177         coro->GetCoroutineManager()->LaunchNative(timerFunc, timer, std::move(coroName),
178                                                   ark::CoroutineLaunchMode::SAME_WORKER,
179                                                   ark::ets::EtsCoroutine::TIMER_CALLBACK, true);
180     }
181     uv_timer_stop(timer);
182 }
183 
RepeatTimer(uv_timer_t * timer,uint64_t timerId)184 void TimerModule::RepeatTimer(uv_timer_t *timer, uint64_t timerId)
185 {
186     auto *coro = ark::ets::EtsCoroutine::GetCurrent();
187     ASSERT(coro != nullptr);
188     auto *env = coro->GetEtsNapiEnv();
189     auto [_, exists] = FindTimerInfo(env, timerId);
190     if (exists) {
191         uv_timer_again(timer);
192     }
193 }
194 
DisarmTimer(uv_timer_t * timer)195 void TimerModule::DisarmTimer(uv_timer_t *timer)
196 {
197     uv_timer_stop(timer);
198     uv_close(reinterpret_cast<uv_handle_t *>(timer), [](uv_handle_t *handle) { delete handle; });
199 }
200 
GetTimerTable(ani_env * env)201 ani_object TimerModule::GetTimerTable(ani_env *env)
202 {
203     ani_module concurrencyModule {};
204     [[maybe_unused]] auto status = env->FindModule("Lstd/concurrency;", &concurrencyModule);
205     ASSERT(status == ANI_OK);
206 
207     ani_class loopCls {};
208     status = env->Module_FindClass(concurrencyModule, "LEventLoop;", &loopCls);
209     ASSERT(status == ANI_OK);
210 
211     ani_ref timerTable {};
212     status = env->Class_GetStaticFieldByName_Ref(loopCls, "timerTable", &timerTable);
213     ASSERT(status == ANI_OK);
214     return static_cast<ani_object>(timerTable);
215 }
216 
FindTimerInfo(ani_env * env,ani_int timerId)217 std::pair<ani_object, bool> TimerModule::FindTimerInfo(ani_env *env, ani_int timerId)
218 {
219     auto timerTable = GetTimerTable(env);
220     ani_ref timerInfoRef {};
221     static constexpr auto SIGNATURE = "I:Lstd/concurrency/Timer;";
222     [[maybe_unused]] ani_status status {};
223     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
224     status = env->Object_CallMethodByName_Ref(timerTable, "findTimer", SIGNATURE, &timerInfoRef, timerId);
225     ASSERT(status == ANI_OK);
226 
227     auto timerInfo = static_cast<ani_object>(timerInfoRef);
228     ani_boolean isUndefined {};
229     status = env->Reference_IsUndefined(timerInfo, &isUndefined);
230     ASSERT(status == ANI_OK);
231     return {timerInfo, !static_cast<bool>(isUndefined)};
232 }
233 
ClearTimerInfo(ani_env * env,ani_int timerId)234 void TimerModule::ClearTimerInfo(ani_env *env, ani_int timerId)
235 {
236     auto timerTable = GetTimerTable(env);
237     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
238     [[maybe_unused]] auto status = env->Object_CallMethodByName_Void(timerTable, "clearTimer", "I:V", timerId);
239     ASSERT(status == ANI_OK);
240 }
241 
IsOneShotTimer(ani_env * env)242 bool TimerModule::TimerInfo::IsOneShotTimer(ani_env *env)
243 {
244     ani_boolean repeat {};
245     [[maybe_unused]] auto status = env->Object_GetFieldByName_Boolean(info_, "repeat", &repeat);
246     ASSERT(status == ANI_OK);
247     return !static_cast<bool>(repeat);
248 }
249 
GetLinkedTimer(ani_env * env)250 uv_timer_t *TimerModule::TimerInfo::GetLinkedTimer(ani_env *env)
251 {
252     ani_long linkedTimer;
253     [[maybe_unused]] auto status = env->Object_GetFieldByName_Long(info_, "linkedTimer", &linkedTimer);
254     ASSERT(status == ANI_OK);
255     return reinterpret_cast<uv_timer_t *>(linkedTimer);
256 }
257 
InvokeCallback(ani_env * env)258 void TimerModule::TimerInfo::InvokeCallback(ani_env *env)
259 {
260     ani_object callback {};
261     [[maybe_unused]] auto status =
262         env->Object_GetFieldByName_Ref(info_, "callback", reinterpret_cast<ani_ref *>(&callback));
263     ASSERT(status == ANI_OK);
264 
265     ani_class callbackCls {};
266     status = env->Object_GetType(callback, reinterpret_cast<ani_type *>(&callbackCls));
267     ASSERT(status == ANI_OK);
268 
269     ani_method invokeMethod {};
270     status = env->Class_FindMethod(callbackCls, ark::ets::INVOKE_METHOD_NAME, nullptr, &invokeMethod);
271     ASSERT(status == ANI_OK);
272 
273     auto *coro = ark::ets::EtsCoroutine::GetCurrent();
274     ark::ets::interop::js::INTEROP_CODE_SCOPE_JS(coro);
275     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
276     status = env->Object_CallMethod_Void(callback, invokeMethod);
277     ASSERT(status == ANI_OK);
278 }
279