# バッチ設計書 12-DoubleLaunchChecker.Schedule

## 概要

本ドキュメントは、Jenkins における DoubleLaunchChecker.Schedule バッチの設計書である。このバッチは AperiodicWork を継承し、同一の JENKINS_HOME ディレクトリを使用して複数の Jenkins インスタンスが同時に起動していないかを定期的にチェックする。

### 本バッチの処理概要

DoubleLaunchChecker.Schedule は Jenkins の重複起動を検知するための監視バッチである。複数インスタンスが同一データディレクトリを共有すると、データ破損や予期しない動作が発生するため、早期検知が重要である。

**業務上の目的・背景**：Jenkins の運用において、コンテキストパスの変更やサーバー移行時に、誤って複数のインスタンスが同じ JENKINS_HOME を指すことがある。この状態はビルド履歴の破損、設定ファイルの競合、プラグインの異常動作など、診断が困難な問題を引き起こす。本バッチはこの問題を自動検知し、管理者に警告することで、深刻なデータ損失を防ぐ。

**バッチの実行タイミング**：60分から90分のランダムな間隔で実行（テスト環境では20秒から30秒）。ランダム化により、複数インスタンスが同時に書き込みを行う確率を下げている。

**主要な処理内容**：
1. JENKINS_HOME 直下の `.owner` ファイルのタイムスタンプを確認
2. 前回自身が書き込んだタイムスタンプと比較
3. タイムスタンプが異なる場合（他インスタンスが更新した場合）、衝突を検知
4. `.owner` ファイルに自インスタンスのプロセス ID を書き込み
5. 衝突検知時は AdministrativeMonitor を有効化し、管理画面に警告を表示

**前後の処理との関連**：このバッチは独立して動作し、他のバッチ処理との依存関係はない。ただし、衝突検知後の対応は管理者が手動で行う必要がある。

**影響範囲**：Jenkins システム全体の整合性に関わる。衝突が検知されずに放置されると、ジョブ設定、ビルド履歴、認証情報など、JENKINS_HOME 内のすべてのデータが破損する可能性がある。

## バッチ種別

重複起動検知 / システム監視

## 実行スケジュール

| 項目 | 内容 |
|-----|------|
| 実行頻度 | 60-90分間隔（ランダム） |
| 実行時刻 | 不定（ランダム間隔） |
| 実行曜日 | 毎日 |
| 実行日 | 毎日 |
| トリガー | AperiodicWork（内部タイマー） |

## 実行条件

### 前提条件

| 条件 | 説明 |
|-----|------|
| Jenkins起動済み | Jenkins インスタンスが起動し、AperiodicWork スケジューラが動作していること |
| JENKINS_HOME存在 | JENKINS_HOME ディレクトリがアクセス可能であること |
| ファイルシステム書き込み可能 | .owner ファイルへの書き込み権限があること |

### 実行可否判定

AperiodicWork として自動的に実行される。AdministrativeMonitor の有効/無効設定により、警告表示の ON/OFF は制御可能だが、チェック処理自体は常に実行される。

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | デフォルト値 | 説明 |
|-------------|-----|-----|-------------|------|
| なし | - | - | - | 外部パラメータは受け付けない |

### 入力データソース

| データソース | 形式 | 説明 |
|-------------|------|------|
| JENKINS_HOME/.owner | テキストファイル | 最後に更新したインスタンスのプロセス ID |
| Jenkins.get().getRootDir() | Java オブジェクト | JENKINS_HOME ディレクトリのパス |

## 出力仕様

### 出力データ

| 出力先 | 形式 | 説明 |
|-------|------|------|
| JENKINS_HOME/.owner | テキストファイル | 自インスタンスのプロセス ID |
| AdministrativeMonitor | Java オブジェクト | 衝突検知時の警告フラグ |
| ログファイル | テキスト | 処理結果のログ出力 |

### 出力ファイル仕様

| 項目 | 内容 |
|-----|------|
| ファイル名 | .owner |
| 出力先 | JENKINS_HOME/ |
| 文字コード | システムデフォルト |
| 内容 | プロセス ID（数値文字列） |

## 処理フロー

### 処理シーケンス

