activerecord 4.2.0 → 5.2.8.1

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 (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -81
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -57
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +5 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +7 -9
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +466 -280
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +439 -330
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -324
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +136 -90
  133. data/lib/active_record/errors.rb +180 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +117 -35
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -339
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +208 -123
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +30 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,8 +1,8 @@
1
- require 'thread'
2
- require 'thread_safe'
3
- require 'monitor'
4
- require 'set'
5
- require 'active_support/core_ext/string/filters'
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+ require "monitor"
6
6
 
7
7
  module ActiveRecord
8
8
  # Raised when a connection could not be obtained within the connection
@@ -11,6 +11,13 @@ module ActiveRecord
11
11
  class ConnectionTimeoutError < ConnectionNotEstablished
12
12
  end
13
13
 
14
+ # Raised when a pool was unable to get ahold of all its connections
15
+ # to perform a "group" action such as
16
+ # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
17
+ # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
18
+ class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
19
+ end
20
+
14
21
  module ConnectionAdapters
15
22
  # Connection pool base class for managing Active Record database
16
23
  # connections.
@@ -33,17 +40,18 @@ module ActiveRecord
33
40
  # Connections can be obtained and used from a connection pool in several
34
41
  # ways:
35
42
  #
36
- # 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
43
+ # 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
44
+ # as with Active Record 2.1 and
37
45
  # earlier (pre-connection-pooling). Eventually, when you're done with
38
46
  # the connection(s) and wish it to be returned to the pool, you call
39
- # ActiveRecord::Base.clear_active_connections!. This will be the
40
- # default behavior for Active Record when used in conjunction with
47
+ # {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
48
+ # This will be the default behavior for Active Record when used in conjunction with
41
49
  # Action Pack's request handling cycle.
42
50
  # 2. Manually check out a connection from the pool with
43
- # ActiveRecord::Base.connection_pool.checkout. You are responsible for
51
+ # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
44
52
  # returning this connection to the pool when finished by calling
45
- # ActiveRecord::Base.connection_pool.checkin(connection).
46
- # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
53
+ # {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
54
+ # 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
47
55
  # obtains a connection, yields it as the sole argument to the block,
48
56
  # and returns it to the pool after the block completes.
49
57
  #
@@ -55,21 +63,25 @@ module ActiveRecord
55
63
  # There are several connection-pooling-related options that you can add to
56
64
  # your database connection configuration:
57
65
  #
58
- # * +pool+: number indicating size of connection pool (default 5)
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).
66
+ # * +pool+: maximum number of connections the pool may manage (default 5).
67
+ # * +idle_timeout+: number of seconds that a connection will be kept
68
+ # unused in the pool before it is automatically disconnected (default
69
+ # 300 seconds). Set this to zero to keep connections forever.
70
+ # * +checkout_timeout+: number of seconds to wait for a connection to
71
+ # become available before giving up and raising a timeout error (default
72
+ # 5 seconds).
73
+ #
74
+ #--
75
+ # Synchronization policy:
76
+ # * all public methods can be called outside +synchronize+
77
+ # * access to these instance variables needs to be in +synchronize+:
78
+ # * @connections
79
+ # * @now_connecting
80
+ # * private methods that require being called in a +synchronize+ blocks
81
+ # are now explicitly documented
67
82
  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.
83
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
84
+ # with which it shares a Monitor.
73
85
  class Queue
74
86
  def initialize(lock = Monitor.new)
75
87
  @lock = lock
@@ -101,7 +113,7 @@ module ActiveRecord
101
113
  end
102
114
  end
103
115
 
104
- # If +element+ is in the queue, remove and return it, or nil.
116
+ # If +element+ is in the queue, remove and return it, or +nil+.
105
117
  def delete(element)
106
118
  synchronize do
107
119
  @queue.delete(element)
@@ -120,86 +132,160 @@ module ActiveRecord
120
132
  # If +timeout+ is not given, remove and return the head the
121
133
  # queue if the number of available elements is strictly
122
134
  # greater than the number of threads currently waiting (that
123
- # is, don't jump ahead in line). Otherwise, return nil.
135
+ # is, don't jump ahead in line). Otherwise, return +nil+.
124
136
  #
125
- # If +timeout+ is given, block if it there is no element
137
+ # If +timeout+ is given, block if there is no element
126
138
  # available, waiting up to +timeout+ seconds for an element to
127
139
  # become available.
128
140
  #
129
141
  # Raises:
130
- # - ConnectionTimeoutError if +timeout+ is given and no element
131
- # becomes available after +timeout+ seconds,
142
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
143
+ # becomes available within +timeout+ seconds,
132
144
  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
145
+ synchronize { internal_poll(timeout) }
140
146
  end
141
147
 
142
148
  private
143
149
 
144
- def synchronize(&block)
145
- @lock.synchronize(&block)
146
- end
150
+ def internal_poll(timeout)
151
+ no_wait_poll || (timeout && wait_poll(timeout))
152
+ end
147
153
 
148
- # Test if the queue currently contains any elements.
149
- def any?
150
- !@queue.empty?
151
- end
154
+ def synchronize(&block)
155
+ @lock.synchronize(&block)
156
+ end
152
157
 
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
158
+ # Test if the queue currently contains any elements.
159
+ def any?
160
+ !@queue.empty?
161
+ end
160
162
 
