activerecord-bogacs 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +82 -0
- data/Gemfile +2 -15
- data/Rakefile +8 -6
- data/activerecord-bogacs.gemspec +6 -6
- data/lib/active_record/bogacs/connection_handler.rb +1 -1
- data/lib/active_record/bogacs/default_pool.rb +250 -90
- data/lib/active_record/bogacs/false_pool.rb +87 -53
- data/lib/active_record/bogacs/pool_support.rb +26 -8
- data/lib/active_record/bogacs/shareable_pool.rb +11 -15
- data/lib/active_record/bogacs/thread_safe/synchronized.rb +18 -22
- data/lib/active_record/bogacs/thread_safe.rb +3 -90
- data/lib/active_record/bogacs/validator.rb +14 -19
- data/lib/active_record/bogacs/version.rb +1 -1
- data/lib/active_record/connection_adapters/adapter_compat.rb +41 -25
- data/lib/active_record/connection_adapters/pool_class.rb +33 -2
- data/test/active_record/bogacs/false_pool_test.rb +66 -78
- data/test/active_record/bogacs/shareable_pool/connection_pool_test.rb +6 -3
- data/test/active_record/bogacs/shareable_pool/connection_sharing_test.rb +3 -2
- data/test/active_record/bogacs/shareable_pool_helper.rb +1 -1
- data/test/active_record/bogacs/validator_test.rb +22 -28
- data/test/active_record/connection_pool_test_methods.rb +24 -20
- data/test/test_helper.rb +40 -25
- metadata +30 -17
| @@ -1,5 +1,8 @@ | |
| 1 1 | 
             
            require 'active_record/version'
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'concurrent/atomic/atomic_boolean'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            require 'active_record/connection_adapters/adapter_compat'
         | 
| 3 6 | 
             
            require 'active_record/bogacs/pool_support'
         | 
| 4 7 | 
             
            require 'active_record/bogacs/thread_safe'
         | 
| 5 8 |  | 
| @@ -16,17 +19,17 @@ module ActiveRecord | |
| 16 19 | 
             
                  attr_reader :size, :spec
         | 
| 17 20 |  | 
| 18 21 | 
             
                  def initialize(spec)
         | 
| 19 | 
            -
                     | 
| 22 | 
            +
                    super()
         | 
| 20 23 |  | 
| 21 24 | 
             
                    @spec = spec
         | 
| 22 25 | 
             
                    @size = nil
         | 
| 23 26 | 
             
                    @automatic_reconnect = nil
         | 
| 27 | 
            +
                    @lock_thread = false
         | 
| 24 28 |  | 
| 25 | 
            -
                    @ | 
| 26 | 
            -
                  end
         | 
| 29 | 
            +
                    @thread_cached_conns = ThreadSafe::Map.new
         | 
| 27 30 |  | 
| 28 | 
            -
             | 
| 29 | 
            -
                   | 
| 31 | 
            +
                    @connected = ::Concurrent::AtomicBoolean.new
         | 
| 32 | 
            +
                  end
         | 
| 30 33 |  | 
| 31 34 | 
             
                  # @private attr_reader :reaper
         | 
| 32 35 | 
             
                  def reaper; end
         | 
| @@ -40,20 +43,21 @@ module ActiveRecord | |
| 40 43 | 
             
                  # #connection can be called any number of times; the connection is
         | 
| 41 44 | 
             
                  # held in a hash keyed by the thread id.
         | 
| 42 45 | 
             
                  def connection
         | 
| 43 | 
            -
                     | 
| 46 | 
            +
                    connection_id = current_connection_id(current_thread)
         | 
| 47 | 
            +
                    @thread_cached_conns[connection_id] ||= checkout
         | 
| 44 48 | 
             
                  end
         | 
| 45 49 |  | 
| 46 50 | 
             
                  # Is there an open connection that is being used for the current thread?
         | 
| 47 51 | 
             
                  def active_connection?
         | 
| 48 | 
            -
                     | 
| 49 | 
            -
                     | 
| 52 | 
            +
                    connection_id = current_connection_id(current_thread)
         | 
| 53 | 
            +
                    @thread_cached_conns[connection_id]
         | 
| 50 54 | 
             
                  end
         | 
| 51 55 |  | 
| 52 56 | 
             
                  # Signal that the thread is finished with the current connection.
         | 
| 53 57 | 
             
                  # #release_connection releases the connection-thread association
         | 
| 54 58 | 
             
                  # and returns the connection to the pool.
         | 
