# 通知設計書 27-プロファイル印刷リスナークラッシュ

## 概要

本ドキュメントは、Juliaのプロファイル印刷リスナータスクがクラッシュした場合に出力されるエラー通知の設計を記述する。

### 本通知の処理概要

本通知は、`profile_printing_listener` 関数内で、SIGINFO/SIGUSR1 シグナルに応答してプロファイルレポートを出力するバックグラウンドタスクが予期しない例外によりクラッシュした場合に出力されるエラーログである。

**業務上の目的・背景**：Julia は非 Windows プラットフォームにおいて、SIGINFO（macOS）または SIGUSR1（Linux等）シグナルを受信した際にプロファイリング結果を表示する機能を持つ。この機能は `profile_printing_listener` というバックグラウンドタスクで実装されている。このタスクが予期しない例外でクラッシュした場合、デバッグのためにエラー情報をログに記録する必要がある。ただし、`InterruptException` は通常のユーザー操作（Ctrl+C等）であるため、エラーとして報告しない。

**通知の送信タイミング**：`profile_printing_listener` タスクの `catch` ブロック内で、捕捉された例外が `InterruptException` でない場合に発生する。

**通知の受信者**：Julia開発者またはシステム管理者。コンソール（stderr）に表示される。

**通知内容の概要**：「Profile printing listener crashed」というメッセージとともに、例外オブジェクトとバックトレースが出力される。

**期待されるアクション**：Julia開発者が例外の原因を調査し、Profile stdlibまたはリスナー実装のバグを修正する。

## 通知種別

ログ（Error） -- Julia標準ロギングフレームワーク（`@error` マクロ）によるコンソール出力

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（タスク内での例外捕捉時に即時出力） |
| 優先度 | 高（ログレベル Error） |
| リトライ | 無し |

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

Julia の標準ロギングフレームワークに従い、`stderr` に出力される。

## 通知テンプレート

### メール通知の場合

該当なし（コンソールログ出力）

### 本文テンプレート

```
┌ Error: Profile printing listener crashed
│   exception =
│    {exception object}
│    Stacktrace:
│     [1] ...
└ @ Base Base.jl:349
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| exception | 捕捉された例外とバックトレース | `ex, catch_backtrace()` | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| タスク例外 | `profile_printing_listener` 内で例外発生 | `!isa(ex, InterruptException)` | InterruptException 以外の例外が発生した場合 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| `isa(ex, InterruptException)` | ユーザーによる割り込み（Ctrl+C等）は正常な操作であるため通知しない |
| Windows プラットフォーム | `start_profile_listener` 自体が呼ばれないため、リスナーが存在しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A["Julia起動・__init__"] --> B{"Sys.iswindows()?"}
    B -->|Yes| C["リスナー不起動"]
    B -->|No| D["start_profile_listener()"]
    D --> E["AsyncCondition 作成"]
    E --> F["profile_printing_listener タスク開始"]
    F --> G["_trywait(cond) ループ"]
    G --> H["Profile.peek_report[] 実行"]
    H --> I{"例外発生?"}
    I -->|No| G
    I -->|Yes| J{"InterruptException?"}
    J -->|Yes| K["静かに終了"]
    J -->|No| L["@error 出力"]
    L --> M["タスク終了"]
```

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

### 参照テーブル一覧

| データ構造 | 参照先 | 参照目的 |
|----------|--------|---------|
| `Profile` module | `require_stdlib(PkgId(..., "Profile"))` | プロファイルレポート表示関数の取得 |

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| Profile stdlib ロード失敗 | `require_stdlib` が失敗 | 例外がキャッチされて `@error` で報告 |
| `peek_report` 実行エラー | プロファイルレポート生成中のエラー | 例外がキャッチされて `@error` で報告 |
| ヒープスナップショットエラー | `take_heap_snapshot` の失敗 | 例外がキャッチされて `@error` で報告 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（クラッシュ後はタスクが終了する） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 制限 | リスナータスクのライフタイムにつき最大1回（例外で終了するため） |

### 配信時間帯

制限なし

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

例外オブジェクトとバックトレースがログに含まれる。プロファイルデータやヒープスナップショットのパスが露出する可能性があるが、これはデバッグ目的の情報である。