```
1. doAperiodicRun() メソッド開始
   └─ Schedule クラスが AperiodicWork により呼び出される
2. DoubleLaunchChecker.execute() を呼び出し
   └─ ExtensionList.lookupSingleton() でシングルトンインスタンスを取得
3. .owner ファイルのタイムスタンプを取得
   └─ timestampFile.lastModified() でファイル更新時刻を確認
4. タイムスタンプの比較
   └─ lastWriteTime（前回自分が書いた時刻）と比較
5. 衝突検知判定
   └─ タイムスタンプが異なり、かつ両方が0でない場合、衝突と判定
6. 衝突時の処理
   └─ collidingId を読み込み、activated フラグを true に設定
7. .owner ファイルへの書き込み
   └─ 自インスタンスの getId()（プロセス ID）を書き込み
8. lastWriteTime の更新
   └─ 書き込み後のタイムスタンプを記録
```

### フローチャート

```mermaid
flowchart TD
    A[Schedule.doAperiodicRun 開始] --> B[DoubleLaunchChecker.execute 呼び出し]
    B --> C[.owner ファイルのタイムスタンプ取得]
    C --> D{タイムスタンプ != 0 かつ<br/>lastWriteTime != 0 かつ<br/>タイムスタンプ != lastWriteTime<br/>かつ isEnabled?}
    D -->|Yes| E[衝突検知]
    E --> F[collidingId を読み込み]
    F --> G[activated = true]
    G --> H[SEVERE ログ出力]
    D -->|No| I[衝突なし]
    H --> J[.owner に自身の ID を書き込み]
    I --> J
    J --> K{書き込み成功?}
    K -->|Yes| L[lastWriteTime を更新]
    K -->|No| M[lastWriteTime = 0 に設定]
    L --> N[処理終了]
    M --> N
```

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

### 操作別データベース影響一覧

このバッチはデータベース操作を行わない。ファイルシステム上の .owner ファイルのみを操作する。

| 処理 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベース操作なし |

### テーブル別操作詳細

該当なし

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | IOException | .owner ファイルの読み込み失敗 | SEVERE ログを出力、collidingId は null のまま |
| - | IOException | .owner ファイルの書き込み失敗 | FINE ログを出力、lastWriteTime を 0 にリセット |

### リトライ仕様

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

リトライは行わない。次回の定期実行（60-90分後）で再度処理される。書き込み失敗時は lastWriteTime を 0 にリセットすることで、次回の衝突検知を安全側に倒す。

### 障害時対応

1. 書き込み権限エラー: JENKINS_HOME のパーミッションを確認し、Jenkins ユーザーに書き込み権限を付与
2. 誤検知: 同一サーバー上で Jenkins を再起動した場合など、.owner ファイルを手動で削除してリセット
3. 真の衝突検知: 重複起動している Jenkins インスタンスを特定し、一方を停止

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

| 項目 | 内容 |
|-----|------|
| トランザクション範囲 | トランザクション管理なし |
| コミットタイミング | - |
| ロールバック条件 | - |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定処理件数 | 1ファイル |
| 目標処理時間 | 数ミリ秒 |
| メモリ使用量上限 | 最小限（ファイル I/O のみ） |

## 排他制御

- ファイルシステムレベルでの排他制御は行わない
- ランダムな実行間隔により、複数インスタンスの同時書き込みを確率的に回避
- 衝突検知自体が目的のため、書き込み競合は期待される動作

## ログ出力

| ログ種別 | 出力タイミング | 出力内容 |
|---------|--------------|---------|
| FINE | 処理開始時 | "running detector" |
| SEVERE | 衝突検知時 | "Collision detected. timestamp={t}, expected={lastWriteTime}" |
| SEVERE | ファイル読み込み失敗時 | "Failed to read collision file" |
| FINE | ファイル書き込み失敗時 | 例外スタックトレース |

## 監視・アラート

| 監視項目 | 閾値 | アラート先 |
|---------|-----|----------|
| 重複起動検知 | 検知時即座 | AdministrativeMonitor（管理画面表示） |

## 備考

- DoubleLaunchChecker は @Extension アノテーションにより AdministrativeMonitor として自動登録される
- Schedule 内部クラスも @Extension アノテーションにより AperiodicWork として自動登録される
- getId() メソッドは ProcessHandle.current().pid() を使用してプロセス ID を取得（Java 9+）
- テスト環境（Main.isUnitTest が true）では、実行間隔が 20-30 秒に短縮される
- getDisplayName() は Messages.DoubleLaunchChecker_duplicate_jenkins_checker() でローカライズされた表示名を返す