161
- # Removes and returns the head of the queue if possible, or nil.
162
- def remove
163
- @queue.shift
164
- end
163
+ # A thread can remove an element from the queue without
164
+ # waiting if and only if the number of currently available
165
+ # connections is strictly greater than the number of waiting
166
+ # threads.
167
+ def can_remove_no_wait?
168
+ @queue.size > @num_waiting
169
+ end
165
170
 
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
171
+ # Removes and returns the head of the queue if possible, or +nil+.
172
+ def remove
173
+ @queue.pop
174
+ end
172
175
 
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
176
+ # Remove and return the head the queue if the number of
177
+ # available elements is strictly greater than the number of
178
+ # threads currently waiting. Otherwise, return +nil+.
179
+ def no_wait_poll
180
+ remove if can_remove_no_wait?
181
+ end
177
182
 
178
- t0 = Time.now
179
- elapsed = 0
180
- loop do
181
- @cond.wait(timeout - elapsed)
183
+ # Waits on the queue up to +timeout+ seconds, then removes and
184
+ # returns the head of the queue.
185
+ def wait_poll(timeout)
186
+ @num_waiting += 1
187
+
188
+ t0 = Time.now
189
+ elapsed = 0
190
+ loop do
191
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
192
+ @cond.wait(timeout - elapsed)
193
+ end
194
+
195
+ return remove if any?
196
+
197
+ elapsed = Time.now - t0
198
+ if elapsed >= timeout
199
+ msg = "could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use" %
200
+ [timeout, elapsed]
201
+ raise ConnectionTimeoutError, msg
202
+ end
203
+ end
204
+ ensure
205
+ @num_waiting -= 1
206
+ end
207
+ end
182
208
 
183
- return remove if any?
209
+ # Adds the ability to turn a basic fair FIFO queue into one
210
+ # biased to some thread.
211
+ module BiasableQueue # :nodoc:
212
+ class BiasedConditionVariable # :nodoc:
213
+ # semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
214
+ # +signal+ and +wait+ methods are only called while holding a lock
215
+ def initialize(lock, other_cond, preferred_thread)
216
+ @real_cond = lock.new_cond
217
+ @other_cond = other_cond
218
+ @preferred_thread = preferred_thread
219
+ @num_waiting_on_real_cond = 0
220
+ end
184
221
 
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
222
+ def broadcast
223
+ broadcast_on_biased
224
+ @other_cond.broadcast
225
+ end
226
+
227
+ def broadcast_on_biased
228
+ @num_waiting_on_real_cond = 0
229
+ @real_cond.broadcast
230
+ end
231
+
232
+ def signal
233
+ if @num_waiting_on_real_cond > 0
234
+ @num_waiting_on_real_cond -= 1
235
+ @real_cond
236
+ else
237
+ @other_cond
238
+ end.signal
191
239
  end
240
+
241
+ def wait(timeout)
242
+ if Thread.current == @preferred_thread
243
+ @num_waiting_on_real_cond += 1
244
+ @real_cond
245
+ else
246
+ @other_cond
247
+ end.wait(timeout)
248
+ end
249
+ end
250
+
251
+ def with_a_bias_for(thread)
252
+ previous_cond = nil
253
+ new_cond = nil
254
+ synchronize do
255
+ previous_cond = @cond
256
+ @cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
257
+ end
258
+ yield
192
259
  ensure
193
- @num_waiting -= 1
260
+ synchronize do
261
+ @cond = previous_cond if previous_cond
262
+ new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
263
+ end
194
264
  end
195
265
  end
196
266
 
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.
267
+ # Connections must be leased while holding the main pool mutex. This is
268
+ # an internal subclass that also +.leases+ returned connections while
269
+ # still in queue's critical section (queue synchronizes with the same
270
+ # <tt>@lock</tt> as the main pool) so that a returned connection is already
271
+ # leased and there is no need to re-enter synchronized block.
272
+ class ConnectionLeasingQueue < Queue # :nodoc:
273
+ include BiasableQueue
274
+
275
+ private
276
+ def internal_poll(timeout)
277
+ conn = super
278
+ conn.lease if conn
279
+ conn
280
+ end
281
+ end
282
+
283
+ # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on
284
+ # +pool+. A reaper instantiated with a zero frequency will never reap
285
+ # the connection pool.
200
286
  #
201
- # Configure the frequency by setting "reaping_frequency" in your
202
- # database yaml file.
287
+ # Configure the frequency by setting +reaping_frequency+ in your database
288
+ # yaml file (default 60 seconds).
203
289
  class Reaper
204
290
  attr_reader :pool, :frequency
205
291
 
@@ -209,20 +295,22 @@ module ActiveRecord
209
295
  end
210
296
 
211
297
  def run
212
- return unless frequency
298
+ return unless frequency && frequency > 0
213
299
  Thread.new(frequency, pool) { |t, p|
214
- while true
300
+ loop do
215
301
  sleep t
216
302
  p.reap
303
+ p.flush
217
304
  end
218
305
  }
219
306
  end
220
307
  end
221
308
 
222
309
  include MonitorMixin
310
+ include QueryCache::ConnectionPoolConfiguration
223
311
 
224
- attr_accessor :automatic_reconnect, :checkout_timeout
225
- attr_reader :spec, :connections, :size, :reaper
312
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
+ attr_reader :spec, :size, :reaper
226
314
 