## 備考

- プロファイルリスナーは `@static if !Sys.iswindows()` で条件付きでのみ開始される
- `AsyncCondition` の `handle` が C コールバック経由で `jl_set_peek_cond` に設定される
- `errormonitor` でラップされているため、タスクのエラーは適切に伝播される
- `JULIA_PROFILE_PEEK_HEAP_SNAPSHOT` 環境変数が `true` の場合、プロファイルピーク時にヒープスナップショットも取得される
- `atexit` ハンドラで `jl_set_peek_cond` を `C_NULL` にリセットし、リスナーを適切にクリーンアップする

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Base.jl | `base/Base.jl` | 335行目: `profile_printing_listener` 関数の引数 `cond::AsyncCondition` |
| 1-2 | asyncevent.jl | `base/asyncevent.jl` | `AsyncCondition` 型の定義（UVイベントループとの連携） |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | Base.jl | `base/Base.jl` | 374-411行目: `__init__` 関数。393-396行目で `start_profile_listener()` を条件付き呼び出し |
| 2-2 | Base.jl | `base/Base.jl` | 355-372行目: `start_profile_listener` 関数 |

**主要処理フロー**:
- **356行目**: `AsyncCondition()` を作成
- **358行目**: `errormonitor(Threads.@spawn(...))` でバックグラウンドタスクを開始
- **359-366行目**: `atexit` ハンドラでクリーンアップを登録
- **371行目**: `jl_set_peek_cond` で C ランタイムにコールバックを設定

#### Step 3: 通知発生箇所を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | Base.jl | `base/Base.jl` | 335-353行目: `profile_printing_listener` 関数 |

**主要処理フロー**:
- **337行目**: `try` ブロック開始
- **338行目**: `while _trywait(cond)` でシグナル待ちループ
- **339行目**: Profile stdlibを遅延ロード
- **340行目**: `invokelatest(profile.peek_report[])` でプロファイルレポート表示
- **341-344行目**: ヒープスナップショットの条件付き取得
- **346行目**: `catch ex` で例外を捕捉
- **347行目**: `!isa(ex, InterruptException)` で割り込みを除外
- **349行目**: `@error "Profile printing listener crashed" exception=ex,catch_backtrace()` でエラー出力

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

```
Base.__init__() [Base.jl:374]
    │
    └─ start_profile_listener() [Base.jl:355]  (非Windowsのみ)
           │
           ├─ AsyncCondition() [Base.jl:356]
           │
           ├─ Threads.@spawn(profile_printing_listener(cond)) [Base.jl:358]
           │      │
           │      └─ profile_printing_listener(cond) [Base.jl:335]
           │             │
           │             ├─ _trywait(cond) [Base.jl:338]
           │             │
           │             ├─ require_stdlib(PkgId(..., "Profile")) [Base.jl:339]
           │             │
           │             ├─ invokelatest(profile.peek_report[]) [Base.jl:340]
           │             │
           │             └─ @error "Profile printing listener crashed" [Base.jl:349]
           │
           ├─ atexit() [Base.jl:359]
           │      └─ jl_set_peek_cond(C_NULL) [Base.jl:361]
           │
           └─ jl_set_peek_cond(cond.handle) [Base.jl:371]
```

### データフロー図

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

SIGINFO/SIGUSR1 ─────────────▶ AsyncCondition 起動
                              │
Profile stdlib ───────────────▶ peek_report[] 呼び出し ──────────▶ プロファイルレポート ──▶ stderr
                              │
JULIA_PROFILE_PEEK_HEAP_SNAPSHOT ─▶ take_heap_snapshot ──────────▶ .heapsnapshot ファイル
                              │
例外発生 ─────────────────────▶ catch ブロック
                              │
                              ├─ InterruptException ──▶ 静かに終了
                              └─ その他 ──▶ @error ──▶ stderr
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Base.jl | `base/Base.jl` | ソース | 通知の発生元。`profile_printing_listener`, `start_profile_listener`, `__init__` |
| asyncevent.jl | `base/asyncevent.jl` | ソース | `AsyncCondition` 型の実装 |
| logging.jl | `base/logging/logging.jl` | ソース | `@error` マクロの実装 |
