Skip to content
This repository
Browse code

Initial conversion to connection pool

So far so good, tests still run clean. Next steps: synchronize connection pool access
and modification, and change allow_concurrency to simply switch a real lock for a
null lock.
commit 6edaa267174dfedaf5b152b9eba25b4eb5e34c99 1 parent 1a81f15
Nick Sieger authored April 19, 2008
128  activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
... ...
@@ -0,0 +1,128 @@
  1
+module ActiveRecord
  2
+  module ConnectionAdapters
  3
+    class ConnectionPool
  4
+      # Check for activity after at least +verification_timeout+ seconds.
  5
+      # Defaults to 0 (always check.)
  6
+      attr_accessor :verification_timeout
  7
+      attr_reader :active_connections, :spec
  8
+
  9
+      def initialize(spec)
  10
+        @verification_timeout = 0
  11
+
  12
+        # The thread id -> adapter cache.
  13
+        @active_connections = {}
  14
+
  15
+        # The ConnectionSpecification for this pool
  16
+        @spec = spec
  17
+      end
  18
+
  19
+      def active_connection_name #:nodoc:
  20
+        Thread.current.object_id
  21
+      end
  22
+
  23
+      def active_connection
  24
+        active_connections[active_connection_name]
  25
+      end
  26
+
  27
+      # Returns the connection currently associated with the class. This can
  28
+      # also be used to "borrow" the connection to do database work unrelated
  29
+      # to any of the specific Active Records.
  30
+      def connection
  31
+        if conn = active_connections[active_connection_name]
  32
+          conn
  33
+        else
  34
+          # retrieve_connection sets the cache key.
  35
+          conn = retrieve_connection
  36
+          active_connections[active_connection_name] = conn
  37
+        end
  38
+      end
  39
+
  40
+      # Clears the cache which maps classes to connections.
  41
+      def clear_active_connections!
  42
+        clear_entries!(@active_connections, [active_connection_name]) do |name, conn|
  43
+          conn.disconnect!
  44
+        end
  45
+      end
  46
+
  47
+      # Clears the cache which maps classes 
  48
+      def clear_reloadable_connections!
  49
+        @active_connections.each do |name, conn|
  50
+          if conn.requires_reloading?
  51
+            conn.disconnect!
  52
+            @active_connections.delete(name)
  53
+          end
  54
+        end
  55
+      end
  56
+
  57
+      # Verify active connections.
  58
+      def verify_active_connections! #:nodoc:
  59
+        remove_stale_cached_threads!(@active_connections) do |name, conn|
  60
+          conn.disconnect!
  61
+        end
  62
+        active_connections.each_value do |connection|
  63
+          connection.verify!(@verification_timeout)
  64
+        end
  65
+      end
  66
+
  67
+      def retrieve_connection #:nodoc:
  68
+        # Name is nil if establish_connection hasn't been called for
  69
+        # some class along the inheritance chain up to AR::Base yet.
  70
+        name = active_connection_name
  71
+        if conn = active_connections[name]
  72
+          # Verify the connection.
  73
+          conn.verify!(@verification_timeout)
  74
+        else
  75
+          self.connection = spec
  76
+          conn = active_connections[name]
  77
+        end
  78
+
  79
+        conn or raise ConnectionNotEstablished
  80
+      end
  81
+
  82
+      # Returns true if a connection that's accessible to this class has already been opened.
  83
+      def connected?
  84
+        active_connections[active_connection_name] ? true : false
  85
+      end
  86
+
  87
+      def disconnect!
  88
+        clear_cache!(@active_connections) do |name, conn|
  89
+          conn.disconnect!
  90
+        end
  91
+      end
  92
+
  93
+      # Set the connection for the class.
  94
+      def connection=(spec) #:nodoc:
  95
+        if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
  96
+          active_connections[active_connection_name] = spec
  97
+        elsif spec.kind_of?(ActiveRecord::Base::ConnectionSpecification)
  98
+          self.connection = ActiveRecord::Base.send(spec.adapter_method, spec.config)
  99
+        else
  100
+          raise ConnectionNotEstablished
  101
+        end
  102
+      end
  103
+
  104
+      private
  105
+        def clear_cache!(cache, &block)
  106
+          cache.each(&block) if block_given?
  107
+          cache.clear
  108
+        end
  109
+
  110
+        # Remove stale threads from the cache.
  111
+        def remove_stale_cached_threads!(cache, &block)
  112
+          stale = Set.new(cache.keys)
  113
+
  114
+          Thread.list.each do |thread|
  115
+            stale.delete(thread.object_id) if thread.alive?
  116
+          end
  117
