# 通知設計書 1-sqlite3_unlock_notify

## 概要

本ドキュメントは、SQLiteのunlock-notify機能である`sqlite3_unlock_notify()` APIの設計について記載する。この機能は共有キャッシュモードでSQLITE_LOCKEDエラーが発生した際に、ブロッキング接続のトランザクション終了を通知するコールバックを登録するためのものである。

### 本通知の処理概要

`sqlite3_unlock_notify()`は、共有キャッシュモードにおいてデータベースロック競合が発生した場合に、アプリケーションがロック解除を待機するためのコールバック登録機能を提供する。

**業務上の目的・背景**：共有キャッシュモードでは、複数のデータベース接続が同一のキャッシュを共有するため、ある接続がロックを保持している間、他の接続は待機する必要がある。SQLITE_LOCKEDエラーをポーリングで処理するのは非効率であるため、ロック解除時にコールバックで通知を受ける仕組みが必要となる。この機能により、アプリケーションはビジーウェイトを避け、効率的にリソースを利用できる。

**通知の送信タイミング**：ブロッキング接続（ロックを保持している接続）のトランザクションが終了（コミットまたはロールバック）した時点で、登録されたコールバックが呼び出される。具体的には、`sqlite3_step()`または`sqlite3_close()`の呼び出し内で実行される。

**通知の受信者**：SQLITE_LOCKEDエラーを受け取った接続（ブロックされた接続）が登録したコールバック関数。同じコールバック関数を登録した複数の接続がある場合、それらのコンテキストポインタは配列としてまとめられ、一度のコールバック呼び出しで通知される。

**通知内容の概要**：コールバック関数には、登録時に指定したコンテキストポインタの配列とその要素数が渡される。これにより、アプリケーションは複数のブロックされた接続を一括で処理できる。

**期待されるアクション**：コールバックを受け取ったアプリケーションは、以前失敗したSQL操作を再試行する。ただし、コールバック内からsqlite3_xxx API関数を呼び出すことは禁止されており、デッドロックやクラッシュの原因となる。

## 通知種別

コールバック関数呼び出し（C言語関数ポインタによるコールバック）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（ブロッキング接続のトランザクション終了時に直接呼び出し） |
| 優先度 | 高（ロック待ち解消のため即座に通知） |
| リトライ | なし（コールバックは1回のみ呼び出される） |

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

1. `sqlite3_unlock_notify()`呼び出し時に、ブロックされた接続（引数`pBlocked`）にコールバック関数とコンテキストポインタを登録
2. ブロッキング接続（`pBlocked->pBlockingConnection`）のトランザクション終了時に、`sqlite3BlockedList`をスキャンして該当する接続のコールバックを呼び出し
3. 同一のコールバック関数を持つ複数の接続がある場合、コンテキストポインタを配列にまとめて一度に通知

## 通知テンプレート

### コールバック関数シグネチャ

```c
void (*xNotify)(void **apArg, int nArg)
```

| 項目 | 内容 |
|-----|------|
| apArg | コンテキストポインタの配列 |
| nArg | 配列の要素数 |

### 本文テンプレート

```
コールバック関数は以下の形式で呼び出される：

xNotify(apArg, nArg)

apArg: 登録時に指定したpNotifyArgの配列
nArg: 配列の要素数（同一コールバックを登録した接続数）
```

### 添付ファイル

該当なし（コールバック関数呼び出しのため）

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| apArg | コンテキストポインタ配列 | sqlite3.pUnlockArg | Yes |
| nArg | 配列要素数 | 登録済み接続数 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | sqlite3_step()完了 | db->autoCommit==true | トランザクション自動コミット時 |
| API呼び出し | sqlite3_close()呼び出し | 常に | 接続クローズ時に登録済みコールバックを呼び出し |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| xNotify==NULL | コールバックがNULLで登録された場合、既存のコールバックがキャンセルされる |
| デッドロック検出 | 循環待ちが検出された場合、SQLITE_LOCKEDを返しコールバックは登録されない |
| pBlockingConnection==0 | ブロッキング接続が存在しない場合、コールバックは即座に呼び出される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[sqlite3_unlock_notify呼び出し] --> B{xNotify==NULL?}
    B -->|Yes| C[既存コールバック解除]
    B -->|No| D{pBlockingConnection==0?}
    D -->|Yes| E[即座にコールバック呼び出し]
    D -->|No| F{デッドロック検出?}
    F -->|Yes| G[SQLITE_LOCKED返却]
    F -->|No| H[コールバック登録]
    H --> I[sqlite3BlockedListに追加]
    I --> J[トランザクション終了待ち]
    J --> K[sqlite3ConnectionUnlocked呼び出し]
    K --> L[登録済みコールバック呼び出し]
    L --> M[sqlite3BlockedListから削除]
    C --> N[終了]
    E --> N
    G --> N
    M --> N
