activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  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 +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  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 +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  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 +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  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 +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  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 +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  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 +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  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 +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  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 +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  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 +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  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 +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  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 +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  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 +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,14 +1,23 @@
1
- require 'thread'
2
- require 'monitor'
3
- require 'set'
4
- require 'active_support/core_ext/module/deprecation'
1
+ # frozen_string_literal: true
2
+
3
+ require "thread"
4
+ require "concurrent/map"
5
+ require "monitor"
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
 
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
+
12
21
  module ConnectionAdapters
13
22
  # Connection pool base class for managing Active Record database
14
23
  # connections.
@@ -31,17 +40,18 @@ module ActiveRecord
31
40
  # Connections can be obtained and used from a connection pool in several
32
41
  # ways:
33
42
  #
34
- # 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
35
45
  # earlier (pre-connection-pooling). Eventually, when you're done with
36
46
  # the connection(s) and wish it to be returned to the pool, you call
37
- # ActiveRecord::Base.clear_active_connections!. This will be the
38
- # 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
39
49
  # Action Pack's request handling cycle.
40
50
  # 2. Manually check out a connection from the pool with
41
- # ActiveRecord::Base.connection_pool.checkout. You are responsible for
51
+ # {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
42
52
  # returning this connection to the pool when finished by calling
43
- # ActiveRecord::Base.connection_pool.checkin(connection).
44
- # 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
45
55
  # obtains a connection, yields it as the sole argument to the block,
46
56
  # and returns it to the pool after the block completes.
47
57
  #
@@ -50,20 +60,257 @@ module ActiveRecord
50
60
  #
51
61
  # == Options
52
62
  #
53
- # There are two connection-pooling-related options that you can add to
63
+ # There are several connection-pooling-related options that you can add to
54
64
  # your database connection configuration:
55
65
  #
56
- # * +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).
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
62
82
  class ConnectionPool
83
+ # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool
84
+ # with which it shares a Monitor.
85
+ class Queue
86
+ def initialize(lock = Monitor.new)
87
+ @lock = lock
88
+ @cond = @lock.new_cond
89
+ @num_waiting = 0
90
+ @queue = []
91
+ end
92
+
93
+ # Test if any threads are currently waiting on the queue.
94
+ def any_waiting?
95
+ synchronize do
96
+ @num_waiting > 0
97
+ end
98
+ end
99
+
100
+ # Returns the number of threads currently waiting on this
101
+ # queue.
102
+ def num_waiting
103
+ synchronize do
104
+ @num_waiting
105
+ end
106
+ end
107
+
108
+ # Add +element+ to the queue. Never blocks.
109
+ def add(element)
110
+ synchronize do
111
+ @queue.push element
112
+ @cond.signal
113
+ end
114
+ end
115
+
116
+ # If +element+ is in the queue, remove and return it, or +nil+.
117
+ def delete(element)
118
+ synchronize do
119
+ @queue.delete(element)
120
+ end
121
+ end
122
+
123
+ # Remove all elements from the queue.
124
+ def clear
125
+ synchronize do
126
+ @queue.clear
127
+ end
128
+ end
129
+
130
+ # Remove the head of the queue.
131
+ #
132
+ # If +timeout+ is not given, remove and return the head the
133
+ # queue if the number of available elements is strictly
134
+ # greater than the number of threads currently waiting (that
135
+ # is, don't jump ahead in line). Otherwise, return +nil+.
136
+ #
137
+ # If +timeout+ is given, block if there is no element
138
+ # available, waiting up to +timeout+ seconds for an element to
139
+ # become available.
140
+ #
141
+ # Raises:
142
+ # - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
143
+ # becomes available within +timeout+ seconds,
144
+ def poll(timeout = nil)
145
+ synchronize { internal_poll(timeout) }
146
+ end
147
+
148
+ private
149
+
150
+ def internal_poll(timeout)
151
+ no_wait_poll || (timeout && wait_poll(timeout))
152
+ end
153
+
154
+ def synchronize(&block)
155
+ @lock.synchronize(&block)
156
+ end
157
+
158
+ # Test if the queue currently contains any elements.
159
+ def any?
160
+ !@queue.empty?
161
+ end
162
+
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
170
+
171
+ # Removes and returns the head of the queue if possible, or +nil+.
172
+ def remove
173
+ @queue.pop
174
+ end
175
+
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
182
+
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
208
+
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
221
+
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
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
259
+ ensure
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
264
+ end
265
+ end
266
+
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.
286
+ #
287
+ # Configure the frequency by setting +reaping_frequency+ in your database
288
+ # yaml file (default 60 seconds).
289
+ class Reaper
290
+ attr_reader :pool, :frequency
291
+
292
+ def initialize(pool, frequency)
293
+ @pool = pool
294
+ @frequency = frequency
295
+ end
296
+
297
+ def run
298
+ return unless frequency && frequency > 0
299
+ Thread.new(frequency, pool) { |t, p|
300
+ loop do
301
+ sleep t
302
+ p.reap
303
+ p.flush
304
+ end
305
+ }
306
+ end
307
+ end
308
+
63
309
  include MonitorMixin
