ActionCableの再起動時にハートビートタイマーを停止
開発環境でコードがリロードされるたびに ActionCable::Server::Base#restart がハートビート用の Concurrent::TimerTask を停止せず、余分なスレッドプールが生成されていました。この PR はそのタイマーを他のリソースと同様に確実に終了させ、リソースリークを防ぎます。
背景
ActionCable のサーバーは restart 時に @worker_pool、@executor、@pubsub を停止し nil に設定しますが、heartbeat timer (@heartbeat_timer) の停止処理が抜けていました。タイマーは BEAT_INTERVAL 毎にブロックを実行し、その中で executor を遅延的に取得するため、restart 後に新たな executor スレッドプールが再生成されます。結果として、開発モードのコードリロードごとにスレッドプールがリークし、サーバーが停止したはずなのにハートビートが残り続けます。
この問題は開発時のパフォーマンス低下だけでなく、長時間稼働させた際に不要なスレッドが蓄積しメモリ使用量が増大する原因となります。
技術的な変更
initialize メソッドで @heartbeat_timer を nil に初期化する行が追加され、他の遅延生成リソースと同様の初期状態が確立されました。
@@
- @remote_connections = @event_loop = @worker_pool = @executor = @pubsub = nil
+ @remote_connections = @event_loop = @worker_pool = @executor = @pubsub = @heartbeat_timer = nil
restart 内に以下のコードが挿入され、タイマーが存在すれば shutdown を呼び出し、続いて nil にクリアします。これにより、setup_heartbeat_timer が次回リクエスト時に再度タイマーを作成するまで、リソースは完全に解放されます。
@@
- # Shutdown the worker pool
- @worker_pool.halt if @worker_pool
- @worker_pool = nil
+ # Shutdown the heartbeat timer
+ @heartbeat_timer.shutdown if @heartbeat_timer
+ @heartbeat_timer = nil
+
+ # Shutdown the worker pool
+ @worker_pool.halt if @worker_pool
+ @worker_pool = nil
テスト BaseTest に #restart shuts down the heartbeat timer が追加され、タイマーが restart 後に running? が偽になることと、インスタンス変数が nil になることを検証しています。4 回のリロードシナリオで緑(成功)が確認されています。
@@
- end
+ end
+
+ test "#restart shuts down the heartbeat timer" do
+ @server.send(:setup_heartbeat_timer)
+ timer = @server.instance_variable_get(:@heartbeat_timer)
+ assert_predicate timer, :running?
+
+ @server.restart
+
+ assert_not timer.running?
+ assert_nil @server.instance_variable_get(:@heartbeat_timer)
+ end
end
設計判断
@heartbeat_timer を他の遅延作成リソースと同様に shutdown と nil でクリアするパターンを採用し、リソース管理の一貫性を保ちました。
この変更は新しい設定キーを導入せず、既存の restart フローに最小限のコード追加で実装されているため、後方互換性への影響はありません。
バグ修正であるため CHANGELOG への記載は省かれていますが、開発環境でのメモリとスレッドの不要な増加を防ぐ重要な改善です。
まとめ
今回の修正により、ActionCable の再起動時に残っていたハートビートタイマーが適切に停止・破棄され、開発コードリロード時のスレッドプールリークが解消されました。リソース管理の統一とリーク防止という観点で、安定した開発体験が提供されます。