Skip to main content

atmos/kernel/
watchdog.rs

1// watchdog.rs - アプリ描画ウォッチドッグ(Phase 3)
2//
3// アプリの draw() はメインループ(Core 0)で同期実行されるため、暴走・ブロックすると
4// UI 全体が固まる。本モジュールは draw の実行時間を計測し、
5//   1. タイマ IRQ から「描画に長時間張り付いている」状態を検出してログ(ハング検知)
6//   2. 規定回数以上「遅い/返らない」アプリを WM が描画スキップ対象にする
7// ことで、1つのアプリの不調が OS 全体を巻き込むのを緩和する。
8//
9// 注意: 真の無限ループからの完全復帰には EL0 サンドボックス(Phase 4)が必要。
10// 本モジュールは検知と「次フレーム以降の隔離」を担う。
11#![allow(dead_code)]
12
13use alloc::string::String;
14use alloc::vec::Vec;
15use spin::Mutex;
16
17/// この時間(ミリ秒)を超えて draw が返らなければ「遅延」とみなす(ログのみ)
18/// 注: QEMU TCG ではブラウザ等のページレイアウトで容易に数百 ms かかるため、
19/// 遅いだけで完走する draw は罰しない。
20const DRAW_BUDGET_MS: u64 = 2000;
21/// IRQ 検出のニアハング(予算の2倍張り付き)がこの回数連続すると Not Responding 扱い
22const OFFENSE_LIMIT: u32 = 3;
23
24struct DrawWatch {
25    /// 現在 draw 実行中のアプリ名(None = 非実行)
26    active_app: Option<String>,
27    /// draw 開始時刻(ms)
28    start_ms: u64,
29    /// タイマ IRQ がハングを既に通報済みか(多重ログ防止)
30    hang_reported: bool,
31    /// アプリ名ごとの累積違反回数
32    offenders: Vec<(String, u32)>,
33    /// Not Responding 確定済みアプリ名
34    not_responding: Vec<String>,
35}
36
37static WATCH: Mutex<DrawWatch> = Mutex::new(DrawWatch {
38    active_app: None,
39    start_ms: 0,
40    hang_reported: false,
41    offenders: Vec::new(),
42    not_responding: Vec::new(),
43});
44
45/// draw 開始を記録する
46pub fn enter(app_name: &str) {
47    let now = crate::kernel::timer::get_system_time_ms();
48    let mut w = WATCH.lock();
49    w.active_app = Some(String::from(app_name));
50    w.start_ms = now;
51    w.hang_reported = false;
52}
53
54/// draw 終了を記録する。
55/// - 予算超過でも完走した draw は「遅い」だけでありログに留める(罰しない)。
56/// - IRQ 側がニアハング(予算2倍張り付き)を検出していた場合のみ違反としてカウントし、
57///   規定回数連続で Not Responding 化する。予算内で完走したら違反カウントをリセット。
58pub fn exit() {
59    let now = crate::kernel::timer::get_system_time_ms();
60    let mut w = WATCH.lock();
61    let elapsed = now.wrapping_sub(w.start_ms);
62    let was_near_hang = w.hang_reported;
63    if let Some(name) = w.active_app.take() {
64        if was_near_hang {
65            // IRQ がニアハングを検出していた: 違反カウントを加算
66            let mut found = false;
67            let mut hit_limit = false;
68            for (n, c) in w.offenders.iter_mut() {
69                if *n == name {
70                    *c += 1;
71                    if *c >= OFFENSE_LIMIT {
72                        hit_limit = true;
73                    }
74                    found = true;
75                    break;
76                }
77            }
78            if !found {
79                w.offenders.push((name.clone(), 1));
80            }
81            crate::println!(
82                "[watchdog] app '{}' near-hang draw took {} ms",
83                name,
84                elapsed
85            );
86            if hit_limit && !w.not_responding.iter().any(|n| n == &name) {
87                w.not_responding.push(name.clone());
88                crate::println!(
89                    "[watchdog] app '{}' marked NOT RESPONDING — its draw will be skipped",
90                    name
91                );
92            }
93        } else if elapsed > DRAW_BUDGET_MS {
94            // 遅いが完走: ログのみ(QEMU TCG のブラウザレイアウト等は正常でも遅い)
95            crate::println!(
96                "[watchdog] app '{}' slow draw: {} ms (budget {} ms)",
97                name,
98                elapsed,
99                DRAW_BUDGET_MS
100            );
101        } else {
102            // 予算内で完走: 連続違反をリセット
103            w.offenders.retain(|(n, _)| n != &name);
104        }
105    }
106}
107
108/// タイマ IRQ から呼ばれ、描画が予算を大きく超えて張り付いていないか確認する。
109/// 戻り値: ハングを新規検出したらアプリ名を返す(呼び出し側でログ済み扱い)。
110pub fn check_from_irq() {
111    // ロックを取れない場合(描画スレッド自身がロック中など)は即諦める(IRQ をブロックしない)
112    if let Some(mut w) = WATCH.try_lock() {
113        if w.hang_reported {
114            return;
115        }
116        if let Some(name) = w.active_app.clone() {
117            let now = crate::kernel::timer::get_system_time_ms();
118            let elapsed = now.wrapping_sub(w.start_ms);
119            if elapsed > DRAW_BUDGET_MS * 2 {
120                w.hang_reported = true;
121                crate::println!(
122                    "[watchdog] HANG suspected: app '{}' has been drawing for {} ms",
123                    name,
124                    elapsed
125                );
126            }
127        }
128    }
129}
130
131/// 指定アプリが Not Responding 確定済みか
132pub fn is_not_responding(app_name: &str) -> bool {
133    let w = WATCH.lock();
134    w.not_responding.iter().any(|n| n == app_name)
135}
136
137/// アプリの復帰(ユーザが閉じた等)時に違反状態をリセットする
138pub fn clear(app_name: &str) {
139    let mut w = WATCH.lock();
140    w.offenders.retain(|(n, _)| n != app_name);
141    w.not_responding.retain(|n| n != app_name);
142}