Module: ActiveRecord::ConnectionAdapters::CockroachDB::TransactionManagerMonkeyPatch

Included in:
TransactionManager
Defined in:
lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb

Instance Method Summary collapse

Instance Method Details

#retryable?(error) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

69
70
71
72
73
74
# File 'lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb', line 69

def retryable?(error)
  return true if serialization_error?(error)
  return true if error.is_a? ActiveRecord::SerializationFailure
  return retryable? error.cause if error.cause
  false
end

#rollback_transaction(transaction = nil) ⇒ Object

OVERRIDE: the ‘rescue ActiveRecord::StatementInvalid` block is new, see comment.

[View source]

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb', line 49

def rollback_transaction(transaction = nil)
  @connection.lock.synchronize do
    transaction ||= @stack.last
    begin
      transaction.rollback
    rescue ActiveRecord::StatementInvalid => err
      # This is important to make Active Record aware the record was not inserted/saved
      # Otherwise Active Record will assume save was successful and it doesn't retry the transaction
      # See this thread for more details:
      # https://github.com/cockroachdb/activerecord-cockroachdb-adapter/issues/258#issuecomment-2256633329
      transaction.rollback_records if err.cause.is_a?(PG::NoActiveSqlTransaction)

      raise
    ensure
      @stack.pop if @stack.last == transaction
    end
    transaction.rollback_records
  end
end

#serialization_error?(error) ⇒ Boolean

Returns:

  • (Boolean)
[View source]

76
77
78
79
80
# File 'lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb', line 76

def serialization_error?(error)
  errors = [error]
  errors << error.cause if error.cause
  errors.any? {|e| e.is_a? PG::TRSerializationFailure }
end

#sleep_rand_seconds(attempts) ⇒ Object

[View source]

82
83
84
85
# File 'lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb', line 82

def sleep_rand_seconds(attempts)
  sleep_seconds = (2 ** attempts + rand) / 10
  sleep(sleep_seconds)
end

#within_new_transaction(isolation: nil, joinable: true, attempts: 0) ⇒ Object

Capture ActiveRecord::SerializationFailure errors caused by transactions that fail due to serialization errors. Failed transactions will be retried until they pass or the max retry limit is exceeded.

[View source]

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/active_record/connection_adapters/cockroachdb/transaction_manager.rb', line 25

def within_new_transaction(isolation: nil, joinable: true, attempts: 0)
  super(isolation: isolation, joinable: joinable)
rescue ActiveRecord::ConnectionNotEstablished => error
  raise unless retryable? error
  raise if attempts >= @connection.max_transaction_retries

  sleep_rand_seconds(attempts)

  unless @connection.active?
    warn "connection isn't active, reconnecting"
    @connection.reconnect!
  end

  within_new_transaction(isolation: isolation, joinable: joinable, attempts: attempts + 1) { yield }
rescue ActiveRecord::StatementInvalid => error
  raise unless retryable? error
  raise if attempts >= @connection.max_transaction_retries

  sleep_rand_seconds(attempts)

  within_new_transaction(isolation: isolation, joinable: joinable, attempts: attempts + 1) { yield }
end