# 機能設計書 10-Lock

## 概要

本ドキュメントは、Hack Standard Library (HSL) のファイル操作モジュール `HH\Lib\File\Lock` クラスの機能設計書である。

### 本機能の処理概要

**業務上の目的・背景**：
複数のプロセスが同じファイルに同時にアクセスする場合、データの整合性を保つためにファイルロック機構が必要となる。例えば、ログファイルへの同時書き込み、設定ファイルの排他的更新、共有リソースへのアクセス制御などの場面で使用される。`Lock`クラスは、`IDisposable`パターンを活用して、ファイルロックの取得と解放を安全に管理する。usingスコープを抜けると自動的にロックが解放されるため、デッドロックやリソースリークを防止できる。

**機能の利用シーン**：
- 複数プロセスからの同時アクセスが想定されるログファイル
- 排他的に更新する必要がある設定ファイル
- プロセス間の同期（ロックファイルによるセマフォ）
- 共有リソースへのアクセス制御
- データベースファイルの整合性保証

**主要な処理内容**：
1. ファイルハンドルの`lock()`または`tryLockx()`メソッドを呼び出し
2. OS\flock()でファイルロックを取得
3. Lockインスタンスを返却
4. usingスコープを抜けると`__dispose()`でロック解放

**関連システム・外部連携**：
- `OS\flock()`でシステムレベルのファイルロックを操作
- `File\Handle`インターフェースの`lock()`/`tryLockx()`メソッドから使用
- `LockType`列挙型でロックの種類を指定

**権限による制御**：ファイルシステムのパーミッションに依存

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 画面なし | - | 本プロジェクトはUIを持たないライブラリのため該当なし |

## 機能種別

ファイルI/O / 排他制御 / リソース管理

## 入力仕様

### 入力パラメータ

#### Lockコンストラクタ（内部使用）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| fd | OS\FileDescriptor | Yes | ロック対象のファイルディスクリプタ | 有効なFileDescriptor |

#### Handle::lock()メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| type | LockType | Yes | ロックの種類（SHARED/EXCLUSIVE） | LockType列挙値 |

#### Handle::tryLockx()メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| type | LockType | Yes | ロックの種類（SHARED/EXCLUSIVE） | LockType列挙値 |

### 入力データソース

- ファイルハンドルのlock()/tryLockx()メソッド経由で呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | Lock | ロックを管理するDisposableオブジェクト |

### 出力先

- 呼び出し元プログラムへの戻り値

## 処理フロー

### 処理シーケンス

```
1. handle->lock($type)またはhandle->tryLockx($type)が呼び出される
   └─ lock(): ロック取得までブロック
   └─ tryLockx(): ロック取得を試行、失敗時は例外
2. OS\flock()でシステムレベルのロックを取得
   └─ lock(): LOCK_SH または LOCK_EX
   └─ tryLockx(): LOCK_SH|LOCK_NB または LOCK_EX|LOCK_NB
3. Lockインスタンスを作成して返却
   └─ FileDescriptorを保持
4. usingスコープ終了時（__dispose）
   └─ OS\flock($fd, LOCK_UN)でロック解放
   └─ EBADFエラー（既にクローズ済み）は無視
```

### フローチャート