227
315
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
228
316
  # object which describes database connection information (e.g. adapter,
@@ -236,62 +324,98 @@ module ActiveRecord
236
324
  @spec = spec
237
325
 
238
326
  @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
239
- @reaper = Reaper.new self, spec.config[:reaping_frequency]
240
- @reaper.run
327
+ if @idle_timeout = spec.config.fetch(:idle_timeout, 300)
328
+ @idle_timeout = @idle_timeout.to_f
329
+ @idle_timeout = nil if @idle_timeout <= 0
330
+ end
241
331
 
242
332
  # default max pool size to 5
243
333
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
244
334
 
245
- # The cache of reserved connections mapped to threads
246
- @reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
335
+ # This variable tracks the cache of threads mapped to reserved connections, with the
336
+ # sole purpose of speeding up the +connection+ method. It is not the authoritative
337
+ # registry of which thread owns which connection. Connection ownership is tracked by
338
+ # the +connection.owner+ attr on each +connection+ instance.
339
+ # The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
340
+ # then that +thread+ does indeed own that +conn+. However, an absence of a such
341
+ # mapping does not mean that the +thread+ doesn't own the said connection. In
342
+ # that case +conn.owner+ attr should be consulted.
343
+ # Access and modification of <tt>@thread_cached_conns</tt> does not require
344
+ # synchronization.
345
+ @thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
247
346
 
248
347
  @connections = []
249
348
  @automatic_reconnect = true
250
349
 
251
- @available = Queue.new self
350
+ # Connection pool allows for concurrent (outside the main +synchronize+ section)
351
+ # establishment of new connections. This variable tracks the number of threads
352
+ # currently in the process of independently establishing connections to the DB.
353
+ @now_connecting = 0
354
+
355
+ @threads_blocking_new_connections = 0
356
+
357
+ @available = ConnectionLeasingQueue.new self
358
+
359
+ @lock_thread = false
360
+
361
+ # +reaping_frequency+ is configurable mostly for historical reasons, but it could
362
+ # also be useful if someone wants a very low +idle_timeout+.
363
+ reaping_frequency = spec.config.fetch(:reaping_frequency, 60)
364
+ @reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f)
365
+ @reaper.run
366
+ end
367
+
368
+ def lock_thread=(lock_thread)
369
+ if lock_thread
370
+ @lock_thread = Thread.current
371
+ else
372
+ @lock_thread = nil
373
+ end
252
374
  end
253
375
 
254
376
  # Retrieve the connection associated with the current thread, or call
255
377
  # #checkout to obtain one if necessary.
256
378
  #
257
379
  # #connection can be called any number of times; the connection is
258
- # held in a hash keyed by the thread id.
380
+ # held in a cache keyed by a thread.
259
381
  def connection
260
- # this is correctly done double-checked locking
261
- # (ThreadSafe::Cache's lookups have volatile semantics)
262
- @reserved_connections[current_connection_id] || synchronize do
263
- @reserved_connections[current_connection_id] ||= checkout
264
- end
382
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
265
383
  end
266
384
 
267
- # Is there an open connection that is being used for the current thread?
385
+ # Returns true if there is an open connection being used for the current thread.
386
+ #
387
+ # This method only works for connections that have been obtained through
388
+ # #connection or #with_connection methods. Connections obtained through
389
+ # #checkout will not be detected by #active_connection?
268
390
  def active_connection?
269
- synchronize do
270
- @reserved_connections.fetch(current_connection_id) {
271
- return false
272
- }.in_use?
273
- end
391
+ @thread_cached_conns[connection_cache_key(current_thread)]
274
392
  end
275
393
 
276
394
  # Signal that the thread is finished with the current connection.
277
395
  # #release_connection releases the connection-thread association
278
396
  # and returns the connection to the pool.
279
- def release_connection(with_id = current_connection_id)
280
- synchronize do
281
- conn = @reserved_connections.delete(with_id)
282
- checkin conn if conn
397
+ #
398
+ # This method only works for connections that have been obtained through
399
+ # #connection or #with_connection methods, connections obtained through
400
+ # #checkout will not be automatically released.
401
+ def release_connection(owner_thread = Thread.current)
402
+ if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
403
+ checkin conn
283
404
  end
284
405
  end
285
406
 
286
- # If a connection already exists yield it to the block. If no connection
407
+ # If a connection obtained through #connection or #with_connection methods
408
+ # already exists yield it to the block. If no such connection
287
409
  # exists checkout a connection, yield it to the block, and checkin the
288
410
  # connection when finished.
289
411
  def with_connection
290
- connection_id = current_connection_id
291
- fresh_connection = true unless active_connection?
292
- yield connection
412
+ unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
413
+ conn = connection
414
+ fresh_connection = true
415
+ end
416
+ yield conn
293
417
  ensure
294
- release_connection(connection_id) if fresh_connection
418
+ release_connection if fresh_connection
295
419
  end
296
420
 
297
421
  # Returns true if a connection has already been opened.
@@ -299,37 +423,103 @@ module ActiveRecord
299
423
  synchronize { @connections.any? }
300
424
  end
301
425
 
