activerecord 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,10 +1,12 @@
1
+ require 'thread'
2
+ require 'thread_safe'
1
3
  require 'monitor'
2
4
  require 'set'
3
- require 'active_support/core_ext/module/synchronization'
4
5
 
5
6
  module ActiveRecord
6
7
  # Raised when a connection could not be obtained within the connection
7
- # acquisition timeout period.
8
+ # acquisition timeout period: because max connections in pool
9
+ # are in use.
8
10
  class ConnectionTimeoutError < ConnectionNotEstablished
9
11
  end
10
12
 
@@ -49,14 +51,179 @@ module ActiveRecord
49
51
  #
50
52
  # == Options
51
53
  #
52
- # There are two connection-pooling-related options that you can add to
54
+ # There are several connection-pooling-related options that you can add to
53
55
  # your database connection configuration:
54
56
  #
55
57
  # * +pool+: number indicating size of connection pool (default 5)
56
- # * +wait_timeout+: number of seconds to block and wait for a connection
58
+ # * +checkout_timeout+: number of seconds to block and wait for a connection
57
59
  # before giving up and raising a timeout error (default 5 seconds).
60
+ # * +reaping_frequency+: frequency in seconds to periodically run the
61
+ # Reaper, which attempts to find and close dead connections, which can
62
+ # occur if a programmer forgets to close a connection at the end of a
63
+ # thread or a thread dies unexpectedly. (Default nil, which means don't
64
+ # run the Reaper).
65
+ # * +dead_connection_timeout+: number of seconds from last checkout
66
+ # after which the Reaper will consider a connection reapable. (default
67
+ # 5 seconds).
58
68
  class ConnectionPool
59
- attr_reader :spec, :connections
69
+ # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
70
+ # with which it shares a Monitor. But could be a generic Queue.
71
+ #
72
+ # The Queue in stdlib's 'thread' could replace this class except
73
+ # stdlib's doesn't support waiting with a timeout.
74
+ class Queue
75
+ def initialize(lock = Monitor.new)
76
+ @lock = lock
77
+ @cond = @lock.new_cond
78
+ @num_waiting = 0
79
+ @queue = []
80
+ end
81
+
82
+ # Test if any threads are currently waiting on the queue.
83
+ def any_waiting?
84
+ synchronize do
85
+ @num_waiting > 0
86
+ end
87
+ end
88
+
89
+ # Return the number of threads currently waiting on this
90
+ # queue.
91
+ def num_waiting
92
+ synchronize do
93
+ @num_waiting
94
+ end
95
+ end
96
+
97
+ # Add +element+ to the queue. Never blocks.
98
+ def add(element)
99
+ synchronize do
100
+ @queue.push element
101
+ @cond.signal
102
+ end
103
+ end
104
+
105
+ # If +element+ is in the queue, remove and return it, or nil.
106
+ def delete(element)
107
+ synchronize do
108
+ @queue.delete(element)
109
+ end
110
+ end
111
+
112
+ # Remove all elements from the queue.
113
+ def clear
114
+ synchronize do
115
+ @queue.clear
116
+ end
117
+ end
118
+
119
+ # Remove the head of the queue.
120
+ #
121
+ # If +timeout+ is not given, remove and return the head the
122
+ # queue if the number of available elements is strictly
123
+ # greater than the number of threads currently waiting (that
124
+ # is, don't jump ahead in line). Otherwise, return nil.
125
+ #
126
+ # If +timeout+ is given, block if it there is no element
127
+ # available, waiting up to +timeout+ seconds for an element to
128
+ # become available.
129
+ #
130
+ # Raises:
131
+ # - ConnectionTimeoutError if +timeout+ is given and no element
132
+ # becomes available after +timeout+ seconds,
133
+ def poll(timeout = nil)
134
+ synchronize do
135
+ if timeout
136
+ no_wait_poll || wait_poll(timeout)
137
+ else
138
+ no_wait_poll
139
+ end
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def synchronize(&block)
146
+ @lock.synchronize(&block)
147
+ end
148
+
149
+ # Test if the queue currently contains any elements.
150
+ def any?
151
+ !@queue.empty?
152
+ end
153
+
154
+ # A thread can remove an element from the queue without
155
+ # waiting if an only if the number of currently available
156
+ # connections is strictly greater than the number of waiting
157
+ # threads.
158
+ def can_remove_no_wait?
159
+ @queue.size > @num_waiting
160
+ end
161
+
162
+ # Removes and returns the head of the queue if possible, or nil.
163
+ def remove
164
+ @queue.shift
165
+ end
166
+
167
+ # Remove and return the head the queue if the number of
168
+ # available elements is strictly greater than the number of
169
+ # threads currently waiting. Otherwise, return nil.
170
+ def no_wait_poll
171
+ remove if can_remove_no_wait?
172
+ end
173
+
174
+ # Waits on the queue up to +timeout+ seconds, then removes and
175
+ # returns the head of the queue.
176
+ def wait_poll(timeout)
177
+ @num_waiting += 1
178
+
179
+ t0 = Time.now
180
+ elapsed = 0
181
+ loop do
182
+ @cond.wait(timeout - elapsed)
183
+
184
+ return remove if any?
185
+
186
+ elapsed = Time.now - t0
187
+ if elapsed >= timeout
188
+ msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
189
+ [timeout, elapsed]
190
+ raise ConnectionTimeoutError, msg
191
+ end
192
+ end
193
+ ensure
194
+ @num_waiting -= 1
195
+ end
196
+ end
197
+
198
+ # Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
199
+ # A reaper instantiated with a nil frequency will never reap the
200
+ # connection pool.
201
+ #
202
+ # Configure the frequency by setting "reaping_frequency" in your
203
+ # database yaml file.
204
+ class Reaper
205
+ attr_reader :pool, :frequency
206
+
207
+ def initialize(pool, frequency)
208
+ @pool = pool
209
+ @frequency = frequency
210
+ end
211
+
212
+ def run
213
+ return unless frequency
214
+ Thread.new(frequency, pool) { |t, p|
215
+ while true
216
+ sleep t
217
+ p.reap
218
+ end
219
+ }
220
+ end
221
+ end
222
+
223
+ include MonitorMixin
224
+
225
+ attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
226
+ attr_reader :spec, :connections, :size, :reaper
60
227
 
