Commits

tenderlove  committed b8f7482

opening a connection will block if the pool is full

  • Participants
  • Parent commits 9a4bfd0

Comments (0)

Files changed (2)

File activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb

 require 'monitor'
 require 'set'
 require 'active_support/core_ext/module/deprecation'
+require 'timeout'
 
 module ActiveRecord
   # Raised when a connection could not be obtained within the connection
 
   # Raised when a connection pool is full and another connection is requested
   class PoolFullError < ConnectionNotEstablished
-    def initialize size, timeout
-      super("Connection pool of size #{size} and timeout #{timeout}s is full")
-    end
   end
 
   module ConnectionAdapters
       attr_accessor :automatic_reconnect, :timeout
       attr_reader :spec, :connections, :size, :reaper
 
+      class Latch # :nodoc:
+        def initialize
+          @mutex = Mutex.new
+          @cond  = ConditionVariable.new
+        end
+
+        def release
+          @mutex.synchronize { @cond.broadcast }
+        end
+
+        def await
+          @mutex.synchronize { @cond.wait @mutex }
+        end
+      end
+
       # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
       # object which describes database connection information (e.g. adapter,
       # host name, username, password, etc), as well as the maximum size for
         # default max pool size to 5
         @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
 
+        @latch = Latch.new
         @connections         = []
         @automatic_reconnect = true
       end
       # #release_connection releases the connection-thread association
       # and returns the connection to the pool.
       def release_connection(with_id = current_connection_id)
-        conn = @reserved_connections.delete(with_id)
-        checkin conn if conn
+        synchronize do
+          conn = @reserved_connections.delete(with_id)
+          checkin conn if conn
+        end
       end
 
       # If a connection already exists yield it to the block. If no connection
       # Raises:
       # - PoolFullError: no connection can be obtained from the pool.
       def checkout
-        # Checkout an available connection
-        synchronize do
-          # Try to find a connection that hasn't been leased, and lease it
-          conn = connections.find { |c| c.lease }
-
-          # If all connections were leased, and we have room to expand,
-          # create a new connection and lease it.
-          if !conn && connections.size < size
-            conn = checkout_new_connection
-            conn.lease
-          end
+        loop do
+          # Checkout an available connection
+          synchronize do
+            # Try to find a connection that hasn't been leased, and lease it
+            conn = connections.find { |c| c.lease }
+
+            # If all connections were leased, and we have room to expand,
+            # create a new connection and lease it.
+            if !conn && connections.size < size
+              conn = checkout_new_connection
+              conn.lease
+            end
 
-          if conn
-            checkout_and_verify conn
-          else
-            raise PoolFullError.new(size, timeout)
+            return checkout_and_verify(conn) if conn
           end
+
+          Timeout.timeout(@timeout, PoolFullError) { @latch.await }
         end
       end
 
 
           release conn
         end
+        @latch.release
       end
 
       # Remove a connection from the connection pool.  The connection will
           # from the reserved hash will be a little easier.
           release conn
         end
+        @latch.release
       end
 
       # Removes dead connections from the pool.  A dead connection can occur
             remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
           end
         end
+        @latch.release
       end
 
       private

File activerecord/test/cases/connection_pool_test.rb

         end
       end
 
+      def test_full_pool_blocks
+        cs = @pool.size.times.map { @pool.checkout }
+        t = Thread.new { @pool.checkout }
+
+        # make sure our thread is in the timeout section
+        Thread.pass until t.status == "sleep"
+
+        connection = cs.first
+        connection.close
+        assert_equal connection, t.join.value
+      end
+
+      def test_removing_releases_latch
+        cs = @pool.size.times.map { @pool.checkout }
+        t = Thread.new { @pool.checkout }
+
+        # make sure our thread is in the timeout section
+        Thread.pass until t.status == "sleep"
+
+        connection = cs.first
+        @pool.remove connection
+        assert_respond_to t.join.value, :execute
+      end
+
       def test_reap_and_active
         @pool.checkout
         @pool.checkout