Redis再接続時に保留中のサブスクライブ確認を保持する修正
Redis の Pub/Sub 接続が切断された際に、ActionCable::SubscriptionAdapter::Redis::Listener が保持していたサブスクライブ成功コールバックが破棄されていた問題を解消します。reset メソッドから @subscribe_callbacks.clear を除去し、再接続後でも未完了の確認が配信されるようにしました。
背景
Listener#reset が呼び出されると @subscribe_callbacks がクリアされ、接続ドロップ直前に送信済みだが応答待ちのサブスクライブ確認が失われていました。その結果、再接続後に SUBSCRIBE が再送されてもコールバックが存在せず、クライアント側で confirm_subscription が決して解決せずにスタックしたままになります。これは Redis クライアントの書き換え(コミット ef812c2652)に伴う回帰であり、同時期にマージされた PR #57690 が対象としているリスナースレッドの終了バグとは別の箇所です。失われたコールバックが原因で、実際にはチャンネルは正常に購読できているにもかかわらず、アプリケーション側で永続的にサブスクライブ待ち状態になるケースが報告されました。
技術的な変更
reset メソッドから @subscribe_callbacks.clear 行が削除されました。変更前後を示すと次のようになります。
@@
def reset
@subscription_lock.synchronize do
@subscribed_client = nil
- @subscribe_callbacks.clear
@when_connected.clear
end
end
この修正により、リセット時に残っているコールバックは保持され、再接続後の resubscribe が送出する SUBSCRIBE の ACK で正しく呼び出されます。既に確認済みのチャンネルはコールバック実行後に自身のエントリを削除するため、古いコールバックが残ることはありません。結果として、再接続シナリオでもサブスクライブフローが中断せずに完了します。
設計判断
@subscribe_callbacks をクリアしない方針は、正しい再接続時の動作を保証することを最優先した選択です。完全にアンサブスクライブされたチャンネルが接続ドロップの間に残っていた場合、コールバックがリークする可能性がありますが、対象チャンネルは @subscribers から除外されているため再サブスクライブの対象にもならず、影響は極めて限定的です。将来的にリーク対策を追加する余地は残しつつ、現在は既存ロジックを大幅に改変せずにバグを解消するというトレードオフが採られました。
まとめ
reset から @subscribe_callbacks.clear を除去したことで、Redis 接続の再接続時に保留中のサブスクライブ確認が失われず、クライアントは正しく confirm_subscription を受け取れるようになりました。テストも追加され、実際のリスナーループをフェイク接続で制御し、競合状態でも期待通りに確認が配信されることが検証されています。