atmos/os_lib/web_engine/queue_batch.rs
1//! 取得待ち行列から「まとめ取得する分」を選ぶ判定(純粋モジュール)。
2//!
3//! 仕様は `spec/resource_loading.md`。
4//!
5//! # なぜ純粋関数に切り出すのか
6//!
7//! 【2026-08-05】同一ホストをまとめて 1 接続で取る機能を 5 回配線して
8//! 5 回とも `[NET][STATS] total=3` でページが読めなくなった。
9//!
10//! 原因は取得処理ではなかった(`[KADIAG]` を入れたら、
11//! **まとめ取得の関数は一度も呼ばれていない**のに止まっていた)。
12//!
13//! 兄弟をまとめる処理は待ち行列から項目を**取り除く**。
14//! ところが「取り除いたが条件によっては使わない」経路があると、
15//! その項目はどこにも戻らず**消える**。
16//! 消えたリソースは二度と取得されないので、ページが完成しない。
17//!
18//! そこで **「取り出す前に、取り出してよいかを決める」** 形にし、
19//! その判定をここへ切り出して単体試験で固定する
20//! (`tests/src/test_queue_batch.rs`)。
21
22/// まとめ取得の候補を選ぶための、1 項目ぶんの情報。
23///
24/// 待ち行列の要素そのものではなく**判定に要る分だけ**を持つ。
25/// 文字列を持たないので試験が書きやすい。
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct Candidate {
28 /// ホストが選定対象と同じか。
29 pub same_host: bool,
30 /// 優先度が選定対象と同じか。
31 pub same_priority: bool,
32 /// HTTPS か(HTTP はまとめ取得の対象外)。
33 pub is_https: bool,
34 /// 一度まとめ取得で失敗して「次は個別に取る」印が付いているか。
35 pub no_batch: bool,
36}
37
38/// そもそもまとめ取得を始めてよいか。**取り出す前に**呼ぶ。
39///
40/// ここで `false` なら待ち行列には一切触らない。
41/// 「取り出したのに使わない」経路を作らないための入口。
42///
43/// - `main_in_flight` … メイン HTML を取得中か。
44/// まとめ取得は 1 本のワーカーを長く占有するので、
45/// メインの取得と競合させない。
46/// - `picked_is_https` / `picked_no_batch` … 選んだ項目自身の性質。
47pub fn may_start_batch(
48 main_in_flight: bool,
49 picked_is_https: bool,
50 picked_no_batch: bool,
51) -> bool {
52 !main_in_flight && picked_is_https && !picked_no_batch
53}
54
55/// まとめ取得へ載せる項目の添字を選ぶ。
56///
57/// `may_start_batch` が `true` のときだけ呼ぶこと。
58/// 返した添字の項目は**必ず**まとめ取得へ渡す
59/// (取り出したまま使わない経路を作らない)。
60///
61/// 返す添字は昇順。呼び出し側が後ろから取り除けば添字がずれない。
62///
63/// 計算量: **O(N)**(N は待ち行列の長さ)。
64pub fn select_batch(cands: &[Candidate], max: usize) -> alloc::vec::Vec<usize> {
65 let mut out = alloc::vec::Vec::new();
66 if max == 0 {
67 return out;
68 }
69 for (i, c) in cands.iter().enumerate() {
70 if out.len() >= max {
71 break;
72 }
73 if c.same_host && c.same_priority && c.is_https && !c.no_batch {
74 out.push(i);
75 }
76 }
77 out
78}