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