Skip to content

fix(lib-jj-helpers): pipeline_lock reclaim の cache 済み content 再奪取レースを修正 - #344

Merged
aloekun merged 1 commit into
masterfrom
fix/pipeline-lock-reclaim-race
Aug 1, 2026
Merged

fix(lib-jj-helpers): pipeline_lock reclaim の cache 済み content 再奪取レースを修正#344
aloekun merged 1 commit into
masterfrom
fix/pipeline-lock-reclaim-race

Conversation

@aloekun

@aloekun aloekun commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • finish_reclaim に検証を追加: sentinel が「gate を正当化した stale content のまま」かを確認してから除去する。不一致・消失・読取失敗は一切触れず Busy に倒す
  • reclaim marker を takeover 完了まで保持する (SentinelGate::Acquired が marker path を運び、takeover_stale_lock が sentinel と共に除去)。takeover 実行中の同一 gate 再作成を構造的に排除
  • 決定論再現テスト + marker 残留 leak guard + 高競合 stress (32 threads×400 rounds、opt-in #[ignore]) を追加
  • reap_orphaned_reclaim_marker 等の doc に残っていた誤った前提 (「同じ path が再作成される余地は無い」) を実態に修正

Context

Why: PR #342 の CI (windows-latest) で concurrent_takeover_with_orphaned_sentinel_single_winner が「得た数: 2」で失敗した。調査の結果 flake ではなく master 由来の実バグ#342 ブランチは lib-jj-helpers に一切触れていない (diff で確認済み)。

メカニズム: 出遅れスレッドが除去前に読んだ stale sentinel content S0 を握ったまま reclaim に入ると、勝者が自己修復を終えて reclaim marker を除去した後でも、content 由来の同一 gate path を再作成して勝ててしまう。旧実装はそこで sentinel を検証なしに除去していたため、勝者の fresh sentinel を破壊して 2 本目の takeover 実行権が発生し、勝者の rename 前に lock を読めば双方 Acquired になる。

なぜ CI でだけ発生したか: 「S0 を読んでから gate を作るまでの µs 窓」での preemption が必要で、2 vCPU runner + 8 スレッドでは現実的に起きるが、多コア開発機ではほぼ起きない (12,800 回 + affinity 2 コア固定でも 0 件)。round 0 で出たのはスレッド起動スタガーが最大のため。ubuntu leg にも同じレースはある (Windows 固有ではない)。

副次的意義: ADR-065 の CI matrix (2 コア Windows runner) が、多コアのローカルでは再現不能な実バグを land 前に炙り出した実例 (ADR-063 の Linux lock レース発見と同型)。

Validation

  • 決定論再現テスト (red→green): 「sentinel = 勝者の fresh 内容、marker 不在」状態で cache 済み stale content の reclaim_stale_sentinel を直接呼ぶ。修正前 FAILED (CI と同じ強奪挙動をスレッド並行なしで 100% 再現)、修正後 ok
  • 別仮説 (write 途中の部分読み取り) は 91.8 万回読取の実測 partial=0 で棄却してから本経路を特定
  • cargo test -p lib-jj-helpers: 全 pass (新規 3 テスト含む)。高競合 stress 32×400 pass
  • cargo test --workspace: 全 pass / clippy -D warnings: clean
  • pnpm push pre-push review: simplicity / security 両 approved、pr_size 209 行

References

Summary by CodeRabbit

  • バグ修正
    • 古いロック情報の回収処理を改善しました。
    • 競合が発生した場合、稼働中のロックを誤って削除せず、安全に処理を継続します。
    • ロックの引き継ぎ完了まで状態を適切に保持し、孤立したロック情報も安全に整理します。
    • 複数の処理が同時にロックを取得しようとした際の安定性を向上しました。

PR #342 CI (windows-latest, 2 コア runner) で
concurrent_takeover_with_orphaned_sentinel_single_winner が「得た数: 2」で失敗した
根本原因の修正。出遅れスレッドが除去前に読んだ stale sentinel content を握ったまま
reclaim に入ると、勝者が finish_reclaim 完了時に reclaim marker を除去した後で
content 由来の同一 gate path を再作成でき、勝者の fresh sentinel を無条件除去して
2 本目の takeover 実行権を得ていた (勝者の rename 前に lock を読めば双方 Acquired)。

2 層で塞ぐ:
- finish_reclaim: sentinel が gate を正当化した stale content のままかを検証してから
  除去する。不一致 / 消失 / 読取失敗は一切触れず Busy (検証済み: 修正前に決定論
  テストが red、修正後 green)。
- reclaim marker を takeover 完了まで保持する (SentinelGate::Acquired が marker path を
  運び、takeover_stale_lock が sentinel と共に除去)。takeover 実行中の同一 gate
  再作成を構造的に排除。

検証: 決定論再現テスト (red→green) + marker 残留 leak guard + 高競合 stress
(32 threads x 400 rounds、opt-in ignored) + 既存 concurrency guard 全 pass +
workspace 全 test pass + clippy -D warnings clean。
verify-concurrency-by-observation (計測で確認、推論に頼らない) に従い、
部分書き込み仮説は 91.8 万回読取の実測 (partial=0) で棄却してから本経路を特定した。
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

stale sentinel の reclaim 前に内容を検証し、競合時は Busy を返すように変更しました。reclaim marker は takeover 完了まで保持します。孤立 marker の回収条件を限定し、競合回帰テストと stress test を追加しました。

Changes

pipeline_lock reclaim

Layer / File(s) Summary
reclaim 検証と取得状態
src/lib-jj-helpers/src/pipeline_lock.rs
reclaim 前に sentinel content を検証します。不一致や読み取り失敗時は sentinel を削除せず、Busy または Unavailable を返します。成功した reclaim marker は takeover 完了まで保持します。
takeover 完了と marker 回収
src/lib-jj-helpers/src/pipeline_lock.rs
takeover 完了後に sentinel と reclaim marker を削除します。孤立した reclaim marker は stale と判定された場合のみ削除します。
競合回帰テスト
src/lib-jj-helpers/src/pipeline_lock/tests.rs
fresh sentinel との競合、孤立 sentinel の自己修復、および 32 スレッド・400 ラウンドの同時 takeover を検証します。

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SentinelGate
  participant finish_reclaim
  participant Sentinel
  participant ReclaimMarker
  SentinelGate->>finish_reclaim: stale content と marker path を渡す
  finish_reclaim->>Sentinel: 現在の内容を検証する
  finish_reclaim-->>SentinelGate: 一致しない場合は Busy
  finish_reclaim->>Sentinel: 一致時に sentinel を再作成する
  finish_reclaim->>ReclaimMarker: takeover 完了まで marker を保持する
  SentinelGate->>ReclaimMarker: takeover 完了後に削除する
  SentinelGate->>Sentinel: takeover 完了後に削除する
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed タイトルは、キャッシュ済みの stale sentinel content による pipeline_lock の reclaim 競合修正という主変更を正確に示しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pipeline-lock-reclaim-race

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: issue_comment (created) / 実行 run
  • CI: CodeRabbit のみ PENDING (Review in progress、実行中)。他の CI check (build/test 等) は本 PR に紐付いていない。mergeStateStatus: UNSTABLE(pending check 起因と推測、MERGEABLE 自体は問題なし)
  • レビュー状況: CodeRabbit はレビュー未着 (処理中の "in progress" 通知コメントのみ、findings 未生成)。人間レビュー・他 bot レビューなし (reviews API 応答は空)。インラインコメントなし。会話コメントも CodeRabbit の進行中通知 1 件のみ。

Verdict: approved

(findings 0 件のため .takt/facets/instructions/analyze-coderabbit.md の Verdict Rules 「findings array が空なら approved」に該当。ただし CodeRabbit レビュー自体がまだ処理中で完了していない点に留意 — 完了後に findings が出れば次回発火時に再分析される)

Applicable Findings (Critical / High / Major)

該当なし (レビュー指摘 0 件)

Applicable Findings (Medium 以下)

該当なし

Filtered (not applicable)

該当なし

Diff 概要 (レビュー指摘が無いため軽量サマリー)

  • 変更ファイル: 2 件 (+182 / -25)
    • src/lib-jj-helpers/src/pipeline_lock.rs: SentinelGate::Acquiredreclaim_marker: Option<PathBuf> を追加し、reclaim 経由確保時に marker を takeover 完了まで保持。finish_reclaim に sentinel content の再検証 (TOCTOU 対策) を追加し、cache 済み stale content を握った出遅れスレッドが勝者の fresh sentinel を破壊する経路を塞ぐ。PR ci: Windows/Linux の 2 OS matrix を新設し hooks smoke test を追加 (ADR-065) #342 CI (windows-latest) で実測された「同時 Acquired 2 つ」の修正。
    • src/lib-jj-helpers/src/pipeline_lock/tests.rs: 上記レースの決定論再現テスト、marker leak 検証テスト、および opt-in の高競合ストレステスト (#[ignore]) を追加。
  • 変更の性質: 既存 pipeline_lock の並行性バグ修正 + regression テスト追加。新規依存や public API の破壊的変更は無し。

次のアクション

  • CodeRabbit のレビュー完了を待ち、findings が出た時点で本 workflow の次回発火時に再分析する (本セッションでは待機・ポーリングしない)。
  • CI が CodeRabbit のみで build/test 系 check が付与されていない点は、想定通りか (PR trigger 条件) を確認しておくと安全。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/lib-jj-helpers/src/pipeline_lock.rs (1)

179-186: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

marker 除去のエラーを sentinel と同じ方針でログしてください。

Line 179-183 は sentinel 除去の失敗を NotFound 以外でログします。Line 184-186 は marker 除去の失敗を完全に無視します。marker が残留すると、同じ stale content を持つ後続スレッドは gate を取得できず Busy に倒れます。SENTINEL_STALE_SECS 経過後に reap_orphaned_reclaim_marker が回収するため自己修復しますが、その間の挙動の原因が観測できません。除去順序 (sentinel → marker) 自体は正しいため、変更はログのみで十分です。

♻️ ログ方針を揃える差分
             if let Some(marker) = reclaim_marker {
-                let _ = std::fs::remove_file(&marker);
+                if let Err(e) = std::fs::remove_file(&marker) {
+                    if e.kind() != std::io::ErrorKind::NotFound {
+                        eprintln!("[pipeline-lock] reclaim marker の除去に失敗 (継続): {}", e);
+                    }
+                }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib-jj-helpers/src/pipeline_lock.rs` around lines 179 - 186, Update the
reclaim_marker removal in the takeover cleanup flow to handle errors like the
sentinel removal: ignore NotFound, but log other failures with eprintln! and the
existing pipeline-lock context. Preserve the current sentinel-then-marker
removal order and do not otherwise change the cleanup behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/lib-jj-helpers/src/pipeline_lock.rs`:
- Around line 179-186: Update the reclaim_marker removal in the takeover cleanup
flow to handle errors like the sentinel removal: ignore NotFound, but log other
failures with eprintln! and the existing pipeline-lock context. Preserve the
current sentinel-then-marker removal order and do not otherwise change the
cleanup behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a384fa4d-5153-4fae-8ec1-a8e71eba6032

📥 Commits

Reviewing files that changed from the base of the PR and between 2b2b37c and 4bfabef.

📒 Files selected for processing (2)
  • src/lib-jj-helpers/src/pipeline_lock.rs
  • src/lib-jj-helpers/src/pipeline_lock/tests.rs

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Monitor 分析 (GitHub Actions バックストップ)

  • トリガー: pull_request_review (submitted) / 実行 run
  • CI: CodeRabbit pass (Review completed)。他の CI check (build/test 等) は本 PR に紐付いていない。mergeStateStatus: UNSTABLE (pending の自 workflow analyze check 起因、mergeable: MERGEABLE 自体は問題なし)。
  • レビュー状況: CodeRabbit がレビューを完了 (COMMENTED, nitpick 1 件)。人間レビュー・他 bot レビューなし (reviewDecision 空)。インラインコメントなし (nitpick は review body 内)。
  • Verdict: approved

(前回発火 (issue_comment, 17:50:11Z 投稿) 時点では CodeRabbit レビューが処理中で findings 0 件だったが、今回のレビュー submitted (17:51:47Z) で完了し nitpick 1 件が確定した — 分析価値のある新情報のため再分析。)

Applicable Findings (Critical / High / Major)

該当なし

Applicable Findings (Medium 以下)

# File (Line) Reviewer Issue Recommended Action
1 src/lib-jj-helpers/src/pipeline_lock.rs (179-186, nitpick / Trivial / Low value) CodeRabbit takeover_stale_lock の reclaim marker 除去 (let _ = std::fs::remove_file(&marker);) が sentinel 除去と異なり NotFound 以外のエラーもログしていない。marker 残留時は後続スレッドが Busy に倒れるが原因が観測できない (SENTINEL_STALE_SECS 経過後に自己修復はする) sentinel 除去と同じ方針 (NotFound は無視、それ以外は eprintln! でログ) に揃える。severity が Trivial のため任意対応

Filtered (not applicable)

該当なし

次のアクション

  • 上記 nitpick は Trivial/Low value のため必須対応ではない。次回このファイルを触るタイミングでの ついで修正 (sentinel 除去と同じログ方針への統一) を検討。
  • build/test 系 CI check が本 PR に付与されていない点は想定通りかを確認しておくと安全 (前回分析から継続する軽微な観察事項)。

@aloekun
aloekun merged commit 6cd1e92 into master Aug 1, 2026
2 checks passed
@aloekun
aloekun deleted the fix/pipeline-lock-reclaim-race branch August 1, 2026 18:01
aloekun added a commit that referenced this pull request Aug 2, 2026
…3) (#346)

PR #342/#343/#344 の post-merge feedback 採用候補 14 件のうち、2026-08-02 の
方針決定 (文書系 10 件 = 1 docs バッチ PR、実装系 4 件 = A/B 系統別 PR) に従い
5 エントリとして登録:

- 順位 359: 文書系 10 件の docs バッチ (dev-conventions 集中、Severity High 2 件含む)
- 順位 360: cargo test コマンド等価性検証テスト (#342/#343 で連続指摘 = Frequency High)
- 順位 361: JJ_VERSION の 2 ファイル一致検証テスト (順位 360 と同一 PR 可)
- 順位 362: git subprocess ブランチ名依存引数の custom lint rule (#343 incident 由来)
- 順位 363: check-ci-coderabbit の detached HEAD 回帰統合テスト (順位 362 と同一 PR 可)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant