Server#each_connection のイテレーションをスナップショット配列へ変更

rails/rails

ActionCable の Server#each_connection が、ハートビートやサーバ再起動時に発生していた RuntimeError を回避するため、ライブハッシュではなく connections が返す配列スナップショットを走査するよう変更されました。これにより、接続の追加・削除が同時に行われても安全にイテレーションでき、呼び出し側のロジックはそのまま利用可能です。

背景

each_connection は従来、connections_map.each_value でハッシュを直接走査していましたが、ワーカースレッドが同時に add_connectionremove_connection を実行すると、Ruby のハッシュはイテレーション中のキー追加を検出して RuntimeError: can't add a new key into hash during iteration を送出します(PR #57691)。この例外は、3 秒間隔のハートビート (executor.post { each_connection(&:beat) })、サーバ再起動時の全接続クローズ (each_connection { |c| c.close(...) })、および接続統計取得 (each_connection.map(&:statistics)) の三つの主要呼び出しで顕在化しました。

この振る舞いは、以前は Array を使用していた実装からハッシュへ切り替えたコミット 8ed78693c5 に起因する回帰です。Array への要素追加はイテレーション中でも安全でしたが、ハッシュへのキー追加は Ruby の仕様上例外を投げるため、ハートビートが途中で中断し、古い接続がクリーンアップされずに残る、あるいはコードリロード時に再起動処理が失敗するといった実運用上の問題が発生していました。

技術的な変更

each_connection の実装を、ハッシュ走査から connections が返す配列走査へ置き換えました。connections メソッドは既に connections_map.values を返すため、イテレーション開始時にハッシュの内容を配列にコピーし、以降の走査はそのスナップショット上で行われます。これにより、走査中にハッシュが変更されても安全です。

@@
-        connections_map.each_value(...)
+        # Iterate a snapshot: the heartbeat, #restart and statistics all walk the
+        # live connections while worker threads concurrently add/remove entries,
+        # and mutating a Hash mid-iteration raises a RuntimeError.
+        connections.each(...)

さらに、イテレーション中に接続が追加されても例外が起きないことを検証するテストが追加されました。テストは beat メソッド内で @server.add_connection(Object.new) を呼び出す擬似接続オブジェクトを作り、assert_nothing_raised@server.each_connection(&:beat) が例外を送出しないことを確認します。

@@
-    @server.each_connection(&:beat)
+    assert_nothing_raised do
+      @server.each_connection(&:beat)
+    end

この変更は each_connection の呼び出し側コードを変更せずに安全性を向上させ、既存の API 互換性を保ちます。

設計判断

イテレーション対象を snapshot 配列へ切り替える選択は、既存の connections ヘルパーを再利用し、追加のロックや専用イテレーションメソッドを導入しないことで実装コストとリスクを最小化した点が特徴です。コードベースへの侵入度を抑えることで、他コンポーネントへの副作用を防ぎながら安全性を確保しています。

代償としてイテレーション毎にハッシュの値配列が生成されますが、ActionCable の主要シナリオで求められる安全性と可読性の向上が、わずかなメモリ割り当て増加を上回ると判断された点が設計上のトレードオフです。

まとめ

Server#each_connectionconnections のスナップショット配列を走査するようになったことで、ハートビート、サーバ再起動、統計取得の三つの重要シナリオが同時接続変更に対して耐性を持ちました。結果として RuntimeError が抑止され、接続のクリーンアップや再起動が確実に実行されるようになり、ActionCable の信頼性が向上します。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
869d7a06

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断(任意)、まとめの各要素が揃っており、3部構成が明確に示されています。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトが正しい形式で使用され、GitHubリンクも PR 番号のリンク記法が正しく記載されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails/ActionCable に熟知したエンジニア向けの内容で、不要な基礎説明はなく適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の段落構成が取られ、トピックセンテンスで始まり、1段落1トピック・長さも適切です。段落間は空行で区切られています。

Diff内容との照合 ⚠ WARNING

コードブロックとDiff内容の一致

コードブロックは Diff に含まれる変更点を正しく示していますが、テスト追加部分の全体(テストメソッド定義等)が省略されているため完全な照合とは言えません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

RuntimeError のメッセージや `connections_map`、`connections` などの用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

PR の説明と一致した技術的根拠が示されており、因果関係も論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の全主張が PR タイトル、説明、Diff に裏付けられており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #57691 や「3 秒間隔」などの数値が正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の内容「Iterate a snapshot of the connections in Server#each_connection」を正しく日本語化しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTS やリリース日程等、PR に記載のない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。