# 通知設計書 74-キャッシュファイル拒否デバッグ

## 概要

本ドキュメントは、Juliaのプリコンパイルキャッシュファイルが各種理由で拒否された場合に出力されるデバッグ通知の設計を定義する。

### 本通知の処理概要

パッケージのロード時にプリコンパイル済みキャッシュファイル（`.ji`ファイル）の有効性を検証し、キャッシュが使用不可と判断された場合にその理由をデバッグレベルで記録する通知機能である。約20箇所の異なるチェックポイントで出力される。

**業務上の目的・背景**：Juliaのプリコンパイルシステムは、パッケージのロード時間を短縮するためにキャッシュファイルを利用する。しかし、キャッシュファイルはソースコードの変更、依存関係の変化、ビルド設定の不一致など様々な理由で無効化される。開発者やJuliaのメンテナがプリコンパイルの問題を診断する際に、どの検証ステップでキャッシュが拒否されたかを把握するために本デバッグ通知が存在する。通常のユーザーには表示されず、`JULIA_DEBUG=loading`等で有効化される。

**通知の送信タイミング**：`stale_cachefile()`関数内および`_tryrequire_from_serialized()`関数内で、キャッシュファイルの各種検証チェックが失敗した時点でデバッグメッセージが出力される。

**通知の受信者**：Juliaの内部動作を診断する開発者、パッケージメンテナ、Juliaコア開発者。

**通知内容の概要**：拒否されたキャッシュファイルのパス、対象モジュール名、拒否理由（バージョン不一致、依存関係不足、チェックサム不正など）が通知される。

**期待されるアクション**：キャッシュファイルの再生成（プリコンパイルの再実行）、依存関係の修正、またはビルド設定の確認。

## 通知種別

ログ出力（`@debug`レベル）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（キャッシュ検証時に即座に出力） |
| 優先度 | 低（デバッグレベル、通常は表示されない） |
| リトライ | なし |

### 送信先決定ロジック

`@debug`マクロを通じてJuliaの標準ログシステムに出力される。デフォルトでは表示されず、`JULIA_DEBUG=loading`環境変数の設定またはカスタムロガーの設定により有効化される。

## 通知テンプレート

### メール通知の場合

該当なし（ログ出力のみ）

### 本文テンプレート

主な拒否理由メッセージ（約20種類）：