```

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

### 参照テーブル一覧

該当なし（インメモリデータ構造のみ使用）

### グローバル変数参照

| 変数名 | 型 | 用途 | 備考 |
|--------|---|------|------|
| sqlite3BlockedList | sqlite3* | ブロックされた接続のリンクリスト先頭 | STATIC_MAINミューテックスで保護 |

### sqlite3構造体メンバー参照

| メンバー名 | 型 | 用途 |
|-----------|---|------|
| pBlockingConnection | sqlite3* | SQLITE_LOCKEDを引き起こした接続 |
| pUnlockConnection | sqlite3* | アンロックを待機している接続 |
| pUnlockArg | void* | コールバックに渡す引数 |
| xUnlockNotify | void(*)(void**,int) | 登録されたコールバック関数 |
| pNextBlocked | sqlite3* | ブロックリストの次の要素 |

### 更新テーブル一覧

該当なし（インメモリデータ構造のみ使用）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| SQLITE_LOCKED | デッドロック検出時 | コールバック登録せず、エラーを返却 |
| SQLITE_MISUSE | 無効なdb引数 | API Armorが有効な場合に検出 |
| メモリ不足 | aArg配列拡張失敗時 | 既存の配列でコールバックを先に呼び出し、処理を継続 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

制限なし（トランザクション終了時に即座に呼び出し）

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

- コールバック関数内からsqlite3_xxx API関数を呼び出すとデッドロックやクラッシュの原因となる
- SQLITE_MUTEX_STATIC_MAINミューテックスで内部データ構造を保護
- マルチスレッド環境では、コールバック登録と呼び出しが競合する可能性があるため、即座にコールバックが呼び出される場合がある

## 備考

- SQLITE_ENABLE_UNLOCK_NOTIFYコンパイルオプションが必要
- DROP TABLE/DROP INDEX実行時のSQLITE_LOCKEDは特殊ケースで、ブロッキング接続が存在しないため即座にコールバックが呼び出される（無限ループの原因となりうる）
- 拡張エラーコードSQLITE_LOCKED_SHAREDCACHEで通常のロックと区別可能

---

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

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

### 推奨読解順序

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

まず、unlock-notify機能で使用されるデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | sqliteInt.h | `src/sqliteInt.h` | sqlite3構造体のunlock-notify関連メンバー（1790-1803行目）を確認 |

**読解のコツ**: `pBlockingConnection`、`pUnlockConnection`、`xUnlockNotify`、`pUnlockArg`、`pNextBlocked`の5つのメンバーの関係を理解することが重要。

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

処理の起点となる公開API関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notify.c | `src/notify.c` | sqlite3_unlock_notify()関数（148-193行目）の実装 |

**主要処理フロー**:
1. **155-157行**: API Armor によるパラメータ検証
2. **158-159行**: ミューテックス取得（db->mutexとSTATIC_MAIN）
3. **161-166行**: xNotify==NULLの場合、既存コールバックをキャンセル
4. **167-172行**: pBlockingConnection==0の場合、即座にコールバック呼び出し
5. **174-185行**: デッドロック検出とコールバック登録処理

#### Step 3: 内部ヘルパー関数を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notify.c | `src/notify.c` | addToBlockedList()（98-108行目）- リストへの追加処理 |
| 3-2 | notify.c | `src/notify.c` | removeFromBlockedList()（83-92行目）- リストからの削除処理 |
| 3-3 | notify.c | `src/notify.c` | enterMutex()/leaveMutex()（113-125行目）- ミューテックス操作 |

**主要処理フロー**:
- **98-108行**: 同一xUnlockNotifyを持つエントリをグループ化してリストに追加
- **83-92行**: 指定された接続をリストから削除

#### Step 4: コールバック呼び出し処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | notify.c | `src/notify.c` | sqlite3ConnectionUnlocked()（229-322行目）- コールバック呼び出し |

**主要処理フロー**:
- **241-315行**: ブロックリストをスキャンし、該当する接続のコールバックを呼び出し
- **252-254行**: 異なるコールバック関数を持つ接続に切り替わった場合、先に呼び出し
- **260-296行**: aArg配列の動的拡張（メモリ不足時の対応含む）
- **317-318行**: 最終的なコールバック呼び出し

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

```
sqlite3_unlock_notify() [公開API]
    |
    +-- enterMutex()
    |       +-- sqlite3_mutex_enter(SQLITE_MUTEX_STATIC_MAIN)
    |       +-- checkListProperties()
    |
    +-- removeFromBlockedList() [xNotify==NULL時]
    |
    +-- xNotify() [pBlockingConnection==0時、即座呼び出し]
    |
    +-- addToBlockedList() [コールバック登録時]
    |
    +-- leaveMutex()
            +-- checkListProperties()
            +-- sqlite3_mutex_leave(SQLITE_MUTEX_STATIC_MAIN)

sqlite3ConnectionUnlocked() [内部関数、トランザクション終了時に呼び出し]
    |
    +-- enterMutex()
    |
    +-- [ブロックリストスキャン]
    |       +-- xUnlockNotify() [コールバック呼び出し]
    |       +-- sqlite3Malloc() [配列拡張時]
    |
    +-- sqlite3_free()
    |
    +-- leaveMutex()
```

### データフロー図

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

sqlite3 *pBlocked    --->  sqlite3_unlock_notify()
xNotify callback     --->       |
pNotifyArg           --->       v
                         [sqlite3BlockedListに登録]
                                |
                                v (トランザクション終了時)
                         sqlite3ConnectionUnlocked()
                                |
                                v
                         [コールバック呼び出し]  --->  xNotify(apArg, nArg)
                                |
                                v
                         [リストから削除]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notify.c | `src/notify.c` | ソース | unlock-notify機能のメイン実装 |
| sqliteInt.h | `src/sqliteInt.h` | ヘッダー | sqlite3構造体定義、内部関数プロトタイプ |
| sqlite.h.in | `src/sqlite.h.in` | ヘッダー | 公開APIドキュメントと関数プロトタイプ |
| btree.c | `src/btree.c` | ソース | sqlite3ConnectionBlocked()呼び出し元 |
| vdbeaux.c | `src/vdbeaux.c` | ソース | sqlite3ConnectionUnlocked()呼び出し元 |
| main.c | `src/main.c` | ソース | sqlite3ConnectionClosed()呼び出し元 |
