# 通知設計書 15-UncaughtExceptionHandler通知

## 概要

本ドキュメントは、OpenSearchプロセス内でキャッチされない例外（Uncaught Exception）が発生した際に、ログとターミナル出力で通知し、致命的エラーの場合はプロセスをhaltするUncaughtExceptionHandler通知の設計を記述する。

### 本通知の処理概要

OpenSearchUncaughtExceptionHandlerは、JavaのThread.UncaughtExceptionHandlerインターフェースを実装し、いずれかのスレッドでキャッチされない例外が発生した際に、エラー内容をログとターミナルに出力し、致命的エラー（Error系）の場合はプロセスを即座にhaltする仕組みである。

**業務上の目的・背景**：分散システムであるOpenSearchでは、予期しない例外がスレッド内で発生した場合、その情報を確実に記録し、致命的なエラーの場合はプロセスを安全に停止させる必要がある。特にOutOfMemoryError、StackOverflowError、InternalErrorなどのError系例外は、JVMの安定性を保証できないため、シャットダウンフックを実行せずにプロセスを即座に停止（halt）する。これにより、不安定な状態でのデータ破損を防止する。

**通知の送信タイミング**：いずれかのスレッドでUncaughtExceptionが発生した時点でJVMによりuncaughtException()メソッドが呼び出される。

**通知の受信者**：ログファイルの読者（運用者）およびターミナル出力の閲覧者が受信者である。Log4jロガーとTerminal.DEFAULTの両方に出力される。

**通知内容の概要**：致命的エラーの場合は「fatal error in thread [スレッド名], exiting」、非致命的の場合は「uncaught exception in thread [スレッド名]」というメッセージとスタックトレースが出力される。

**期待されるアクション**：運用者はログを確認し、致命的エラーの場合はノードの再起動と根本原因の調査を行う。非致命的エラーの場合は影響範囲を評価し、必要に応じて対処を行う。終了コードから障害種別を識別できる（128: InternalError、127: OutOfMemoryError、126: StackOverflowError、125: UnknownError、124: IOError、1: その他）。

## 通知種別

ログ出力通知 + ターミナル出力通知（プロセス内通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（例外発生スレッド上で実行） |
| 優先度 | 最高（致命的エラーの場合はプロセスhalt） |
| リトライ | なし |

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

全スレッドで発生するUncaughtExceptionが対象。致命的エラー（Error系）と非致命的エラーで処理が分岐する。

## 通知テンプレート

### メール通知の場合

該当なし（ログ・ターミナル出力のみ）。

### 本文テンプレート

```
致命的エラーの場合:
fatal error in thread [{threadName}], exiting
{stackTrace}

非致命的エラーの場合:
uncaught exception in thread [{threadName}]
{stackTrace}
```

### 添付ファイル

該当なし。

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| threadName | 例外が発生したスレッド名 | Thread.getName() | Yes |
| stackTrace | 例外のスタックトレース | Throwable.printStackTrace() | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| JVMランタイム | Thread.uncaughtException() | 常に（キャッチされない例外発生時） | いずれかのスレッドでキャッチされない例外が発生 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| なし | キャッチされない例外は常に通知される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[スレッドでキャッチされない例外発生] --> B[uncaughtException呼び出し]
    B --> C{isFatalUncaught? Errorクラス?}
    C -->|Yes: Fatal| D[onFatalUncaught]
    C -->|No: Non-Fatal| E[onNonFatalUncaught]
    D --> F[ログにERRORレベルで出力]
    D --> G[Terminal.DEFAULTにエラー出力]
    D --> H[Terminal.DEFAULT.flush]
    H --> I{例外の型を判定}
    I -->|InternalError| J[halt 128]
    I -->|OutOfMemoryError| K[halt 127]
    I -->|StackOverflowError| L[halt 126]
    I -->|UnknownError| M[halt 125]
    I -->|IOError| N[halt 124]
    I -->|その他Error| O[halt 1]
    E --> P[ログにERRORレベルで出力]
    E --> Q[Terminal.DEFAULTにエラー出力]
    E --> R[Terminal.DEFAULT.flush]
    R --> S[処理続行]