310
+ include QueryCache::ConnectionPoolConfiguration
64
311
 
65
- attr_accessor :automatic_reconnect
66
- attr_reader :spec, :connections
312
+ attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
313
+ attr_reader :spec, :size, :reaper
67
314
 
68
315
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
69
316
  # object which describes database connection information (e.g. adapter,
@@ -76,59 +323,99 @@ module ActiveRecord
76
323
 
77
324
  @spec = spec
78
325
 
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
326
+ @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
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
87
331
 
88
332
  # default max pool size to 5
89
333
  @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
90
334
 
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)
346
+
91
347
  @connections = []
92
348
  @automatic_reconnect = true
349
+
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
93
374
  end
94
375
 
95
376
  # Retrieve the connection associated with the current thread, or call
96
377
  # #checkout to obtain one if necessary.
97
378
  #
98
379
  # #connection can be called any number of times; the connection is
99
- # held in a hash keyed by the thread id.
380
+ # held in a cache keyed by a thread.
100
381
  def connection
101
- synchronize do
102
- @reserved_connections[current_connection_id] ||= checkout
103
- end
382
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
104
383
  end
105
384
 
106
- # 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?
107
390
  def active_connection?
108
- synchronize do
109
- @reserved_connections.fetch(current_connection_id) {
110
- return false
111
- }.in_use?
112
- end
391
+ @thread_cached_conns[connection_cache_key(current_thread)]
113
392
  end
114
393
 
115
394
  # Signal that the thread is finished with the current connection.
116
395
  # #release_connection releases the connection-thread association
117
396
  # and returns the connection to the pool.
118
- def release_connection(with_id = current_connection_id)
119
- conn = synchronize { @reserved_connections.delete(with_id) }
120
- 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
404
+ end
121
405
  end
122
406
 
123
- # 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
124
409
  # exists checkout a connection, yield it to the block, and checkin the
125
410
  # connection when finished.
126
411
  def with_connection
127
- connection_id = current_connection_id
128
- fresh_connection = true unless active_connection?
129
- 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
130
417
  ensure
131
- release_connection(connection_id) if fresh_connection
418
+ release_connection if fresh_connection
132
419
  end
133
420
 
134
421
  # Returns true if a connection has already been opened.
@@ -136,353 +423,665 @@ module ActiveRecord
136
423
  synchronize { @connections.any? }
137
424
  end
138
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
+
139
441
  # Disconnects all connections in the pool, and clears the pool.
140
- def disconnect!
141
- synchronize do
142
- @reserved_connections = {}
143
- @connections.each do |conn|
144
- checkin conn
145
- 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
146
459
  end
147
- @connections = []
148
460
  end
149
461
  end
150
462
 
151
- # Clears the cache which maps classes.
152
- 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:
153
479
  synchronize do
154
- @reserved_connections = {}
480
+ return if @connections.nil? # already discarded
155
481
  @connections.each do |conn|
156
- checkin conn
157
- conn.disconnect! if conn.requires_reloading?
158
- end
159
- @connections.delete_if do |conn|
160
- conn.requires_reloading?
482
+ conn.discard!
161
483
  end
484
+ @connections = @available = @thread_cached_conns = nil
162
485
  end
163
486
  end
164
487
 
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!
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
172
507
  end
173
508
  end
174
509
  end
175
510
 
176
- def columns
177
- with_connection do |c|
178
- c.schema_cache.columns
179
- end
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
+
523
+ # Check-out a database connection from the pool, indicating that you want
524
+ # to use it. You should call #checkin when you no longer need this.
525
+ #
526
+ # This is done by either returning and leasing existing connection, or by
527
+ # creating a new connection and leasing it.
528
+ #
529
+ # If all connections are leased and the pool is at capacity (meaning the
530
+ # number of currently leased connections is greater than or equal to the
531
+ # size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
532
+ #
533
+ # Returns: an AbstractAdapter object.
534
+ #
535
+ # Raises:
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))
180
539
  end
