Skip to content

Commit 02f5de1

Browse files
committed
Add retry handling to connection establishment too
1 parent da52b0d commit 02f5de1

File tree

4 files changed

+70
-46
lines changed

4 files changed

+70
-46
lines changed

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -557,21 +557,42 @@ def all_foreign_keys_valid?
557557
def active?
558558
end
559559

560-
# Disconnects from the database if already connected, and establishes a
561-
# new connection with the database. Implementors should call super
562-
# immediately after establishing the new connection (and while still
563-
# holding @lock).
560+
# Disconnects from the database if already connected, and establishes a new
561+
# connection with the database. Implementors should define private #reconnect
562+
# instead.
564563
def reconnect!(restore_transactions: false)
565-
enable_lazy_transactions!
566-
@raw_connection_dirty = false
567-
@verified = true
564+
retries_available = connection_retries
565+
566+
@lock.synchronize do
567+
reconnect
568+
569+
enable_lazy_transactions!
570+
@raw_connection_dirty = false
571+
@verified = true
572+
573+
reset_transaction(restore: restore_transactions) do
574+
clear_cache!(new_connection: true)
575+
configure_connection
576+
end
577+
rescue => original_exception
578+
translated_exception = translate_exception_class(original_exception, nil, nil)
579+
580+
if retries_available > 0
581+
retries_available -= 1
582+
583+
if retryable_connection_error?(translated_exception)
584+
backoff(connection_retries - retries_available)
585+
retry
586+
end
587+
end
568588

569-
reset_transaction(restore: restore_transactions) do
570-
clear_cache!(new_connection: true)
571-
configure_connection
589+
@verified = false
590+
591+
raise translated_exception
572592
end
573593
end
574594

595+
575596
# Disconnects from the database if already connected. Otherwise, this
576597
# method does nothing.
577598
def disconnect!
@@ -845,14 +866,24 @@ def with_raw_connection(allow_retry: false, uses_transaction: true)
845866
@lock.synchronize do
846867
materialize_transactions if uses_transaction
847868

848-
retries_available = 0
869+
retries_available = allow_retry ? connection_retries : 0
849870

850-
if reconnect_can_restore_state?
871+
if @verified
872+
# Cool, we're confident the connection's ready to use. (Note this might have
873+
# become true during the above #materialize_transactions.)
874+
elsif reconnect_can_restore_state?
851875
if allow_retry
852-
retries_available = connection_retries
853-
elsif !@verified
876+
# Not sure about the connection yet, but if anything goes wrong we can
877+
# just reconnect and re-run our query
878+
else
879+
# We can reconnect if needed, but we don't trust the upcoming query to be
880+
# safely re-runnable: let's verify the connection to be sure
854881
verify!
855882
end
883+
else
884+
# We don't know whether the connection is okay, but it also doesn't matter:
885+
# we wouldn't be able to reconnect anyway. We're just going to run our query
886+
# and hope for the best.
856887
end
857888

858889
begin
@@ -868,7 +899,8 @@ def with_raw_connection(allow_retry: false, uses_transaction: true)
868899
if retryable_query_error?(translated_exception)
869900
backoff(connection_retries - retries_available)
870901
retry
871-
elsif retryable_connection_error?(translated_exception)
902+
elsif retryable_connection_error?(translated_exception) &&
903+
reconnect_can_restore_state?
872904
reconnect!(restore_transactions: true)
873905
retry
874906
end
@@ -894,6 +926,10 @@ def backoff(counter)
894926
sleep 0.1 * counter
895927
end
896928

929+
def reconnect
930+
raise NotImplementedError
931+
end
932+
897933
# Returns a raw connection for internal use with methods that are known
898934
# to both be thread-safe and not rely upon actual server communication.
899935
# This is useful for e.g. string escaping methods.

activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,6 @@ def active?
123123
@raw_connection.ping
124124
end
125125

126-
def reconnect!(restore_transactions: false)
127-
@lock.synchronize do
128-
@raw_connection.close
129-
connect
130-
super
131-
end
132-
end
133126
alias :reset! :reconnect!
134127

135128
# Disconnects from the database if already connected.
@@ -158,6 +151,11 @@ def connect
158151
@raw_connection = self.class.new_client(@config)
159152
end
160153

154+
def reconnect
155+
@raw_connection.close
156+
connect
157+
end
158+
161159
def configure_connection
162160
@raw_connection.query_options[:as] = :array
163161
@raw_connection.query_options[:database_timezone] = default_timezone

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -325,19 +325,6 @@ def reload_type_map # :nodoc:
325325
end
326326
end
327327

328-
# Close then reopen the connection.
329-
def reconnect!(restore_transactions: false)
330-
@lock.synchronize do
331-
begin
332-
@raw_connection.reset
333-
rescue PG::ConnectionBad
334-
connect
335-
end
336-
337-
super
338-
end
339-
end
340-
341328
def reset!
342329
@lock.synchronize do
343330
unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
@@ -885,6 +872,12 @@ def connect
885872
@raw_connection = self.class.new_client(@connection_parameters)
886873
end
887874

875+
def reconnect
876+
@raw_connection.reset
877+
rescue PG::ConnectionBad
878+
connect
879+
end
880+
888881
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
889882
# This is called by #connect and should not be called manually.
890883
def configure_connection

activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -174,17 +174,6 @@ def active?
174174
!@raw_connection.closed?
175175
end
176176

177-
def reconnect!(restore_transactions: false)
178-
@lock.synchronize do
179-
if active?
180-
@raw_connection.rollback rescue nil
181-
else
182-
connect
183-
end
184-
185-
super
186-
end
187-
end
188177
alias :reset! :reconnect!
189178

190179
# Disconnects from the database if already connected. Otherwise, this
@@ -633,6 +622,14 @@ def connect
633622
)
634623
end
635624

625+
def reconnect
626+
if active?
627+
@raw_connection.rollback rescue nil
628+
else
629+
connect
630+
end
631+
end
632+
636633
def configure_connection
637634
@raw_connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
638635

0 commit comments

Comments
 (0)