Module: ArInception
- Defined in:
- lib/ar_inception.rb,
lib/ar_inception/version.rb
Constant Summary collapse
- MAX_ESCAPE_DEPTH =
10- VERSION =
"0.1.0"
Class Method Summary collapse
Class Method Details
.install ⇒ Object
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 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/ar_inception.rb', line 9 def self.install return if ActiveRecord::Base.respond_to?(:escape_transaction) # # These changes are only used for the non-threaded implementation # ActiveRecord::ConnectionAdapters::ConnectionPool.class_eval do # # for every nested escape, this is incremented to ensure a # fresh connection. This must be a thread-local because the suffix # is relative to the base per-thread connection. in other words, # the connection_id is a compound key of (thread_id, escape_level) # Thread.current["_connection_id_suffix"] = 0 # # Increases/decreases "escape count" by the specified amount, usually +/- 1 # Returns new escape count. # # May throw an exception if we've escaped too deep or the escape level has # been corrupted somehow # def adjust_escape_suffix(val) res = (Thread.current["_connection_id_suffix"] || 0) + val raise "Decrementing escape suffix below 0" if res < 0 raise "Decrementing escape suffix above 10" if res > ArInception::MAX_ESCAPE_DEPTH Thread.current["_connection_id_suffix"] = res end private # # ConnectionPool uses 'current_connection_id' as a key into a collection of connections. # each thread is intended to have its own # def current_connection_id_with_escape base = current_connection_id_without_escape suffix = Thread.current["_connection_id_suffix"] || 0 suffix == 0 ? base : "#{base}-#{suffix}" end alias_method_chain :current_connection_id, :escape end class << ActiveRecord::Base # # Evaluate the block outside any current ActiveRecord transactional context; # i.e., every DB interaction will be done with a connection that has no # open transactions. This may be the current per-Thread connection or # a freshly allocated one. # # The primary use for this is to commit an update to the database that # will survive a rollback in any currently open transaction. # # This method may block or raise an exception if the connection pool is # exhausted. # # # This implementation does not create new threads, but rather uses a different connection in # the current thread for the duration of the 'escape_transaction' dynamic scope. # # The advantage of this approach is that any code which depends on thread-locals # will still be available # def escape_transaction if connection.open_transactions == 0 yield else conn = nil adjusted = nil begin adjusted = connection_pool.adjust_escape_suffix(1) conn = connection yield ensure # return the connection to the pool and ActiveRecord::Base.connection_pool.checkin(conn) if conn connection_pool.adjust_escape_suffix(-1) if adjusted end end end # # Evaluate the block outside any current ActiveRecord transactional context; # i.e., every DB interaction will be done with a connection that has no # open transactions. As this implementation evaluates the block in a # new thread, it will definitely use a different connection from the one # 'current' in the calling code, and any thread-locals accessed in the # dynamic scope will be different (or absent) during the block's evaluation. # # The primary use for this is to commit an update to the database that # will survive a rollback in any currently open transaction. # # This method may block or raise an exception if the connection pool is # exhausted. # def escape_transaction_via_thread Thread.new do begin yield ensure # return this thread's connection to the pool ActiveRecord::Base.clear_active_connections! end end.join end end end |