426
+ # Returns an array containing the connections currently in the pool.
427
+ # Access to the array does not require synchronization on the pool because
428
+ # the array is newly created and not retained by the pool.
429
+ #
430
+ # However; this method bypasses the ConnectionPool's thread-safe connection
431
+ # access pattern. A returned connection may be owned by another thread,
432
+ # unowned, or by happen-stance owned by the calling thread.
433
+ #
434
+ # Calling methods on a connection without ownership is subject to the
435
+ # thread-safety guarantees of the underlying method. Many of the methods
436
+ # on connection adapter classes are inherently multi-thread unsafe.
437
+ def connections
438
+ synchronize { @connections.dup }
439
+ end
440
+
302
441
  # Disconnects all connections in the pool, and clears the pool.
303
- def disconnect!
304
- synchronize do
305
- @reserved_connections.clear
306
- @connections.each do |conn|
307
- checkin conn
308
- conn.disconnect!
442
+ #
443
+ # Raises:
444
+ # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
445
+ # connections in the pool within a timeout interval (default duration is
446
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
447
+ def disconnect(raise_on_acquisition_timeout = true)
448
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
449
+ synchronize do
450
+ @connections.each do |conn|
451
+ if conn.in_use?
452
+ conn.steal!
453
+ checkin conn
454
+ end
455
+ conn.disconnect!
456
+ end
457
+ @connections = []
458
+ @available.clear
309
459
  end
310
- @connections = []
311
- @available.clear
312
460
  end
313
461
  end
314
462
 
315
- # Clears the cache which maps classes.
316
- def clear_reloadable_connections!
463
+ # Disconnects all connections in the pool, and clears the pool.
464
+ #
465
+ # The pool first tries to gain ownership of all connections. If unable to
466
+ # do so within a timeout interval (default duration is
467
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
468
+ # disconnected without any regard for other connection owning threads.
469
+ def disconnect!
470
+ disconnect(false)
471
+ end
472
+
473
+ # Discards all connections in the pool (even if they're currently
474
+ # leased!), along with the pool itself. Any further interaction with the
475
+ # pool (except #spec and #schema_cache) is undefined.
476
+ #
477
+ # See AbstractAdapter#discard!
478
+ def discard! # :nodoc:
317
479
  synchronize do
318
- @reserved_connections.clear
480
+ return if @connections.nil? # already discarded
319
481
  @connections.each do |conn|
320
- checkin conn
321
- conn.disconnect! if conn.requires_reloading?
482
+ conn.discard!
322
483
  end
323
- @connections.delete_if do |conn|
324
- conn.requires_reloading?
325
- end
326
- @available.clear
327
- @connections.each do |conn|
328
- @available.add conn
484
+ @connections = @available = @thread_cached_conns = nil
485
+ end
486
+ end
487
+
488
+ # Clears the cache which maps classes and re-connects connections that
489
+ # require reloading.
490
+ #
491
+ # Raises:
492
+ # - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
493
+ # connections in the pool within a timeout interval (default duration is
494
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
495
+ def clear_reloadable_connections(raise_on_acquisition_timeout = true)
496
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
497
+ synchronize do
498
+ @connections.each do |conn|
499
+ if conn.in_use?
500
+ conn.steal!
501
+ checkin conn
502
+ end
503
+ conn.disconnect! if conn.requires_reloading?
504
+ end
505
+ @connections.delete_if(&:requires_reloading?)
506
+ @available.clear
329
507
  end
330
508
  end
331
509
  end
332
510
 
511
+ # Clears the cache which maps classes and re-connects connections that
512
+ # require reloading.
513
+ #
514
+ # The pool first tries to gain ownership of all connections. If unable to
515
+ # do so within a timeout interval (default duration is
516
+ # <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
517
+ # clears the cache and reloads connections without any regard for other
518
+ # connection owning threads.
519
+ def clear_reloadable_connections!
520
+ clear_reloadable_connections(false)
521
+ end
522
+
333
523
  # Check-out a database connection from the pool, indicating that you want
334
524
  # to use it. You should call #checkin when you no longer need this.
335
525
  #
@@ -343,123 +533,361 @@ module ActiveRecord
343
533
  # Returns: an AbstractAdapter object.
344
534
  #
345
535
  # Raises:
346
- # - ConnectionTimeoutError: no connection can be obtained from the pool.
347
- def checkout
348
- synchronize do
349
- conn = acquire_connection
350
- conn.lease
351
- checkout_and_verify(conn)
352
- end
536
+ # - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
537
+ def checkout(checkout_timeout = @checkout_timeout)
538
+ checkout_and_verify(acquire_connection(checkout_timeout))
353
539
  end
354
540
 
355
541
  # Check-in a database connection back into the pool, indicating that you
356
542
  # no longer need this connection.
357
543
  #
358
544
  # +conn+: an AbstractAdapter object, which was obtained by earlier by
359
- # calling +checkout+ on this pool.
545
+ # calling #checkout on this pool.
360
546
  def checkin(conn)
361
- synchronize do
362
- owner = conn.owner
363
-
364
- conn._run_checkin_callbacks do
365
- conn.expire
366
- end
547
+ conn.lock.synchronize do
548
+ synchronize do
549
+ remove_connection_from_thread_cache conn
367
550
 
368
- release owner
551
+ conn._run_checkin_callbacks do
552
+ conn.expire
553
+ end
369
554
 
370
- @available.add conn
555
+ @available.add conn
556
+ end
371
557
  end
