Skip to content
This repository
Browse code

Extract a base class for connection pools, start to flesh out reserve…

…/release API
  • Loading branch information...
commit 029952edf464b94184d9b48f3bdff49d2746d721 1 parent 51349ec
Nick Sieger authored August 04, 2008
189  activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -3,135 +3,156 @@
3 3
 
4 4
 module ActiveRecord
5 5
   module ConnectionAdapters
  6
+    # Connection pool API for ActiveRecord database connections.
6 7
     class ConnectionPool
7  
-      delegate :verification_timeout, :to => "::ActiveRecord::Base"
8  
-      attr_reader :active_connections, :spec
  8
+      # Factory method for connection pools.
  9
+      # Determines pool type to use based on contents of connection specification.
  10
+      # FIXME: specification configuration TBD.
  11
+      def self.create(spec)
  12
+        ConnectionPerThread.new(spec)
  13
+      end
9 14
 
  15
+      delegate :verification_timeout, :to => "::ActiveRecord::Base"
  16
+      attr_reader :spec
10 17
       def initialize(spec)
11  
-        # The thread id -> adapter cache.
12  
-        @active_connections = {}
13  
-
14  
-        # The ConnectionSpecification for this pool
15 18
         @spec = spec
16  
-
  19
+        # The cache of reserved connections mapped to threads
  20
+        @reserved_connections = {}
17 21
         # The mutex used to synchronize pool access
18 22
         @connection_mutex = Monitor.new
19 23
       end
20 24
 
21  
-      def active_connection_name #:nodoc:
22  
-        Thread.current.object_id
  25
+      # Retrieve the connection reserved for the current thread, or call #reserve to obtain one
  26
+      # if necessary.
  27
+      def open_connection
  28
+        if conn = @reserved_connections[active_connection_name]
  29
+          conn.verify!(verification_timeout)
  30
+          conn
  31
+        else
  32
+          @reserved_connections[active_connection_name] = reserve
  33
+        end
23 34
       end
  35
+      alias connection open_connection
24 36
 
25  
-      def active_connection
26  
-        active_connections[active_connection_name]
  37
+      def close_connection
  38
+        conn = @reserved_connections.delete(active_connection_name)
  39
+        release conn if conn
27 40
       end
28 41
 
29  
-      # Returns the connection currently associated with the class. This can
30  
-      # also be used to "borrow" the connection to do database work unrelated
31  
-      # to any of the specific Active Records.
32  
-      def connection
33  
-        if conn = active_connections[active_connection_name]
34  
-          conn
35  
-        else
36  
-          # retrieve_connection sets the cache key.
37  
-          conn = retrieve_connection
38  
-          active_connections[active_connection_name] = conn
39  
-        end
  42
+      # Returns true if a connection has already been opened.
  43
+      def connected?
  44
+        !connections.empty?
40 45
       end
41 46
 
42  
-      # Clears the cache which maps classes to connections.
43  
-      def clear_active_connections!
44  
-        clear_entries!(@active_connections, [active_connection_name]) do |name, conn|
  47
+      # Reserve (check-out) a database connection for the current thread.
  48
+      def reserve
  49
+        raise NotImplementedError, "reserve is an abstract method"
  50
+      end
  51
+      alias checkout reserve
  52
+
  53
+      # Release (check-in) a database connection for the current thread.
  54
+      def release(connection)
  55
+        raise NotImplementedError, "release is an abstract method"
  56
+      end
  57
+      alias checkin release
  58
+
  59
+      # Disconnect all connections in the pool.
  60
+      def disconnect!
  61
+        @reserved_connections.each do |name,conn|
  62
+          release(conn)
  63
+        end
  64
+        connections.each do |conn|
45 65
           conn.disconnect!
46 66
         end
  67
+        @reserved_connections = {}
47 68
       end
48 69
 
49 70
       # Clears the cache which maps classes
50 71
       def clear_reloadable_connections!
51  
-        @active_connections.each do |name, conn|
  72
+        @reserved_connections.each do |name, conn|
  73
+          release(conn)
  74
+        end
  75
+        @reserved_connections = {}
  76
+        connections.each do |conn|
52 77
           if conn.requires_reloading?
53 78
             conn.disconnect!
54  
-            @active_connections.delete(name)
  79
+            remove_connection conn
55 80
           end
56 81
         end
57 82
       end
58 83
 
59 84
       # Verify active connections.
60 85
       def verify_active_connections! #:nodoc:
61  
-        remove_stale_cached_threads!(@active_connections) do |name, conn|
62  
-          conn.disconnect!
  86
+        remove_stale_cached_threads!(@reserved_connections) do |name, conn|
  87
+          release(conn)
63 88
         end
64  
-        active_connections.each_value do |connection|
  89
+        connections.each do |connection|
65 90
           connection.verify!(verification_timeout)
66 91
         end
67 92
       end
68 93
 
69  
-      def retrieve_connection #:nodoc:
70  
-        # Name is nil if establish_connection hasn't been called for
71  
-        # some class along the inheritance chain up to AR::Base yet.
72  
-        name = active_connection_name
73  
-        if conn = active_connections[name]
74  
-          # Verify the connection.
75  
-          conn.verify!(verification_timeout)
76  
-        else
77  
-          self.set_connection spec
78  
-          conn = active_connections[name]
79  
-        end
  94
