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