• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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