# 通知設計書 49-pidfile削除失敗警告

## 概要

本ドキュメントは、Julia の FileWatching.Pidfile モジュールにおける pidfile 削除失敗警告（`@warn "failed to remove pidfile on close"`）の通知設計について記載する。

### 本通知の処理概要

`LockMonitor` のクローズ処理において、pidfile の削除に失敗した場合にユーザーに警告を発行する通知である。

**業務上の目的・背景**：pidfile ロック機構では、ロックの解放時にロックファイル（pidfile）を削除する必要がある。しかし、ファイルの削除が失敗するケースが存在する（他プロセスが既に削除した、権限の問題、Windows でのファイルロック等）。削除に失敗したロックファイルが残留すると、後続のプロセスがロックを取得できなくなる可能性がある。本警告は、ロック解放が不完全であることをユーザーに通知し、手動での対処を促す目的で発行される。

**通知の送信タイミング**：`Base.close(lock::LockMonitor)` 内で、pidfile の削除結果が `true` でなかった場合（削除失敗またはスキップ）に送信される。

**通知の受信者**：pidfile ロック機構を使用するプロセスを実行している開発者・運用者。

**通知内容の概要**：pidfile の削除に失敗したこと、対象のパス、および削除結果（`removed` の値）を通知する。

**期待されるアクション**：ユーザーは残留した pidfile を手動で確認・削除する。原因が再現する場合は、ファイルシステムの権限設定やプロセスの競合状況を調査する。

## 通知種別

ログ出力（`@warn` マクロによる標準エラー出力へのWarnレベルログ）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（`@warn` マクロによる即時ログ出力） |
| 優先度 | 中（Warn レベル） |
| リトライ | なし（ログ出力のため） |

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

Julia のログシステムに委譲される。デフォルトでは `stderr` に出力される。

## 通知テンプレート

### メール通知の場合

該当なし（ログ出力のみ）

### 本文テンプレート

```
┌ Warning: failed to remove pidfile on close
│   path = "<pidfileのパス>"
│   removed = <削除結果>
└ @ FileWatching.Pidfile stdlib/FileWatching/src/pidfile.jl:370
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| `path` | pidfile のファイルパス | `lock.path` | Yes |
| `removed` | 削除結果（`false` または `IOError` オブジェクト） | `tryrmopenfile` の返り値 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| API呼び出し | `close(lock::LockMonitor)` | `removed !== true` | pidfile の削除結果が成功（`true`）でない場合 |
| GCファイナライザ | `finalizer(close, lock)` によるGC時クローズ | 同上 | GCによる自動クローズ時も同様 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| `isopen(lock.fd)` が `false` | 既にクローズされている場合は早期リターン |
| `samefile` 不一致 | `lock.fd` と `path` のファイルが異なる場合は削除をスキップ（他プロセスのロックを削除しない） |
| 削除成功 | `removed === true` の場合は警告なし |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A["close(lock::LockMonitor) 呼び出し"] --> B["update Timer を停止"]
    B --> C{"isopen(lock.fd)?"}
    C -->|No| D["return false"]
    C -->|Yes| E["stat(path) 取得"]
    E -->|IOError| F["removed = IOError"]
    E -->|成功| G{"samefile(stat(lock.fd), stat(path))?"}
    G -->|No| H["削除スキップ"]
    G -->|Yes| I["tryrmopenfile(path)"]
    I --> J["close(lock.fd)"]
    F --> J
    H --> J
    J --> K{"removed === true?"}
    K -->|Yes| L["return true"]
    K -->|No| M["@warn 警告出力"]
    M --> N["return false"]
```

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

### 参照テーブル一覧

該当なし（ファイルシステムのみ使用）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| stat失敗 | パスが既に削除済み、権限不足 | `removed = IOError` として警告を出力 |
| 削除失敗 | ファイルロック、権限不足 | `tryrmopenfile` が `IOError` を返し警告を出力 |
| ファイル不一致 | 他プロセスがpidfileを置き換え済み | 削除をスキップし警告を出力 |

### リトライ仕様

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

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 上限 | close 操作1回につき最大1回 |

### 配信時間帯

制限なし

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

- ログメッセージに pidfile のフルパスと `IOError` の詳細が含まれる可能性がある
- `samefile` チェックにより、他プロセスのロックファイルを誤って削除することを防止している

## 備考

- `tryrmopenfile` は Windows 環境で特殊処理を行う（rename してから delete）
- GC のファイナライザ経由で `close` が呼ばれた場合も本警告が発行される可能性がある
- `removed` が `false` の場合は `samefile` チェックに失敗したケース、`IOError` の場合は `stat` または `rm` が失敗したケース

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | pidfile.jl | `stdlib/FileWatching/src/pidfile.jl` | `LockMonitor` 構造体（57-86行目）、特に `path`, `fd`, `update` フィールド |

**読解のコツ**: `LockMonitor` は `mutable struct` で `finalizer(close, lock)` が登録される。`update` は mtime 更新用の Timer。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | pidfile.jl | `stdlib/FileWatching/src/pidfile.jl` | `Base.close(lock::LockMonitor)` 関数（349-372行目） |

**主要処理フロー**:
1. **350-351行目**: `update` Timer を停止
2. **352行目**: `isopen(lock.fd)` チェック
3. **354-363行目**: `stat(path)` を取得（IOError は `removed = ex` として捕捉）
4. **364行目**: `samefile(stat(lock.fd), pathstat)` で同一ファイルか確認
5. **366行目**: `tryrmopenfile(path)` で削除試行
6. **368行目**: `close(lock.fd)` でファイルディスクリプタをクローズ
7. **369-370行目**: `removed === true` でない場合に `@warn` を発行（本通知）

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | pidfile.jl | `stdlib/FileWatching/src/pidfile.jl` | `tryrmopenfile` 関数（316-342行目） |

**主要処理フロー**:
- **320行目**: Windows の場合は rename してから削除
- **335-342行目**: `rm(path)` を試行し、成功なら `true`、`IOError` ならその例外を返す

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

```
close(lock::LockMonitor)              [pidfile.jl:349]
    │
    ├─ close(lock.update)              [350-351行目: Timer停止]
    │
    ├─ stat(lock.path)                 [355-363行目]
    │      └─ IOError → removed = ex
    │
    ├─ samefile(stat(lock.fd), pathstat) [364行目]
    │
    ├─ tryrmopenfile(lock.path)         [366行目]
    │      ├─ (Windows) rename → rm
    │      └─ rm(path) → true / IOError
    │
    ├─ close(lock.fd)                   [368行目]
    │
    └─ removed !== true → @warn         [370行目: 本通知]
```

### データフロー図

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

lock.path ────────────▶ stat() → pathstat
lock.fd ──────────────▶ stat() → fdstat
                            │
                            ▼
                    samefile(fdstat, pathstat)
                            │
                    true → tryrmopenfile()
                            │
                    removed の値チェック
                            │
                    !== true → @warn ─────────▶ ログ出力 (stderr)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| pidfile.jl | `stdlib/FileWatching/src/pidfile.jl` | ソース | 通知発行元、pidfile ロック機構全体のロジック |