+      synchronize :open_connection, :close_connection, :reserve, :release,
  95
+        :clear_reloadable_connections!, :verify_active_connections!,
  96
+        :connected?, :disconnect!, :with => :@connection_mutex
80 97
 
81  
-        conn or raise ConnectionNotEstablished
  98
+      private
  99
+      def active_connection_name #:nodoc:
  100
+        Thread.current.object_id
82 101
       end
83 102
 
84  
-      # Returns true if a connection that's accessible to this class has already been opened.
85  
-      def connected?
86  
-        active_connections[active_connection_name] ? true : false
  103
+      def remove_connection(conn)
  104
+        raise NotImplementedError, "remove_connection is an abstract method"
87 105
       end
88 106
 
89  
-      # Disconnect all connections in the pool.
90  
-      def disconnect!
91  
-        clear_cache!(@active_connections) do |name, conn|
92  
-          conn.disconnect!
93  
-        end
  107
+      # Array containing all connections (reserved or available) in the pool.
  108
+      def connections
  109
+        raise NotImplementedError, "connections is an abstract method"
94 110
       end
95 111
 
96  
-      # Set the connection for the class.
97  
-      def set_connection(spec) #:nodoc:
98  
-        if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
99  
-          active_connections[active_connection_name] = spec
100  
-        elsif spec.kind_of?(ActiveRecord::Base::ConnectionSpecification)
101  
-          config = spec.config.reverse_merge(:allow_concurrency => ActiveRecord::Base.allow_concurrency)
102  
-          self.set_connection ActiveRecord::Base.send(spec.adapter_method, config)
103  
-        else
104  
-          raise ConnectionNotEstablished
  112
+      # Remove stale threads from the cache.
  113
+      def remove_stale_cached_threads!(cache, &block)
  114
+        keys = Set.new(cache.keys)
  115
+
  116
+        Thread.list.each do |thread|
  117
+          keys.delete(thread.object_id) if thread.alive?
  118
+        end
  119
+        keys.each do |key|
  120
+          next unless cache.has_key?(key)
  121
+          block.call(key, cache[key])
  122
+          cache.delete(key)
105 123
         end
106 124
       end
  125
+    end
  126
+
  127
+    class ConnectionPerThread < ConnectionPool
  128
+      def active_connection
  129
+        @reserved_connections[active_connection_name]
  130
+      end
107 131
 
108  
-      synchronize :active_connection, :connection, :clear_active_connections!,
109  
-        :clear_reloadable_connections!, :verify_active_connections!, :retrieve_connection,
110  
-        :connected?, :disconnect!, :set_connection, :with => :@connection_mutex
  132
+      def active_connections; @reserved_connections; end
111 133
 
112  
-      private
113  
-        def clear_cache!(cache, &block)
114  
-          cache.each(&block) if block_given?
115  
-          cache.clear
116  
-        end
  134
+      def reserve
  135
+        new_connection
  136
+      end
117 137
 
118  
-        # Remove stale threads from the cache.
119  
-        def remove_stale_cached_threads!(cache, &block)
120  
-          stale = Set.new(cache.keys)
  138
+      def release(conn)
  139
+        conn.disconnect!
  140
+      end
121 141
 
122  
-          Thread.list.each do |thread|
123  
-            stale.delete(thread.object_id) if thread.alive?
124  
-          end
125  
-          clear_entries!(cache, stale, &block)
126  
-        end
  142
+      private
  143
+      # Set the connection for the class.
  144
+      def new_connection
  145
+        config = spec.config.reverse_merge(:allow_concurrency => ActiveRecord::Base.allow_concurrency)
  146
+        ActiveRecord::Base.send(spec.adapter_method, config)
  147
+      end
127 148
 
128  
-        def clear_entries!(cache, keys, &block)
129  
-          keys.each do |key|
130  
-            next unless cache.has_key?(key)
131  
-            block.call(key, cache[key])
132  
-            cache.delete(key)
133  
-          end
134  
-        end
  149
+      def connections
  150
+        @reserved_connections.values
  151
+      end
  152
+
  153
+      def remove_connection(conn)
  154
+        @reserved_connections.delete_if {|k,v| v == conn}
  155
+      end
135 156
     end
136 157
 
137 158
     module ConnectionHandlerMethods
@@ -144,7 +165,7 @@ def connection_pools
144 165
       end
145 166
 
146 167
       def establish_connection(name, spec)
147  
-        @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
  168
+        @connection_pools[name] = ConnectionAdapters::ConnectionPool.create(spec)
148 169
       end
149 170
 
150 171
       # for internal use only and for testing
@@ -158,7 +179,7 @@ def active_connections #:nodoc:
158 179
 
159 180
       # Clears the cache which maps classes to connections.
160 181
       def clear_active_connections!
161  
-        @connection_pools.each_value {|pool| pool.clear_active_connections! }
  182
+        @connection_pools.each_value {|pool| pool.close_connection }
162 183
       end
163 184
 
164 185
       # Clears the cache which maps classes

0 notes on commit 029952e

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