File: transaction_connection_validator.rb

package info (click to toggle)
ruby-sequel 5.97.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 11,188 kB
  • sloc: ruby: 123,115; makefile: 3
file content (78 lines) | stat: -rw-r--r-- 2,923 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# frozen-string-literal: true
#
# The transaction_connection_validator extension automatically
# retries a transaction on a connection if an disconnect error
# is raised when sending the statement to begin a new
# transaction, as long as the user has not already checked out
# a connection.  This is safe to do because no other queries
# have been issued on the connection, and no user-level code
# is run before retrying.
#
# This approach to connection validation can be significantly
# lower overhead than the connection_validator extension,
# though it does not handle all cases handled by the
# connection_validator extension.  However, it performs the
# validation checks on every new transaction, so it will
# automatically handle disconnected connections in some cases
# where the connection_validator extension will not by default
# (as the connection_validator extension only checks
# connections if they have not been used in the last hour by
# default).
#
# Related module: Sequel::TransactionConnectionValidator

#
module Sequel
  module TransactionConnectionValidator
    class DisconnectRetry < DatabaseDisconnectError
      # The connection that raised the disconnect error
      attr_accessor :connection

      # The underlying disconnect error, in case it needs to be reraised.
      attr_accessor :database_error
    end

    # Rescue disconnect errors raised when beginning a new transaction.  If there
    # is a disconnnect error, it should be safe to retry the transaction using a
    # new connection, as we haven't yielded control to the user yet.
    def transaction(opts=OPTS)
      super
    rescue DisconnectRetry => e
      if synchronize(opts[:server]){|conn| conn.equal?(e.connection)}
        # If retrying would use the same connection, that means the
        # connection was not removed from the pool, which means the caller has
        # already checked out the connection, and retrying will not be successful.
        # In this case, we can only reraise the exception.
        raise e.database_error
      end

      num_retries ||= 0 
      num_retries += 1
      retry if num_retries < 5

      raise e.database_error
    end

    private

    # Reraise disconnect errors as DisconnectRetry so they can be retried.
    def begin_new_transaction(conn, opts)
      super
    rescue Sequel::DatabaseDisconnectError, *database_error_classes => e
      if e.is_a?(Sequel::DatabaseDisconnectError) || disconnect_error?(e, OPTS)
        exception = DisconnectRetry.new(e.message)
        exception.set_backtrace([])
        exception.connection = conn
        unless e.is_a?(Sequel::DatabaseError)
          e = Sequel.convert_exception_class(e, database_error_class(e, OPTS))
        end
        exception.database_error = e
        raise exception
      end

      raise
    end
  end

  Database.register_extension(:transaction_connection_validator, TransactionConnectionValidator)
end