# 通知設計書 25-ソースマップ参照解除通知

## 概要

本ドキュメントは、Bun DevServerにおけるソースマップ参照カウント管理のための参照解除通知機能の設計を定義する。この通知により、クライアント側で不要になったソースマップの参照をサーバー側で解除し、メモリリークを防止する。

### 本通知の処理概要

この通知は、HMRクライアントがソースマップへの参照を不要とした際に、WebSocket経由でDevServerに参照解除を要求するメッセージを送信する機能である。DevServerはこのメッセージを受信し、SourceMapStoreの参照カウントを減らす。

**業務上の目的・背景**：DevServerでは、ホットリロードによって多数のJavaScriptバンドルとソースマップが生成される。各ソースマップはエラースタックトレースの解析に使用されるが、クライアントがコードを更新すると古いソースマップは不要になる。参照カウント方式により、どのクライアントもソースマップを参照していない状態になったときにメモリを解放できる。この通知機能がないと、開発セッション中にメモリ使用量が増加し続け、最終的にパフォーマンス低下やメモリ不足につながる可能性がある。

**通知の送信タイミング**：以下のイベント発生時にクライアントからサーバーへ送信される：
1. HMRによってモジュールが新しいバージョンに置き換えられたとき
2. ソースマップのガベージコレクション実行時
3. WebSocket切断時（サーバー側で自動的に処理）

**通知の受信者**：DevServer（HmrSocket.onMessage）

**通知内容の概要**：
- メッセージID: `unref_source_map` ('u')
- ペイロード: ソースマップのキー（u64、Little-endian）

**期待されるアクション**：DevServerはSourceMapStoreのunref関数を呼び出し、該当ソースマップの参照カウントを1減らす。参照カウントが0になった場合、ソースマップはメモリから解放される。

## 通知種別

WebSocket通知（クライアントからサーバーへ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（WebSocket） |
| 優先度 | 低 |
| リトライ | 無し |
| 方向 | クライアント → サーバー |

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

HMRソケット接続を通じてDevServerに送信される。各クライアントは自身が参照しているソースマップのみを参照解除できる。

## 通知テンプレート

### メッセージフォーマット

| 項目 | 内容 |
|-----|------|
| プロトコル | WebSocket |
| 形式 | バイナリ |
| エンコーディング | Little-endian |

### 本文テンプレート

```
IncomingMessageId.unref_source_map (1バイト: 'u')
└─ source_map_key (u64): ソースマップの識別キー
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| source_map_key | ソースマップの64bit識別キー | SourceMapStore.Key | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| モジュール更新 | HMRによるモジュール置換 | 古いソースマップが存在 | 新バージョンロード時に旧版の参照を解除 |
| GC | ソースマップGC | gcSizeを超過 | 古いソースマップをLRUで削除 |
| 切断 | WebSocket切断 | 自動処理 | サーバー側でreferenced_source_mapsを走査 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 参照がない | referenced_source_mapsにエントリがない場合 |
| 既に解除済み | 重複した参照解除要求は無視される |

## 処理フロー

### 送信フロー（クライアント側）

```mermaid
flowchart TD
    A[モジュール更新/GC発生] --> B[古いソースマップを特定]
    B --> C{参照あり?}
    C -->|No| D[処理終了]
    C -->|Yes| E[unref_source_mapメッセージ生成]
    E --> F[WebSocket送信]
    F --> D
```

### 受信フロー（サーバー側）

```mermaid
flowchart TD
    A[WebSocketメッセージ受信] --> B{メッセージID = unref_source_map?}
    B -->|No| C[他の処理へ]
    B -->|Yes| D[ソースマップキーを読み取り]
    D --> E{referenced_source_mapsにあり?}
    E -->|No| F[警告ログ出力]
    E -->|Yes| G[referenced_source_mapsから削除]
    G --> H[source_maps.unref呼び出し]
    H --> I{ref_count == 0?}
    I -->|No| J[処理終了]
    I -->|Yes| K[ソースマップをメモリから解放]
    F --> J
    K --> J
