Skip to content

Commit 7d5443b

Browse files
committed
Handle deadlock victim (1205) exceptions outside of a transactions by retrying the query.
Add SQLServerAdapter.retry_deadlock_victim option to disable this handling.
1 parent bebde18 commit 7d5443b

File tree

3 files changed

+44
-2
lines changed

3 files changed

+44
-2
lines changed

lib/active_record/connection_adapters/sqlserver/errors.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ module ActiveRecord
33
class LostConnection < WrappedDatabaseException
44
end
55

6+
class DeadlockVictim < WrappedDatabaseException
7+
end
8+
69
module ConnectionAdapters
710
module Sqlserver
811
module Errors

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class SQLServerAdapter < AbstractAdapter
179179
attr_reader :database_version, :database_year, :spid
180180

181181
cattr_accessor :native_text_database_type, :native_binary_database_type, :native_string_database_type,
182-
:log_info_schema_queries, :enable_default_unicode_types, :auto_connect,
182+
:log_info_schema_queries, :enable_default_unicode_types, :auto_connect, :retry_deadlock_victim,
183183
:cs_equality_operator, :lowercase_schema_reflection, :auto_connect_duration
184184

185185
self.enable_default_unicode_types = true
@@ -347,6 +347,11 @@ def auto_connect_duration
347347
@@auto_connect_duration ||= 10
348348
end
349349

350+
def retry_deadlock_victim
351+
@@retry_deadlock_victim.is_a?(FalseClass) ? false : true
352+
end
353+
alias :retry_deadlock_victim? :retry_deadlock_victim
354+
350355
def native_string_database_type
351356
@@native_string_database_type || (enable_default_unicode_types ? 'nvarchar' : 'varchar')
352357
end
@@ -381,6 +386,8 @@ def translate_exception(e, message)
381386
RecordNotUnique.new(message,e)
382387
when /conflicted with the foreign key constraint/i
383388
InvalidForeignKey.new(message,e)
389+
when /has been chosen as the deadlock victim/i
390+
DeadlockVictim.new(message,e)
384391
when *lost_connection_messages
385392
LostConnection.new(message,e)
386393
else
@@ -485,7 +492,10 @@ def with_auto_reconnect
485492
begin
486493
yield
487494
rescue Exception => e
488-
retry if translate_exception(e,e.message).is_a?(LostConnection) && auto_reconnected?
495+
case translate_exception(e,e.message)
496+
when LostConnection; retry if auto_reconnected?
497+
when DeadlockVictim; retry if retry_deadlock_victim? && self.open_transactions == 0
498+
end
489499
raise
490500
end
491501
end

test/cases/connection_test_sqlserver.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,26 @@ def setup
101101
assert @connection.spid.nil?
102102
end
103103

104+
if connection_mode_dblib?
105+
should 'handle deadlock victim exception (1205) outside a transaction by retrying the query' do
106+
query = "SELECT 1 as [one]"
107+
expected = @connection.execute(query)
108+
109+
# Execute the query to get a handle of the expected result, which will
110+
# be returned after a simulated deadlock victim (1205).
111+
raw_conn = @connection.instance_variable_get(:@connection)
112+
stubbed_handle = raw_conn.execute(query)
113+
@connection.send(:finish_statement_handle, stubbed_handle)
114+
raw_conn.stubs(:execute).raises(deadlock_victim_exception(query)).then.returns(stubbed_handle)
115+
116+
result = nil
117+
assert_nothing_raised do
118+
result = @connection.execute(query)
119+
end
120+
assert_equal expected, result
121+
end
122+
end
123+
104124
should 'be able to disconnect and reconnect at will' do
105125
@connection.disconnect!
106126
assert !@connection.active?
@@ -197,6 +217,15 @@ def assert_all_odbc_statements_used_are_closed(&block)
197217
GC.enable
198218
end
199219

220+
def deadlock_victim_exception(sql)
221+
require 'tiny_tds/error'
222+
error = TinyTds::Error.new("Transaction (Process ID #{Process.pid}) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.: #{sql}")
223+
error.severity = 13
224+
error.db_error_number = 1205
225+
error
226+
end
227+
228+
200229
def with_auto_connect(boolean)
201230
existing = ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect
202231
ActiveRecord::ConnectionAdapters::SQLServerAdapter.auto_connect = boolean

0 commit comments

Comments
 (0)