1 /*
2 * Copyright (c) 2022 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 "sys_timer.h"
17
18 #include <cinttypes>
19
20 #ifdef ENABLE_HITRACE_HELPER_METER
21 #include "helper/hitrace_helper.h"
22 #endif
23 #include "native_engine/native_engine.h"
24 #include "tools/log.h"
25
26 #ifdef ENABLE_CONTAINER_SCOPE
27 using OHOS::Ace::ContainerScope;
28 #endif
29
30 namespace OHOS::JsSysModule {
31 using namespace Commonlibrary::Concurrent::Common;
32
33 uint32_t Timer::timeCallbackId = 0;
34 uint32_t deleteTimerCount = 0;
35 std::map<uint32_t, TimerCallbackInfo*> Timer::timerTable;
36 std::mutex Timer::timeLock;
37
~TimerCallbackInfo()38 TimerCallbackInfo::~TimerCallbackInfo()
39 {
40 #ifdef ENABLE_HITRACE_HELPER_METER
41 HITRACE_HELPER_METER_NAME("~TimerCallbackInfo address = " + std::to_string(reinterpret_cast<std::uintptr_t>(this)));
42 #endif
43 Helper::NapiHelper::DeleteReference(env_, callback_);
44 callback_ = nullptr;
45 for (size_t idx = 0; idx < argc_; idx++) {
46 Helper::NapiHelper::DeleteReference(env_, argv_[idx]);
47 argv_[idx] = nullptr;
48 }
49 Helper::CloseHelp::DeletePointer(argv_, true);
50
51 uv_timer_stop(timeReq_);
52 uv_close(reinterpret_cast<uv_handle_t*>(timeReq_), [](uv_handle_t* handle) {
53 if (handle != nullptr) {
54 handle->data = nullptr;
55 delete (uv_timer_t*)handle;
56 handle = nullptr;
57 }
58 });
59 }
60
61 typedef napi_value (*SetTimeFunction)(napi_env env, napi_callback_info info, bool repeat);
62
SetTimeOutFaker(napi_env env,napi_callback_info cbinfo,bool repeat)63 napi_value Timer::SetTimeOutFaker(napi_env env, napi_callback_info cbinfo, bool repeat)
64 {
65 std::lock_guard<std::mutex> lock(timeLock);
66 uint32_t tId = timeCallbackId++;
67 HILOG_WARN("Timer is deactivated on current JS Thread, timer id = %{public}" PRIu32, tId);
68 return Helper::NapiHelper::CreateUint32(env, tId);
69 }
70
71 struct TimeData {
72 napi_env env_;
73 SetTimeFunction func_;
74 };
75
CleanUpHook(void * data)76 void Timer::CleanUpHook(void* data)
77 {
78 auto that = reinterpret_cast<TimeData*>(data);
79 Timer::ClearEnvironmentTimer(that->env_);
80 that->func_ = Timer::SetTimeOutFaker;
81 that->env_ = nullptr;
82 }
83
CleanUpTimeData(void * data)84 void Timer::CleanUpTimeData(void* data)
85 {
86 auto that = reinterpret_cast<TimeData*>(data);
87 delete that;
88 }
89
RegisterTime(napi_env env)90 bool Timer::RegisterTime(napi_env env)
91 {
92 if (env == nullptr) {
93 return false;
94 }
95 auto data = new TimeData();
96 data->env_ = env;
97 data->func_ = SetTimeoutInner;
98 napi_add_env_cleanup_hook(env, CleanUpHook, data);
99
100 napi_add_cleanup_finalizer(env, CleanUpTimeData, data);
101
102 napi_property_descriptor properties[] = {
103 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION_WITH_DATA("setTimeout", SetTimeout, data),
104 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION_WITH_DATA("setInterval", SetInterval, data),
105 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearTimeout", ClearTimer),
106 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearInterval", ClearTimer)
107 };
108 napi_value globalObj = Helper::NapiHelper::GetGlobalObject(env);
109 napi_status status = napi_define_properties(env, globalObj, sizeof(properties) / sizeof(properties[0]), properties);
110 return status == napi_ok;
111 }
112
SetTimeout(napi_env env,napi_callback_info cbinfo)113 napi_value Timer::SetTimeout(napi_env env, napi_callback_info cbinfo)
114 {
115 void *data = nullptr;
116 napi_get_cb_info(env, cbinfo, 0, nullptr, nullptr, &data);
117 return reinterpret_cast<TimeData*>(data)->func_(env, cbinfo, false);
118 }
119
SetInterval(napi_env env,napi_callback_info cbinfo)120 napi_value Timer::SetInterval(napi_env env, napi_callback_info cbinfo)
121 {
122 void *data = nullptr;
123 napi_get_cb_info(env, cbinfo, 0, nullptr, nullptr, &data);
124 return reinterpret_cast<TimeData*>(data)->func_(env, cbinfo, true);
125 }
126
ClearTimer(napi_env env,napi_callback_info cbinfo)127 napi_value Timer::ClearTimer(napi_env env, napi_callback_info cbinfo)
128 {
129 // 1. check args
130 size_t argc = 1;
131 napi_value argv[1];
132 napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
133 if (argc < 1) {
134 return nullptr;
135 }
136
137 uint32_t tId;
138 napi_status status = napi_get_value_uint32(env, argv[0], &tId);
139 if (status != napi_ok) {
140 HILOG_DEBUG("first param should be number");
141 return nullptr;
142 }
143
144 {
145 std::lock_guard<std::mutex> lock(timeLock);
146 auto iter = timerTable.find(tId);
147 if (iter == timerTable.end()) {
148 // timer already cleared
149 return nullptr;
150 }
151 TimerCallbackInfo* callbackInfo = iter->second;
152 if (callbackInfo->env_ != env) {
153 HILOG_ERROR("The timer was not created in current thread and cannot be deleted, please check js code." \
154 " TimerID:%{public}u", tId);
155 } else {
156 timerTable.erase(tId);
157 Helper::CloseHelp::DeletePointer(callbackInfo, false);
158 HILOG_INFO("ID: %{public}u, cnt: %{public}u", tId, ++deleteTimerCount);
159 #ifdef ENABLE_HITRACE_HELPER_METER
160 HITRACE_HELPER_METER_NAME("DeleteTimer ID: " + std::to_string(tId) + ", count: "
161 + std::to_string(deleteTimerCount));
162 #endif
163 }
164 }
165 return Helper::NapiHelper::GetUndefinedValue(env);
166 }
167
TimerCallback(uv_timer_t * handle)168 void Timer::TimerCallback(uv_timer_t* handle)
169 {
170 TimerCallbackInfo* callbackInfo = static_cast<TimerCallbackInfo*>(handle->data);
171 if (callbackInfo == nullptr) {
172 return;
173 }
174 // Save the following parameters to ensure that they can still obtained if callback clears the callbackInfo.
175 bool repeat = callbackInfo->repeat_;
176 uint32_t tId = callbackInfo->tId_;
177 napi_env env = callbackInfo->env_;
178 #ifdef ENABLE_CONTAINER_SCOPE
179 ContainerScope containerScope(callbackInfo->containerScopeId_);
180 #endif
181 #ifdef ENABLE_HITRACE_HELPER_METER
182 HITRACE_HELPER_METER_NAME("TimerCallback callbackInfo address = "
183 + std::to_string(reinterpret_cast<std::uintptr_t>(callbackInfo)) + ", tId = " + std::to_string(tId));
184 #endif
185 napi_handle_scope scope = nullptr;
186 napi_open_handle_scope(env, &scope);
187 if (scope == nullptr) {
188 return;
189 }
190 napi_value callback = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->callback_);
191 napi_value undefinedValue = Helper::NapiHelper::GetUndefinedValue(env);
192 napi_value callbackResult = nullptr;
193 napi_value* callbackArgv = new napi_value[callbackInfo->argc_];
194 for (size_t idx = 0; idx < callbackInfo->argc_; idx++) {
195 callbackArgv[idx] = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->argv_[idx]);
196 }
197
198 napi_call_function(env, undefinedValue, callback,
199 callbackInfo->argc_, callbackArgv, &callbackResult);
200 Helper::CloseHelp::DeletePointer(callbackArgv, true);
201 if (Helper::NapiHelper::IsExceptionPending(env)) {
202 HILOG_ERROR("Pending exception in TimerCallback. Triggering HandleUncaughtException");
203 reinterpret_cast<NativeEngine*>(env)->HandleUncaughtException();
204 napi_close_handle_scope(env, scope);
205 DeleteTimer(tId, callbackInfo);
206 return;
207 }
208
209 // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
210 std::lock_guard<std::mutex> lock(timeLock);
211 if (timerTable.find(tId) == timerTable.end()) {
212 napi_close_handle_scope(env, scope);
213 return;
214 }
215 if (!repeat) {
216 timerTable.erase(tId);
217 napi_close_handle_scope(env, scope);
218 Helper::CloseHelp::DeletePointer(callbackInfo, false);
219 } else {
220 napi_close_handle_scope(env, scope);
221 uv_timer_again(handle);
222 }
223 }
224
SetTimeoutInnerCore(napi_env env,napi_value * argv,size_t argc,bool repeat)225 napi_value Timer::SetTimeoutInnerCore(napi_env env, napi_value* argv, size_t argc, bool repeat)
226 {
227 int32_t timeout = 0;
228 if (argc > 1) {
229 napi_status status = napi_get_value_int32(env, argv[1], &timeout);
230 if (status != napi_ok) {
231 HILOG_WARN("timeout should be number");
232 timeout = 0;
233 }
234 }
235 if (timeout < 0) {
236 HILOG_DEBUG("timeout < 0 is unreasonable");
237 }
238 // 2. get callback args
239 size_t callbackArgc = argc >= 2 ? argc - 2 : 0; // 2 include callback and timeout
240 napi_ref* callbackArgv = nullptr;
241 if (callbackArgc > 0) {
242 callbackArgv = new napi_ref[callbackArgc];
243 for (size_t idx = 0; idx < callbackArgc; idx++) {
244 callbackArgv[idx] =
245 Helper::NapiHelper::CreateReference(env, argv[idx + 2], 1); // 2 include callback and timeout
246 }
247 }
248 // 3. generate time callback id
249 // 4. generate time callback info
250 // 5. push callback info into timerTable
251 uint32_t tId = 0;
252 TimerCallbackInfo* callbackInfo = nullptr;
253 {
254 std::lock_guard<std::mutex> lock(timeLock);
255 tId = timeCallbackId++;
256 napi_ref callbackRef = Helper::NapiHelper::CreateReference(env, argv[0], 1);
257 callbackInfo = new TimerCallbackInfo(env, tId, timeout, callbackRef, repeat, callbackArgc, callbackArgv);
258 #ifdef ENABLE_CONTAINER_SCOPE
259 callbackInfo->containerScopeId_ = ContainerScope::CurrentId();
260 #endif
261 if (timerTable.find(tId) != timerTable.end()) {
262 HILOG_ERROR("timerTable occurs error");
263 } else {
264 timerTable[tId] = callbackInfo;
265 }
266 }
267
268 HILOG_DEBUG("SetTimeoutInnerCore function call before libuv! tId = %{public}u,timeout = %{public}u", tId, timeout);
269 #ifdef ENABLE_HITRACE_HELPER_METER
270 HITRACE_HELPER_METER_NAME("SetTimeoutInnerCore function call before libuv! tId = " + std::to_string(tId)
271 + ", timeout = " + std::to_string(timeout));
272 #endif
273 // 6. start timer
274 uv_loop_t* loop = Helper::NapiHelper::GetLibUV(env);
275 NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
276 uv_update_time(loop);
277 uv_timer_start(callbackInfo->timeReq_, TimerCallback, timeout >= 0 ? timeout : 1, timeout > 0 ? timeout : 1);
278 if (engine->IsMainThread()) {
279 uv_async_send(&loop->wq_async);
280 }
281 return Helper::NapiHelper::CreateUint32(env, tId);
282 }
283
SetTimeoutInner(napi_env env,napi_callback_info cbinfo,bool repeat)284 napi_value Timer::SetTimeoutInner(napi_env env, napi_callback_info cbinfo, bool repeat)
285 {
286 size_t argc = Helper::NapiHelper::GetCallbackInfoArgc(env, cbinfo);
287 if (argc < 1) {
288 napi_throw_error(env, nullptr, "StartTimeoutOrInterval, callback info is nullptr.");
289 return nullptr;
290 }
291 napi_value* argv = new napi_value[argc];
292 Helper::ObjectScope<napi_value> scope(argv, true);
293 napi_value thisVar = nullptr;
294 napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, nullptr);
295 if (!Helper::NapiHelper::IsCallable(env, argv[0])) {
296 HILOG_ERROR("Set callback timer failed with invalid parameter.");
297 return Helper::NapiHelper::GetUndefinedValue(env);
298 }
299
300 return SetTimeoutInnerCore(env, argv, argc, repeat);
301 }
302
ClearEnvironmentTimer(napi_env env)303 void Timer::ClearEnvironmentTimer(napi_env env)
304 {
305 std::lock_guard<std::mutex> lock(timeLock);
306 auto iter = timerTable.begin();
307 while (iter != timerTable.end()) {
308 TimerCallbackInfo* callbackInfo = iter->second;
309 if (callbackInfo->env_ == env) {
310 iter = timerTable.erase(iter);
311 Helper::CloseHelp::DeletePointer(callbackInfo, false);
312 } else {
313 iter++;
314 }
315 }
316 }
317
HasTimer(napi_env env)318 bool Timer::HasTimer(napi_env env)
319 {
320 std::lock_guard<std::mutex> lock(timeLock);
321 auto iter = std::find_if(timerTable.begin(), timerTable.end(), [env](const auto &item) {
322 return item.second->env_ == env;
323 });
324 return iter != timerTable.end();
325 }
326
DeleteTimer(uint32_t tId,TimerCallbackInfo * callbackInfo)327 void Timer::DeleteTimer(uint32_t tId, TimerCallbackInfo* callbackInfo)
328 {
329 std::lock_guard<std::mutex> lock(timeLock);
330 // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
331 if (timerTable.find(tId) != timerTable.end()) {
332 timerTable.erase(tId);
333 Helper::CloseHelp::DeletePointer(callbackInfo, false);
334 }
335 }
336 } // namespace Commonlibrary::JsSysModule
337