# 機能設計書 37-async_hooks

## 概要

本ドキュメントは、Node.js の `async_hooks` モジュール（非同期コンテキスト追跡API）の機能設計書である。このモジュールは、非同期リソースのライフサイクルをトラッキングするためのAPIを提供し、非同期処理の追跡やコンテキスト伝搬を可能にする。

### 本機能の処理概要

async_hooks モジュールは、Node.js の非同期操作（タイマー、Promise、I/O操作など）のライフサイクルイベント（init、before、after、destroy、promiseResolve）を監視するためのフックシステムを提供する。また、AsyncLocalStorage を通じて非同期コンテキストの伝搬機能も提供する。

**業務上の目的・背景**：非同期処理が多用されるNode.jsでは、リクエストのトレースやコンテキストの伝搬が困難な場合がある。async_hooks はこの問題を解決し、APMツールやロギングフレームワークがリクエストを追跡することを可能にする。

**機能の利用シーン**：
- APM（Application Performance Monitoring）ツールでのリクエストトレース
- 分散トレーシングでのコンテキスト伝搬
- リクエストスコープのロギング
- 非同期リソースのリーク検出
- カスタム非同期リソースの作成

**主要な処理内容**：
1. 非同期フックの作成・有効化・無効化（`createHook()` / `enable()` / `disable()`）
2. 実行コンテキストの取得（`executionAsyncId()` / `triggerAsyncId()`）
3. カスタム非同期リソースの作成（`AsyncResource`）
4. 非同期ローカルストレージ（`AsyncLocalStorage`）
5. 実行中の非同期リソースへのアクセス（`executionAsyncResource()`）

**関連システム・外部連携**：
- APMツール（DataDog、New Relic等）
- ロギングフレームワーク（winston、pino等）
- 分散トレーシングシステム（OpenTelemetry等）

**権限による制御**：特段の権限制御は存在しない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | CLIツールのため画面なし |

## 機能種別

コンテキスト追跡 / 非同期監視 / データ伝搬

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| callbacks.init | Function | No | リソース初期化時のコールバック | 関数であること |
| callbacks.before | Function | No | コールバック実行前のコールバック | 関数であること |
| callbacks.after | Function | No | コールバック実行後のコールバック | 関数であること |
| callbacks.destroy | Function | No | リソース破棄時のコールバック | 関数であること |
| callbacks.promiseResolve | Function | No | Promise解決時のコールバック | 関数であること |
| type | string | Yes | AsyncResourceの型名 | 空でない文字列 |
| triggerAsyncId | number | No | トリガーとなった非同期ID | 有効な非同期ID |

### 入力データソース

- Node.js ランタイムからの非同期イベント通知
- アプリケーションコードからの明示的な呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| asyncId | number | 非同期リソースの一意識別子 |
| triggerAsyncId | number | トリガーとなった非同期ID |
| executionAsyncResource | object | 実行中の非同期リソース |

### 出力先

- コールバック関数への引数として渡される
- 関数の戻り値として返される

## 処理フロー

### 処理シーケンス

```
1. フック作成
   └─ createHook({ init, before, after, destroy, promiseResolve })
2. フック有効化
   └─ hook.enable() でフックをアクティブに
3. 非同期操作発生
   └─ タイマー、I/O、Promise等の開始
4. init コールバック呼び出し
   └─ 新しい非同期リソースが作成された時
5. before コールバック呼び出し
   └─ 非同期コールバックが実行される直前
6. 非同期コールバック実行
7. after コールバック呼び出し
   └─ 非同期コールバックが実行された直後
8. destroy コールバック呼び出し
   └─ 非同期リソースが破棄された時
```

### フローチャート