61
228
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
62
229
  # object which describes database connection information (e.g. adapter,
@@ -65,26 +232,25 @@ module ActiveRecord
65
232
  #
66
233
  # The default ConnectionPool maximum size is 5.
67
234
  def initialize(spec)
68
- @spec = spec
69
-
70
- # The cache of reserved connections mapped to threads
71
- @reserved_connections = {}
235
+ super()
72
236
 
73
- # The mutex used to synchronize pool access
74
- @connection_mutex = Monitor.new
75
- @queue = @connection_mutex.new_cond
237
+ @spec = spec
76
238
 
77
- # default 5 second timeout unless on ruby 1.9
78
- @timeout =
79
- if RUBY_VERSION < '1.9'
80
- spec.config[:wait_timeout] || 5
81
- end
239
+ @checkout_timeout = spec.config[:checkout_timeout] || 5
240
+ @dead_connection_timeout = spec.config[:dead_connection_timeout] || 5
241
+ @reaper = Reaper.new self, spec.config[:reaping_frequency]
242
+ @reaper.run
82
243
 
83
244
  # default max pool size to 5
84
245
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
85
246
 
86
- @connections = []
87
- @checked_out = []
247
+ # The cache of reserved connections mapped to threads
248
+ @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
249
+
250
+ @connections = []
251
+ @automatic_reconnect = true
252
+
253
+ @available = Queue.new self
88
254
  end
89
255
 
90
256
  # Retrieve the connection associated with the current thread, or call
@@ -93,23 +259,38 @@ module ActiveRecord
93
259
  # #connection can be called any number of times; the connection is
94
260
  # held in a hash keyed by the thread id.
95
261
  def connection
