activerecord 3.2.22.5 → 4.2.11.3

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