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 "flutter/fml/thread.h"
24
25 #include "base/log/event_report.h"
26 #include "base/log/log.h"
27 #include "base/thread/background_task_executor.h"
28 #include "base/utils/utils.h"
29 #include "bridge/common/utils/engine_helper.h"
30 #include "core/common/ace_application_info.h"
31 #include "core/common/ace_engine.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 std::unique_ptr<fml::Thread> g_anrThread;
50
PostTaskToTaskRunner(Task && task,uint32_t delayTime)51 bool PostTaskToTaskRunner(Task&& task, uint32_t delayTime)
52 {
53 if (!g_anrThread || !task) {
54 return false;
55 }
56
57 auto anrTaskRunner = g_anrThread->GetTaskRunner();
58 if (delayTime > 0) {
59 anrTaskRunner->PostDelayedTask(std::move(task), fml::TimeDelta::FromSeconds(delayTime));
60 } else {
61 anrTaskRunner->PostTask(std::move(task));
62 }
63 return true;
64 }
65
66 #if defined(OHOS_PLATFORM) || defined(ANDROID_PLATFORM)
67 constexpr int32_t GC_CHECK_PERIOD = 1;
68
InitializeGcTrigger()69 void InitializeGcTrigger()
70 {
71 }
72 #endif // #if defined(OHOS_PLATFORM) || defined(ANDROID_PLATFORM)
73
74 } // namespace
75
76 class ThreadWatcher final : public Referenced {
77 public:
78 ThreadWatcher(int32_t instanceId, TaskExecutor::TaskType type, bool useUIAsJSThread = false);
79 ~ThreadWatcher() override;
80
81 void SetTaskExecutor(const RefPtr<TaskExecutor>& taskExecutor);
82
83 void BuriedBomb(uint64_t bombId);
84 void DefusingBomb();
85
86 private:
87 void InitThreadName();
88 void CheckAndResetIfNeeded();
89 bool IsThreadStuck();
90 void HiviewReport() const;
91 void RawReport(RawEventType type) const;
92 void PostCheckTask();
93 void TagIncrease();
94 void Check();
95 void ShowDialog() const;
96 void DefusingTopBomb();
97 void DetonatedBomb();
98
99 mutable std::shared_mutex mutex_;
100 int32_t instanceId_ = 0;
101 TaskExecutor::TaskType type_;
102 std::string threadName_;
103 int32_t loopTime_ = 0;
104 int32_t threadTag_ = 0;
105 int32_t freezeCount_ = 0;
106 State state_ = State::NORMAL;
107 WeakPtr<TaskExecutor> taskExecutor_;
108 std::queue<uint64_t> inputTaskIds_;
109 bool canShowDialog_ = true;
110 int32_t showDialogCount_ = 0;
111 bool useUIAsJSThread_ = false;
112 };
113
ThreadWatcher(int32_t instanceId,TaskExecutor::TaskType type,bool useUIAsJSThread)114 ThreadWatcher::ThreadWatcher(int32_t instanceId, TaskExecutor::TaskType type, bool useUIAsJSThread)
115 : instanceId_(instanceId), type_(type), useUIAsJSThread_(useUIAsJSThread)
116 {
117 InitThreadName();
118 PostTaskToTaskRunner(
119 [weak = Referenced::WeakClaim(this)]() {
120 auto sp = weak.Upgrade();
121 if (sp) {
122 sp->Check();
123 }
124 },
125 NORMAL_CHECK_PERIOD);
126 }
127
~ThreadWatcher()128 ThreadWatcher::~ThreadWatcher() {}
129
SetTaskExecutor(const RefPtr<TaskExecutor> & taskExecutor)130 void ThreadWatcher::SetTaskExecutor(const RefPtr<TaskExecutor>& taskExecutor)
131 {
132 taskExecutor_ = taskExecutor;
133 }
134
BuriedBomb(uint64_t bombId)135 void ThreadWatcher::BuriedBomb(uint64_t bombId)
136 {
137 std::unique_lock<std::shared_mutex> lock(mutex_);
138 inputTaskIds_.emplace(bombId);
139 }
140
DefusingBomb()141 void ThreadWatcher::DefusingBomb()
142 {
143 auto taskExecutor = taskExecutor_.Upgrade();
144 if (taskExecutor) {
145 taskExecutor->PostTask(
146 [weak = Referenced::WeakClaim(this)]() {
147 auto sp = weak.Upgrade();
148 if (sp) {
149 sp->DefusingTopBomb();
150 }
151 },
152 type_);
153 }
154 }
155
DefusingTopBomb()156 void ThreadWatcher::DefusingTopBomb()
157 {
158 std::unique_lock<std::shared_mutex> lock(mutex_);
159 if (inputTaskIds_.empty()) {
160 return;
161 }
162
163 inputTaskIds_.pop();
164 }
165
InitThreadName()166 void ThreadWatcher::InitThreadName()
167 {
168 switch (type_) {
169 case TaskExecutor::TaskType::JS:
170 threadName_ = JS_THREAD_NAME;
171 break;
172 case TaskExecutor::TaskType::UI:
173 threadName_ = UI_THREAD_NAME;
174 break;
175 default:
176 threadName_ = UNKNOWN_THREAD_NAME;
177 break;
178 }
179 }
180
DetonatedBomb()181 void ThreadWatcher::DetonatedBomb()
182 {
183 std::shared_lock<std::shared_mutex> lock(mutex_);
184 if (inputTaskIds_.empty()) {
185 return;
186 }
187
188 uint64_t currentTime = GetMilliseconds();
189 uint64_t bombId = inputTaskIds_.front();
190
191 if (currentTime - bombId > ANR_INPUT_FREEZE_TIME) {
192 LOGE("Detonated the Bomb, which bombId is %{public}s and currentTime is %{public}s",
193 std::to_string(bombId).c_str(), std::to_string(currentTime).c_str());
194 if (canShowDialog_) {
195 ShowDialog();
196 canShowDialog_ = false;
197 showDialogCount_ = 0;
198 } else {
199 LOGE("Can not show dialog when detonated the Bomb.");
200 }
201
202 std::queue<uint64_t> empty;
203 std::swap(empty, inputTaskIds_);
204 }
205 }
206
Check()207 void ThreadWatcher::Check()
208 {
209 int32_t period = NORMAL_CHECK_PERIOD;
210 if (!IsThreadStuck()) {
211 if (state_ == State::FREEZE) {
212 RawReport(RawEventType::RECOVER);
213 }
214 freezeCount_ = 0;
215 state_ = State::NORMAL;
216 canShowDialog_ = true;
217 showDialogCount_ = 0;
218 } else {
219 if (state_ == State::NORMAL) {
220 HiviewReport();
221 RawReport(RawEventType::WARNING);
222 state_ = State::WARNING;
223 period = WARNING_CHECK_PERIOD;
224 } else if (state_ == State::WARNING) {
225 RawReport(RawEventType::FREEZE);
226 state_ = State::FREEZE;
227 period = FREEZE_CHECK_PERIOD;
228 DetonatedBomb();
229 } else {
230 if (!canShowDialog_) {
231 showDialogCount_++;
232 if (showDialogCount_ >= ANR_DIALOG_BLOCK_TIME) {
233 canShowDialog_ = true;
234 showDialogCount_ = 0;
235 }
236 }
237
238 if (++freezeCount_ >= 5) {
239 RawReport(RawEventType::FREEZE);
240 freezeCount_ = 0;
241 }
242 period = FREEZE_CHECK_PERIOD;
243 DetonatedBomb();
244 }
245 }
246
247 PostTaskToTaskRunner(
248 [weak = Referenced::WeakClaim(this)]() {
249 auto sp = weak.Upgrade();
250 if (sp) {
251 sp->Check();
252 }
253 },
254 period);
255 }
256
CheckAndResetIfNeeded()257 void ThreadWatcher::CheckAndResetIfNeeded()
258 {
259 {
260 std::shared_lock<std::shared_mutex> lock(mutex_);
261 if (loopTime_ < INT32_MAX) {
262 return;
263 }
264 }
265
266 std::unique_lock<std::shared_mutex> lock(mutex_);
267 loopTime_ = 0;
268 threadTag_ = 0;
269 }
270
IsThreadStuck()271 bool ThreadWatcher::IsThreadStuck()
272 {
273 bool res = false;
274 {
275 std::shared_lock<std::shared_mutex> lock(mutex_);
276 if (threadTag_ != loopTime_) {
277 std::string abilityName;
278 if (AceEngine::Get().GetContainer(instanceId_) != nullptr) {
279 abilityName = AceEngine::Get().GetContainer(instanceId_)->GetHostClassName();
280 }
281 LOGE("thread stuck, ability: %{public}s, instanceId: %{public}d, thread: %{public}s, looptime: %{public}d, "
282 "checktime: %{public}d",
283 abilityName.c_str(), instanceId_, threadName_.c_str(), loopTime_, threadTag_);
284 // or threadTag_ != loopTime_ will always be true
285 threadTag_ = loopTime_;
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_);
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 LOGD("thread check, instanceId: %{public}d, thread: %{public}s, looptime: %{public}d, "
356 "checktime: %{public}d", instanceId_, threadName_.c_str(), loopTime_, threadTag_);
357 }
358
WatchDog()359 WatchDog::WatchDog()
360 {
361 if (!g_anrThread) {
362 g_anrThread = std::make_unique<fml::Thread>("anr");
363 }
364 #if defined(OHOS_PLATFORM) || defined(ANDROID_PLATFORM)
365 PostTaskToTaskRunner(InitializeGcTrigger, GC_CHECK_PERIOD);
366 #endif
367 }
368
~WatchDog()369 WatchDog::~WatchDog()
370 {
371 g_anrThread.reset();
372 }
373
Register(int32_t instanceId,const RefPtr<TaskExecutor> & taskExecutor,bool useUIAsJSThread)374 void WatchDog::Register(int32_t instanceId, const RefPtr<TaskExecutor>& taskExecutor, bool useUIAsJSThread)
375 {
376 Watchers watchers = {
377 .jsWatcher = AceType::MakeRefPtr<ThreadWatcher>(instanceId, TaskExecutor::TaskType::JS),
378 .uiWatcher = AceType::MakeRefPtr<ThreadWatcher>(instanceId, TaskExecutor::TaskType::UI, useUIAsJSThread),
379 };
380 watchers.uiWatcher->SetTaskExecutor(taskExecutor);
381 if (!useUIAsJSThread) {
382 watchers.jsWatcher->SetTaskExecutor(taskExecutor);
383 } else {
384 watchers.jsWatcher = nullptr;
385 }
386 const auto resExecutor = watchMap_.try_emplace(instanceId, watchers);
387 if (!resExecutor.second) {
388 LOGW("Duplicate instance id: %{public}d when register to watch dog", instanceId);
389 }
390 }
391
Unregister(int32_t instanceId)392 void WatchDog::Unregister(int32_t instanceId)
393 {
394 int32_t num = static_cast<int32_t>(watchMap_.erase(instanceId));
395 if (num == 0) {
396 LOGW("Unregister from watch dog failed with instanceID %{public}d", instanceId);
397 }
398 }
399
BuriedBomb(int32_t instanceId,uint64_t bombId)400 void WatchDog::BuriedBomb(int32_t instanceId, uint64_t bombId)
401 {
402 auto iter = watchMap_.find(instanceId);
403 if (iter == watchMap_.end()) {
404 return;
405 }
406
407 Watchers watchers = iter->second;
408 PostTaskToTaskRunner(
409 [watchers, bombId]() {
410 if (watchers.jsWatcher) {
411 watchers.jsWatcher->BuriedBomb(bombId);
412 }
413
414 if (watchers.uiWatcher) {
415 watchers.uiWatcher->BuriedBomb(bombId);
416 }
417 },
418 IMMEDIATELY_PERIOD);
419 }
420
DefusingBomb(int32_t instanceId)421 void WatchDog::DefusingBomb(int32_t instanceId)
422 {
423 auto iter = watchMap_.find(instanceId);
424 if (iter == watchMap_.end()) {
425 return;
426 }
427
428 Watchers watchers = iter->second;
429 PostTaskToTaskRunner(
430 [watchers]() {
431 if (watchers.jsWatcher) {
432 watchers.jsWatcher->DefusingBomb();
433 }
434
435 if (watchers.uiWatcher) {
436 watchers.uiWatcher->DefusingBomb();
437 }
438 },
439 IMMEDIATELY_PERIOD);
440 }
441
442 } // namespace OHOS::Ace
443