96
- @reserved_connections[current_connection_id] ||= checkout
262
+ # this is correctly done double-checked locking
263
+ # (ThreadSafe::Cache's lookups have volatile semantics)
264
+ @reserved_connections[current_connection_id] || synchronize do
265
+ @reserved_connections[current_connection_id] ||= checkout
266
+ end
267
+ end
268
+
269
+ # Is there an open connection that is being used for the current thread?
270
+ def active_connection?
271
+ synchronize do
272
+ @reserved_connections.fetch(current_connection_id) {
273
+ return false
274
+ }.in_use?
275
+ end
97
276
  end
98
277
 
99
278
  # Signal that the thread is finished with the current connection.
100
279
  # #release_connection releases the connection-thread association
101
280
  # and returns the connection to the pool.
102
281
  def release_connection(with_id = current_connection_id)
103
- conn = @reserved_connections.delete(with_id)
104
- checkin conn if conn
282
+ synchronize do
283
+ conn = @reserved_connections.delete(with_id)
284
+ checkin conn if conn
285
+ end
105
286
  end
106
287
 
107
- # If a connection already exists yield it to the block. If no connection
288
+ # If a connection already exists yield it to the block. If no connection
108
289
  # exists checkout a connection, yield it to the block, and checkin the
109
290
  # connection when finished.
110
291
  def with_connection
111
292
  connection_id = current_connection_id
112
- fresh_connection = true unless @reserved_connections[connection_id]
293
+ fresh_connection = true unless active_connection?
113
294
  yield connection
114
295
  ensure
115
296
  release_connection(connection_id) if fresh_connection
@@ -117,94 +298,64 @@ module ActiveRecord
117
298
 
118
299
  # Returns true if a connection has already been opened.
119
300
  def connected?
120
- !@connections.empty?
301
+ synchronize { @connections.any? }
121
302
  end
122
303
 
123
304
  # Disconnects all connections in the pool, and clears the pool.
124
305
  def disconnect!
125
- @reserved_connections.each do |name,conn|
126
- checkin conn
127
- end
128
- @reserved_connections = {}
129
- @connections.each do |conn|
130
- conn.disconnect!
306
+ synchronize do
307
+ @reserved_connections.clear
308
+ @connections.each do |conn|
309
+ checkin conn
310
+ conn.disconnect!
311
+ end
312
+ @connections = []
313
+ @available.clear
131
314
  end
132
- @connections = []
133
315
  end
134
316
 
135
- # Clears the cache which maps classes
317
+ # Clears the cache which maps classes.
136
318
  def clear_reloadable_connections!
137
- @reserved_connections.each do |name, conn|
138
- checkin conn
139
- end
140
- @reserved_connections = {}
141
- @connections.each do |conn|
142
- conn.disconnect! if conn.requires_reloading?
143
- end
144
- @connections.delete_if do |conn|
145
- conn.requires_reloading?
146
- end
147
- end
148
-
149
- # Verify active connections and remove and disconnect connections
150
- # associated with stale threads.
151
- def verify_active_connections! #:nodoc:
152
- clear_stale_cached_connections!
153
- @connections.each do |connection|
154
- connection.verify!
319
+ synchronize do
320
+ @reserved_connections.clear
321
+ @connections.each do |conn|
322
+ checkin conn
323
+ conn.disconnect! if conn.requires_reloading?
324
+ end
325
+ @connections.delete_if do |conn|
326
+ conn.requires_reloading?
327
+ end
328
+ @available.clear
329
+ @connections.each do |conn|
330
+ @available.add conn
331
+ end
155
332
  end
156
333
  end
157
334
 
158
- # Return any checked-out connections back to the pool by threads that
159
- # are no longer alive.
160
- def clear_stale_cached_connections!
161
- keys = @reserved_connections.keys - Thread.list.find_all { |t|
162
- t.alive?
163
- }.map { |thread| thread.object_id }
164
-
165
- keys.each do |key|
166
- checkin @reserved_connections[key]
167
- @reserved_connections.delete(key)
168
- end
335
+ def clear_stale_cached_connections! # :nodoc:
336
+ reap
169
337
  end