372
558
  end
373
559
 
374
- # Remove a connection from the connection pool. The connection will
560
+ # Remove a connection from the connection pool. The connection will
375
561
  # remain open and active but will no longer be managed by this pool.
376
562
  def remove(conn)
563
+ needs_new_connection = false
564
+
377
565
  synchronize do
566
+ remove_connection_from_thread_cache conn
567
+
378
568
  @connections.delete conn
379
569
  @available.delete conn
380
570
 
381
- release conn.owner
382
-
383
- @available.add checkout_new_connection if @available.any_waiting?
571
+ # @available.any_waiting? => true means that prior to removing this
572
+ # conn, the pool was at its max size (@connections.size == @size).
573
+ # This would mean that any threads stuck waiting in the queue wouldn't
574
+ # know they could checkout_new_connection, so let's do it for them.
575
+ # Because condition-wait loop is encapsulated in the Queue class
576
+ # (that in turn is oblivious to ConnectionPool implementation), threads
577
+ # that are "stuck" there are helpless. They have no way of creating
578
+ # new connections and are completely reliant on us feeding available
579
+ # connections into the Queue.
580
+ needs_new_connection = @available.any_waiting?
384
581
  end
582
+
583
+ # This is intentionally done outside of the synchronized section as we
584
+ # would like not to hold the main mutex while checking out new connections.
585
+ # Thus there is some chance that needs_new_connection information is now
586
+ # stale, we can live with that (bulk_make_new_connections will make
587
+ # sure not to exceed the pool's @size limit).
588
+ bulk_make_new_connections(1) if needs_new_connection
385
589
  end
386
590
 
387
- # Recover lost connections for the pool. A lost connection can occur if
591
+ # Recover lost connections for the pool. A lost connection can occur if
388
592
  # a programmer forgets to checkin a connection at the end of a thread
389
593
  # or a thread dies unexpectedly.
390
594
  def reap
391
595
  stale_connections = synchronize do
392
596
  @connections.select do |conn|
393
597
  conn.in_use? && !conn.owner.alive?
598
+ end.each do |conn|
599
+ conn.steal!
394
600
  end
395
601
  end
396
602
 
397
603
  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
604
+ if conn.active?
605
+ conn.reset!
606
+ checkin conn
607
+ else
608
+ remove conn
405
609
  end
406
610
  end
407
611
  end
408
612
 
409
- private
613
+ # Disconnect all connections that have been idle for at least
614
+ # +minimum_idle+ seconds. Connections currently checked out, or that were
615
+ # checked in less than +minimum_idle+ seconds ago, are unaffected.
616
+ def flush(minimum_idle = @idle_timeout)
617
+ return if minimum_idle.nil?
410
618
 
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)
619
+ idle_connections = synchronize do
620
+ @connections.select do |conn|
621
+ !conn.in_use? && conn.seconds_idle >= minimum_idle
622
+ end.each do |conn|
623
+ conn.lease
624
+
625
+ @available.delete conn
626
+ @connections.delete conn
627
+ end
426
628
  end
427
- end
428
629
 
429
- def release(owner)
430
- thread_id = owner.object_id
630
+ idle_connections.each do |conn|
631
+ conn.disconnect!
632
+ end
633
+ end
431
634
 
432
- @reserved_connections.delete thread_id
635
+ # Disconnect all currently idle connections. Connections currently checked
636
+ # out are unaffected.
637
+ def flush!
638
+ reap
639
+ flush(-1)
433
640
  end
434
641
 
435
- def new_connection
436
- Base.send(spec.adapter_method, spec.config)
642
+ def num_waiting_in_queue # :nodoc:
643
+ @available.num_waiting
437
644
  end
438
645
 
439
- def current_connection_id #:nodoc:
440
- Base.connection_id ||= Thread.current.object_id
646
+ # Return connection pool's usage statistic
647
+ # Example:
648
+ #
649
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
650
+ def stat
651
+ synchronize do
652
+ {
653
+ size: size,
654
+ connections: @connections.size,
655
+ busy: @connections.count { |c| c.in_use? && c.owner.alive? },
656
+ dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
657
+ idle: @connections.count { |c| !c.in_use? },
658
+ waiting: num_waiting_in_queue,
659
+ checkout_timeout: checkout_timeout
660
+ }
661
+ end
441
662
  end
442
663
 
443
- def checkout_new_connection
444
- raise ConnectionNotEstablished unless @automatic_reconnect
664
+ private
665
+ #--
666
+ # this is unfortunately not concurrent
667
+ def bulk_make_new_connections(num_new_conns_needed)
668
+ num_new_conns_needed.times do
669
+ # try_to_checkout_new_connection will not exceed pool's @size limit
670
+ if new_conn = try_to_checkout_new_connection
671
+ # make the new_conn available to the starving threads stuck @available Queue
672
+ checkin(new_conn)
673
+ end
674
+ end
675
+ end
445
676
 
446
- c = new_connection
447
- c.pool = self
448
- @connections << c
449
- c
450
- end
677
+ #--
678
+ # From the discussion on GitHub:
679
+ # https://github.com/rails/rails/pull/14938#commitcomment-6601951
680
+ # This hook-in method allows for easier monkey-patching fixes needed by
681
+ # JRuby users that use Fibers.
682
+ def connection_cache_key(thread)
683
+ thread
684
+ end
451
685
 