181
- deprecate :columns
182
540
 
183
- def columns_hash
184
- with_connection do |c|
185
- c.schema_cache.columns_hash
541
+ # Check-in a database connection back into the pool, indicating that you
542
+ # no longer need this connection.
543
+ #
544
+ # +conn+: an AbstractAdapter object, which was obtained by earlier by
545
+ # calling #checkout on this pool.
546
+ def checkin(conn)
547
+ conn.lock.synchronize do
548
+ synchronize do
549
+ remove_connection_from_thread_cache conn
550
+
551
+ conn._run_checkin_callbacks do
552
+ conn.expire
553
+ end
554
+
555
+ @available.add conn
556
+ end
186
557
  end
187
558
  end
188
- deprecate :columns_hash
189
559
 
190
- def primary_keys
191
- with_connection do |c|
192
- c.schema_cache.primary_keys
560
+ # Remove a connection from the connection pool. The connection will
561
+ # remain open and active but will no longer be managed by this pool.
562
+ def remove(conn)
563
+ needs_new_connection = false
564
+
565
+ synchronize do
566
+ remove_connection_from_thread_cache conn
567
+
568
+ @connections.delete conn
569
+ @available.delete conn
570
+
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?
193
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
194
589
  end
195
- deprecate :primary_keys
196
590
 
197
- def clear_cache!
198
- with_connection do |c|
199
- c.schema_cache.clear!
591
+ # Recover lost connections for the pool. A lost connection can occur if
592
+ # a programmer forgets to checkin a connection at the end of a thread
593
+ # or a thread dies unexpectedly.
594
+ def reap
595
+ stale_connections = synchronize do
596
+ @connections.select do |conn|
597
+ conn.in_use? && !conn.owner.alive?
598
+ end.each do |conn|
599
+ conn.steal!
600
+ end
601
+ end
602
+
603
+ stale_connections.each do |conn|
604
+ if conn.active?
605
+ conn.reset!
606
+ checkin conn
607
+ else
608
+ remove conn
609
+ end
200
610
  end
201
611
  end
202
- deprecate :clear_cache!
203
612
 
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)
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?
618
+
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
628
+ end
629
+
630
+ idle_connections.each do |conn|
631
+ conn.disconnect!
219
632
  end
220
633
  end
221
634
 
222
- # Check-out a database connection from the pool, indicating that you want
223
- # to use it. You should call #checkin when you no longer need this.
224
- #
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.
232
- #
233
- # Returns: an AbstractAdapter object.
635
+ # Disconnect all currently idle connections. Connections currently checked
636
+ # out are unaffected.
637
+ def flush!
638
+ reap
639
+ flush(-1)
640
+ end
641
+
642
+ def num_waiting_in_queue # :nodoc:
643
+ @available.num_waiting
644
+ end
645
+
646
+ # Return connection pool's usage statistic
647
+ # Example:
234
648
  #
235
- # Raises:
236
- # - ConnectionTimeoutError: no connection can be obtained from the pool
237
- # within the timeout period.
238
- def checkout
649
+ # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
650
+ def stat
239
651
  synchronize do
240
- waited_time = 0
241
-
242
- loop do
243
- conn = @connections.find { |c| c.lease }
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
662
+ end
244
663
 
245
- unless conn
246
- if @connections.size < @size
247
- conn = checkout_new_connection
248
- conn.lease
249
- end
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)
250
673
  end
674
+ end
675
+ end
251
676
 
252
- if conn
253
- checkout_and_verify conn
254
- return conn
255
- 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
256
685
 
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
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
260
700
 
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!
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
275
720
  end
276
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
277
743
  end
278
- end
279
744
 
280
- # Check-in a database connection back into the pool, indicating that you
281
- # no longer need this connection.
282
- #
283
- # +conn+: an AbstractAdapter object, which was obtained by earlier by
284
- # calling +checkout+ on this pool.
285
- def checkin(conn)
286
- synchronize do
287
- conn.run_callbacks :checkin do
288
- conn.expire
289
- @queue.signal
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
290
760
  end
291
761
 
292
- release conn
762
+ msg << " (#{thread_report.join(', ')})" if thread_report.any?
763
+
764
+ raise ExclusiveConnectionTimeoutError, msg
293
765
  end