```mermaid
flowchart TD
    A[lock/tryLockx開始] --> B{メソッド判定}
    B -->|lock| C[OS::flock で LOCK_SH/LOCK_EX]
    B -->|tryLockx| D[OS::flock で LOCK_SH|LOCK_NB / LOCK_EX|LOCK_NB]
    C --> E{ロック取得成功?}
    D --> F{ロック取得成功?}
    E -->|No| G[ブロックして待機]
    G --> C
    E -->|Yes| H[Lock インスタンス作成]
    F -->|No| I{EAGAIN?}
    I -->|Yes| J[AlreadyLockedException]
    I -->|No| K[他のErrnoException]
    F -->|Yes| H
    H --> L[Lock 返却]
    L --> M{using スコープ終了?}
    M -->|Yes| N[__dispose: LOCK_UN]
    N --> O{EBADF?}
    O -->|Yes| P[エラー無視]
    O -->|No| Q[正常終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 共有ロック | 複数プロセスが同時に共有ロックを保持可能 | LockType::SHARED |
| BR-02 | 排他ロック | 排他ロックは同時に1プロセスのみ保持可能 | LockType::EXCLUSIVE |
| BR-03 | ロック待機 | lock()はロック取得までブロック | lock()使用時 |
| BR-04 | 即時失敗 | tryLockx()はロック取得できなければ即座に例外 | tryLockx()使用時 |
| BR-05 | 自動解放 | usingスコープを抜けると自動的にロック解放 | IDisposable使用時 |

### LockType詳細

| タイプ | フラグ | 説明 |
|--------|--------|------|
| SHARED | LOCK_SH | 読み取りロック。複数プロセスが同時に保持可能 |
| EXCLUSIVE | LOCK_EX | 書き込みロック。単一プロセスのみ保持可能 |

### ロック互換性マトリクス

|  | 既存SHARED | 既存EXCLUSIVE |
|--|------------|---------------|
| 新規SHARED | 許可 | ブロック/失敗 |
| 新規EXCLUSIVE | ブロック/失敗 | ブロック/失敗 |

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

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| AlreadyLockedException | File例外 | tryLockx()でロック取得失敗（EAGAIN） | ロック解放を待つか、リトライする |
| EBADF | OS\ErrnoException | ファイルが既にクローズされている | 無視される（__dispose内） |
| EINTR | OS\ErrnoException | シグナルにより中断 | 自動リトライ（OS依存） |
| EWOULDBLOCK | OS\ErrnoException | ノンブロッキングでロック取得失敗 | AlreadyLockedExceptionに変換 |

### リトライ仕様

- lock()はロック取得までブロック（実質的に自動リトライ）
- tryLockx()は即座に結果を返す（リトライは呼び出し元の責任）

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

該当なし（ファイルロックはトランザクションではない）

## パフォーマンス要件

- lock()はブロッキングシステムコールのため、非同期コードの実行を妨げる
- 長時間のロック保持は他のプロセスをブロックする可能性
- ロックの粒度（ファイル全体）に注意

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

- ファイルロックは協調的（advisory）であり、強制的ではない
- ロックを無視してファイルにアクセスすることは可能
- NFS上のファイルロックは環境依存の動作をする場合がある
- デッドロック回避は呼び出し元の責任

## 備考

- Hackの`using`文はDisposableパターンを強制する
- `<<__ReturnDisposable>>`属性がlock()/tryLockx()に付与されている
- ファイルクローズ後のロック解放はエラーにならない（EBADFを無視）
- 同一プロセス内での二重ロックの動作はOS依存

---

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

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

### 推奨読解順序

#### Step 1: Lockクラスの構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Lock.php | `src/file/Lock.php` | Lockクラス定義（23-37行目） |

**読解のコツ**: IDisposableを実装し、__dispose()でロック解放を行う。

**主要処理フロー**:
- **23行目**: `implements \IDisposable` - Disposableパターン
- **25-26行目**: コンストラクタでFileDescriptorを受け取る
- **28-36行目**: `__dispose()` - ロック解放処理
- **30行目**: `OS\flock($this->fd, _OS\LOCK_UN)` - ロック解放
- **31-35行目**: EBADFエラーの無視

#### Step 2: LockTypeを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | LockType.php | `src/file/LockType.php` | LockType列挙型（15-28行目） |

**主要なタイプ**:
- **21行目**: `SHARED = OS\LOCK_SH` - 共有ロック
- **27行目**: `EXCLUSIVE = OS\LOCK_EX` - 排他ロック

#### Step 3: ロック取得処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | CloseableFileHandle.php | `src/file/_Private/CloseableFileHandle.php` | lock()とtryLockx()（50-67行目） |

**主要処理フロー**:
- **51-54行目**: `lock()` - OS\flock()でブロッキングロック取得
- **57-67行目**: `tryLockx()` - LOCK_NBでノンブロッキングロック試行
- **59行目**: `$type | OS\LOCK_NB` - ノンブロッキングフラグを追加
- **61-64行目**: EAGAIN時にAlreadyLockedExceptionを投げる

#### Step 4: テストコードで使用例を確認

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | FileTest.php | `tests/file/FileTest.php` | testLock()（160-189行目） |

**主要処理フロー**:
- **166-169行目**: 共有ロック同士は共存可能
- **173-177行目**: 共有ロック中に排他ロックは失敗
- **181-187行目**: 排他ロック中に共有ロックは失敗

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

```
CloseableFileHandle::lock($type)
    |
    +-- OS\flock($this->impl, $type)  [LOCK_SH または LOCK_EX]
    |       |
    |       +-- ブロックしてロック取得を待機
    |
    +-- new File\Lock($this->impl)
            |
            +-- implements IDisposable
            +-- __dispose()
                    |
                    +-- OS\flock($fd, LOCK_UN)

CloseableFileHandle::tryLockx($type)
    |
    +-- OS\flock($this->impl, $type | OS\LOCK_NB)
    |       |
    |       +-- [成功] new File\Lock($this->impl)
    |       +-- [EAGAIN] throw File\AlreadyLockedException
    |       +-- [その他] throw OS\ErrnoException
    |
    +-- new File\Lock($this->impl)
```

### データフロー図

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

LockType (SHARED/EXCLUSIVE)
    |
    v
┌─────────────────────────────────────────────────────────────────────┐
│ lock() / tryLockx()                                                 │
│   - OS\flock() でシステムレベルのロックを取得                        │
│   - lock(): ブロッキング                                            │
│   - tryLockx(): ノンブロッキング（LOCK_NB）                         │
└─────────────────────────────────────────────────────────────────────┘
    |
    v
┌─────────────────────────────────────────────────────────────────────┐
│ Lock                                                                │
│   - FileDescriptor を保持                                           │
│   - IDisposable で自動解放                                          │
└─────────────────────────────────────────────────────────────────────┘
    |
    v
using ($handle->lock(LockType::EXCLUSIVE)) {
    // 排他的ファイルアクセス
}
// ← 自動的にLOCK_UN
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| Lock.php | `src/file/Lock.php` | ソース | Lockクラスの定義 |
| LockType.php | `src/file/LockType.php` | ソース | LockType列挙型の定義 |
| AlreadyLockedException.php | `src/file/AlreadyLockedException.php` | ソース | ロック失敗時の例外 |
| Handle.php | `src/file/Handle.php` | ソース | lock()/tryLockx()インターフェース |
| CloseableFileHandle.php | `src/file/_Private/CloseableFileHandle.php` | ソース | lock()/tryLockx()実装 |
| FileTest.php | `tests/file/FileTest.php` | テスト | ロック機能のテスト |
