1 /*
2 * Copyright (c) 2023 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 "core/common/watch_dog.h"
17
18 #include <cerrno>
19 #include <csignal>
20 #include <pthread.h>
21 #include <shared_mutex>
22
23 #include "base/log/event_report.h"
24 #include "base/log/log.h"
25 #include "base/thread/background_task_executor.h"
26 #include "base/utils/utils.h"
27 #include "bridge/common/utils/engine_helper.h"
28 #include "core/common/ace_application_info.h"
29 #include "core/common/ace_engine.h"
30 #include "core/common/task_runner_adapter.h"
31 #include "core/common/task_runner_adapter_factory.h"
32
33 namespace OHOS::Ace {
34 namespace {
35
36 constexpr int32_t NORMAL_CHECK_PERIOD = 3;
37 constexpr int32_t WARNING_CHECK_PERIOD = 2;
38 constexpr int32_t FREEZE_CHECK_PERIOD = 1;
39 constexpr char JS_THREAD_NAME[] = "JS";
40 constexpr char UI_THREAD_NAME[] = "UI";
41 constexpr char UNKNOWN_THREAD_NAME[] = "unknown thread";
42 constexpr uint64_t ANR_INPUT_FREEZE_TIME = 5000;
43 constexpr int32_t IMMEDIATELY_PERIOD = 0;
44 constexpr int32_t ANR_DIALOG_BLOCK_TIME = 20;
45
46 enum class State { NORMAL, WARNING, FREEZE };
47
48 using Task = std::function<void()>;
49 RefPtr<TaskRunnerAdapter> g_anrThread;
50
PostTaskToTaskRunner(Task && task,uint32_t delayTime,const std::string & name)51 bool PostTaskToTaskRunner(Task&& task, uint32_t delayTime, const std::string& name)
52 {
53 if (!g_anrThread || !task) {
54 return false;
55 }
56
57 if (delayTime > 0) {
58 g_anrThread->PostDelayedTask(std::move(task), delayTime, name);
59 } else {
60 g_anrThread->PostTask(std::move(task), name);
61 }
62 return true;
63 }
64
65 #if defined(OHOS_PLATFORM) || defined(ANDROID_PLATFORM)
66 constexpr int32_t GC_CHECK_PERIOD = 1;
67
InitializeGcTrigger()68 void InitializeGcTrigger()
69 {
70 }
71 #endif // #if defined(OHOS_PLATFORM) || defined(ANDROID_PLATFORM)
72
73 } // namespace
74
75 class ThreadWatcher final : public Referenced {
76 public:
77 ThreadWatcher(int32_t instanceId, TaskExecutor::TaskType type, bool useUIAsJSThread = false);
78 ~ThreadWatcher() override;
79
80 void SetTaskExecutor(const RefPtr<TaskExecutor>& taskExecutor);
81
82 void BuriedBomb(uint64_t bombId);
83 void DefusingBomb();
84
85 private:
86 void InitThreadName();
87 void CheckAndResetIfNeeded();
88 bool IsThreadStuck();
89 void HiviewReport() const;
90 void RawReport(RawEventType type) const;
91 void PostCheckTask();
92 void TagIncrease();
93 void Check();
94 void ShowDialog() const;
95 void DefusingTopBomb();
96 void DetonatedBomb();
97
98 mutable std::shared_mutex mutex_;
99 int32_t instanceId_ = 0;
100 TaskExecutor::TaskType type_;
101 std::string threadName_;
102 int32_t loopTime_ = 0;
103 int32_t threadTag_ = 0;
104 int32_t freezeCount_ = 0;
105 State state_ = State::NORMAL;
106 WeakPtr<TaskExecutor> taskExecutor_;
107 std::queue<uint64_t> inputTaskIds_;
108 bool canShowDialog_ = true;
109 int32_t showDialogCount_ = 0;
110 bool useUIAsJSThread_ = false;
111 };
112
ThreadWatcher(int32_t instanceId,TaskExecutor::TaskType type,bool useUIAsJSThread)113 ThreadWatcher::ThreadWatcher(int32_t instanceId, TaskExecutor::TaskType type, bool useUIAsJSThread)
114 : instanceId_(instanceId), type_(type), useUIAsJSThread_(useUIAsJSThread)
115 {
116 InitThreadName();
117 PostTaskToTaskRunner(
118 [weak = Referenced::WeakClaim(this)]() {
119 auto sp = weak.Upgrade();
120 if (sp) {
121 sp->Check();
122 }
123 },
124 NORMAL_CHECK_PERIOD, "ArkUIWatchDogCheck");
125 }
126
~ThreadWatcher()127 ThreadWatcher::~ThreadWatcher() {}
128
SetTaskExecutor(const RefPtr<TaskExecutor> & taskExecutor)129 void ThreadWatcher::SetTaskExecutor(const RefPtr<TaskExecutor>& taskExecutor)
130 {
131 taskExecutor_ = taskExecutor;
132 }
133
BuriedBomb(uint64_t bombId)134 void ThreadWatcher::BuriedBomb(uint64_t bombId)
135 {
136 std::unique_lock<std::shared_mutex> lock(mutex_);
137 inputTaskIds_.emplace(bombId);
138 }
139
DefusingBomb()140 void ThreadWatcher::DefusingBomb()
141 {
142 auto taskExecutor = taskExecutor_.Upgrade();
143 if (taskExecutor) {
144 taskExecutor->PostTask(
145 [weak = Referenced::WeakClaim(this)]() {
146 auto sp = weak.Upgrade();
147 if (sp) {
148 sp->DefusingTopBomb();
149 }
150 },
151 type_, "ArkUIWatchDogDefusingTopBomb");
152 }
153 }
154
DefusingTopBomb()155 void ThreadWatcher::DefusingTopBomb()
156 {
157 std::unique_lock<std::shared_mutex> lock(mutex_);
158 if (inputTaskIds_.empty()) {
159 return;
160 }
161
162 inputTaskIds_.pop();
163 }
164
InitThreadName()165 void ThreadWatcher::InitThreadName()
166 {
167 switch (type_) {
168 case TaskExecutor::TaskType::JS:
169 threadName_ = JS_THREAD_NAME;
170 break;
171 case TaskExecutor::TaskType::UI:
172 threadName_ = UI_THREAD_NAME;
173 break;
174 default:
175 threadName_ = UNKNOWN_THREAD_NAME;
176 break;
177 }
178 }
179
DetonatedBomb()180 void ThreadWatcher::DetonatedBomb()
181 {
182 std::shared_lock<std::shared_mutex> lock(mutex_);
183 if (inputTaskIds_.empty()) {
184 return;
185 }
186 uint64_t currentTime = GetMilliseconds();
187 uint64_t bombId = inputTaskIds_.front();
188 if (currentTime - bombId > ANR_INPUT_FREEZE_TIME) {
189 LOGE("Detonated the Bomb, which bombId is %{public}s and currentTime is %{public}s",
190 std::to_string(bombId).c_str(), std::to_string(currentTime).c_str());
191 if (canShowDialog_) {
192 ShowDialog();
193 canShowDialog_ = false;
194 showDialogCount_ = 0;
195 } else {
196 LOGE("Can not show dialog when detonated the Bomb.");
197 }
198
199 std::queue<uint64_t> empty;
200 std::swap(empty, inputTaskIds_);
201 }
202 }
203
Check()204 void ThreadWatcher::Check()
205 {
206 int32_t period = NORMAL_CHECK_PERIOD;
207 if (!IsThreadStuck()) {
208 if (state_ == State::FREEZE) {
209 RawReport(RawEventType::RECOVER);
210 }
211 freezeCount_ = 0;
212 state_ = State::NORMAL;
213 canShowDialog_ = true;
214 showDialogCount_ = 0;
215 } else {
216 if (state_ == State::NORMAL) {
217 HiviewReport();
218 RawReport(RawEventType::WARNING);
219 state_ = State::WARNING;
220 period = WARNING_CHECK_PERIOD;
221 } else if (state_ == State::WARNING) {
222 RawReport(RawEventType::FREEZE);
223 state_ = State::FREEZE;
224 period = FREEZE_CHECK_PERIOD;
225 DetonatedBomb();
226 } else {
227 if (!canShowDialog_) {
228 showDialogCount_++;
229 if (showDialogCount_ >= ANR_DIALOG_BLOCK_TIME) {
230 canShowDialog_ = true;
231 showDialogCount_ = 0;
232 }
233 }
234
235 if (++freezeCount_ >= 5) {
236 RawReport(RawEventType::FREEZE);
237 freezeCount_ = 0;
238 }
239 period = FREEZE_CHECK_PERIOD;
240 DetonatedBomb();
241 }
242 }
243
244 PostTaskToTaskRunner(
245 [weak = Referenced::WeakClaim(this)]() {
246 auto sp = weak.Upgrade();
247 if (sp) {
248 sp->Check();
249 }
250 },
251 period, "ArkUIWatchDogCheck");
252 }
253
CheckAndResetIfNeeded()254 void ThreadWatcher::CheckAndResetIfNeeded()
255 {
256 {
257 std::shared_lock<std::shared_mutex> lock(mutex_);
258 if (loopTime_ < INT32_MAX) {
259 return;
260 }
261 }
262
263 std::unique_lock<std::shared_mutex> lock(mutex_);
264 loopTime_ = 0;
265 threadTag_ = 0;
266 }
267
IsThreadStuck()268 bool ThreadWatcher::IsThreadStuck()
269 {
270 bool res = false;
271 {
272 std::shared_lock<std::shared_mutex> lock(mutex_);
273 if (threadTag_ != loopTime_) {
274 std::string abilityName;
275 if (AceEngine::Get().GetContainer(instanceId_) != nullptr) {
276 abilityName = AceEngine::Get().GetContainer(instanceId_)->GetHostClassName();
277 }
278 LOGE("thread stuck, ability: %{public}s, instanceId: %{public}d, thread: %{public}s, looptime: %{public}d, "
279 "checktime: %{public}d",
280 abilityName.c_str(), instanceId_, threadName_.c_str(), loopTime_, threadTag_);
281 // or threadTag_ != loopTime_ will always be true
282 {
283 std::unique_lock<std::shared_mutex> lock(mutex_);
284 threadTag_ = loopTime_;
285 }
286 res = true;
287 }
288 }
289 CheckAndResetIfNeeded();
290 PostCheckTask();
291 return res;
292 }
293
HiviewReport() const294 void ThreadWatcher::HiviewReport() const
295 {
296 if (type_ == TaskExecutor::TaskType::JS) {
297 EventReport::SendJsException(JsExcepType::JS_THREAD_STUCK);
298 } else if (type_ == TaskExecutor::TaskType::UI) {
299 EventReport::SendRenderException(RenderExcepType::UI_THREAD_STUCK);
300 }
301 }
302
RawReport(RawEventType type) const303 void ThreadWatcher::RawReport(RawEventType type) const
304 {
305 std::string message;
306 if (type == RawEventType::FREEZE &&
307 (type_ == TaskExecutor::TaskType::JS || (useUIAsJSThread_ && (type_ == TaskExecutor::TaskType::UI)))) {
308 auto engine = EngineHelper::GetEngine(instanceId_);
309 message = engine ? engine->GetStacktraceMessage() : "";
310 }
311 int32_t tid = 0;
312 auto taskExecutor = taskExecutor_.Upgrade();
313 if (taskExecutor) {
314 tid = taskExecutor->GetTid(type_);
315 }
316 std::string threadInfo = "Blocked thread id = " + std::to_string(tid) + "\n";
317 threadInfo += "JSVM instance id = " + std::to_string(instanceId_) + "\n";
318 message = threadInfo + message;
319 EventReport::ANRRawReport(type, AceApplicationInfo::GetInstance().GetUid(),
320 AceApplicationInfo::GetInstance().GetPackageName(), AceApplicationInfo::GetInstance().GetProcessName(),
321 message);
322 }
323
ShowDialog() const324 void ThreadWatcher::ShowDialog() const
325 {
326 EventReport::ANRShowDialog(AceApplicationInfo::GetInstance().GetUid(),
327 AceApplicationInfo::GetInstance().GetPackageName(), AceApplicationInfo::GetInstance().GetProcessName());
328 }
329
PostCheckTask()330 void ThreadWatcher::PostCheckTask()
331 {
332 auto taskExecutor = taskExecutor_.Upgrade();
333 if (taskExecutor) {
334 // post task to specified thread to check it
335 taskExecutor->PostTask(
336 [weak = Referenced::WeakClaim(this)]() {
337 auto sp = weak.Upgrade();
338 if (sp) {
339 sp->TagIncrease();
340 }
341 },
342 type_, "ArkUIWatchDogTagIncrease");
343 std::unique_lock<std::shared_mutex> lock(mutex_);
344 ++loopTime_;
345 } else {
346 LOGW("task executor with instanceId %{public}d invalid when check %{public}s thread whether stuck or not",
347 instanceId_, threadName_.c_str());
348 }
349 }
350
TagIncrease()351 void ThreadWatcher::TagIncrease()
352 {
353 std::unique_lock<std::shared_mutex> lock(mutex_);
354 ++threadTag_;
355 }
356
WatchDog()357 WatchDog::WatchDog()
358 {
359 if (!g_anrThread) {
360 g_anrThread = TaskRunnerAdapterFactory::Create(false, "anr");
361 }
362 #if defined(OHOS_PLATFORM) || defined(ANDROID_PLATFORM)
363 PostTaskToTaskRunner(InitializeGcTrigger, GC_CHECK_PERIOD, "ArkUIWatchDogInitGcTrigger");
364 #endif
365 }
366
~WatchDog()367 WatchDog::~WatchDog()
368 {
369 g_anrThread.Reset();
370 }
371
Register(int32_t instanceId,const RefPtr<TaskExecutor> & taskExecutor,bool useUIAsJSThread)372 void WatchDog::Register(int32_t instanceId, const RefPtr<TaskExecutor>& taskExecutor, bool useUIAsJSThread)
373 {
374 Watchers watchers = {
375 .jsWatcher = AceType::MakeRefPtr<ThreadWatcher>(instanceId, TaskExecutor::TaskType::JS),
376 .uiWatcher = AceType::MakeRefPtr<ThreadWatcher>(instanceId, TaskExecutor::TaskType::UI, useUIAsJSThread),
377 };
378 watchers.uiWatcher->SetTaskExecutor(taskExecutor);
379 if (!useUIAsJSThread) {
380 watchers.jsWatcher->SetTaskExecutor(taskExecutor);
381 } else {
382 watchers.jsWatcher = nullptr;
383 }
384 const auto resExecutor = watchMap_.try_emplace(instanceId, watchers);
385 if (!resExecutor.second) {
386 LOGW("Duplicate instance id: %{public}d when register to watch dog", instanceId);
387 }
388 }
389
Unregister(int32_t instanceId)390 void WatchDog::Unregister(int32_t instanceId)
391 {
392 int32_t num = static_cast<int32_t>(watchMap_.erase(instanceId));
393 if (num == 0) {
394 LOGW("Unregister from watch dog failed with instanceID %{public}d", instanceId);
395 }
396 }
397
BuriedBomb(int32_t instanceId,uint64_t bombId)398 void WatchDog::BuriedBomb(int32_t instanceId, uint64_t bombId)
399 {
400 auto iter = watchMap_.find(instanceId);
401 if (iter == watchMap_.end()) {
402 return;
403 }
404
405 Watchers watchers = iter->second;
406 PostTaskToTaskRunner(
407 [watchers, bombId]() {
408 if (watchers.jsWatcher) {
409 watchers.jsWatcher->BuriedBomb(bombId);
410 }
411
412 if (watchers.uiWatcher) {
413 watchers.uiWatcher->BuriedBomb(bombId);
414 }
415 },
416 IMMEDIATELY_PERIOD, "ArkUIWatchDogBuriedBomb");
417 }
418
DefusingBomb(int32_t instanceId)419 void WatchDog::DefusingBomb(int32_t instanceId)
420 {
421 auto iter = watchMap_.find(instanceId);
422 if (iter == watchMap_.end()) {
423 return;
424 }
425
426 Watchers watchers = iter->second;
427 PostTaskToTaskRunner(
428 [watchers]() {
429 if (watchers.jsWatcher) {
430 watchers.jsWatcher->DefusingBomb();
431 }
432
433 if (watchers.uiWatcher) {
434 watchers.uiWatcher->DefusingBomb();
435 }
436 },
437 IMMEDIATELY_PERIOD, "ArkUIWatchDogDefusingBomb");
438 }
439
440 } // namespace OHOS::Ace
441