Skip to main content

atmos/os_lib/web_engine/
cache.rs

1// web_engine/cache.rs - HTTP disk cache (SylFS /cache/browser/<host>/<path_hash>)
2
3use alloc::string::String;
4use alloc::vec::Vec;
5
6/// URL を SylFS のキャッシュパスへ変換する
7pub(super) fn cache_path_for(host: &str, path: &str) -> String {
8    // 名前の組み立ては純粋モジュールへ切り出した(試験あり)。
9    super::cache_path::cache_path_for(host, path)
10}
11
12/// ネットワーク取得済みバイト列をキャッシュに書き込む
13pub(super) fn write_cache(host: &str, path: &str, data: &[u8]) {
14    // 【2026-07-31】以前あったサイズ上限(約 85KB)は撤去した。**もう実態と合っていない**。
15    // SylFS はエクステント方式へ移行済みで、1 ファイルの上限は
16    // 実質ボリュームサイズまで拡大している(`kernel/fs/mod.rs` の
17    // `MAX_INLINE_EXTENTS` + オーバーフローセクタ)。
18    //
19    // 古い上限が残っていたため、Font Awesome の woff2(101088 バイト)や
20    // Noto Sans JP(5.3MB)が**キャッシュされず毎回ダウンロードし直し**に
21    // なっていた。非同期取得は完了しているのに、次のレイアウトで
22    // キャッシュ命中しないので永久に「取得中」のままだった。
23    // 【2026-08-25】**空データは保存しない**。
24    //
25    // 一度 0 バイトを書くと、以後は毎回キャッシュ命中して
26    // **恒久的に空が返り続ける**(フォントなら永久に豆腐、
27    // CSS なら永久に素の HTML)。ディスクキャッシュは
28    // 再起動をまたぐので、自力では回復しない。
29    //
30    // 呼び出しは 6 箇所あり、各所で番人を書くと必ずどこかが漏れる。
31    // **入口で一度だけ守る**(実際 `font_queue` の 1 箇所は
32    // `!bytes.is_empty()` を持っていたが、まとめ取得側は持っていなかった)。
33    if data.is_empty() {
34        crate::warn!("[CACHE] 空データは保存しない host={} path={}", host, path);
35        return;
36    }
37    let cp = cache_path_for(host, path);
38    // 【2026-08-05】取得を 4 並列にしてから、複数ワーカーが同時にここへ入り
39    // ビットマップを壊していた(`kernel::fs::FS_LOCK` の説明を参照)。
40    // 保存と読み返しの間も手放さない(間に別スレッドが割り込むと
41    // 「保存したのに読めない」が再発する)。
42    let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
43    let fs = crate::kernel::fs::get_fs();
44    // 保存結果を握り潰さない。失敗したら次回また取得しに行くので、
45    // なぜキャッシュが効かないのか分かるようにしておく。
46    let _t = super::perf::start();
47    let saved = fs.save_file(&cp, data, "browser_cache");
48    super::perf::add(super::perf::Slot::CacheWrite, _t);
49    // 【2026-08-03】保存は成功を返すのに、次のレイアウトで読み出すと
50    // 見つからない事象を追う(フォント 8 本取得成功・キャッシュ命中 1 件)。
51    // **不整合なときだけ**記録する(成功時は黙る。L-3)。
52    if saved.is_ok() {
53        match fs.read_file(&cp) {
54            Some((_m, d)) if d.len() == data.len() => {}
55            Some((_m, d)) => {
56                crate::warn!(
57                    "[CACHE] 保存直後の読み返しでサイズが不一致 key={} 書込={} 読出={}",
58                    cp,
59                    data.len(),
60                    d.len()
61                );
62            }
63            None => {
64                crate::warn!(
65                    "[CACHE] 保存は成功したのに読み返せない key={} bytes={}",
66                    cp,
67                    data.len()
68                );
69            }
70        }
71    }
72    if let Err(e) = saved {
73        crate::warn!(
74            "[CACHE] 保存に失敗 host={} path={} bytes={} err={}",
75            host,
76            path,
77            data.len(),
78            e
79        );
80    }
81}
82
83/// キャッシュからバイト列を読み込む。存在しなければ None
84pub(super) fn read_cache(host: &str, path: &str) -> Option<Vec<u8>> {
85    let cp = cache_path_for(host, path);
86    // 【2026-08-05】取得を 4 並列にしてから、複数ワーカーが同時にここへ入り
87    // ビットマップを壊していた(`kernel::fs::FS_LOCK` の説明を参照)。
88    // 保存と読み返しの間も手放さない(間に別スレッドが割り込むと
89    // 「保存したのに読めない」が再発する)。
90    let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
91    let fs = crate::kernel::fs::get_fs();
92    // 【2026-07-29】空のキャッシュエントリは「取得済み」として扱わない。
93    // 過去に 0 バイトを書き込んでしまった場合、それを返し続けると
94    // 恒久的に空の CSS/JS が使われる(再起動をまたいで残るため自己回復しない)。
95    // 空はミス扱いにして再取得させる。書き込み側でも空は保存しない。
96    let _t = super::perf::start();
97    let got = fs.read_file(&cp).map(|(_meta, data)| data);
98    super::perf::add(super::perf::Slot::CacheRead, _t);
99    got.filter(|d| !d.is_empty())
100}
101
102/// /cache/browser/ 以下の全キャッシュを削除する
103pub fn clear_browser_cache() {
104    // 一覧取得と削除の間に別スレッドが書き込むと、消したはずの枠へ
105    // 中途半端な状態が残る。操作全体でロックを保持する。
106    let _fs_guard = crate::kernel::fs::FS_LOCK.lock();
107    let fs = crate::kernel::fs::get_fs();
108    let files = fs.list_dir("/cache/browser/");
109    for meta in files {
110        let name = meta.get_filename();
111        if name.starts_with("/cache/browser/") {
112            let _ = fs.delete_file(name);
113        }
114    }
115    crate::info!("[SYS] BROWSER: cache cleared");
116}