Channel#stop_stream_from がシンボルを文字列へ変換しストリーム解除を正しく行うよう改善
ActionCable の stop_stream_from がシンボルをキーにした場合にストリームを解除できなかった不具合を、引数を文字列へ強制変換することで修正し、テストを追加して動作を検証した。
背景
stream_from は受け取った broadcasting を String(broadcasting) に変換してハッシュキーとして保存するため、シンボルでも文字列キーに統一されています。
しかし stop_stream_from は変換処理がなく、生の引数でハッシュから削除しようとした結果、シンボルと文字列のキーが一致せずストリームが残存しました。
この不整合は stream_from :chat と stop_stream_from :chat の組み合わせで発生し、接続が閉じられるまで pubsub.unsubscribe が呼ばれないという実害を生んでいました。
技術的な変更
stop_stream_from の冒頭に broadcasting = String(broadcasting) を追加し、stream_from と同様に引数を文字列へ強制変換することでキーの統一を実現しました。
@@ -119,6 +119,8 @@ def stream_for(broadcastables, ...)
# Unsubscribes streams from the named `broadcasting`.
def stop_stream_from(broadcasting)
+ broadcasting = String(broadcasting)
+
callback = streams.delete(broadcasting)
if callback
pubsub.unsubscribe(broadcasting, callback)
さらに stream_test.rb にシンボルを用いたストリーム登録と解除のテストを追加し、解除後の購読者数が 0 になることを確認しています。
@@ -219,6 +219,21 @@ def channel.subscribed
assert_equal 1, subscribers["room_two"].size
end
+ test "stop_stream_from with a non-string broadcasting matches stream_from" do
+ connection = Connection.new(server, socket)
+
+ channel = ChatChannel.new connection, "{id: 3}"
+ channel.subscribe_to_channel
+
+ channel.stream_from :room_one
+
+ assert_equal 1, subscribers_of(connection).size
+
+ channel.stop_stream_from :room_one
+
+ assert_equal 0, subscribers_of(connection).size
+ end
設計判断
この変更は 同族メソッド間の振る舞いを統一 することを目的としており、既存のロジックに最小限の一行追加で実装されています。
stream_from の実装を変更せずに stop_stream_from 側で型変換を行う方針は、後方互換性を保ちつつバグ修正だけを行うというトレードオフを示しています。
結果として、シンボル・文字列問わず同一キーでストリームを管理でき、開発者は stop_stream_from を安全に呼び出すことが可能になりました。
まとめ
stop_stream_from に文字列への強制変換を導入し、stream_from とキー生成ロジックを揃えることで、ストリーム解除漏れという実装バグを解消しました。テスト追加により挙動が保証され、ActionCable のストリーム管理の一貫性が向上しました。