Skip to content

Commit af98678

Browse files
authored
Retry if connection fails (#75)
1 parent 6ed5f22 commit af98678

File tree

5 files changed

+73
-1
lines changed

5 files changed

+73
-1
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ The options are:
8686
- `silence_polling` - whether to silence Active Record logs emitted when polling (Defaults to true)
8787
- `use_skip_locked` - whether to use `FOR UPDATE SKIP LOCKED` when performing trimming. This will be automatically detected in the future, and for now, you'd only need to set this to `false` if your database doesn't support it. For MySQL, that'd be versions < 8, and for PostgreSQL, versions < 9.5. If you use SQLite, this has no effect, as writes are sequential. (Defaults to true)
8888
- `trim_batch_size` - the batch size to use when deleting old records (default: `100`)
89+
- `reconnect_attempts` - Supports a number of connection attempts or an array of
90+
durations to wait between attempts. (Defaults to 1 retry attempt)
8991

9092

9193
## Trimming

lib/action_cable/subscription_adapter/solid_cable.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def listener
3939
end
4040

4141
class Listener < ::ActionCable::SubscriptionAdapter::SubscriberMap
42+
CONNECTION_ERRORS = [ ActiveRecord::ConnectionFailed ]
4243
Stop = Class.new(Exception)
4344

4445
def initialize(event_loop)
@@ -51,11 +52,17 @@ def initialize(event_loop)
5152
# for specific sections of code, rather than acquired.
5253
@critical = Concurrent::Semaphore.new(0)
5354

55+
@reconnect_attempt = 0
56+
5457
@thread = Thread.new do
5558
Thread.current.name = "solid_cable_listener"
5659
Thread.current.report_on_exception = true
5760

58-
listen
61+
begin
62+
listen
63+
rescue *CONNECTION_ERRORS
64+
retry if retry_connecting?
65+
end
5966
end
6067
end
6168

@@ -107,6 +114,7 @@ def invoke_callback(*)
107114
private
108115
attr_reader :event_loop, :thread
109116
attr_writer :last_id
117+
attr_accessor :reconnect_attempt
110118

111119
def last_id
112120
@last_id ||= last_message_id
@@ -146,6 +154,22 @@ def with_polling_volume
146154
yield
147155
end
148156
end
157+
158+
def reconnect_attempts
159+
@reconnect_attempts ||= ::SolidCable.reconnect_attempts
160+
end
161+
162+
def retry_connecting?
163+
self.reconnect_attempt += 1
164+
165+
return false if reconnect_attempt > reconnect_attempts.size
166+
167+
sleep_t = reconnect_attempts[reconnect_attempt - 1]
168+
169+
sleep(sleep_t) if sleep_t > 0
170+
171+
true
172+
end
149173
end
150174
end
151175
end

lib/solid_cable.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ def trim_chance
4848
2
4949
end
5050

51+
def reconnect_attempts
52+
attempts = cable_config.fetch(:reconnect_attempts, 1)
53+
attempts = Array.new(attempts, 0) if attempts.is_a?(Integer)
54+
attempts
55+
end
56+
5157
private
5258
def cable_config
5359
Rails.application.config_for("cable")

test/lib/action_cable/subscription_adapter/solid_cable_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,30 @@ class ActionCable::SubscriptionAdapter::SolidCableTest < ActionCable::TestCase
175175
end
176176
end
177177

178+
test "retries after a connection failure and keeps listening" do
179+
with_cable_config reconnect_attempts: [0] do
180+
raised = false
181+
original = SolidCable::Message.method(:broadcastable)
182+
183+
SolidCable::Message.stub(:broadcastable, lambda { |channels, last_id|
184+
if raised
185+
original.call(channels, last_id)
186+
else
187+
raised = true
188+
raise ActiveRecord::ConnectionFailed, "boom"
189+
end
190+
}) do
191+
subscribe_as_queue("reconnect-channel") do |queue|
192+
@tx_adapter.broadcast("reconnect-channel", "hello")
193+
194+
assert_equal "hello", next_message_in_queue(queue)
195+
end
196+
end
197+
198+
assert raised
199+
end
200+
end
201+
178202
private
179203
def cable_config
180204
{ adapter: "solid_cable", message_retention: "1.second",

test/solid_cable_test.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,20 @@ class SolidCableTest < ActiveSupport::TestCase
4646
assert_equal 42, SolidCable.trim_batch_size
4747
end
4848
end
49+
50+
test "reconnect_attempts defaults to a single zero" do
51+
assert_equal [ 0 ], SolidCable.reconnect_attempts
52+
end
53+
54+
test "reconnect_attempts accepts an integer" do
55+
with_cable_config reconnect_attempts: 3 do
56+
assert_equal [ 0, 0, 0 ], SolidCable.reconnect_attempts
57+
end
58+
end
59+
60+
test "reconnect_attempts accepts an array" do
61+
with_cable_config reconnect_attempts: [ 0, 1, 2 ] do
62+
assert_equal [ 0, 1, 2 ], SolidCable.reconnect_attempts
63+
end
64+
end
4965
end

0 commit comments

Comments
 (0)