```
Rejecting cache file {cachefile} for {modkey} because it could not be opened
Rejecting cache file {cachefile} due to it containing an incompatible cache header
Rejecting cache file {cachefile} for {modkey} since it was parsed for a different Julia syntax version
Rejecting cache file {cachefile} for {modkey} since it would require usage of pkgimage
Rejecting cache file {cachefile} for {modkey} since pkgimage {ocachefile} was not found
Rejecting cache file {cachefile} for {modkey} since it is for {id} instead
Rejecting cache file {cachefile} because it was made with a different julia version
Rejecting cache file {cachefile} because module {req_key} is already loaded and incompatible.
Rejecting cache file {cachefile} because dependency {req_key} not found.
Rejecting cache file {cachefile} because it provides the wrong build_id
Rejecting cache file {cachefile} because it is for file {filename} not file {modpath}
Rejecting cache file {cachefile} because uuid mapping for {modkey} has changed
Rejecting cache file {cachefile} because it has an invalid checksum
Rejecting cache file {cachefile} because {ocachefile} has an invalid checksum
Rejecting cache file {cachefile} because preferences hash does not match
Rejecting cache file {path_to_try} because module {modkey} got loaded at a different version than expected.
Rejecting cache file {path_to_try} because required dependency {modkey} with build ID ... is missing from the cache.
Rejecting cache file {path_to_try} because required dependency {modkey} failed to load from cache file for {modcachepath}.
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| 該当なし | - | - | - |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| cachefile | キャッシュファイルのパス | `stale_cachefile()`の引数 | Yes |
| modkey | モジュールの`PkgId` | `stale_cachefile()`の引数 | Yes |
| ocachefile | オブジェクトキャッシュファイルのパス | `ocachefile_from_cachefile()`の戻り値 | No |
| req_key | 必要な依存モジュールの`PkgId` | キャッシュヘッダの`required_modules`から取得 | No |
| build_id | ビルドID（UUID形式） | キャッシュヘッダから取得 | No |
| path_to_try | 試行中のキャッシュファイルパス | `_tryrequire_from_serialized()`のループ変数 | No |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| キャッシュ検証 | ファイルオープン失敗 | IOError/SystemError発生 | loading.jl:4246 |
| キャッシュ検証 | キャッシュヘッダ不正 | チェックサムがゼロ | loading.jl:4252 |
| キャッシュ検証 | 構文バージョン不一致 | syntax_version不一致 | loading.jl:4270 |
| キャッシュ検証 | pkgimage使用不可 | JLOptions().use_pkgimages==0 | loading.jl:4279 |
| キャッシュ検証 | クローンターゲット不一致 | check_clone_targets失敗 | loading.jl:4285 |
| キャッシュ検証 | pkgimageファイル不在 | isfile(ocachefile)==false | loading.jl:4293 |
| キャッシュ検証 | モジュールID不一致 | id.first != modkey | loading.jl:4302 |
| キャッシュ検証 | Juliaバージョン不一致 | Coreモジュールのbuild_id不一致 | loading.jl:4336 |
| キャッシュ検証 | 依存モジュール互換性なし | 既にロード済みの依存が不一致 | loading.jl:4342 |
| キャッシュ検証 | 依存モジュール未検出 | locate_package_load_spec==nothing | loading.jl:4349 |
| キャッシュ検証 | ビルドID不一致 | build_id != req_build_id | loading.jl:4368 |
| キャッシュ検証 | ソースファイルパス不一致 | !samefile(includes[1], modpath) | loading.jl:4383 |
| キャッシュ検証 | UUID マッピング変更 | identify_package結果が不一致 | loading.jl:4392 |
| キャッシュ検証 | チェックサム不正 | isvalid_file_crc==false | loading.jl:4403 |
| キャッシュ検証 | pkgimageチェックサム不正 | isvalid_pkgimage_crc==false | loading.jl:4410 |
| キャッシュ検証 | プリファレンスハッシュ不一致 | prefs_hash != curr_prefs_hash | loading.jl:4418 |
| 依存解決 | モジュールバージョン不一致 | 依存モジュールが異なるバージョンでロード済み | loading.jl:2241 |
| 依存解決 | 依存モジュールキャッシュ不在 | 必要な依存のキャッシュが見つからない | loading.jl:2270 |
| 依存解決 | 依存モジュールロード失敗 | _include_from_serializedの失敗 | loading.jl:2294 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| デバッグレベルが無効 | `@debug`はデフォルトで抑制されている |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[パッケージrequire] --> B[キャッシュファイル検索]
    B --> C[stale_cachefile呼び出し]
    C --> D{ファイルオープン可能?}
    D -->|No| E["@debug Rejecting... could not be opened"]
    D -->|Yes| F{ヘッダ有効?}
    F -->|No| G["@debug Rejecting... incompatible header"]
    F -->|Yes| H{各種検証チェック}
    H -->|失敗| I["@debug Rejecting... 理由別メッセージ"]
    H -->|全て通過| J[キャッシュ有効と判断]
    E --> K[次のキャッシュファイルを試行]
    G --> K
    I --> K
    J --> L[キャッシュからモジュールロード]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| 該当なし | - | ファイルシステムとメモリ上のデータのみ参照 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| 該当なし | - | ログ出力のみ |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| IOError | キャッシュファイルオープン失敗 | デバッグログ出力して次のキャッシュを試行 |
| チェックサム不正 | キャッシュファイル破損 | デバッグログ出力して次のキャッシュを試行 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし（ただし複数のキャッシュファイルを順次試行） |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（パッケージロード時に発生）

## セキュリティ考慮事項

- デバッグメッセージにファイルシステムのパス情報が含まれる
- ビルドIDやUUID情報がログに出力される可能性がある
- デフォルトでは表示されないため、通常の運用では情報漏洩リスクは低い

## 備考

- `stale_cachefile()`関数はキャッシュファイルの「陳腐化チェック」だけでなく、ほぼ全てのファイル検証を担当している（関数名はやや不正確）
- `reasons`引数（`Dict{String,Int}`型）を渡すことで、拒否理由を構造化データとして収集可能（プリコンパイルUIで使用）
- `record_reason()`ヘルパーで拒否理由がカウントされ、ユーザーフレンドリーなエラーメッセージ生成に活用される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

キャッシュファイルの検証に関連する主要なデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | loading.jl | `base/loading.jl` | `PkgId`構造体：パッケージの識別子 |
| 1-2 | loading.jl | `base/loading.jl` | `CacheFlags`構造体：コンパイルオプションフラグ |
| 1-3 | loading.jl | `base/loading.jl` | キャッシュヘッダの構造（modules, includes, requires, required_modules等） |

**読解のコツ**: `stale_cachefile`の引数リストから、キャッシュ検証に必要な情報（`modkey`, `build_id`, `modspec`, `cachefile`）を把握すること。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | loading.jl | `base/loading.jl` | `stale_cachefile()`関数（4235-4427行目）：キャッシュファイル検証の主要関数 |

**主要処理フロー**:
1. **4242-4248行目**: ファイルオープン試行
2. **4250-4254行目**: キャッシュヘッダの有効性チェック
3. **4256行目**: `parse_cache_header()`でヘッダ解析
4. **4260-4267行目**: キャッシュフラグの一致チェック
5. **4269-4296行目**: 構文バージョン、pkgimage関連のチェック
6. **4300-4304行目**: モジュールID一致チェック
7. **4319-4372行目**: 依存モジュールの検証ループ
8. **4376-4399行目**: ソースファイルの陳腐化チェック
9. **4402-4421行目**: チェックサムとプリファレンスハッシュの検証

#### Step 3: 依存解決中のキャッシュ拒否を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | loading.jl | `base/loading.jl` | `_tryrequire_from_serialized()`関数（2225-2304行目付近）：依存解決中のキャッシュ拒否 |

**主要処理フロー**:
- **2241行目**: 依存モジュールが異なるバージョンでロード済みの場合の拒否
- **2270行目**: 必要な依存のキャッシュが見つからない場合の拒否
- **2294行目**: 依存モジュールのキャッシュからのロード失敗時の拒否

### プログラム呼び出し階層図

```
require(pkg) (loading.jl)
    |
    +-- _tryrequire_from_serialized()
    |      |
    |      +-- stale_cachefile(modkey, build_id, modspec, cachefile)
    |      |      |
    |      |      +-- open(cachefile) -- 失敗時: @debug "Rejecting... could not be opened"
    |      |      +-- isvalid_cache_header() -- 失敗時: @debug "Rejecting... incompatible header"
    |      |      +-- parse_cache_header()
    |      |      +-- jl_match_cache_flags() -- 失敗時: @debug "Rejecting... flags mismatched"
    |      |      +-- 各種検証チェック -- 失敗時: @debug "Rejecting... {理由}"
    |      |
    |      +-- canstart_loading() -- バージョン不一致時: @debug "Rejecting..."
    |      +-- _include_from_serialized() -- 失敗時: @debug "Rejecting..."
    |
    +-- _include_from_serialized()
```

### データフロー図

```
[入力]                          [処理]                              [出力]

cachefile パス ──────────> stale_cachefile()
                               |
キャッシュヘッダ ──────────> parse_cache_header() ──> modules, includes,
                               |                      requires, flags...
現在の環境情報 ──────────> 各種比較チェック
                               |
                               +── チェック失敗 ──> @debug "Rejecting..." ──> ログ出力
                               |
                               +── 全チェック通過 ──> (depmods, ocachefile, build_id) ──> キャッシュロード
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| loading.jl | `base/loading.jl` | ソース | `stale_cachefile()`（4235-4427行目）、`_tryrequire_from_serialized()`（2225-2304行目付近）で約20箇所の`@debug`拒否メッセージを出力 |