452
- def checkout_and_verify(c)
453
- c._run_checkout_callbacks do
454
- c.verify!
686
+ def current_thread
687
+ @lock_thread || Thread.current
688
+ end
689
+
690
+ # Take control of all existing connections so a "group" action such as
691
+ # reload/disconnect can be performed safely. It is no longer enough to
692
+ # wrap it in +synchronize+ because some pool's actions are allowed
693
+ # to be performed outside of the main +synchronize+ block.
694
+ def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
695
+ with_new_connections_blocked do
696
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
697
+ yield
698
+ end
699
+ end
700
+
701
+ def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
702
+ collected_conns = synchronize do
703
+ # account for our own connections
704
+ @connections.select { |conn| conn.owner == Thread.current }
705
+ end
706
+
707
+ newly_checked_out = []
708
+ timeout_time = Time.now + (@checkout_timeout * 2)
709
+
710
+ @available.with_a_bias_for(Thread.current) do
711
+ loop do
712
+ synchronize do
713
+ return if collected_conns.size == @connections.size && @now_connecting == 0
714
+ remaining_timeout = timeout_time - Time.now
715
+ remaining_timeout = 0 if remaining_timeout < 0
716
+ conn = checkout_for_exclusive_access(remaining_timeout)
717
+ collected_conns << conn
718
+ newly_checked_out << conn
719
+ end
720
+ end
721
+ end
722
+ rescue ExclusiveConnectionTimeoutError
723
+ # <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
724
+ # timeouts and are expected to just give up: we've obtained as many connections
725
+ # as possible, note that in a case like that we don't return any of the
726
+ # +newly_checked_out+ connections.
727
+
728
+ if raise_on_acquisition_timeout
729
+ release_newly_checked_out = true
730
+ raise
731
+ end
732
+ rescue Exception # if something else went wrong
733
+ # this can't be a "naked" rescue, because we have should return conns
734
+ # even for non-StandardErrors
735
+ release_newly_checked_out = true
736
+ raise
737
+ ensure
738
+ if release_newly_checked_out && newly_checked_out
739
+ # releasing only those conns that were checked out in this method, conns
740
+ # checked outside this method (before it was called) are not for us to release
741
+ newly_checked_out.each { |conn| checkin(conn) }
742
+ end
743
+ end
744
+
745
+ #--
746
+ # Must be called in a synchronize block.
747
+ def checkout_for_exclusive_access(checkout_timeout)
748
+ checkout(checkout_timeout)
749
+ rescue ConnectionTimeoutError
750
+ # this block can't be easily moved into attempt_to_checkout_all_existing_connections's
751
+ # rescue block, because doing so would put it outside of synchronize section, without
752
+ # being in a critical section thread_report might become inaccurate
753
+ msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup
754
+
755
+ thread_report = []
756
+ @connections.each do |conn|
757
+ unless conn.owner == Thread.current
758
+ thread_report << "#{conn} is owned by #{conn.owner}"
759
+ end
760
+ end
761
+
762
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
763
+
764
+ raise ExclusiveConnectionTimeoutError, msg
765
+ end
766
+
767
+ def with_new_connections_blocked
768
+ synchronize do
769
+ @threads_blocking_new_connections += 1
770
+ end
771
+
772
+ yield
773
+ ensure
774
+ num_new_conns_required = 0
775
+
776
+ synchronize do
777
+ @threads_blocking_new_connections -= 1
778
+
779
+ if @threads_blocking_new_connections.zero?
780
+ @available.clear
781
+
782
+ num_new_conns_required = num_waiting_in_queue
783
+
784
+ @connections.each do |conn|
785
+ next if conn.in_use?
786
+
787
+ @available.add conn
788
+ num_new_conns_required -= 1
789
+ end
790
+ end
791
+ end
792
+
793
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
794
+ end
795
+
796
+ # Acquire a connection by one of 1) immediately removing one
797
+ # from the queue of available connections, 2) creating a new
798
+ # connection if the pool is not at capacity, 3) waiting on the
799
+ # queue for a connection to become available.
800
+ #
801
+ # Raises:
802
+ # - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
803
+ #
804
+ #--
805
+ # Implementation detail: the connection returned by +acquire_connection+
806
+ # will already be "+connection.lease+ -ed" to the current thread.
807
+ def acquire_connection(checkout_timeout)
808
+ # NOTE: we rely on <tt>@available.poll</tt> and +try_to_checkout_new_connection+ to
809
+ # +conn.lease+ the returned connection (and to do this in a +synchronized+
810
+ # section). This is not the cleanest implementation, as ideally we would
811
+ # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
812
+ # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
813
+ # of the said methods and avoid an additional +synchronize+ overhead.
814
+ if conn = @available.poll || try_to_checkout_new_connection
815
+ conn
816
+ else
817
+ reap
818
+ @available.poll(checkout_timeout)
819
+ end
820
+ end
821
+
822
+ #--
823
+ # if owner_thread param is omitted, this must be called in synchronize block
824
+ def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
825
+ @thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
826
+ end
827
+ alias_method :release, :remove_connection_from_thread_cache
828
+
829
+ def new_connection
830
+ Base.send(spec.adapter_method, spec.config).tap do |conn|
831
+ conn.schema_cache = schema_cache.dup if schema_cache
832
+ end
833
+ end
834
+
835
+ # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
836
+ # to the DB is done outside main synchronized section.
837
+ #--
838
+ # Implementation constraint: a newly established connection returned by this
839
+ # method must be in the +.leased+ state.
840
+ def try_to_checkout_new_connection
841
+ # first in synchronized section check if establishing new conns is allowed
842
+ # and increment @now_connecting, to prevent overstepping this pool's @size
843
+ # constraint
844
+ do_checkout = synchronize do
845
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
846
+ @now_connecting += 1
847
+ end
848
+ end
849
+ if do_checkout
850
+ begin
851
+ # if successfully incremented @now_connecting establish new connection
852
+ # outside of synchronized section
853
+ conn = checkout_new_connection
854
+ ensure
855
+ synchronize do
856
+ if conn
857
+ adopt_connection(conn)
858
+ # returned conn needs to be already leased
859
+ conn.lease
860
+ end
861
+ @now_connecting -= 1
862
+ end
863
+ end
864
+ end
865
+ end
866
+
867
+ def adopt_connection(conn)
868
+ conn.pool = self
869
+ @connections << conn
870
+ end
871
+
872
+ def checkout_new_connection
873
+ raise ConnectionNotEstablished unless @automatic_reconnect
874
+ new_connection
875
+ end
876
+
877
+ def checkout_and_verify(c)
878
+ c._run_checkout_callbacks do
879
+ c.verify!
880
+ end
881
+ c
882
+ rescue
883
+ remove c
884
+ c.disconnect!
885
+ raise
455
886
  end
