非同期リエーパー処理完了を待機して接続リークチェックを正確化
ActiveRecord の接続リーク検出ロジックが、非同期リエーパーが存在する環境でも正確に動作するよう、メンテナンス完了を待機する処理を追加しました。これにより、リエーパーの非同期タスクが残っている間に発生する誤検知が防がれます。
背景
非同期リエーパー が構成されているテスト環境では、reaper_lock は作業のキューイングのみを保護し、実際のメンテナンスタスクは別スレッドで実行されます。その結果、check_connection_leaks がロック直後に接続状態を確認すると、まだ完了していない非同期タスクが所有する接続が残っていることがあり、誤ってリークと判定されるケースがありました。
この問題は、主に非同期 Executor が有効な場合にのみ顕在化し、従来の同期モードのテストでは検出されませんでした。そのため、テスト結果の信頼性を損なうリスクが指摘されていました。
技術的な変更
check_connection_leaks メソッド内部で、reaper_lock の保護下に wait_for_pool_maintenance を呼び出すコードが追加されました。これにより、ロック取得後にプールのメンテナンスが完全に完了するまで待機した上で、従来通り pool.reap と接続の検査が行われます。
@@ -62,6 +62,8 @@ def check_connection_leaks
pool.reaper_lock do
+ wait_for_pool_maintenance(pool)
pool.reap
pool.connections.each do |conn|
新たに導入された `wait_for_pool_maintenance` メソッドは、5 秒のデッドラインまでポーリングを行い、`@maintaining` が 0 かつ非同期 Executor の `completed_task_count` が `scheduled_task_count` と一致することを確認します。条件が満たされるかタイムアウトになるまで 0.05 秒間隔でスリープしながら待機します。
```ruby:activerecord/test/cases/test_case.rb
@@
def wait_for_pool_maintenance(pool)
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 5
while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
pool.synchronize do
executor = pool.async_executor
return if pool.instance_variable_get(:@maintaining).zero? &&
executor&.completed_task_count == executor&.scheduled_task_count
end
sleep 0.05
end
end
同時に、`activerecord/test/cases/test_case_test.rb` には新しいテストケースが追加され、非同期リエーパーが実行中でも `check_connection_leaks` が正しく動作することが検証されています。テストはフェイク Executor と `Concurrent::Event` を用いて、メンテナンス中の接続が一時的に使用中であることをシミュレートし、待機ロジックが期待通りに機能することを確認します。
## 設計判断
今回の改修は、既存の **`check_connection_leaks`** のシグネチャや呼び出し側ロジックを変更せず、内部で待機処理を挿入する形で実装されました。これにより、従来の同期モードの動作に影響を与えず、非同期 Executor が設定されていない環境でも同一コードがそのまま動作します。新メソッドはプライベートヘルパーとして実装され、テストコードからスタブ化可能な設計となっています。
また、デッドラインとポーリング間隔を固定値で設定することで実装をシンプルに保ち、過度なパラメータ化を避けています。テストは新旧両方のシナリオを網羅し、リグレッションのリスクを低減する方針が取られました。
## まとめ
`check_connection_leaks` に非同期リエーパーの完了待機を組み込んだことで、非同期 Executor 環境でも接続リークの誤検知が防止されました。既存コードへの侵入を最小限に抑えつつ、テストカバレッジを拡充した設計判断が特徴です。