```

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

### 参照テーブル一覧

本通知はデータベースを使用しない。すべてのデータはメモリ上のDevServer構造から取得される。

| 構造体名 | 用途 | 備考 |
|---------|------|------|
| SourceMapStore | ソースマップの参照カウント管理 | dev.source_maps |
| HmrSocket.referenced_source_maps | クライアントごとの参照マップ | WebSocket接続ごとに管理 |

### 更新テーブル一覧

| 構造体名 | 操作 | 概要 |
|---------|------|------|
| SourceMapStore.Entry | UPDATE | ref_countのデクリメント |
| HmrSocket.referenced_source_maps | DELETE | 参照エントリの削除 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 不正なペイロード | メッセージ長が不正 | WebSocket切断 |
| 存在しないキー | referenced_source_mapsにキーがない | 警告ログを出力し、処理を継続 |
| 読み取りエラー | バイナリパースエラー | WebSocket切断 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0回（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

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

### 配信時間帯

制限なし（開発環境でのみ使用）

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

- この機能は開発環境専用であり、本番環境では使用されない
- クライアントは自身のreferenced_source_mapsに登録されたキーのみを参照解除可能
- 不正なキーの参照解除要求は無視される（他クライアントのソースマップは解除不可）

## 備考

- 参照解除は「最善努力」で行われ、失敗しても致命的な問題は発生しない
- WebSocket切断時にサーバー側で自動的に参照解除が行われるため、クライアント側の明示的な通知は補助的な役割
- ソースマップの「弱参照」（weak_refs）機構も存在し、一定時間後に自動的に解放される

---

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

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

### 推奨読解順序

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

ソースマップの参照カウント管理構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | SourceMapStore.zig | `src/bake/DevServer/SourceMapStore.zig` | Key、Entry構造体の定義（行10-70） |
| 1-2 | SourceMapStore.zig | `src/bake/DevServer/SourceMapStore.zig` | WeakRef構造体、weak_refs管理（行14-29） |
| 1-3 | HmrSocket.zig | `src/bake/DevServer/HmrSocket.zig` | referenced_source_mapsフィールド（行11） |

**読解のコツ**: SourceMapStore.Keyはu64をラップしたGenericIndex。上位32bitがgenerationを、下位32bitがルートバンドルインデックスを表す。

#### Step 2: サーバー側受信処理を理解する

DevServer側でのメッセージ処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | HmrSocket.zig | `src/bake/DevServer/HmrSocket.zig` | onMessage関数のunref_source_map処理（行207-218） |
| 2-2 | DevServer.zig | `src/bake/DevServer.zig` | IncomingMessageId.unref_source_mapの定義（行3912-3914） |

**主要処理フロー**:
1. **行208-209**: fixedBufferStreamでペイロードをパース
2. **行211-212**: u64のsource_map_idを読み取り
3. **行213-216**: referenced_source_mapsからエントリを検索・削除
4. **行217**: source_maps.unrefで参照カウントをデクリメント

#### Step 3: 参照解除処理の詳細を理解する

SourceMapStoreのunref処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | SourceMapStore.zig | `src/bake/DevServer/SourceMapStore.zig` | unref関数（行374-376） |
| 3-2 | SourceMapStore.zig | `src/bake/DevServer/SourceMapStore.zig` | unrefCount関数（行378-381） |
| 3-3 | SourceMapStore.zig | `src/bake/DevServer/SourceMapStore.zig` | unrefAtIndex関数（行384-394） |

**主要処理フロー**:
- **行386**: ref_countをデクリメント
- **行390-393**: ref_countが0になったらdeinitしてentriesから削除

#### Step 4: WebSocket切断時の自動処理を理解する

クライアント切断時の参照解除処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | HmrSocket.zig | `src/bake/DevServer/HmrSocket.zig` | onClose関数（行237-262） |

**主要処理フロー**:
- **行255-258**: referenced_source_mapsをイテレートしてすべてのキーをunref
- **行259**: referenced_source_mapsをdeinit

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

```
[クライアント側]
モジュール更新/GC
    │
    └─ WebSocket.send('u' + source_map_key)

[サーバー側]
HmrSocket.onMessage() [HmrSocket.zig:43]
    │
    └─ case .unref_source_map [HmrSocket.zig:207]
           │
           ├─ fixedBufferStream でパース
           │
           ├─ SourceMapStore.Key.init()
           │
           ├─ referenced_source_maps.fetchRemove()
           │      │
           │      └─ [エントリがない場合]
           │             └─ debugWarn("no entry found")
           │
           └─ source_maps.unref() [SourceMapStore.zig:374]
                  │
                  └─ unrefCount() [SourceMapStore.zig:378]
                         │
                         └─ unrefAtIndex() [SourceMapStore.zig:384]
                                │
                                ├─ ref_count -= 1
                                │
                                └─ [ref_count == 0]
                                       │
                                       ├─ entry.deinit()
                                       └─ entries.swapRemoveAt()

[WebSocket切断時]
HmrSocket.onClose() [HmrSocket.zig:237]
    │
    ├─ onUnsubscribe(subscriptions)
    │
    └─ referenced_source_maps.keyIterator()
           │
           └─ for each key:
                  └─ source_maps.unref(key)
```

### データフロー図

```
[クライアント]                [サーバー]                    [メモリ]

モジュール更新         HmrSocket.onMessage          SourceMapStore
     │           ───▶        │                  ───▶     │
     │                       │                           │
     ▼                       ▼                           ▼
'u' + key           パース & 検証                ref_count--
(9バイト)                  │                           │
                           │                           │
                           ▼                           ▼
                    unref(key)                   [ref_count == 0?]
                                                       │
                                                       ▼
                                                 Entry.deinit()
                                                 メモリ解放
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| SourceMapStore.zig | `src/bake/DevServer/SourceMapStore.zig` | ソース | ソースマップの参照カウント管理、メモリ解放 |
| HmrSocket.zig | `src/bake/DevServer/HmrSocket.zig` | ソース | WebSocketメッセージ処理、参照マップ管理 |
| DevServer.zig | `src/bake/DevServer.zig` | ソース | IncomingMessageIdの定義 |
| stack-trace.ts | `src/bake/client/stack-trace.ts` | ソース | クライアント側ソースマップ管理（参照ポイント） |