```mermaid
flowchart TD
    A[createHook/callbacks] --> B[hook.enable/]
    B --> C[非同期操作開始]
    C --> D[init/asyncId, type, triggerAsyncId, resource]
    D --> E{コールバック実行?}
    E -->|Yes| F[before/asyncId]
    F --> G[コールバック実行]
    G --> H[after/asyncId]
    H --> I{リソース破棄?}
    E -->|No wait| I
    I -->|Yes| J[destroy/asyncId]
    I -->|No| E
    J --> K[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | asyncId一意性 | asyncIdは各非同期リソースに対して一意 | 常時 |
| BR-02 | フックの順序 | init → before → (callback) → after → destroy の順 | 常時 |
| BR-03 | コンテキスト伝搬 | triggerAsyncIdで親子関係を追跡可能 | 常時 |
| BR-04 | destroyタイミング | destroyはGCに依存するため即時ではない | 常時 |

### 計算ロジック

特段の複雑な計算ロジックはなし。

## データベース操作仕様

該当なし

### 操作別データベース影響一覧

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ERR_ASYNC_CALLBACK | Error | コールバックが関数でない | 関数を渡す |
| ERR_ASYNC_TYPE | Error | typeが空文字列 | 有効な文字列を渡す |
| ERR_INVALID_ASYNC_ID | Error | 無効な非同期ID | 有効なIDを使用 |

### リトライ仕様

リトライは行わない。

## トランザクション仕様

該当なし

## パフォーマンス要件

- フックはパフォーマンスオーバーヘッドを生じるため、本番環境での使用は注意が必要
- initフックは非同期操作のたびに呼ばれるため、軽量な処理にすること
- `trackPromises: false` でPromiseフックを無効化可能

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

- コールバック内でのエラーはcatchされないため、例外処理に注意
- 機密データを含むコンテキストの取り扱いに注意

## 備考

- `AsyncLocalStorage` は `async_hooks` の上位抽象で、より使いやすいAPI
- Node.js内部の多くのモジュールが`async_hooks`を使用している
- `executionAsyncResource()` で現在実行中のリソースにアクセス可能

---

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

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

### 推奨読解順序

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

まず、async_hooks モジュールの主要クラスとエクスポートを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | async_hooks.js | `lib/async_hooks.js` | module.exports（282-296行目） |

**読解のコツ**: `AsyncHook` と `AsyncResource` が主要なクラス。`AsyncLocalStorage` は条件付きで異なる実装が選択される。

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

モジュールのエクスポートと主要関数を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | async_hooks.js | `lib/async_hooks.js` | internal_async_hooks インポート（31行目） |
| 2-2 | async_hooks.js | `lib/async_hooks.js` | module.exports（282-296行目） |

**主要処理フロー**:
1. **31行目**: internal_async_hooks から内部関数をインポート
2. **39-59行目**: 内部関数の取得（executionAsyncId、triggerAsyncId等）
3. **282-296行目**: エクスポートされるAPI

#### Step 3: AsyncHook クラスの実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | async_hooks.js | `lib/async_hooks.js` | AsyncHook クラス（75-166行目） |
| 3-2 | async_hooks.js | `lib/async_hooks.js` | createHook 関数（169-171行目） |

**主要処理フロー**:
- **75-106行目**: コンストラクタ - コールバック検証とシンボル設定
- **108-141行目**: enable() - フックの有効化
- **143-165行目**: disable() - フックの無効化
- **169-171行目**: createHook - AsyncHookインスタンス作成

#### Step 4: AsyncResource クラスの実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | async_hooks.js | `lib/async_hooks.js` | AsyncResource クラス（179-278行目） |

**主要処理フロー**:
- **179-217行目**: コンストラクタ - リソース初期化とemitInit
- **219-232行目**: runInAsyncScope() - 非同期スコープ内でコード実行
- **234-240行目**: emitDestroy() - 破棄イベント発火
- **250-277行目**: bind() - 関数をリソースにバインド
- **274-277行目**: 静的 bind() - ユーティリティメソッド

#### Step 5: AsyncLocalStorageを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | async_hooks.js | `lib/async_hooks.js` | AsyncLocalStorage getter（284-288行目） |

**主要処理フロー**:
- **284-288行目**: AsyncContextFrame.enabled に基づいて実装を選択
  - 有効時: `internal/async_local_storage/async_context_frame`
  - 無効時: `internal/async_local_storage/async_hooks`

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

```
createHook({ init, before, after, destroy, promiseResolve })
    │
    └─ new AsyncHook({ init, before, after, destroy, promiseResolve })
           │
           └─ this[init_symbol] = init
              this[before_symbol] = before
              this[after_symbol] = after
              this[destroy_symbol] = destroy
              this[promise_resolve_symbol] = promiseResolve

hook.enable()
    │
    ├─ getHookArrays() → [hooks_array, hook_fields]
    ├─ ArrayPrototypePush(hooks_array, this)
    │
    └─ hook_fields[kTotals] > 0 → enableHooks()
           └─ ネイティブフック有効化

[非同期操作発生時]
    │
    ├─ initHooksExist() チェック
    │      └─ emitInit(asyncId, type, triggerAsyncId, resource)
    │
    ├─ emitBefore(asyncId, triggerAsyncId)
    │      └─ 各フックの before コールバック呼び出し
    │
    ├─ [コールバック実行]
    │
    ├─ emitAfter(asyncId)
    │      └─ 各フックの after コールバック呼び出し
    │
    └─ emitDestroy(asyncId)
           └─ 各フックの destroy コールバック呼び出し
```

### データフロー図

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

非同期操作開始 ─────────▶ emitInit() ───────────────▶ init コールバック
      │                        │
      ▼                        ▼
コールバック ─────────────▶ emitBefore() ──────────────▶ before コールバック
実行予定                       │
      │                        ▼
      │                   コールバック実行
      │                        │
      ▼                        ▼
コールバック ─────────────▶ emitAfter() ───────────────▶ after コールバック
完了                           │
      │                        ▼
      ▼
リソース破棄 ─────────────▶ emitDestroy() ─────────────▶ destroy コールバック
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| async_hooks.js | `lib/async_hooks.js` | ソース | メインモジュール実装 |
| internal/async_hooks.js | `lib/internal/async_hooks.js` | ソース | 内部実装 |
| async_context_frame.js | `lib/internal/async_context_frame.js` | ソース | AsyncContextFrame |
| async_hooks.js | `lib/internal/async_local_storage/async_hooks.js` | ソース | AsyncLocalStorage（hooks版） |
| async_context_frame.js | `lib/internal/async_local_storage/async_context_frame.js` | ソース | AsyncLocalStorage（frame版） |
| validators.js | `lib/internal/validators.js` | ソース | 入力バリデーション |
| errors.js | `lib/internal/errors.js` | ソース | エラーコード定義 |