+          clear_entries!(cache, stale, &block)
  118
+        end
  119
+
  120
+        def clear_entries!(cache, keys, &block)
  121
+          keys.each do |key|
  122
+            block.call(key, cache[key])
  123
+            cache.delete(key)
  124
+          end
  125
+        end
  126
+    end
  127
+  end
  128
+end
198  activerecord/lib/active_record/connection_adapters/abstract/connection_specification.rb
@@ -14,156 +14,45 @@ def initialize (config, adapter_method)
14 14
     cattr_accessor :verification_timeout, :instance_writer => false
15 15
     @@verification_timeout = 0
16 16
 
17  
-    # The class -> [adapter_method, config] map
  17
+    # The class -> connection pool map
18 18
     @@defined_connections = {}
19 19
 
20  
-    # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
21  
-    @@active_connections = {}
22  
-
23 20
     class << self
24  
-      # Retrieve the connection cache.
25  
-      def thread_safe_active_connections #:nodoc:
26  
-        @@active_connections[Thread.current.object_id] ||= {}
27  
-      end
28  
-     
29  
-      def single_threaded_active_connections #:nodoc:
30  
-        @@active_connections
31  
-      end
32  
-     
33  
-      # pick up the right active_connection method from @@allow_concurrency
34  
-      if @@allow_concurrency
35  
-        alias_method :active_connections, :thread_safe_active_connections
36  
-      else
37  
-        alias_method :active_connections, :single_threaded_active_connections
38  
-      end
39  
-     
40  
-      # set concurrency support flag (not thread safe, like most of the methods in this file)
41  
-      def allow_concurrency=(threaded) #:nodoc:
42  
-        logger.debug "allow_concurrency=#{threaded}" if logger
43  
-        return if @@allow_concurrency == threaded
44  
-        clear_all_cached_connections!
45  
-        @@allow_concurrency = threaded
46  
-        method_prefix = threaded ? "thread_safe" : "single_threaded"
47  
-        sing = (class << self; self; end)
48  
-        [:active_connections, :scoped_methods].each do |method|
49  
-          sing.send(:alias_method, method, "#{method_prefix}_#{method}")
50  
-        end
51  
-        log_connections if logger
52  
-      end
53  
-      
54  
-      def active_connection_name #:nodoc:
55  
-        @active_connection_name ||=
56  
-           if active_connections[name] || @@defined_connections[name]
57  
-             name
58  
-           elsif self == ActiveRecord::Base
59  
-             nil
60  
-           else
61  
-             superclass.active_connection_name
62  
-           end
63  
-      end
64  
-
65  
-      def clear_active_connection_name #:nodoc:
66  
-        @active_connection_name = nil
67  
-        subclasses.each { |klass| klass.clear_active_connection_name }
  21
+      # for internal use only
  22
+      def active_connections
  23
+        @@defined_connections.inject([]) {|arr,kv| arr << kv.last.active_connection}.compact.uniq
68 24
       end
69 25
 
70 26
       # Returns the connection currently associated with the class. This can
71 27
       # also be used to "borrow" the connection to do database work unrelated
72 28
       # to any of the specific Active Records.
73 29
       def connection
74  
-        if defined?(@active_connection_name) && (conn = active_connections[@active_connection_name])
75  
-          conn
76  
-        else
77  
-          # retrieve_connection sets the cache key.
78  
-          conn = retrieve_connection
79  
-          active_connections[@active_connection_name] = conn
80  
-        end
  30
+        retrieve_connection
81 31
       end
82 32
 
83 33
       # Clears the cache which maps classes to connections.
84 34
       def clear_active_connections!
85  
-        clear_cache!(@@active_connections) do |name, conn|
86  
-          conn.disconnect!
  35
+        clear_cache!(@@defined_connections) do |name, pool|
  36
+          pool.disconnect!
87 37
         end
88 38
       end
89 39
       
90 40
       # Clears the cache which maps classes 
91 41
       def clear_reloadable_connections!
92  
-        if @@allow_concurrency
93  
-          # With concurrent connections @@active_connections is
94  
-          # a hash keyed by thread id.
95  
-          @@active_connections.each do |thread_id, conns|
96  
-            conns.each do |name, conn|
97  
-              if conn.requires_reloading?
98  
-                conn.disconnect!
99  
-                @@active_connections[thread_id].delete(name)
100  
-              end
101  
-            end
102  
-          end
103  
-        else
104  
-          @@active_connections.each do |name, conn|
105  
-            if conn.requires_reloading?
106  
-              conn.disconnect!
107  
-              @@active_connections.delete(name)
108  
-            end
109  
-          end
  42
