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