activerecord 3.2.19 → 5.0.0

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

Potentially problematic release.


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

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