```

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

### 参照テーブル一覧

該当なし。

### 更新テーブル一覧

該当なし。

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 致命的エラー（Error系） | InternalError, OutOfMemoryError, StackOverflowError, UnknownError, IOError等 | ログ出力後、固有の終了コードでRuntime.halt() |
| 非致命的エラー | RuntimeException等のError以外の例外 | ログ出力のみ、プロセスは継続 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | なし |
| リトライ間隔 | なし |
| リトライ対象エラー | なし |

## 配信設定

### レート制限

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

### 配信時間帯

制限なし。

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

- スタックトレースにはシステムの内部構造が含まれるため、ログファイルへのアクセス制御が重要
- halt()呼び出しにはAccessController.doPrivilegedによるセキュリティ制御が適用されている
- haltはシャットダウンフックを実行しないため、不安定な状態からの安全な停止が可能

## 備考

- isFatalUncaught()はThrowableがErrorクラスのインスタンスかどうかで判定する（78-80行目）
- halt()はRuntime.getRuntime().halt()を使用し、シャットダウンフックを実行しない
- PrivilegedHaltActionを通じてセキュリティマネージャの制御下でhaltを実行
- Terminal.DEFAULT.flush()によりスタックトレースがhalt前に確実に出力される

---

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

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

### 推奨読解順序

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

UncaughtExceptionHandlerのインターフェースとエラー分類を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | OpenSearchUncaughtExceptionHandler.java | `server/src/main/java/org/opensearch/bootstrap/OpenSearchUncaughtExceptionHandler.java` | クラス構造とisFatalUncaught()の判定ロジック（78-80行目） |

**読解のコツ**: JavaのThread.UncaughtExceptionHandlerインターフェースの仕組みを前提知識として理解しておくこと。

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

uncaughtException()メソッドの分岐処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | OpenSearchUncaughtExceptionHandler.java | `server/src/main/java/org/opensearch/bootstrap/OpenSearchUncaughtExceptionHandler.java` | uncaughtException()（52行目〜）の致命的/非致命的分岐 |

**主要処理フロー**:
1. **53行目**: isFatalUncaught(t)で致命的かどうかを判定
2. **55行目**: 致命的の場合onFatalUncaught()を呼び出し
3. **57-71行目**: finally節で例外型に応じた終了コードでhalt()
4. **74行目**: 非致命的の場合onNonFatalUncaught()を呼び出し

#### Step 3: 通知処理の詳細を理解する

ログ出力とターミナル出力の詳細を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | OpenSearchUncaughtExceptionHandler.java | `server/src/main/java/org/opensearch/bootstrap/OpenSearchUncaughtExceptionHandler.java` | onFatalUncaught()（82行目〜）とonNonFatalUncaught()（91行目〜）の出力処理 |

**主要処理フロー**:
- **83-88行目**: onFatalUncaught - ログERROR出力、Terminal.DEFAULT出力、flush
- **92-97行目**: onNonFatalUncaught - 同様のパターンだがhaltなし

#### Step 4: halt処理の詳細

PrivilegedHaltActionによるプロセス停止処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | OpenSearchUncaughtExceptionHandler.java | `server/src/main/java/org/opensearch/bootstrap/OpenSearchUncaughtExceptionHandler.java` | PrivilegedHaltAction（104行目〜）でRuntime.halt()を実行 |

**主要処理フロー**:
- **100-101行目**: halt()でAccessController.doPrivilegedを使用
- **116行目**: Runtime.getRuntime().halt(status)でシャットダウンフックなしで停止

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

```
JVM (Thread.UncaughtExceptionHandler)
    |
    +-- OpenSearchUncaughtExceptionHandler.uncaughtException(thread, throwable)
            |
            +-- isFatalUncaught(throwable)
            |
            +-- [Fatal] onFatalUncaught(threadName, throwable)
            |       |
            |       +-- Logger.error(message, throwable)
            |       +-- Terminal.DEFAULT.errorPrintln(message)
            |       +-- Throwable.printStackTrace(Terminal.DEFAULT.getErrorWriter())
            |       +-- Terminal.DEFAULT.flush()
            |
            +-- [Fatal] halt(exitCode)
            |       |
            |       +-- AccessController.doPrivileged(PrivilegedHaltAction)
            |               |
            |               +-- Runtime.getRuntime().halt(exitCode)
            |
            +-- [Non-Fatal] onNonFatalUncaught(threadName, throwable)
                    |
                    +-- Logger.error(message, throwable)
                    +-- Terminal.DEFAULT.errorPrintln(message)
                    +-- Throwable.printStackTrace(Terminal.DEFAULT.getErrorWriter())
                    +-- Terminal.DEFAULT.flush()
```

### データフロー図

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

キャッチされない例外  -->  uncaughtException()               --> Log4j ERRORログ
  (Thread, Throwable)         |                               --> Terminal.DEFAULT出力
                          isFatalUncaught()                   --> [Fatal] halt(exitCode)
                              |                                   128: InternalError
                          Fatal/NonFatal分岐                      127: OutOfMemoryError
                                                                  126: StackOverflowError
                                                                  125: UnknownError
                                                                  124: IOError
                                                                  1: その他
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| OpenSearchUncaughtExceptionHandler.java | `server/src/main/java/org/opensearch/bootstrap/OpenSearchUncaughtExceptionHandler.java` | ソース | UncaughtException通知の主要実装 |
| Bootstrap.java | `server/src/main/java/org/opensearch/bootstrap/Bootstrap.java` | ソース | ハンドラの登録元 |
| OpenSearchUncaughtExceptionHandlerTests.java | `server/src/test/java/org/opensearch/bootstrap/OpenSearchUncaughtExceptionHandlerTests.java` | テスト | ハンドラのテスト |
| DieWithDignityIT.java | `qa/die-with-dignity/src/javaRestTest/java/org/opensearch/qa/die_with_dignity/DieWithDignityIT.java` | テスト | 致命的エラー時の統合テスト |
