ActionCableのunsubscribeを冪等化
ActionCable が存在しないサブスクリプションへの unsubscribe 呼び出しで RuntimeError を発生させていた問題を解消し、解除操作を冪等 (idempotent) にしました。これにより、レースコンディションやリコネクト時に不要な例外が上がらず、エラーモニタリングへのノイズが減少します。
背景
ActionCable は、クライアントが既にサーバ上に存在しないサブスクリプションを unsubscribe した際に RuntimeError をスローしていました。#25381 で報告された通り、再接続やサーバ再起動、Turbo Streams による高速な subscribe/unsubscribe サイクルでこの例外が頻発し、execute_command の rescue ブロックが介在して rescue_from ハンドラが呼び出され、Sentry 等のエラートラッキングに不要なノイズが送られていました。次に同じ問題に対処した #30702 ではエラーメッセージの改善のみが行われ、例外自体は残っていました。
技術的な変更
エラーハンドリングの分離
MissingIdentifier という新しい例外クラスを Subscriptions::Error の下に追加し、unsubscribe コマンドで identifier が欠如している場合に明示的に raise MissingIdentifier します。これにより「identifier が無い」ケースと「既知だが見つからない」ケースを区別でき、テストでも MissingIdentifier が捕捉されるようになりました。
find の振る舞いを nil 返しに変更
従来 find はサブスクリプションが存在しないと UnknownSubscription を raise していましたが、今回の変更で単に subscriptions[data["identifier"]] を返すだけにしました。呼び出し側で nil 判定を行うことにより、異なる文脈でのエラーハンドリングを柔軟に選択できます。
remove の idempotent 化
remove メソッドは find の結果を subscription 変数に格納し、nil であれば何もしない early return を導入しました。これにより、存在しないサブスクリプションへの解除要求は安全な no‑op となり、例外は発生しません。
perform_action の厳格化
perform_action は依然として find の結果が nil の場合に UnknownSubscription を raise します。これはクライアント側のバグを示す重要なシグナルであり、解除操作の冪等化とは対照的にエラーを保持する設計判断です。
テストの拡充
-
unsubscribe command without an identifierがMissingIdentifierを期待するよう変更。 -
unsubscribe from non‑existent subscriptionが例外を出さずに処理が完了することを確認するテストを追加。 -
perform action on non‑existent subscriptionが引き続きUnknownSubscriptionを raise することを検証するテストを追加。
class MissingIdentifier < Error
def initialize
super "Identifier is required"
end
end
def remove(data)
raise MissingIdentifier unless data["identifier"].present?
logger.info "Unsubscribing from channel: #{data['identifier']}"
subscription = find(data)
remove_subscription(subscription) if subscription
end
def find(data)
subscriptions[data["identifier"]]
end
設計判断
冪等性とエラーハンドリングを分離する という方針が採用されました。unsubscribe は「対象が存在しなくても成功」とみなす idempotent な操作とし、remove 側で nil 判定して何もしない実装にしたことで、レースコンディション下でも安定した動作が保証されます。一方で perform_action はクライアントバグを即座に知らせるために例外を保持し、用途ごとに異なるエラーレスポンスを提供できる設計です。MissingIdentifier の導入は入力バリデーションを明示化し、エラーメッセージの意味付けを強化しています。
まとめ
本 PR は ActionCable のサブスクリプション解除を冪等化し、不要な RuntimeError を排除しました。find を nil 返しにしたことで呼び出し側が適切に振る舞いを選択でき、unsubscribe が安全な no‑op になる一方、perform_action では依然としてクライアントバグを示す例外が発生します。これにより、実運用環境でのエラーロギングノイズが減り、開発者は本質的な問題に集中できるようになります。