338
+ deprecate :clear_stale_cached_connections! => "Please use #reap instead"
170
339
 
171
340
  # Check-out a database connection from the pool, indicating that you want
172
341
  # to use it. You should call #checkin when you no longer need this.
173
342
  #
174
- # This is done by either returning an existing connection, or by creating
175
- # a new connection. If the maximum number of connections for this pool has
176
- # already been reached, but the pool is empty (i.e. they're all being used),
177
- # then this method will wait until a thread has checked in a connection.
178
- # The wait time is bounded however: if no connection can be checked out
179
- # within the timeout specified for this pool, then a ConnectionTimeoutError
180
- # exception will be raised.
343
+ # This is done by either returning and leasing existing connection, or by
344
+ # creating a new connection and leasing it.
345
+ #
346
+ # If all connections are leased and the pool is at capacity (meaning the
347
+ # number of currently leased connections is greater than or equal to the
348
+ # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
181
349
  #
182
350
  # Returns: an AbstractAdapter object.
183
351
  #
184
352
  # Raises:
185
- # - ConnectionTimeoutError: no connection can be obtained from the pool
186
- # within the timeout period.
353
+ # - ConnectionTimeoutError: no connection can be obtained from the pool.
187
354
  def checkout
188
- # Checkout an available connection
189
- @connection_mutex.synchronize do
190
- loop do
191
- conn = if @checked_out.size < @connections.size
192
- checkout_existing_connection
193
- elsif @connections.size < @size
194
- checkout_new_connection
195
- end
196
- return conn if conn
197
- # No connections available; wait for one
198
- if @queue.wait(@timeout)
199
- next
200
- else
201
- # try looting dead threads
202
- clear_stale_cached_connections!
203
- if @size == @checked_out.size
204
- raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
205
- end
206
- end
207
- end
355
+ synchronize do
356
+ conn = acquire_connection
357
+ conn.lease
358
+ checkout_and_verify(conn)
208
359
  end
209
360
  end
210
361
 
@@ -214,41 +365,97 @@ module ActiveRecord
214
365
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
215
366
  # calling +checkout+ on this pool.
216
367
  def checkin(conn)
217
- @connection_mutex.synchronize do
218
- conn.send(:_run_checkin_callbacks) do
219
- @checked_out.delete conn
220
- @queue.signal
368
+ synchronize do
369
+ conn.run_callbacks :checkin do
370
+ conn.expire
221
371
  end
372
+
373
+ release conn
374
+
375
+ @available.add conn
376
+ end
377
+ end
378
+
379
+ # Remove a connection from the connection pool. The connection will
380
+ # remain open and active but will no longer be managed by this pool.
381
+ def remove(conn)
382
+ synchronize do
383
+ @connections.delete conn
384
+ @available.delete conn
385
+
386
+ # FIXME: we might want to store the key on the connection so that removing
387
+ # from the reserved hash will be a little easier.
388
+ release conn
389
+
390
+ @available.add checkout_new_connection if @available.any_waiting?
222
391
  end
223
392
  end
224
393
 
225
- synchronize :clear_reloadable_connections!, :verify_active_connections!,
226
- :connected?, :disconnect!, :with => :@connection_mutex
394
+ # Removes dead connections from the pool. A dead connection can occur
395
+ # if a programmer forgets to close a connection at the end of a thread
396
+ # or a thread dies unexpectedly.
397
+ def reap
398
+ synchronize do
399
+ stale = Time.now - @dead_connection_timeout
400
+ connections.dup.each do |conn|
401
+ if conn.in_use? && stale > conn.last_use && !conn.active?
402
+ remove conn
403
+ end
404
+ end
405
+ end
406
+ end
227
407
 
228
408
  private