456
- c
457
- end
458
887
  end
459
888
 
460
889
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
461
- # for keeping separate connection pools for Active Record models that connect
462
- # to different databases.
890
+ # for keeping separate connection pools that connect to different databases.
463
891
  #
464
892
  # For example, suppose that you have 5 models, with the following hierarchy:
465
893
  #
@@ -470,7 +898,7 @@ module ActiveRecord
470
898
  # end
471
899
  #
472
900
  # class Book < ActiveRecord::Base
473
- # establish_connection "library_db"
901
+ # establish_connection :library_db
474
902
  # end
475
903
  #
476
904
  # class ScaryBook < Book
@@ -501,36 +929,67 @@ module ActiveRecord
501
929
  # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
502
930
  # All Active Record models use this handler to determine the connection pool that they
503
931
  # should use.
932
+ #
933
+ # The ConnectionHandler class is not coupled with the Active models, as it has no knowledge
934
+ # about the model. The model needs to pass a specification name to the handler,
935
+ # in order to look up the correct connection pool.
504
936
  class ConnectionHandler
505
- def initialize
506
- # These caches are keyed by klass.name, NOT klass. Keying them by klass
507
- # alone would lead to memory leaks in development mode as all previous
508
- # instances of the class would stay in memory.
509
- @owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
510
- h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
937
+ def self.create_owner_to_pool # :nodoc:
938
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
939
+ # Discard the parent's connection pools immediately; we have no need
940
+ # of them
941
+ discard_unowned_pools(h)
942
+
943
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
944
+ end
945
+ end
946
+
947
+ def self.unowned_pool_finalizer(pid_map) # :nodoc:
948
+ lambda do |_|
949
+ discard_unowned_pools(pid_map)
511
950
  end
512
- @class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
513
- h[k] = ThreadSafe::Cache.new
951
+ end
952
+
953
+ def self.discard_unowned_pools(pid_map) # :nodoc:
954
+ pid_map.each do |pid, pools|
955
+ pools.values.compact.each(&:discard!) unless pid == Process.pid
514
956
  end
515
957
  end
516
958
 
959
+ def initialize
960
+ # These caches are keyed by spec.name (ConnectionSpecification#name).
961
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
962
+
963
+ # Backup finalizer: if the forked child never needed a pool, the above
964
+ # early discard has not occurred
965
+ ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool)
966
+ end
967
+
517
968
  def connection_pool_list
518
969
  owner_to_pool.values.compact
519
970
  end
971
+ alias :connection_pools :connection_pool_list
520
972
 
521
- def connection_pools
522
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
523
- In the next release, this will return the same as `#connection_pool_list`.
524
- (An array of pools, rather than a hash mapping specs to pools.)
525
- MSG
973
+ def establish_connection(config)
974
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
975
+ spec = resolver.spec(config)
526
976
 
527
- Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
528
- end
977
+ remove_connection(spec.name)
978
+
979
+ message_bus = ActiveSupport::Notifications.instrumenter
980
+ payload = {
981
+ connection_id: object_id
982
+ }
983
+ if spec
984
+ payload[:spec_name] = spec.name
985
+ payload[:config] = spec.config
986
+ end
987
+
988
+ message_bus.instrument("!connection.active_record", payload) do
989
+ owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
990
+ end
529
991
 
530
- def establish_connection(owner, spec)
531
- @class_to_pool.clear
532
- raise RuntimeError, "Anonymous class is not allowed." unless owner.name
533
- owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
992
+ owner_to_pool[spec.name]
534
993
  end
535
994
 
536
995
  # Returns true if there are any active connections among the connection
@@ -547,6 +1006,8 @@ module ActiveRecord
547
1006
  end
548
1007
 
