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
RegisterTime(napi_env env)84 bool Timer::RegisterTime(napi_env env)
85 {
86 if (env == nullptr) {
87 return false;
88 }
89 thread_local auto data = new TimeData();
90 data->env_ = env;
91 data->func_ = SetTimeoutInner;
92 napi_add_env_cleanup_hook(env, CleanUpHook, data);
93
94 napi_property_descriptor properties[] = {
95 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION_WITH_DATA("setTimeout", SetTimeout, data),
96 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION_WITH_DATA("setInterval", SetInterval, data),
97 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearTimeout", ClearTimer),
98 DECLARE_NAPI_DEFAULT_PROPERTY_FUNCTION("clearInterval", ClearTimer)
99 };
100 napi_value globalObj = Helper::NapiHelper::GetGlobalObject(env);
101 napi_status status = napi_define_properties(env, globalObj, sizeof(properties) / sizeof(properties[0]), properties);
102 return status == napi_ok;
103 }
104
SetTimeout(napi_env env,napi_callback_info cbinfo)105 napi_value Timer::SetTimeout(napi_env env, napi_callback_info cbinfo)
106 {
107 void *data = nullptr;
108 napi_get_cb_info(env, cbinfo, 0, nullptr, nullptr, &data);
109 return reinterpret_cast<TimeData*>(data)->func_(env, cbinfo, false);
110 }
111
SetInterval(napi_env env,napi_callback_info cbinfo)112 napi_value Timer::SetInterval(napi_env env, napi_callback_info cbinfo)
113 {
114 void *data = nullptr;
115 napi_get_cb_info(env, cbinfo, 0, nullptr, nullptr, &data);
116 return reinterpret_cast<TimeData*>(data)->func_(env, cbinfo, true);
117 }
118
ClearTimer(napi_env env,napi_callback_info cbinfo)119 napi_value Timer::ClearTimer(napi_env env, napi_callback_info cbinfo)
120 {
121 // 1. check args
122 size_t argc = 1;
123 napi_value argv[1];
124 napi_get_cb_info(env, cbinfo, &argc, argv, nullptr, nullptr);
125 if (argc < 1) {
126 HILOG_ERROR("the number of params must be one");
127 return nullptr;
128 }
129
130 uint32_t tId;
131 napi_status status = napi_get_value_uint32(env, argv[0], &tId);
132 if (status != napi_ok) {
133 HILOG_DEBUG("first param should be number");
134 return nullptr;
135 }
136
137 {
138 std::lock_guard<std::mutex> lock(timeLock);
139 auto iter = timerTable.find(tId);
140 if (iter == timerTable.end()) {
141 // timer already cleared
142 return nullptr;
143 }
144 TimerCallbackInfo* callbackInfo = iter->second;
145 if (callbackInfo->env_ != env) {
146 HILOG_ERROR("Timer is deleting by another thread, please check js code. TimerID:%{public}u", tId);
147 } else {
148 timerTable.erase(tId);
149 Helper::CloseHelp::DeletePointer(callbackInfo, false);
150 HILOG_INFO("DeleteTimer ID: %{public}u, count: %{public}u", tId, ++deleteTimerCount);
151 #ifdef ENABLE_HITRACE_HELPER_METER
152 HITRACE_HELPER_METER_NAME("DeleteTimer ID: " + std::to_string(tId) + ", count: "
153 + std::to_string(deleteTimerCount));
154 #endif
155 }
156 }
157 return Helper::NapiHelper::GetUndefinedValue(env);
158 }
159
TimerCallback(uv_timer_t * handle)160 void Timer::TimerCallback(uv_timer_t* handle)
161 {
162 TimerCallbackInfo* callbackInfo = static_cast<TimerCallbackInfo*>(handle->data);
163 if (callbackInfo == nullptr) {
164 return;
165 }
166 // Save the following parameters to ensure that they can still obtained if callback clears the callbackInfo.
167 bool repeat = callbackInfo->repeat_;
168 uint32_t tId = callbackInfo->tId_;
169 napi_env env = callbackInfo->env_;
170 #ifdef ENABLE_CONTAINER_SCOPE
171 ContainerScope containerScope(callbackInfo->containerScopeId_);
172 #endif
173 #ifdef ENABLE_HITRACE_HELPER_METER
174 HITRACE_HELPER_METER_NAME("TimerCallback callbackInfo address = "
175 + std::to_string(reinterpret_cast<std::uintptr_t>(callbackInfo)) + ", tId = " + std::to_string(tId));
176 #endif
177 napi_handle_scope scope = nullptr;
178 napi_open_handle_scope(env, &scope);
179 if (scope == nullptr) {
180 return;
181 }
182 napi_value callback = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->callback_);
183 napi_value undefinedValue = Helper::NapiHelper::GetUndefinedValue(env);
184 napi_value callbackResult = nullptr;
185 napi_value* callbackArgv = new napi_value[callbackInfo->argc_];
186 for (size_t idx = 0; idx < callbackInfo->argc_; idx++) {
187 callbackArgv[idx] = Helper::NapiHelper::GetReferenceValue(env, callbackInfo->argv_[idx]);
188 }
189
190 napi_call_function(env, undefinedValue, callback,
191 callbackInfo->argc_, callbackArgv, &callbackResult);
192 Helper::CloseHelp::DeletePointer(callbackArgv, true);
193 if (Helper::NapiHelper::IsExceptionPending(env)) {
194 HILOG_ERROR("Pending exception in TimerCallback. Triggering HandleUncaughtException");
195 reinterpret_cast<NativeEngine*>(env)->HandleUncaughtException();
196 napi_close_handle_scope(env, scope);
197 DeleteTimer(tId, callbackInfo);
198 return;
199 }
200
201 // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
202 std::lock_guard<std::mutex> lock(timeLock);
203 if (timerTable.find(tId) == timerTable.end()) {
204 napi_close_handle_scope(env, scope);
205 return;
206 }
207 if (!repeat) {
208 timerTable.erase(tId);
209 napi_close_handle_scope(env, scope);
210 Helper::CloseHelp::DeletePointer(callbackInfo, false);
211 } else {
212 napi_close_handle_scope(env, scope);
213 uv_timer_again(handle);
214 }
215 }
216
SetTimeoutInnerCore(napi_env env,napi_value * argv,size_t argc,bool repeat)217 napi_value Timer::SetTimeoutInnerCore(napi_env env, napi_value* argv, size_t argc, bool repeat)
218 {
219 int32_t timeout = 0;
220 if (argc > 1) {
221 napi_status status = napi_get_value_int32(env, argv[1], &timeout);
222 if (status != napi_ok) {
223 HILOG_WARN("timeout should be number");
224 timeout = 0;
225 }
226 }
227 if (timeout < 0) {
228 HILOG_DEBUG("timeout < 0 is unreasonable");
229 }
230 // 2. get callback args
231 size_t callbackArgc = argc >= 2 ? argc - 2 : 0; // 2 include callback and timeout
232 napi_ref* callbackArgv = nullptr;
233 if (callbackArgc > 0) {
234 callbackArgv = new napi_ref[callbackArgc];
235 for (size_t idx = 0; idx < callbackArgc; idx++) {
236 callbackArgv[idx] =
237 Helper::NapiHelper::CreateReference(env, argv[idx + 2], 1); // 2 include callback and timeout
238 }
239 }
240 // 3. generate time callback id
241 // 4. generate time callback info
242 // 5. push callback info into timerTable
243 uint32_t tId = 0;
244 TimerCallbackInfo* callbackInfo = nullptr;
245 {
246 std::lock_guard<std::mutex> lock(timeLock);
247 tId = timeCallbackId++;
248 napi_ref callbackRef = Helper::NapiHelper::CreateReference(env, argv[0], 1);
249 callbackInfo = new TimerCallbackInfo(env, tId, timeout, callbackRef, repeat, callbackArgc, callbackArgv);
250 #ifdef ENABLE_CONTAINER_SCOPE
251 callbackInfo->containerScopeId_ = ContainerScope::CurrentId();
252 #endif
253 if (timerTable.find(tId) != timerTable.end()) {
254 HILOG_ERROR("timerTable occurs error");
255 } else {
256 timerTable[tId] = callbackInfo;
257 }
258 }
259
260 HILOG_DEBUG("SetTimeoutInnerCore function call before libuv! tId = %{public}u,timeout = %{public}u", tId, timeout);
261 #ifdef ENABLE_HITRACE_HELPER_METER
262 HITRACE_HELPER_METER_NAME("SetTimeoutInnerCore function call before libuv! tId = " + std::to_string(tId)
263 + ", timeout = " + std::to_string(timeout));
264 #endif
265 // 6. start timer
266 uv_loop_t* loop = Helper::NapiHelper::GetLibUV(env);
267 NativeEngine* engine = reinterpret_cast<NativeEngine*>(env);
268 uv_update_time(loop);
269 uv_timer_start(callbackInfo->timeReq_, TimerCallback, timeout >= 0 ? timeout : 1, timeout > 0 ? timeout : 1);
270 if (engine->IsMainThread()) {
271 uv_async_send(&loop->wq_async);
272 }
273 return Helper::NapiHelper::CreateUint32(env, tId);
274 }
275
SetTimeoutInner(napi_env env,napi_callback_info cbinfo,bool repeat)276 napi_value Timer::SetTimeoutInner(napi_env env, napi_callback_info cbinfo, bool repeat)
277 {
278 size_t argc = Helper::NapiHelper::GetCallbackInfoArgc(env, cbinfo);
279 if (argc < 1) {
280 napi_throw_error(env, nullptr, "StartTimeoutOrInterval, callback info is nullptr.");
281 return nullptr;
282 }
283 napi_value* argv = new napi_value[argc];
284 Helper::ObjectScope<napi_value> scope(argv, true);
285 napi_value thisVar = nullptr;
286 napi_get_cb_info(env, cbinfo, &argc, argv, &thisVar, nullptr);
287 if (!Helper::NapiHelper::IsCallable(env, argv[0])) {
288 HILOG_ERROR("Set callback timer failed with invalid parameter.");
289 return Helper::NapiHelper::GetUndefinedValue(env);
290 }
291
292 return SetTimeoutInnerCore(env, argv, argc, repeat);
293 }
294
ClearEnvironmentTimer(napi_env env)295 void Timer::ClearEnvironmentTimer(napi_env env)
296 {
297 std::lock_guard<std::mutex> lock(timeLock);
298 auto iter = timerTable.begin();
299 while (iter != timerTable.end()) {
300 TimerCallbackInfo* callbackInfo = iter->second;
301 if (callbackInfo->env_ == env) {
302 iter = timerTable.erase(iter);
303 Helper::CloseHelp::DeletePointer(callbackInfo, false);
304 } else {
305 iter++;
306 }
307 }
308 }
309
HasTimer(napi_env env)310 bool Timer::HasTimer(napi_env env)
311 {
312 std::lock_guard<std::mutex> lock(timeLock);
313 auto iter = std::find_if(timerTable.begin(), timerTable.end(), [env](const auto &item) {
314 return item.second->env_ == env;
315 });
316 return iter != timerTable.end();
317 }
318
DeleteTimer(uint32_t tId,TimerCallbackInfo * callbackInfo)319 void Timer::DeleteTimer(uint32_t tId, TimerCallbackInfo* callbackInfo)
320 {
321 std::lock_guard<std::mutex> lock(timeLock);
322 // callback maybe contain ClearTimer, so we need check to avoid use-after-free and double-free of callbackInfo
323 if (timerTable.find(tId) != timerTable.end()) {
324 timerTable.erase(tId);
325 Helper::CloseHelp::DeletePointer(callbackInfo, false);
326 }
327 }
328 } // namespace Commonlibrary::JsSysModule
329