409
+
410
+ # Acquire a connection by one of 1) immediately removing one
411
+ # from the queue of available connections, 2) creating a new
412
+ # connection if the pool is not at capacity, 3) waiting on the
413
+ # queue for a connection to become available.
414
+ #
415
+ # Raises:
416
+ # - ConnectionTimeoutError if a connection could not be acquired
417
+ def acquire_connection
418
+ if conn = @available.poll
419
+ conn
420
+ elsif @connections.size < @size
421
+ checkout_new_connection
422
+ else
423
+ @available.poll(@checkout_timeout)
424
+ end
425
+ end
426
+
427
+ def release(conn)
428
+ thread_id = if @reserved_connections[current_connection_id] == conn
429
+ current_connection_id
430
+ else
431
+ @reserved_connections.keys.find { |k|
432
+ @reserved_connections[k] == conn
433
+ }
434
+ end
435
+
436
+ @reserved_connections.delete thread_id if thread_id
437
+ end
438
+
229
439
  def new_connection
230
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
440
+ Base.send(spec.adapter_method, spec.config)
231
441
  end
232
442
 
233
443
  def current_connection_id #:nodoc:
234
- Thread.current.object_id
444
+ Base.connection_id ||= Thread.current.object_id
235
445
  end
236
446
 
237
447
  def checkout_new_connection
448
+ raise ConnectionNotEstablished unless @automatic_reconnect
449
+
238
450
  c = new_connection
451
+ c.pool = self
239
452
  @connections << c
240
- checkout_and_verify(c)
241
- end
242
-
243
- def checkout_existing_connection
244
- c = (@connections - @checked_out).first
245
- checkout_and_verify(c)
453
+ c
246
454
  end
247
455
 
248
456
  def checkout_and_verify(c)
249
457
  c.run_callbacks :checkout do
250
458
  c.verify!
251
- @checked_out << c
252
459
  end
253
460
  c
254
461
  end
@@ -276,37 +483,58 @@ module ActiveRecord
276
483
  #
277
484
  # Normally there is only a single ConnectionHandler instance, accessible via
278
485
  # ActiveRecord::Base.connection_handler. Active Record models use this to
279
- # determine that connection pool that they should use.
486
+ # determine the connection pool that they should use.
280
487
  class ConnectionHandler
281
- attr_reader :connection_pools
488
+ def initialize
489
+ # These caches are keyed by klass.name, NOT klass. Keying them by klass
490
+ # alone would lead to memory leaks in development mode as all previous
491
+ # instances of the class would stay in memory.
492
+ @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
493
+ h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
494
+ end
495
+ @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
496
+ h[k] = ThreadSafe::Cache.new
497
+ end
498
+ end
499
+
500
+ def connection_pool_list
501
+ owner_to_pool.values.compact
502
+ end
282
503
 
283
- def initialize(pools = {})
284
- @connection_pools = pools
504
+ def connection_pools
505
+ ActiveSupport::Deprecation.warn(
506
+ "In the next release, this will return the same as #connection_pool_list. " \
507
+ "(An array of pools, rather than a hash mapping specs to pools.)"
508
+ )
509
+ Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
285
510
  end
286
511
 
287
- def establish_connection(name, spec)
288
- @connection_pools[name] = ConnectionAdapters::ConnectionPool.new(spec)
512
+ def establish_connection(owner, spec)
513
+ @class_to_pool.clear
514
+ raise RuntimeError, "Anonymous class is not allowed." unless owner.name
515
+ owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
516
+ end
517
+
518
+ # Returns true if there are any active connections among the connection
519
+ # pools that the ConnectionHandler is managing.
520
+ def active_connections?
521
+ connection_pool_list.any?(&:active_connection?)
289
522
  end
290
523
 
291
524
  # Returns any connections in use by the current thread back to the pool,
292
525
  # and also returns connections to the pool cached by threads that are no
293
526
  # longer alive.
294
527
  def clear_active_connections!
295
- @connection_pools.each_value {|pool| pool.release_connection }
528
+ connection_pool_list.each(&:release_connection)
296
529
  end
297
530
 
298
- # Clears the cache which maps classes
531
+ # Clears the cache which maps classes.
299
532
  def clear_reloadable_connections!