294
- end
295
766
 
296
- private
767
+ def with_new_connections_blocked
768
+ synchronize do
769
+ @threads_blocking_new_connections += 1
770
+ end
297
771
 
298
- def release(conn)
299
- synchronize do
300
- thread_id = nil
772
+ yield
773
+ ensure
774
+ num_new_conns_required = 0
301
775
 
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
- }
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
308
791
  end
309
792
 
310
- @reserved_connections.delete thread_id if thread_id
793
+ bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
311
794
  end
312
- end
313
795
 
314
- def new_connection
315
- ActiveRecord::Base.send(spec.adapter_method, spec.config)
316
- end
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
317
821
 
318
- def current_connection_id #:nodoc:
319
- ActiveRecord::Base.connection_id ||= Thread.current.object_id
320
- end
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
321
828
 
322
- def checkout_new_connection
323
- raise ConnectionNotEstablished unless @automatic_reconnect
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
324
834
 
325
- c = new_connection
326
- c.pool = self
327
- @connections << c
328
- c
329
- end
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
330
866
 
331
- def checkout_and_verify(c)
332
- c.run_callbacks :checkout do
333
- c.verify!
867
+ def adopt_connection(conn)
868
+ conn.pool = self
869
+ @connections << conn
334
870
  end
335
- c
336
- end
337
871
 
338
- def active_connections
339
- @connections.find_all { |c| c.in_use? }
340
- end
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
886
+ end
341
887
  end
342
888
 
343
889
  # ConnectionHandler is a collection of ConnectionPool objects. It is used
344
- # for keeping separate connection pools for Active Record models that connect
345
- # to different databases.
890
+ # for keeping separate connection pools that connect to different databases.
346
891
  #
347
892
  # For example, suppose that you have 5 models, with the following hierarchy:
348
893
  #
349
- # |
350
- # +-- Book
351
- # | |
352
- # | +-- ScaryBook
353
- # | +-- GoodBook
354
- # +-- Author
355
- # +-- BankAccount
894
+ # class Author < ActiveRecord::Base
895
+ # end
356
896
  #
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.
897
+ # class BankAccount < ActiveRecord::Base
898
+ # end
362
899
  #
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.
900
+ # class Book < ActiveRecord::Base
901
+ # establish_connection :library_db
902
+ # end
903
+ #
904
+ # class ScaryBook < Book
905
+ # end
906
+ #
907
+ # class GoodBook < Book
908
+ # end
909
+ #
910
+ # And a database.yml that looked like this:
911
+ #
912
+ # development:
913
+ # database: my_application
914
+ # host: localhost
915
+ #
916
+ # library_db:
917
+ # database: library
918
+ # host: some.library.org
919
+ #
920
+ # Your primary database in the development environment is "my_application"
921
+ # but the Book model connects to a separate database called "library_db"
922
+ # (this can even be a database on a different machine).
923
+ #
924
+ # Book, ScaryBook and GoodBook will all use the same connection pool to
925
+ # "library_db" while Author, BankAccount, and any other models you create
926
+ # will use the default connection pool to "my_application".
927
+ #
928
+ # The various connection pools are managed by a single instance of
929
+ # ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
930
+ # All Active Record models use this handler to determine the connection pool that they
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.
366
936
  class ConnectionHandler
367
- attr_reader :connection_pools
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)
368
942
 
369
- def initialize(pools = {})
370
- @connection_pools = pools
371
- @class_to_pool = {}
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)
950
+ end
372
951
  end
373
952
 
374
- def establish_connection(name, spec)
375
- @connection_pools[spec] ||= ConnectionAdapters::ConnectionPool.new(spec)
376
- @class_to_pool[name] = @connection_pools[spec]
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
956
+ end
957
+ end
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
+
968
+ def connection_pool_list
969
+ owner_to_pool.values.compact
970
+ end
971
+ alias :connection_pools :connection_pool_list
972
+
973
+ def establish_connection(config)
974
+ resolver = ConnectionSpecification::Resolver.new(Base.configurations)
975
+ spec = resolver.spec(config)
976
+
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
991
+
992
+ owner_to_pool[spec.name]
377
993
  end
378
994
 
379
995
  # Returns true if there are any active connections among the connection
380
996
  # pools that the ConnectionHandler is managing.
381
997
  def active_connections?
382
- connection_pools.values.any? { |pool| pool.active_connection? }
998
+ connection_pool_list.any?(&:active_connection?)
383
999
  end