549
1008
  # Clears the cache which maps classes.
1009
+ #
1010
+ # See ConnectionPool#clear_reloadable_connections! for details.
550
1011
  def clear_reloadable_connections!
551
1012
  connection_pool_list.each(&:clear_reloadable_connections!)
552
1013
  end
@@ -555,105 +1016,72 @@ module ActiveRecord
555
1016
  connection_pool_list.each(&:disconnect!)
556
1017
  end
557
1018
 
1019
+ # Disconnects all currently idle connections.
1020
+ #
1021
+ # See ConnectionPool#flush! for details.
1022
+ def flush_idle_connections!
1023
+ connection_pool_list.each(&:flush!)
1024
+ end
1025
+
558
1026
  # Locate the connection of the nearest super class. This can be an
559
1027
  # active or defined connection: if it is the latter, it will be
560
1028
  # opened and set as the active connection for the class it was defined
561
1029
  # for (not necessarily the current class).
562
- def retrieve_connection(klass) #:nodoc:
563
- pool = retrieve_connection_pool(klass)
564
- raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
565
- conn = pool.connection
566
- raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
567
- conn
1030
+ def retrieve_connection(spec_name) #:nodoc:
1031
+ pool = retrieve_connection_pool(spec_name)
1032
+ raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
1033
+ pool.connection
568
1034
  end
569
1035
 
570
1036
  # Returns true if a connection that's accessible to this class has
571
1037
  # already been opened.
572
- def connected?(klass)
573
- conn = retrieve_connection_pool(klass)
1038
+ def connected?(spec_name)
1039
+ conn = retrieve_connection_pool(spec_name)
574
1040
  conn && conn.connected?
575
1041
  end
576
1042
 
577
1043
  # Remove the connection for this class. This will close the active
578
1044
  # connection and the defined connection (if they exist). The result
579
- # can be used as an argument for establish_connection, for easily
1045
+ # can be used as an argument for #establish_connection, for easily
580
1046
  # re-establishing the connection.
581
- def remove_connection(owner)
582
- if pool = owner_to_pool.delete(owner.name)
583
- @class_to_pool.clear
1047
+ def remove_connection(spec_name)
1048
+ if pool = owner_to_pool.delete(spec_name)
584
1049
  pool.automatic_reconnect = false
585
1050
  pool.disconnect!
586
1051
  pool.spec.config
587
1052
  end
588
1053
  end
589
1054
 
590
- # Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
1055
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
591
1056
  # This makes retrieving the connection pool O(1) once the process is warm.
592
1057
  # When a connection is established or removed, we invalidate the cache.
593
- #
594
- # Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
595
- # However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
596
- # #fetch is significantly slower than #[]. So in the nil case, no caching will
597
- # take place, but that's ok since the nil case is not the common one that we wish
598
- # to optimise for.
599
- def retrieve_connection_pool(klass)
600
- class_to_pool[klass.name] ||= begin
601
- until pool = pool_for(klass)
602
- klass = klass.superclass
603
- break unless klass <= Base
604
- end
605
-
606
- class_to_pool[klass.name] = pool
607
- end
608
- end
609
-
610
- private
611
-
612
- def owner_to_pool
613
- @owner_to_pool[Process.pid]
614
- end
615
-
616
- def class_to_pool
617
- @class_to_pool[Process.pid]
618
- end
619
-
620
- def pool_for(owner)
621
- owner_to_pool.fetch(owner.name) {
622
- if ancestor_pool = pool_from_any_process_for(owner)
1058
+ def retrieve_connection_pool(spec_name)
1059
+ owner_to_pool.fetch(spec_name) do
1060
+ # Check if a connection was previously established in an ancestor process,
1061
+ # which may have been forked.
1062
+ if ancestor_pool = pool_from_any_process_for(spec_name)
623
1063
  # A connection was established in an ancestor process that must have
624
1064
  # subsequently forked. We can't reuse the connection, but we can copy
625
1065
  # the specification and establish a new connection with it.
626
- establish_connection owner, ancestor_pool.spec
1066
+ establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
1067
+ pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
1068
+ end
627
1069
  else
628
- owner_to_pool[owner.name] = nil
1070
+ owner_to_pool[spec_name] = nil
629
1071
  end
630
- }
631
- end
632
-
633
- def pool_from_any_process_for(owner)
634
- owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
635
- owner_to_pool && owner_to_pool[owner.name]
636
- end
637
- end
638
-
639
- class ConnectionManagement
640
- def initialize(app)
641
- @app = app
1072
+ end
642
1073
  end
643
1074
 
644
- def call(env)
645
- testing = env['rack.test']
1075
+ private
646
1076
 
647
- response = @app.call(env)
648
- response[2] = ::Rack::BodyProxy.new(response[2]) do
649
- ActiveRecord::Base.clear_active_connections! unless testing
1077
+ def owner_to_pool
1078
+ @owner_to_pool[Process.pid]
650
1079
  end
651
1080
 
652
- response
653
- rescue Exception
654
- ActiveRecord::Base.clear_active_connections! unless testing
655
- raise
656
- end
1081
+ def pool_from_any_process_for(spec_name)
1082
+ owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
1083
+ owner_to_pool && owner_to_pool[spec_name]
1084
+ end
657
1085
  end
658
1086
  end
659
1087
  end