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