# 通知設計書 78-DLL移動デバッグ

## 概要

本ドキュメントは、ロード中のDLLファイルを削除できず一時パスに移動する場合に出力されるデバッグ通知の設計を定義する。

### 本通知の処理概要

Windows環境で使用中のDLLファイルを削除しようとした際に、OSの制約により削除が失敗し、代わりに一時パスにリネーム（移動）する処理のデバッグ通知機能である。

**業務上の目的・背景**：Windowsでは、プロセスにロードされているDLLファイルは削除できないというOS固有の制約がある。しかし、パッケージの更新やGC時にDLLファイルの削除が試行されることがある。本機能は、削除不可能なDLLを一時パスにリネームすることで、後で`Pkg.gc()`によるクリーンアップを可能にする。デバッグ通知により、この回避処理がいつ発生したかを開発者が追跡できる。

**通知の送信タイミング**：`delayed_delete_dll()`関数が呼び出された時点（`rm()`関数内で`.dll`ファイルの削除が`UV_EACCES`エラーで失敗した場合）に出力される。

**通知の受信者**：Windows環境でのパッケージ管理の問題を診断する開発者。

**通知内容の概要**：削除を試みたDLLファイルのパスと、移動先の一時パスが通知される。

**期待されるアクション**：通常は自動処理で対応不要。`Pkg.gc()`の実行で一時パスのファイルが最終的にクリーンアップされる。

## 通知種別

ログ出力（`@debug`レベル）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（DLL削除失敗時に即座に出力） |
| 優先度 | 低（デバッグレベル、通常は表示されない） |
| リトライ | なし |

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

`@debug`マクロを通じてJuliaの標準ログシステムに出力される。デフォルトでは表示されない。

## 通知テンプレート

### メール通知の場合

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

### 本文テンプレート

```
Could not delete DLL most likely because it is loaded, moving to a temporary path
```

追加情報として`path`と`temp_path`が構造化データとしてログに含まれる。

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| 該当なし | - | - | - |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| path | 削除を試みたDLLファイルのパス | `delayed_delete_dll()`の引数 | Yes |
| temp_path | 移動先の一時パス | `tempname()`で生成 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ファイル操作 | `.dll`ファイルの`rm()`が`UV_EACCES`で失敗 | `allow_delayed_delete && Sys.iswindows()` | file.jl:290-293経由でdelayed_delete_dll呼び出し |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| デバッグレベルが無効 | `@debug`はデフォルトで抑制されている |
| Windows以外のOS | この処理はWindows専用 |
| `.dll`以外のファイル | `endswith(path, ".dll")`チェック |
| `allow_delayed_delete`がfalse | `Pkg.gc()`から呼ばれた場合は遅延削除を抑制 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A["rm(path)呼び出し"] --> B{unlink失敗?}
    B -->|No| C[正常終了]
    B -->|Yes| D{Windows & .dll & UV_EACCES?}
    D -->|No| E[エラー再送出]
    D -->|Yes| F[delayed_delete_dll呼び出し]
    F --> G[一時パス生成]
    G --> H["@debug Could not delete DLL"]
    H --> I[delayed_delete_refディレクトリ作成]
    I --> J[一時パスをファイルに記録]
    J --> K["rename(path, temp_path)"]
    K --> L[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| 該当なし | - | ファイルシステムのみ参照 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| 該当なし | - | ファイルシステム上でのリネームと参照ファイルの作成 |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| rename失敗 | 一時パスへのリネームが失敗した場合 | 例外が伝搬される（通常は発生しない） |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

制限なし

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

- ファイルシステムのパス情報（DLLの元パスと一時パス）がデバッグログに含まれる
- Windows固有の機能であり、一時ファイルへの参照情報が`julia_delayed_deletes_ref`ディレクトリに保存される

## 備考

- この機能はWindows専用（`@static if Sys.iswindows()`で囲まれている）
- 一時パスは元のDLLと同じドライブ上に作成される（`tempname(abspath(dirname(path)))`）
- 遅延削除されたDLLの一時パスは`delayed_delete_ref()`ディレクトリ内のファイルに記録され、`Pkg.gc()`が後でクリーンアップする
- `rename()`が使用される（`mv()`ではない。`mv()`は再帰的に`rm()`を呼び出す可能性があるため）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | file.jl | `base/file.jl` | `delayed_delete_ref()`関数（258行目）：遅延削除参照ディレクトリのパス |
| 1-2 | file.jl | `base/file.jl` | `TEMP_CLEANUP`辞書：一時ファイルのクリーンアップ管理 |

**読解のコツ**: 遅延削除の仕組みは、(1)DLLをリネーム、(2)リネーム先を記録ファイルに保存、(3)Pkg.gc()で記録を読み取り削除、という3段階。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | file.jl | `base/file.jl` | `rm()`関数（281-324行目）：DLL削除失敗時のdelayed_delete_dll呼び出し |
| 2-2 | file.jl | `base/file.jl` | `delayed_delete_dll()`関数（329-341行目）：遅延削除の実装 |

**主要処理フロー**:
1. **290行目**: `Sys.iswindows()`チェック
2. **291行目**: `allow_delayed_delete && err.code==Base.UV_EACCES && endswith(path, ".dll")`条件チェック
3. **292行目**: `delayed_delete_dll(path)`呼び出し
4. **331行目**: `tempname(abspath(dirname(path)))`で同一ドライブ上に一時パス生成
5. **332行目**: `@debug "Could not delete DLL..."`デバッグログ出力
6. **333行目**: `mkpath(delayed_delete_ref())`で参照ディレクトリ作成
7. **334-338行目**: 一時パスをファイルに記録
8. **340行目**: `rename(path, temp_path)`でDLLをリネーム

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

```
rm(path; allow_delayed_delete=true) (file.jl:281)
    |
    +-- unlink(path) -- 失敗: IOError(UV_EACCES)
    |
    +-- [Windows & .dll] delayed_delete_dll(path) (file.jl:329)
           |
           +-- tempname(abspath(dirname(path))) (file.jl:331)
           +-- @debug "Could not delete DLL..." (file.jl:332)
           +-- mkpath(delayed_delete_ref()) (file.jl:333)
           +-- mktemp(delayed_delete_ref()) (file.jl:334)
           +-- print(io, temp_path) (file.jl:336)
           +-- rename(path, temp_path) (file.jl:340)
```

### データフロー図

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

path (DLLパス) ──────────> delayed_delete_dll()
                               |
                               +-- tempname() ──> temp_path（一時パス）
                               +-- @debug ──────────────> デバッグログ
                               +-- mktemp() ──> 参照ファイル作成
                               +-- rename() ──> DLLをtemp_pathに移動
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| file.jl | `base/file.jl` | ソース | `delayed_delete_dll()`関数（329-341行目）でのデバッグログ出力、`rm()`関数（281-324行目）からの呼び出し |