+        clear_cache!(@@defined_connections) do |name, pool|
  43
+          pool.clear_reloadable_connections!
110 44
         end
111 45
       end
112 46
 
113 47
       # Verify active connections.
114 48
       def verify_active_connections! #:nodoc:
115  
-        if @@allow_concurrency
116  
-          remove_stale_cached_threads!(@@active_connections) do |name, conn|
117  
-            conn.disconnect!
118  
-          end
119  
-        end
120  
-        
121  
-        active_connections.each_value do |connection|
122  
-          connection.verify!(@@verification_timeout)
123  
-        end
  49
+        @@defined_connections.each_value {|pool| pool.verify_active_connections!}
124 50
       end
125 51
 
126 52
       private
127  
-        def clear_cache!(cache, thread_id = nil, &block)
128  
-          if cache
129  
-            if @@allow_concurrency
130  
-              thread_id ||= Thread.current.object_id
131  
-              thread_cache, cache = cache, cache[thread_id]
132  
-              return unless cache
133  
-            end
134  
-
135  
-            cache.each(&block) if block_given?
136  
-            cache.clear
137  
-          end
138  
-        ensure
139  
-          if thread_cache && @@allow_concurrency
140  
-            thread_cache.delete(thread_id)
141  
-          end
142  
-        end
143  
-
144  
-        # Remove stale threads from the cache.
145  
-        def remove_stale_cached_threads!(cache, &block)
146  
-          stale = Set.new(cache.keys)
147  
-
148  
-          Thread.list.each do |thread|
149  
-            stale.delete(thread.object_id) if thread.alive?
150  
-          end
151  
-
152  
-          stale.each do |thread_id|
153  
-            clear_cache!(cache, thread_id, &block)
154  
-          end
155  
-        end
156  
-        
157  
-        def clear_all_cached_connections!
158  
-          if @@allow_concurrency
159  
-            @@active_connections.each_value do |connection_hash_for_thread|
160  
-              connection_hash_for_thread.each_value {|conn| conn.disconnect! }
161  
-              connection_hash_for_thread.clear
162  
-            end
163  
-          else
164  
-            @@active_connections.each_value {|conn| conn.disconnect! }
165  
-          end
166  
-          @@active_connections.clear          
  53
+        def clear_cache!(cache, &block)
  54
+          cache.each(&block) if block_given?
  55
+          cache.clear
167 56
         end
168 57
     end
169 58
 
@@ -208,9 +97,7 @@ def self.establish_connection(spec = nil)
208 97
           raise AdapterNotSpecified unless defined? RAILS_ENV
209 98
           establish_connection(RAILS_ENV)
210 99
         when ConnectionSpecification
211  
-          clear_active_connection_name
212  
-          @active_connection_name = name
213  
-          @@defined_connections[name] = spec
  100
+          @@defined_connections[name] = ConnectionAdapters::ConnectionPool.new(spec)
214 101
         when Symbol, String
215 102
           if configuration = configurations[spec.to_s]
216 103
             establish_connection(configuration)
@@ -248,26 +135,20 @@ def self.establish_connection(spec = nil)
248 135
     # opened and set as the active connection for the class it was defined
249 136
     # for (not necessarily the current class).
250 137
     def self.retrieve_connection #:nodoc:
251  
-      # Name is nil if establish_connection hasn't been called for
252  
-      # some class along the inheritance chain up to AR::Base yet.
253  
-      if name = active_connection_name
254  
-        if conn = active_connections[name]
255  
-          # Verify the connection.
256  
-          conn.verify!(@@verification_timeout)
257  
-        elsif spec = @@defined_connections[name]
258  
-          # Activate this connection specification.
259  
-          klass = name.constantize
260  
-          klass.connection = spec
261  
-          conn = active_connections[name]
262  
-        end
263  
-      end
  138
+      pool = retrieve_connection_pool
  139
+      (pool && pool.connection) or raise ConnectionNotEstablished
  140
+    end
264 141
 
265  
-      conn or raise ConnectionNotEstablished
  142
+    def self.retrieve_connection_pool
  143
+      pool = @@defined_connections[name]
  144
+      return pool if pool
  145
+      return nil if ActiveRecord::Base == self
  146
+      superclass.retrieve_connection_pool
266 147
     end
267 148
 
268 149
     # Returns true if a connection that's accessible to this class has already been opened.
269 150
     def self.connected?
270  
-      active_connections[active_connection_name] ? true : false
  151
+      retrieve_connection_pool.connected?
271 152
     end
272 153
 
273 154
     # Remove the connection for this class. This will close the active