384
1000
 
385
- # Returns any connections in use by the current thread back to the pool.
1001
+ # Returns any connections in use by the current thread back to the pool,
1002
+ # and also returns connections to the pool cached by threads that are no
1003
+ # longer alive.
386
1004
  def clear_active_connections!
387
- @connection_pools.each_value {|pool| pool.release_connection }
1005
+ connection_pool_list.each(&:release_connection)
388
1006
  end
389
1007
 
390
1008
  # Clears the cache which maps classes.
1009
+ #
1010
+ # See ConnectionPool#clear_reloadable_connections! for details.
391
1011
  def clear_reloadable_connections!
392
- @connection_pools.each_value {|pool| pool.clear_reloadable_connections! }
1012
+ connection_pool_list.each(&:clear_reloadable_connections!)
393
1013
  end
394
1014
 
395
1015
  def clear_all_connections!
396
- @connection_pools.each_value {|pool| pool.disconnect! }
1016
+ connection_pool_list.each(&:disconnect!)
397
1017
  end
398
1018
 
399
- # Verify active connections.
400
- def verify_active_connections! #:nodoc:
401
- @connection_pools.each_value {|pool| pool.verify_active_connections! }
1019
+ # Disconnects all currently idle connections.
1020
+ #
1021
+ # See ConnectionPool#flush! for details.
1022
+ def flush_idle_connections!
1023
+ connection_pool_list.each(&:flush!)
402
1024
  end
403
1025
 
404
1026
  # Locate the connection of the nearest super class. This can be an
405
1027
  # active or defined connection: if it is the latter, it will be
406
1028
  # opened and set as the active connection for the class it was defined
407
1029
  # for (not necessarily the current class).
408
- def retrieve_connection(klass) #:nodoc:
409
- pool = retrieve_connection_pool(klass)
410
- (pool && pool.connection) or raise ConnectionNotEstablished
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
411
1034
  end
412
1035
 
413
1036
  # Returns true if a connection that's accessible to this class has
414
1037
  # already been opened.
415
- def connected?(klass)
416
- conn = retrieve_connection_pool(klass)
1038
+ def connected?(spec_name)
1039
+ conn = retrieve_connection_pool(spec_name)
417
1040
  conn && conn.connected?
418
1041
  end
419
1042
 
420
1043
  # Remove the connection for this class. This will close the active
421
1044
  # connection and the defined connection (if they exist). The result
422
- # can be used as an argument for establish_connection, for easily
1045
+ # can be used as an argument for #establish_connection, for easily
423
1046
  # 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
432
- end
433
-
434
- 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
445
-
446
- def initialize(body, testing = false)
447
- @body = body
448
- @testing = testing
1047
+ def remove_connection(spec_name)
1048
+ if pool = owner_to_pool.delete(spec_name)
1049
+ pool.automatic_reconnect = false
1050
+ pool.disconnect!
1051
+ pool.spec.config
449
1052
  end
1053
+ end
450
1054
 
451
- def method_missing(method_sym, *arguments, &block)
452
- @body.send(method_sym, *arguments, &block)
1055
+ # Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
1056
+ # This makes retrieving the connection pool O(1) once the process is warm.
1057
+ # When a connection is established or removed, we invalidate the cache.
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)
1063
+ # A connection was established in an ancestor process that must have
1064
+ # subsequently forked. We can't reuse the connection, but we can copy
1065
+ # the specification and establish a new connection with it.
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
1069
+ else
1070
+ owner_to_pool[spec_name] = nil
1071
+ end
453
1072
  end
1073
+ end
454
1074
 
455
- def respond_to?(method_sym, include_private = false)
456
- super || @body.respond_to?(method_sym)
457
- end
1075
+ private
458
1076
 
459
- def each(&block)
460
- body.each(&block)
1077
+ def owner_to_pool
1078
+ @owner_to_pool[Process.pid]
461
1079
  end
462
1080
 
463
- def close
464
- body.close if body.respond_to?(:close)
465
-
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
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]
469
1084
  end
470
- end
471
-
472
- def initialize(app)
473
- @app = app
474
- end
475
-
476
- def call(env)
477
- testing = env.key?('rack.test')
478
-
479
- status, headers, body = @app.call(env)
480
-
481
- [status, headers, Proxy.new(body, testing)]
482
- rescue
483
- ActiveRecord::Base.clear_active_connections! unless testing
484
- raise
485
- end
486
1085
  end
487
1086
  end
488
1087
  end