| 55 | 
            -
                  def release_connection( | 
| 56 | 
            -
                    conn = @ | 
| 59 | 
            +
                  def release_connection(owner_thread = Thread.current)
         | 
| 60 | 
            +
                    conn = @thread_cached_conns.delete(current_connection_id(owner_thread))
         | 
| 57 61 | 
             
                    checkin conn if conn
         | 
| 58 62 | 
             
                  end
         | 
| 59 63 |  | 
| @@ -62,40 +66,63 @@ module ActiveRecord | |
| 62 66 | 
             
                  # connection when finished.
         | 
| 63 67 | 
             
                  def with_connection
         | 
| 64 68 | 
             
                    connection_id = current_connection_id
         | 
| 65 | 
            -
                     | 
| 66 | 
            -
             | 
| 69 | 
            +
                    unless conn = @thread_cached_conns[connection_id]
         | 
| 70 | 
            +
                      conn = connection
         | 
| 71 | 
            +
                      fresh_connection = true
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
                    yield conn
         | 
| 67 74 | 
             
                  ensure
         | 
| 68 | 
            -
                    release_connection | 
| 75 | 
            +
                    release_connection if fresh_connection
         | 
| 69 76 | 
             
                  end
         | 
| 70 77 |  | 
| 71 78 | 
             
                  # Returns true if a connection has already been opened.
         | 
| 72 | 
            -
                  def connected?; @connected end
         | 
| 79 | 
            +
                  def connected?; @connected.true? end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  # @private replacement for attr_reader :connections
         | 
| 82 | 
            +
                  def connections; @thread_cached_conns.values end
         | 
| 73 83 |  | 
| 74 84 | 
             
                  # Disconnects all connections in the pool, and clears the pool.
         | 
| 75 85 | 
             
                  def disconnect!
         | 
| 76 86 | 
             
                    synchronize do
         | 
| 77 | 
            -
                      @connected | 
| 78 | 
            -
             | 
| 79 | 
            -
                      connections = @reserved_connections.values
         | 
| 80 | 
            -
                      @reserved_connections.clear
         | 
| 87 | 
            +
                      @connected.make_false
         | 
| 81 88 |  | 
| 89 | 
            +
                      connections = @thread_cached_conns.values
         | 
| 90 | 
            +
                      @thread_cached_conns.clear
         | 
| 82 91 | 
             
                      connections.each do |conn|
         | 
| 83 | 
            -
                         | 
| 92 | 
            +
                        if conn.in_use?
         | 
| 93 | 
            +
                          conn.steal!
         | 
| 94 | 
            +
                          checkin conn
         | 
| 95 | 
            +
                        end
         | 
| 84 96 | 
             
                        conn.disconnect!
         | 
| 85 97 | 
             
                      end
         | 
| 86 98 | 
             
                    end
         | 
| 87 99 | 
             
                  end
         | 
| 88 100 |  | 
| 101 | 
            +
                  def discard! # :nodoc:
         | 
| 102 | 
            +
                    synchronize do
         | 
| 103 | 
            +
                      return if @thread_cached_conns.nil? # already discarded
         | 
| 104 | 
            +
                      @connected.make_false
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                      connections.each do |conn|
         | 
| 107 | 
            +
                        conn.discard!
         | 
| 108 | 
            +
                      end
         | 
| 109 | 
            +
                      @thread_cached_conns = nil
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 89 113 | 
             
                  # Clears the cache which maps classes.
         | 
| 90 114 | 
             
                  def clear_reloadable_connections!
         | 
| 91 115 | 
             
                    synchronize do
         | 
| 92 | 
            -
                      @connected | 
| 116 | 
            +
                      @connected.make_false
         | 
| 93 117 |  | 
| 94 | 
            -
                      connections = @ | 
| 95 | 
            -
                      @ | 
| 118 | 
            +
                      connections = @thread_cached_conns.values
         | 
| 119 | 
            +
                      @thread_cached_conns.clear
         | 
| 96 120 |  | 
| 97 121 | 
             
                      connections.each do |conn|
         | 
| 98 | 
            -
                         | 
| 122 | 
            +
                        if conn.in_use?
         | 
| 123 | 
            +
                          conn.steal!
         | 
| 124 | 
            +
                          checkin conn
         | 
| 125 | 
            +
                        end
         | 
| 99 126 | 
             
                        conn.disconnect! if conn.requires_reloading?
         | 
| 100 127 | 
             
                      end
         | 
| 101 128 | 
             
                    end
         | 
| @@ -107,7 +134,7 @@ module ActiveRecord | |
| 107 134 | 
             
                  def verify_active_connections!
         | 
| 108 135 | 
             
                    synchronize do
         | 
| 109 136 | 
             
                      clear_stale_cached_connections!
         | 
| 110 | 
            -
                      @ | 
| 137 | 
            +
                      @thread_cached_conns.values.each(&:verify!)
         | 
| 111 138 | 
             
                    end
         | 
| 112 139 | 
             
                  end if ActiveRecord::VERSION::MAJOR < 4
         | 
| 113 140 |  | 
| @@ -115,12 +142,12 @@ module ActiveRecord | |
| 115 142 | 
             
                  # are no longer alive.
         | 
| 116 143 | 
             
                  # @private AR 3.2 compatibility
         | 
| 117 144 | 
             
                  def clear_stale_cached_connections!
         | 
| 118 | 
            -
                    keys = Thread.list.find_all { |t| t.alive? }.map( | 
| 119 | 
            -
                    keys = @ | 
| 145 | 
            +
                    keys = Thread.list.find_all { |t| t.alive? }.map { |t| current_connection_id(t) }
         | 
| 146 | 
            +
                    keys = @thread_cached_conns.keys - keys
         | 
| 120 147 | 
             
                    keys.each do |key|
         | 
| 121 | 
            -
                      if conn = @ | 
| 122 | 
            -
                        checkin conn,  | 
| 123 | 
            -
                        @ | 
| 148 | 
            +
                      if conn = @thread_cached_conns[key]
         | 
| 149 | 
            +
                        checkin conn, true # no release
         | 
| 150 | 
            +
                        @thread_cached_conns.delete(key)
         | 
| 124 151 | 
             
                      end
         | 
| 125 152 | 
             
                    end
         | 
| 126 153 | 
             
                  end if ActiveRecord::VERSION::MAJOR < 4
         | 
| @@ -131,12 +158,12 @@ module ActiveRecord | |
| 131 158 | 
             
                  # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
         | 
| 132 159 | 
             
                  # @raise [ActiveRecord::ConnectionTimeoutError] no connection can be obtained from the pool
         | 
| 133 160 | 
             
                  def checkout
         | 
| 134 | 
            -
                    # | 
| 135 | 
            -
             | 
| 161 | 
            +
                    conn = checkout_new_connection # acquire_connection
         | 
| 162 | 
            +
                    synchronize do
         | 
| 136 163 | 
             
                      conn.lease
         | 
| 137 164 | 
             
                      _run_checkout_callbacks(conn) # checkout_and_verify(conn)
         | 
| 138 | 
            -
             | 
| 139 | 
            -
                     | 
| 165 | 
            +
                    end
         | 
| 166 | 
            +
                    conn
         | 
| 140 167 | 
             
                  end
         | 
| 141 168 |  | 
| 142 169 | 
             
                  # Check-in a database connection back into the pool, indicating that you
         | 
| @@ -145,13 +172,9 @@ module ActiveRecord | |
| 145 172 | 
             
                  # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter] connection
         | 
| 146 173 | 
             
                  # object, which was obtained earlier by calling #checkout on this pool
         | 
| 147 174 | 
             
                  # @see #checkout
         | 
| 148 | 
            -
                  def checkin(conn,  | 
| 149 | 
            -
                    release(conn)  | 
| 150 | 
            -
                     | 
| 151 | 
            -
                      _run_checkin_callbacks(conn)
         | 
| 152 | 
            -
                      #release conn
         | 
| 153 | 
            -
                      #@available.add conn
         | 
| 154 | 
            -
                    #end
         | 
| 175 | 
            +
                  def checkin(conn, released = nil)
         | 
| 176 | 
            +
                    release(conn) unless released
         | 
| 177 | 
            +
                    _run_checkin_callbacks(conn)
         | 
| 155 178 | 
             
                  end
         | 
| 156 179 |  | 
| 157 180 | 
             
                  # Remove a connection from the connection pool.  The connection will
         | 
| @@ -165,6 +188,21 @@ module ActiveRecord | |
| 165 188 | 
             
                    # we do not really manage the connection pool - nothing to do ...
         | 
| 166 189 | 
             
                  end
         | 
| 167 190 |  | 
| 191 | 
            +
                  def flush(minimum_idle = nil)
         | 
| 192 | 
            +
                    # we do not really manage the connection pool
         | 
| 193 | 
            +
                  end
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  def flush!
         | 
| 196 | 
            +
                    reap
         | 
| 197 | 
            +
                    flush(-1)
         | 
| 198 | 
            +
                  end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                  def stat
         | 
| 201 | 
            +
                    {
         | 
| 202 | 
            +
                      connections: connections.size
         | 
| 203 | 
            +
                    }
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
             | 
| 168 206 | 
             
                  private
         | 
| 169 207 |  | 
| 170 208 | 
             
                  # @raise [ActiveRecord::ConnectionTimeoutError]
         | 
| @@ -181,34 +219,30 @@ module ActiveRecord | |
| 181 219 | 
             
                    checkout_new_connection
         | 
| 182 220 | 
             
                  end
         | 
| 183 221 |  | 
| 184 | 
            -
                  def release(conn, owner =  | 
| 185 | 
            -
                    thread_id = owner | 
| 222 | 
            +
                  def release(conn, owner = conn.owner)
         | 
| 223 | 
            +
                    thread_id = current_connection_id(owner) unless owner.nil?
         | 
| 186 224 |  | 
| 187 225 | 
             
                    thread_id ||=
         | 
| 188 | 
            -
                      if @ | 
| 226 | 
            +
                      if @thread_cached_conns[conn_id = current_connection_id].equal?(conn)
         | 
| 189 227 | 
             
                        conn_id
         | 
| 190 228 | 
             
                      else
         | 
| 191 | 
            -
                        connections = @ | 
| 192 | 
            -
                        connections.keys.find { |k| connections[k] | 
| 229 | 
            +
                        connections = @thread_cached_conns
         | 
| 230 | 
            +
                        connections.keys.find { |k| connections[k].equal?(conn) }
         | 
| 193 231 | 
             
                      end
         | 
| 194 232 |  | 
| 195 | 
            -
                    @ | 
| 233 | 
            +
                    @thread_cached_conns.delete_pair(thread_id, conn) if thread_id
         | 
| 196 234 | 
             
                  end
         | 
| 197 235 |  | 
| 198 236 | 
             
                  def checkout_new_connection
         | 
| 199 | 
            -
                    # NOTE: automatic reconnect  | 
| 200 | 
            -
                    #raise ConnectionNotEstablished unless @automatic_reconnect
         | 
| 201 | 
            -
             | 
| 237 | 
            +
                    # NOTE: automatic reconnect makes no sense for us!
         | 
| 202 238 | 
             
                    begin
         | 
| 203 239 | 
             
                      conn = new_connection
         | 
| 204 | 
            -
                    rescue ConnectionTimeoutError => e
         | 
| 205 | 
            -
                      raise e
         | 
| 206 240 | 
             
                    rescue => e
         | 
| 207 | 
            -
                      raise ConnectionTimeoutError, e.message if timeout_error?(e)
         | 
| 241 | 
            +
                      raise ConnectionTimeoutError, e.message if timeout_error?(e) && !e.is_a?(ConnectionTimeoutError)
         | 
| 208 242 | 
             
                      raise e
         | 
| 209 243 | 
             
                    end
         | 
| 244 | 
            +
                    @connected.make_true
         | 
| 210 245 | 
             
                    conn.pool = self
         | 
| 211 | 
            -
                    synchronize { @connected = true } if @connected != true
         | 
| 212 246 | 
             
                    conn
         | 
| 213 247 | 
             
                  end
         | 
| 214 248 |  | 
| @@ -1,20 +1,34 @@ | |
| 1 | 
            +
            require 'active_record/connection_adapters/abstract/query_cache'
         | 
| 1 2 |  | 
| 2 3 | 
             
            module ActiveRecord
         | 
| 3 4 | 
             
              module Bogacs
         | 
| 4 5 | 
             
                module PoolSupport
         | 
| 5 6 |  | 
| 6 | 
            -
                   | 
| 7 | 
            -
                     | 
| 8 | 
            -
                   | 
| 7 | 
            +
                  def self.included(base)
         | 
| 8 | 
            +
                    base.send :include, ActiveRecord::ConnectionAdapters::QueryCache::ConnectionPoolConfiguration
         | 
| 9 | 
            +
                  end if ActiveRecord::ConnectionAdapters::QueryCache.const_defined? :ConnectionPoolConfiguration
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  attr_accessor :schema_cache
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def lock_thread=(lock_thread)
         | 
| 14 | 
            +
                    if lock_thread
         | 
| 15 | 
            +
                      @lock_thread = Thread.current
         | 
| 16 | 
            +
                    else
         | 
| 17 | 
            +
                      @lock_thread = nil
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
                  end if ActiveRecord::VERSION::MAJOR > 4
         | 
| 9 20 |  | 
| 10 21 | 
             
                  def new_connection
         | 
| 11 | 
            -
                    Base.send(spec.adapter_method, spec.config)
         | 
| 22 | 
            +
                    conn = Base.send(spec.adapter_method, spec.config)
         | 
| 23 | 
            +
                    conn.schema_cache = schema_cache.dup if schema_cache && conn.respond_to?(:schema_cache=)
         | 
| 24 | 
            +
                    conn
         | 
| 12 25 | 
             
                  end
         | 
| 13 26 |  | 
| 14 | 
            -
                   | 
| 15 | 
            -
             | 
| 16 | 
            -
                     | 
| 27 | 
            +
                  # @private
         | 
| 28 | 
            +
                  def current_connection_id(owner_thread = Thread.current)
         | 
| 29 | 
            +
                    owner_thread.object_id
         | 
| 17 30 | 
             
                  end
         | 
| 31 | 
            +
                  alias_method :connection_cache_key, :current_connection_id # for AR (5.2) compatibility
         | 
| 18 32 |  | 
| 19 33 | 
             
                  # @note Method not part of the pre 4.0 API (does no exist).
         | 
| 20 34 | 
             
                  def remove(conn)
         | 
| @@ -37,6 +51,10 @@ module ActiveRecord | |
| 37 51 |  | 
| 38 52 | 
             
                  private
         | 
| 39 53 |  | 
| 54 | 
            +
                  def current_thread
         | 
| 55 | 
            +
                    @lock_thread || Thread.current
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 40 58 | 
             
                  if ActiveRecord::VERSION::STRING > '4.2'
         | 
| 41 59 |  | 
| 42 60 | 
             
                    def _run_checkin_callbacks(conn)
         | 
| @@ -89,4 +107,4 @@ module ActiveRecord | |
| 89 107 |  | 
| 90 108 | 
             
                end
         | 
| 91 109 | 
             
              end
         | 
| 92 | 
            -
            end
         | 
| 110 | 
            +
            end
         | 
| @@ -1,7 +1,8 @@ | |
| 1 1 | 
             
            require 'active_record/version'
         | 
| 2 2 | 
             
            require 'active_record/connection_adapters/abstract/connection_pool'
         | 
| 3 3 |  | 
| 4 | 
            -
            require ' | 
| 4 | 
            +
            require 'concurrent/atomic/atomic_reference'
         | 
| 5 | 
            +
            require 'concurrent/thread_safe/util/cheap_lockable.rb'
         | 
| 5 6 |  | 
| 6 7 | 
             
            require 'active_record/bogacs/thread_safe'
         | 
| 7 8 | 
             
            require 'active_record/bogacs/pool_support'
         | 
| @@ -16,14 +17,9 @@ module ActiveRecord | |
| 16 17 | 
             
                class ShareablePool < ConnectionAdapters::ConnectionPool # NOTE: maybe do not override?!
         | 
| 17 18 | 
             
                  include PoolSupport
         | 
| 18 19 |  | 
| 19 | 
            -
                   | 
| 20 | 
            -
                    include ThreadSafe::CheapLockable
         | 
| 21 | 
            -
                  else
         | 
| 22 | 
            -
                    alias_method :cheap_synchronize, :synchronize
         | 
| 23 | 
            -
                  end
         | 
| 20 | 
            +
                  include ThreadSafe::Synchronized
         | 
| 24 21 |  | 
| 25 | 
            -
                   | 
| 26 | 
            -
                  AtomicReference = ThreadSafe::AtomicReference
         | 
| 22 | 
            +
                  AtomicReference = ::Concurrent::AtomicReference
         | 
| 27 23 |  | 
| 28 24 | 
             
                  DEFAULT_SHARED_POOL = 0.25 # only allow 25% of the pool size to be shared
         | 
| 29 25 | 
             
                  MAX_THREAD_SHARING = 5 # not really a strict limit but should hold
         | 
| @@ -60,7 +56,7 @@ module ActiveRecord | |
| 60 56 | 
             
                  def release_connection(with_id = current_connection_id)
         | 
| 61 57 | 
             
                    if reserved_conn = @reserved_connections[with_id]
         | 
| 62 58 | 
             
                      if shared_count = @shared_connections[reserved_conn]
         | 
| 63 | 
            -
                         | 
| 59 | 
            +
                        synchronize do # lock due #get_shared_connection ... not needed ?!
         | 
| 64 60 | 
             
                          # NOTE: the other option is to not care about shared here at all ...
         | 
| 65 61 | 
             
                          if shared_count.get == 0 # releasing a shared connection
         | 
| 66 62 | 
             
                            release_shared_connection(reserved_conn)
         | 
| @@ -75,18 +71,18 @@ module ActiveRecord | |
| 75 71 |  | 
| 76 72 | 
             
                  # @override
         | 
| 77 73 | 
             
                  def disconnect!
         | 
| 78 | 
            -
                     | 
| 74 | 
            +
                    synchronize { @shared_connections.clear; super }
         | 
| 79 75 | 
             
                  end
         | 
| 80 76 |  | 
| 81 77 | 
             
                  # @override
         | 
| 82 78 | 
             
                  def clear_reloadable_connections!
         | 
| 83 | 
            -
                     | 
| 79 | 
            +
                    synchronize { @shared_connections.clear; super }
         | 
| 84 80 | 
             
                  end
         | 
| 85 81 |  | 
| 86 82 | 
             
                  # @override
         | 
| 87 83 | 
             
                  # @note called from #reap thus the pool should work with reaper
         | 
| 88 84 | 
             
                  def remove(conn)
         | 
| 89 | 
            -
                     | 
| 85 | 
            +
                    synchronize { @shared_connections.delete(conn); super }
         | 
| 90 86 | 
             
                  end
         | 
| 91 87 |  | 
| 92 88 | 
             
            #      # Return any checked-out connections back to the pool by threads that
         | 
| @@ -135,7 +131,7 @@ module ActiveRecord | |
| 135 131 | 
             
                        emulated_checkout(connection); shared = true
         | 
| 136 132 | 
             
                        DEBUG && debug("with_shared_conn 20 got shared = #{connection.to_s}")
         | 
| 137 133 | 
             
                      else
         | 
| 138 | 
            -
                         | 
| 134 | 
            +
                        synchronize do
         | 
| 139 135 | 
             
                          # check shared again as/if threads end up sync-ing up here :
         | 
| 140 136 | 
             
                          if connection = get_shared_connection
         | 
| 141 137 | 
             
                            emulated_checkout(connection)
         | 
| @@ -218,7 +214,7 @@ module ActiveRecord | |
| 218 214 | 
             
                    end
         | 
| 219 215 |  | 
| 220 216 | 
             
                    # we did as much as could without a lock - now sync due possible release
         | 
| 221 | 
            -
                     | 
| 217 | 
            +
                    synchronize do # TODO although this likely might be avoided ...
         | 
| 222 218 | 
             
                      # should try again if possibly the same connection got released :
         | 
| 223 219 | 
             
                      unless least_count = @shared_connections[least_shared]
         | 
| 224 220 | 
             
                        DEBUG && debug(" get_shared_conn retry (connection got released)")
         | 
| @@ -238,7 +234,7 @@ module ActiveRecord | |
| 238 234 | 
             
                  def rem_shared_connection(connection)
         | 
| 239 235 | 
             
                    if shared_count = @shared_connections[connection]
         | 
| 240 236 | 
             
                       # shared_count.update { |v| v - 1 } # NOTE: likely fine without lock!
         | 
| 241 | 
            -
                        | 
| 237 | 
            +
                       synchronize do # give it back to the pool
         | 
| 242 238 | 
             
                         shared_count.update { |v| v - 1 } # might give it back if :
         | 
| 243 239 | 
             
                         release_shared_connection(connection) if shared_count.get == 0
         | 
| 244 240 | 
             
                       end
         | 
| @@ -1,33 +1,29 @@ | |
| 1 | 
            +
            require 'concurrent/thread_safe/util/cheap_lockable.rb'
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            # @note Inpspired by thread_safe gem's Util::CheapLockable mixin.
         | 
| 2 4 | 
             
            module ActiveRecord::Bogacs
         | 
| 3 5 | 
             
              module ThreadSafe
         | 
| 4 | 
            -
                 | 
| 5 | 
            -
                if engine == 'rbx'
         | 
| 6 | 
            +
                if defined? JRUBY_VERSION
         | 
| 6 7 | 
             
                  module Synchronized
         | 
| 7 | 
            -
                     | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
                       | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                       | 
| 14 | 
            -
                        :: | 
| 8 | 
            +
                    if defined? ::JRuby::Util.synchronized # since JRuby 9.3
         | 
| 9 | 
            +
                      def synchronize
         | 
| 10 | 
            +
                        ::JRuby::Util.synchronized(self) { yield }
         | 
| 11 | 
            +
                      end
         | 
| 12 | 
            +
                    else
         | 
| 13 | 
            +
                      require 'jruby'
         | 
| 14 | 
            +
                      def synchronize
         | 
| 15 | 
            +
                        ::JRuby.reference0(self).synchronized { yield }
         | 
| 15 16 | 
             
                      end
         | 
| 16 17 | 
             
                    end
         | 
| 17 18 | 
             
                  end
         | 
| 18 | 
            -
                 | 
| 19 | 
            +
                else
         | 
| 20 | 
            +
                  require 'concurrent/thread_safe/util/cheap_lockable.rb'
         | 
| 21 | 
            +
             | 
| 19 22 | 
             
                  module Synchronized
         | 
| 20 | 
            -
                     | 
| 21 | 
            -
                     | 
| 22 | 
            -
                     | 
| 23 | 
            -
                    def synchronize
         | 
| 24 | 
            -
                      ::JRuby.reference0(self).synchronized { yield }
         | 
| 25 | 
            -
                    end
         | 
| 23 | 
            +
                    include ::Concurrent::ThreadSafe::Util::CheapLockable
         | 
| 24 | 
            +
                    alias_method :synchronize, :cheap_synchronize
         | 
| 25 | 
            +
                    public :synchronize
         | 
| 26 26 | 
             
                  end
         | 
| 27 | 
            -
                else
         | 
| 28 | 
            -
                  require 'thread'; require 'monitor'
         | 
| 29 | 
            -
                  # on MRI fallback to built-in MonitorMixin
         | 
| 30 | 
            -
                  Synchronized = MonitorMixin
         | 
| 31 27 | 
             
                end
         | 
| 32 28 | 
             
              end
         | 
| 33 | 
            -
            end
         | 
| 29 | 
            +
            end
         | 
| @@ -2,98 +2,11 @@ module ActiveRecord | |
| 2 2 | 
             
              module Bogacs
         | 
| 3 3 | 
             
                module ThreadSafe
         | 
| 4 4 |  | 
| 5 | 
            -
                   | 
| 6 | 
            -
             | 
| 7 | 
            -
                      require 'concurrent/map.rb'
         | 
| 8 | 
            -
                    rescue LoadError => e
         | 
| 9 | 
            -
                      begin
         | 
| 10 | 
            -
                        require 'thread_safe'
         | 
| 11 | 
            -
                      rescue
         | 
| 12 | 
            -
                        warn "activerecord-bogacs needs gem 'concurrent-ruby', '~> 1.0' (or the old 'thread_safe' gem)" +
         | 
| 13 | 
            -
                             " please install or add it to your Gemfile"
         | 
| 14 | 
            -
                        raise e
         | 
| 15 | 
            -
                      end
         | 
| 16 | 
            -
                    end
         | 
| 17 | 
            -
                  #end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                  #load_map # always pre-load thread_safe
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  if defined? ::Concurrent::Map
         | 
| 22 | 
            -
                    Map = ::Concurrent::Map
         | 
| 23 | 
            -
                  else
         | 
| 24 | 
            -
                    Map = ::ThreadSafe::Cache
         | 
| 25 | 
            -
                  end
         | 
| 5 | 
            +
                  require 'concurrent/map.rb'
         | 
| 6 | 
            +
                  Map = ::Concurrent::Map
         | 
| 26 7 |  | 
| 27 8 | 
             
                  autoload :Synchronized, 'active_record/bogacs/thread_safe/synchronized'
         | 
| 28 9 |  | 
| 29 | 
            -
                  def self.load_atomic_reference
         | 
| 30 | 
            -
                    return const_get :AtomicReference if const_defined? :AtomicReference
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                    begin
         | 
| 33 | 
            -
                      require 'concurrent/atomic/atomic_reference.rb'
         | 
| 34 | 
            -
                    rescue LoadError => e
         | 
| 35 | 
            -
                      begin
         | 
| 36 | 
            -
                        require 'atomic'
         | 
| 37 | 
            -
                      rescue LoadError
         | 
| 38 | 
            -
                        warn "shareable pool needs gem 'concurrent-ruby', '>= 0.9.1' (or the old 'atomic' gem) " <<
         | 
| 39 | 
            -
                             "please install or add it to your Gemfile"
         | 
| 40 | 
            -
                        raise e
         | 
| 41 | 
            -
                      end
         | 
| 42 | 
            -
                    end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
                    if defined? ::Concurrent::AtomicReference
         | 
| 45 | 
            -
                      const_set :AtomicReference, ::Concurrent::AtomicReference
         | 
| 46 | 
            -
                    else
         | 
| 47 | 
            -
                      const_set :AtomicReference, ::Atomic
         | 
| 48 | 
            -
                    end
         | 
| 49 | 
            -
                  end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                  def self.load_cheap_lockable(required = true)
         | 
| 52 | 
            -
                    return const_get :CheapLockable if const_defined? :CheapLockable
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                    begin
         | 
| 55 | 
            -
                      require 'concurrent/thread_safe/util/cheap_lockable.rb'
         | 
| 56 | 
            -
                    rescue LoadError => e
         | 
| 57 | 
            -
                      begin
         | 
| 58 | 
            -
                        require 'thread_safe'
         | 
| 59 | 
            -
                      rescue
         | 
| 60 | 
            -
                        return nil unless required
         | 
| 61 | 
            -
                        warn "activerecord-bogacs needs gem 'concurrent-ruby', '~> 1.0' (or the old 'thread_safe' gem)" +
         | 
| 62 | 
            -
                             " please install or add it to your Gemfile"
         | 
| 63 | 
            -
                        raise e
         | 
| 64 | 
            -
                      end
         | 
| 65 | 
            -
                    end
         | 
| 66 | 
            -
             | 
| 67 | 
            -
                    if defined? ::Concurrent::ThreadSafe::Util::CheapLockable
         | 
| 68 | 
            -
                      const_set :CheapLockable, ::Concurrent::ThreadSafe::Util::CheapLockable
         | 
| 69 | 
            -
                    else
         | 
| 70 | 
            -
                      const_set :CheapLockable, ::ThreadSafe::Util::CheapLockable
         | 
| 71 | 
            -
                    end
         | 
| 72 | 
            -
                  end
         | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
                  def self.load_monotonic_clock(required = true)
         | 
| 76 | 
            -
                    return const_get :MONOTONIC_CLOCK if const_defined? :MONOTONIC_CLOCK
         | 
| 77 | 
            -
             | 
| 78 | 
            -
                    begin
         | 
| 79 | 
            -
                      require 'concurrent/utility/monotonic_time.rb'
         | 
| 80 | 
            -
                    rescue LoadError => e
         | 
| 81 | 
            -
                      return nil unless required
         | 
| 82 | 
            -
                      warn "activerecord-bogacs needs gem 'concurrent-ruby', '~> 1.0'" +
         | 
| 83 | 
            -
                           " please install or add it to your Gemfile"
         | 
| 84 | 
            -
                      raise e
         | 
| 85 | 
            -
                    end
         | 
| 86 | 
            -
             | 
| 87 | 
            -
                    if defined? ::Concurrent.monotonic_time
         | 
| 88 | 
            -
                      const_set :MONOTONIC_CLOCK, ::Concurrent.const_get(:GLOBAL_MONOTONIC_CLOCK)
         | 
| 89 | 
            -
                    end
         | 
| 90 | 
            -
                  end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
                  # @note Only works when {@link #load_monotonic_time} succeeds.
         | 
| 93 | 
            -
                  #def self.monotonic_time
         | 
| 94 | 
            -
                  #  MONOTONIC_CLOCK.get_time # java.lang.System.nanoTime() / 1_000_000_000.0
         | 
| 95 | 
            -
                  #end
         | 
| 96 | 
            -
             | 
| 97 10 | 
             
                end
         | 
| 98 11 | 
             
              end
         | 
| 99 | 
            -
            end
         | 
| 12 | 
            +
            end
         | 
| @@ -1,10 +1,5 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
              require 'concurrent/timer_task'
         | 
| 4 | 
            -
            rescue LoadError => e
         | 
| 5 | 
            -
              warn "activerecord-bogacs' validator feature needs gem 'concurrent-ruby', please install or add it to your Gemfile"
         | 
| 6 | 
            -
              raise e
         | 
| 7 | 
            -
            end
         | 
| 1 | 
            +
            require 'concurrent/executors'
         | 
| 2 | 
            +
            require 'concurrent/timer_task'
         | 
| 8 3 |  | 
| 9 4 | 
             
            require 'active_record/connection_adapters/adapter_compat'
         | 
| 10 5 | 
             
            require 'active_record/bogacs/thread_safe'
         | 
| @@ -81,20 +76,19 @@ module ActiveRecord | |
| 81 76 | 
             
                    connections = pool.connections.dup
         | 
| 82 77 | 
             
                    connections.map! do |conn|
         | 
| 83 78 | 
             
                      if conn
         | 
| 84 | 
            -
                        owner = conn.owner
         | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 79 | 
            +
                        if owner = conn.owner
         | 
| 80 | 
            +
                          if owner.alive?
         | 
| 81 | 
            +
                            nil # owner.alive? ... do not touch
         | 
| 82 | 
            +
                          else # stale-conn (reaping)
         | 
| 87 83 | 
             
                            pool.remove conn # remove is synchronized
         | 
| 88 84 | 
             
                            conn.disconnect! rescue nil
         | 
| 89 85 | 
             
                            nil
         | 
| 90 | 
            -
                          elsif ! owner # NOTE: this is likely a nasty bug
         | 
| 91 | 
            -
                            logger && logger.warn("[validator] found in-use connection ##{conn.object_id} without owner - removing from pool")
         | 
| 92 | 
            -
                            pool.remove_without_owner conn # synchronized
         | 
| 93 | 
            -
                            conn.disconnect! rescue nil
         | 
| 94 | 
            -
                            nil
         | 
| 95 | 
            -
                          else
         | 
| 96 | 
            -
                            nil # owner.alive? ... do not touch
         | 
| 97 86 | 
             
                          end
         | 
| 87 | 
            +
                        elsif conn.in_use? # no owner? (likely a nasty bug)
         | 
| 88 | 
            +
                          logger && logger.warn("[validator] found in-use connection ##{conn.object_id} without owner - removing from pool")
         | 
| 89 | 
            +
                          pool.remove_without_owner conn # synchronized
         | 
| 90 | 
            +
                          conn.disconnect! rescue nil
         | 
| 91 | 
            +
                          nil
         | 
| 98 92 | 
             
                        else
         | 
| 99 93 | 
             
                          conn # conn not in-use - candidate for validation
         | 
| 100 94 | 
             
                        end
         | 
| @@ -170,7 +164,8 @@ module ActiveRecord | |
| 170 164 |  | 
| 171 165 | 
             
                    def release_without_owner(conn)
         | 
| 172 166 | 
             
                      if owner_id = cached_conn_owner_id(conn)
         | 
| 173 | 
            -
                        thread_cached_conns.delete owner_id | 
| 167 | 
            +
                        thread_cached_conns.delete owner_id
         | 
| 168 | 
            +
                        return true
         | 
| 174 169 | 
             
                      end
         | 
| 175 170 | 
             
                    end
         | 
| 176 171 |  | 
| @@ -179,4 +174,4 @@ module ActiveRecord | |
| 179 174 | 
             
                end
         | 
| 180 175 |  | 
| 181 176 | 
             
              end
         | 
| 182 | 
            -
            end
         | 
| 177 | 
            +
            end
         |