@@ -275,35 +156,10 @@ def self.connected?
275 156
     # can be used as an argument for establish_connection, for easily
276 157
     # re-establishing the connection.
277 158
     def self.remove_connection(klass=self)
278  
-      spec = @@defined_connections[klass.name]
279  
-      konn = active_connections[klass.name]
280  
-      @@defined_connections.delete_if { |key, value| value == spec }
281  
-      active_connections.delete_if { |key, value| value == konn }
282  
-      konn.disconnect! if konn
283  
-      spec.config if spec
284  
-    end
285  
-
286  
-    # Set the connection for the class.
287  
-    def self.connection=(spec) #:nodoc:
288  
-      if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
289  
-        active_connections[name] = spec
290  
-      elsif spec.kind_of?(ConnectionSpecification)
291  
-        config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
292  
-        self.connection = self.send(spec.adapter_method, config)
293  
-      elsif spec.nil?
294  
-        raise ConnectionNotEstablished
295  
-      else
296  
-        establish_connection spec
297  
-      end
298  
-    end
299  
-
300  
-    # connection state logging
301  
-    def self.log_connections #:nodoc:
302  
-      if logger
303  
-        logger.info "Defined connections: #{@@defined_connections.inspect}"
304  
-        logger.info "Active connections: #{active_connections.inspect}"
305  
-        logger.info "Active connection name: #{@active_connection_name}"
306  
-      end
  159
+      pool = @@defined_connections[klass.name]
  160
+      @@defined_connections.delete_if { |key, value| value == pool }
  161
+      pool.disconnect! if pool
  162
+      pool.spec.config if pool
307 163
     end
308 164
   end
309 165
 end
1  activerecord/lib/active_record/connection_adapters/abstract_adapter.rb 100644 → 100755
@@ -8,6 +8,7 @@
8 8
 require 'active_record/connection_adapters/abstract/database_statements'
9 9
 require 'active_record/connection_adapters/abstract/quoting'
10 10
 require 'active_record/connection_adapters/abstract/connection_specification'
  11
+require 'active_record/connection_adapters/abstract/connection_pool'
11 12
 require 'active_record/connection_adapters/abstract/query_cache'
12 13
 
13 14
 module ActiveRecord
39  activerecord/test/cases/threaded_connections_test.rb
@@ -8,41 +8,32 @@ class ThreadedConnectionsTest < ActiveRecord::TestCase
8 8
 
9 9
     fixtures :topics
10 10
 
11  
-  def setup
12  
-    @connection = ActiveRecord::Base.remove_connection
13  
-    @connections = []
14  
-    @allow_concurrency = ActiveRecord::Base.allow_concurrency
15  
-  end
  11
+    def setup
  12
+      @connection = ActiveRecord::Base.remove_connection
  13
+      @connections = []
  14
+    end
16 15
 
17  
-  def teardown
18  
-    # clear the connection cache
19  
-    ActiveRecord::Base.send(:clear_all_cached_connections!)
20  
-    # set allow_concurrency to saved value
21  
-    ActiveRecord::Base.allow_concurrency = @allow_concurrency
22  
-    # reestablish old connection
23  
-    ActiveRecord::Base.establish_connection(@connection)
24  
-  end
  16
+    def teardown
  17
+      # clear the connection cache
  18
+      ActiveRecord::Base.clear_active_connections!
  19
+      # reestablish old connection
  20
+      ActiveRecord::Base.establish_connection(@connection)
  21
+    end
25 22
 
26  
-  def gather_connections(use_threaded_connections)
27  
-    ActiveRecord::Base.allow_concurrency = use_threaded_connections
28  
-    ActiveRecord::Base.establish_connection(@connection)
  23
+    def gather_connections
  24
+      ActiveRecord::Base.establish_connection(@connection)
29 25
 
30 26
       5.times do
31 27
         Thread.new do
32 28
           Topic.find :first
33  
-          @connections << ActiveRecord::Base.active_connections.values.first
  29
+          @connections << ActiveRecord::Base.active_connections.first
34 30
         end.join
35 31
       end
36 32
     end
37 33
 
38 34
     def test_threaded_connections
39  
-      gather_connections(true)
40  
-      assert_equal @connections.uniq.length, 5
41  
-    end
42  
-
43  
-    def test_unthreaded_connections
44  
-      gather_connections(false)
45  
-      assert_equal @connections.uniq.length, 1
  35
+      gather_connections
  36
+      assert_equal @connections.length, 5
46 37
     end
47 38
   end
48 39
 end

0 notes on commit 6edaa26

Please sign in to comment.
Something went wrong with that request. Please try again.