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)


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.



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)


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



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.



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