Avoid race condition in connection maintenance thread during leak check
この修正は、接続プールの保守スレッドとリークチェックスレッド間で発生していた競合状態を明示的に同期させ、テストの不安定さを解消します。
背景
test_check_connection_leaks_does_not_report_connections_held_for_async_reape... は、reaper_lock 内で保守作業中の接続がリークとして誤検知されないことを検証するテストです。PR #57744 では、非同期実行環境下で保守作業が完了するまで待機させることでフラークを防ごうとしましたが、結果として別のレースが発生しテストが再び不安定になりました。今回の PR は、その新たなレースを解消するためにテストコードに同期ポイントを追加しています。
技術的な変更
追加された同期オブジェクト
maintenance_started = Concurrent::Event.new
Concurrent::Event はスレッド間で「開始」を通知できるシンプルな同期プリミティブです。保守処理が開始した瞬間に maintenance_started.set が呼び出され、テスト側は maintenance_started.wait でその完了を待ちます。
保守ブロック内部でのシグナル送信
pool.send(:sequential_maintenance, ->(candidate) { candidate.equal?(conn) }) do
maintenance_started.set
release_maintenance.wait
end
保守スレッドが対象接続を検出した直後にシグナルを送ることで、テストは「保守が実際にチェックアウトを行った」ことを確実に把握できます。
テスト側での待機順序の変更
maintenance_started.wait
assert_predicate conn, :in_use?
従来は wait_for(message: ..., timeout: 1) { conn.in_use? } で暗黙的にポーリングしていましたが、ここでは明示的にシグナル待ちを行い、続いて assert_predicate conn, :in_use? によって接続が使用中であることを確認します。これにより、保守スレッドが接続を取得する前にテストがアサートを実行する可能性が排除されます。
設計判断
テストコードのみでレースを防止するという選択は、実装コードに変更を加えずにテストの信頼性を高めるという方針に沿っています。Concurrent::Event を用いた明示的同期は、スレッド間の順序を保証しつつ、テスト以外の動作に影響を与えません。さらに、assert_predicate を使用したシンプルなアサーションに置き換えることで、テストの可読性と意図が明確になります。これらの変更は、保守スレッドのロジックそのものを変更せず、テスト環境だけで安全にレース条件を除去する実装パターンの一例です。
まとめ
今回の修正は、保守スレッドとリークチェックスレッド間の競合を Concurrent::Event による同期 で排除し、テストのフラークを根本的に解消します。テストコードのみに限定した調整であり、実装やパフォーマンスへの副作用はありません。結果として、接続プールの保守ロジックを安全に検証できる信頼性の高いテストが提供されます。