300
- @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
533
+ connection_pool_list.each(&:clear_reloadable_connections!)
301
534
  end
302
535
 
303
536
  def clear_all_connections!
304
- @connection_pools.each_value {|pool| pool.disconnect! }
305
- end
306
-
307
- # Verify active connections.
308
- def verify_active_connections! #:nodoc:
309
- @connection_pools.each_value {|pool| pool.verify_active_connections! }
537
+ connection_pool_list.each(&:disconnect!)
310
538
  end
311
539
 
312
540
  # Locate the connection of the nearest super class. This can be an
@@ -329,20 +557,61 @@ module ActiveRecord
329
557
  # connection and the defined connection (if they exist). The result
330
558
  # can be used as an argument for establish_connection, for easily
331
559
  # re-establishing the connection.
332
- def remove_connection(klass)
333
- pool = @connection_pools[klass.name]
334
- return nil unless pool
335
-
336
- @connection_pools.delete_if { |key, value| value == pool }
337
- pool.disconnect!
338
- pool.spec.config
560
+ def remove_connection(owner)
561
+ if pool = owner_to_pool.delete(owner.name)
562
+ @class_to_pool.clear
563
+ pool.automatic_reconnect = false
564
+ pool.disconnect!
565
+ pool.spec.config
566
+ end
339
567
  end
340
568
 
569
+ # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
570
+ # This makes retrieving the connection pool O(1) once the process is warm.
571
+ # When a connection is established or removed, we invalidate the cache.
572
+ #
573
+ # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
574
+ # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
575
+ # #fetch is significantly slower than #[]. So in the nil case, no caching will
576
+ # take place, but that's ok since the nil case is not the common one that we wish
577
+ # to optimise for.
341
578
  def retrieve_connection_pool(klass)
342
- pool = @connection_pools[klass.name]
343
- return pool if pool
344
- return nil if ActiveRecord::Base == klass
345
- retrieve_connection_pool klass.superclass
579
+ class_to_pool[klass.name] ||= begin
580
+ until pool = pool_for(klass)
581
+ klass = klass.superclass
582
+ break unless klass <= Base
583
+ end
584
+
585
+ class_to_pool[klass.name] = pool
586
+ end
587
+ end
588
+
589
+ private
590
+
591
+ def owner_to_pool
592
+ @owner_to_pool[Process.pid]
593
+ end
594
+
595
+ def class_to_pool
596
+ @class_to_pool[Process.pid]
597
+ end
598
+
599
+ def pool_for(owner)
600
+ owner_to_pool.fetch(owner.name) {
601
+ if ancestor_pool = pool_from_any_process_for(owner)
602
+ # A connection was established in an ancestor process that must have
603
+ # subsequently forked. We can't reuse the connection, but we can copy
604
+ # the specification and establish a new connection with it.
605
+ establish_connection owner, ancestor_pool.spec
606
+ else
607
+ owner_to_pool[owner.name] = nil
608
+ end
609
+ }
610
+ end
611
+
612
+ def pool_from_any_process_for(owner)
613
+ owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
614
+ owner_to_pool && owner_to_pool[owner.name]
346
615
  end
347
616
  end
348
617
 
@@ -352,13 +621,17 @@ module ActiveRecord
352
621
  end
353
622
 
354
623
  def call(env)
355
- @app.call(env)
356
- ensure
357
- # Don't return connection (and perform implicit rollback) if
358
- # this request is a part of integration test
359
- unless env.key?("rack.test")
360
- ActiveRecord::Base.clear_active_connections!
624
+ testing = env.key?('rack.test')
625
+
626
+ response = @app.call(env)
627
+ response[2] = ::Rack::BodyProxy.new(response[2]) do
628
+ ActiveRecord::Base.clear_active_connections! unless testing
361
629
  end
630
+
631
+ response
632
+ rescue
633
+ ActiveRecord::Base.clear_active_connections! unless testing
634
+ raise
362
635
  end
363
636
  end
364
637
  end