activerecord 7.2.4 → 8.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1013 -878
  3. data/README.rdoc +1 -1
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/association.rb +69 -27
  6. data/lib/active_record/associations/association_scope.rb +1 -1
  7. data/lib/active_record/associations/belongs_to_association.rb +2 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +36 -8
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +7 -3
  14. data/lib/active_record/associations/collection_proxy.rb +26 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/foreign_association.rb +3 -1
  19. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  20. data/lib/active_record/associations/has_one_association.rb +1 -0
  21. data/lib/active_record/associations/join_dependency.rb +3 -2
  22. data/lib/active_record/associations/preloader/association.rb +3 -3
  23. data/lib/active_record/associations/preloader/batch.rb +7 -1
  24. data/lib/active_record/associations/preloader/branch.rb +1 -0
  25. data/lib/active_record/associations/singular_association.rb +8 -3
  26. data/lib/active_record/associations.rb +192 -24
  27. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  28. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  29. data/lib/active_record/attribute_methods/query.rb +34 -0
  30. data/lib/active_record/attribute_methods/serialization.rb +18 -5
  31. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  32. data/lib/active_record/attributes.rb +3 -0
  33. data/lib/active_record/autosave_association.rb +69 -27
  34. data/lib/active_record/base.rb +1 -2
  35. data/lib/active_record/callbacks.rb +1 -2
  36. data/lib/active_record/coders/column_serializer.rb +1 -1
  37. data/lib/active_record/coders/json.rb +16 -6
  38. data/lib/active_record/coders/yaml_column.rb +2 -2
  39. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  40. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  41. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -13
  42. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +429 -93
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +148 -74
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +124 -34
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +129 -63
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +132 -65
  53. data/lib/active_record/connection_adapters/column.rb +14 -4
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
  56. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  57. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -66
  59. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  60. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
  61. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  62. data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
  63. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +79 -61
  64. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  65. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
  67. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  69. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
  72. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
  73. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +156 -68
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
  75. data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
  76. data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
  77. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  78. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  79. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  80. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  81. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +15 -16
  82. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +104 -37
  83. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  84. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +41 -66
  85. data/lib/active_record/connection_adapters/trilogy_adapter.rb +15 -18
  86. data/lib/active_record/connection_adapters.rb +1 -56
  87. data/lib/active_record/connection_handling.rb +25 -2
  88. data/lib/active_record/core.rb +33 -17
  89. data/lib/active_record/counter_cache.rb +35 -8
  90. data/lib/active_record/database_configurations/database_config.rb +9 -1
  91. data/lib/active_record/database_configurations/hash_config.rb +74 -9
  92. data/lib/active_record/database_configurations/url_config.rb +15 -3
  93. data/lib/active_record/database_configurations.rb +7 -3
  94. data/lib/active_record/delegated_type.rb +1 -1
  95. data/lib/active_record/disable_joins_association_relation.rb +5 -1
  96. data/lib/active_record/dynamic_matchers.rb +54 -69
  97. data/lib/active_record/encryption/config.rb +3 -1
  98. data/lib/active_record/encryption/encryptable_record.rb +8 -8
  99. data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
  100. data/lib/active_record/encryption/encrypted_fixtures.rb +12 -7
  101. data/lib/active_record/encryption/encryptor.rb +28 -8
  102. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  103. data/lib/active_record/encryption/scheme.rb +9 -2
  104. data/lib/active_record/enum.rb +33 -30
  105. data/lib/active_record/errors.rb +33 -9
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/explain_registry.rb +51 -2
  108. data/lib/active_record/filter_attribute_handler.rb +76 -0
  109. data/lib/active_record/fixtures.rb +2 -4
  110. data/lib/active_record/future_result.rb +15 -9
  111. data/lib/active_record/gem_version.rb +2 -2
  112. data/lib/active_record/inheritance.rb +9 -4
  113. data/lib/active_record/insert_all.rb +14 -9
  114. data/lib/active_record/locking/optimistic.rb +8 -1
  115. data/lib/active_record/locking/pessimistic.rb +5 -0
  116. data/lib/active_record/log_subscriber.rb +31 -29
  117. data/lib/active_record/message_pack.rb +13 -0
  118. data/lib/active_record/middleware/shard_selector.rb +34 -17
  119. data/lib/active_record/migration/command_recorder.rb +56 -13
  120. data/lib/active_record/migration/compatibility.rb +37 -24
  121. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  122. data/lib/active_record/migration.rb +48 -42
  123. data/lib/active_record/model_schema.rb +40 -15
  124. data/lib/active_record/nested_attributes.rb +14 -12
  125. data/lib/active_record/persistence.rb +163 -133
  126. data/lib/active_record/query_cache.rb +22 -15
  127. data/lib/active_record/query_logs.rb +100 -52
  128. data/lib/active_record/query_logs_formatter.rb +17 -28
  129. data/lib/active_record/querying.rb +21 -9
  130. data/lib/active_record/railtie.rb +35 -30
  131. data/lib/active_record/railties/controller_runtime.rb +11 -6
  132. data/lib/active_record/railties/databases.rake +26 -38
  133. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  134. data/lib/active_record/railties/job_runtime.rb +10 -11
  135. data/lib/active_record/reflection.rb +54 -22
  136. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  137. data/lib/active_record/relation/batches.rb +147 -73
  138. data/lib/active_record/relation/calculations.rb +60 -43
  139. data/lib/active_record/relation/delegation.rb +25 -15
  140. data/lib/active_record/relation/finder_methods.rb +73 -43
  141. data/lib/active_record/relation/merger.rb +12 -10
  142. data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
  143. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  144. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  145. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  146. data/lib/active_record/relation/predicate_builder.rb +23 -8
  147. data/lib/active_record/relation/query_attribute.rb +3 -1
  148. data/lib/active_record/relation/query_methods.rb +200 -96
  149. data/lib/active_record/relation/spawn_methods.rb +7 -7
  150. data/lib/active_record/relation/where_clause.rb +2 -9
  151. data/lib/active_record/relation.rb +127 -87
  152. data/lib/active_record/result.rb +109 -24
  153. data/lib/active_record/runtime_registry.rb +42 -58
  154. data/lib/active_record/sanitization.rb +9 -6
  155. data/lib/active_record/schema.rb +1 -1
  156. data/lib/active_record/schema_dumper.rb +17 -10
  157. data/lib/active_record/schema_migration.rb +2 -1
  158. data/lib/active_record/scoping/named.rb +5 -2
  159. data/lib/active_record/scoping.rb +0 -1
  160. data/lib/active_record/signed_id.rb +44 -16
  161. data/lib/active_record/statement_cache.rb +24 -20
  162. data/lib/active_record/store.rb +53 -22
  163. data/lib/active_record/structured_event_subscriber.rb +85 -0
  164. data/lib/active_record/table_metadata.rb +6 -23
  165. data/lib/active_record/tasks/abstract_tasks.rb +83 -0
  166. data/lib/active_record/tasks/database_tasks.rb +85 -85
  167. data/lib/active_record/tasks/mysql_database_tasks.rb +4 -58
  168. data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
  169. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  170. data/lib/active_record/test_databases.rb +18 -4
  171. data/lib/active_record/test_fixtures.rb +52 -6
  172. data/lib/active_record/testing/query_assertions.rb +8 -2
  173. data/lib/active_record/timestamp.rb +4 -2
  174. data/lib/active_record/token_for.rb +1 -1
  175. data/lib/active_record/transaction.rb +2 -5
  176. data/lib/active_record/transactions.rb +56 -32
  177. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  178. data/lib/active_record/type/internal/timezone.rb +7 -0
  179. data/lib/active_record/type/json.rb +13 -2
  180. data/lib/active_record/type/serialized.rb +16 -4
  181. data/lib/active_record/type/type_map.rb +1 -1
  182. data/lib/active_record/type_caster/connection.rb +2 -1
  183. data/lib/active_record/validations/associated.rb +1 -1
  184. data/lib/active_record/validations/uniqueness.rb +8 -8
  185. data/lib/active_record.rb +85 -50
  186. data/lib/arel/alias_predication.rb +2 -0
  187. data/lib/arel/collectors/bind.rb +2 -2
  188. data/lib/arel/collectors/sql_string.rb +1 -1
  189. data/lib/arel/collectors/substitute_binds.rb +2 -2
  190. data/lib/arel/crud.rb +6 -11
  191. data/lib/arel/nodes/binary.rb +1 -1
  192. data/lib/arel/nodes/count.rb +2 -2
  193. data/lib/arel/nodes/function.rb +4 -10
  194. data/lib/arel/nodes/named_function.rb +2 -2
  195. data/lib/arel/nodes/node.rb +2 -2
  196. data/lib/arel/nodes/sql_literal.rb +1 -1
  197. data/lib/arel/nodes.rb +0 -2
  198. data/lib/arel/predications.rb +1 -3
  199. data/lib/arel/select_manager.rb +7 -2
  200. data/lib/arel/table.rb +3 -7
  201. data/lib/arel/visitors/dot.rb +0 -3
  202. data/lib/arel/visitors/postgresql.rb +55 -0
  203. data/lib/arel/visitors/sqlite.rb +55 -8
  204. data/lib/arel/visitors/to_sql.rb +4 -22
  205. data/lib/arel.rb +3 -1
  206. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  207. metadata +15 -12
  208. data/lib/active_record/explain_subscriber.rb +0 -34
  209. data/lib/active_record/normalization.rb +0 -163
  210. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "thread"
4
3
  require "concurrent/map"
5
4
  require "monitor"
6
5
 
@@ -9,27 +8,22 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
9
8
 
10
9
  module ActiveRecord
11
10
  module ConnectionAdapters
12
- module AbstractPool # :nodoc:
13
- end
14
-
15
11
  class NullPool # :nodoc:
16
- include ConnectionAdapters::AbstractPool
17
-
18
- class NullConfig # :nodoc:
12
+ class NullConfig
19
13
  def method_missing(...)
20
14
  nil
21
15
  end
22
16
  end
23
- NULL_CONFIG = NullConfig.new # :nodoc:
17
+ NULL_CONFIG = NullConfig.new
24
18
 
25
19
  def initialize
26
20
  super()
27
- @mutex = Mutex.new
21
+ @monitor = Monitor.new
28
22
  @server_version = nil
29
23
  end
30
24
 
31
25
  def server_version(connection) # :nodoc:
32
- @server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
26
+ @server_version || @monitor.synchronize { @server_version ||= connection.get_database_version }
33
27
  end
34
28
 
35
29
  def schema_reflection
@@ -37,8 +31,8 @@ module ActiveRecord
37
31
  end
38
32
 
39
33
  def schema_cache; end
40
- def connection_class; end
41
34
  def query_cache; end
35
+ def connection_descriptor; end
42
36
  def checkin(_); end
43
37
  def remove(_); end
44
38
  def async_executor; end
@@ -50,6 +44,11 @@ module ActiveRecord
50
44
  def dirties_query_cache
51
45
  true
52
46
  end
47
+
48
+ def pool_transaction_isolation_level; end
49
+ def pool_transaction_isolation_level=(isolation_level)
50
+ raise NotImplementedError, "This method should never be called"
51
+ end
53
52
  end
54
53
 
55
54
  # = Active Record Connection Pool
@@ -102,13 +101,21 @@ module ActiveRecord
102
101
  # There are several connection-pooling-related options that you can add to
103
102
  # your database connection configuration:
104
103
  #
105
- # * +pool+: maximum number of connections the pool may manage (default 5).
106
- # * +idle_timeout+: number of seconds that a connection will be kept
107
- # unused in the pool before it is automatically disconnected (default
108
- # 300 seconds). Set this to zero to keep connections forever.
109
104
  # * +checkout_timeout+: number of seconds to wait for a connection to
110
105
  # become available before giving up and raising a timeout error (default
111
106
  # 5 seconds).
107
+ # * +idle_timeout+: number of seconds that a connection will be kept
108
+ # unused in the pool before it is automatically disconnected (default
109
+ # 300 seconds). Set this to zero to keep connections forever.
110
+ # * +keepalive+: number of seconds between keepalive checks if the
111
+ # connection has been idle (default 600 seconds).
112
+ # * +max_age+: number of seconds the pool will allow the connection to
113
+ # exist before retiring it at next checkin. (default Float::INFINITY).
114
+ # * +max_connections+: maximum number of connections the pool may manage (default 5).
115
+ # Set to +nil+ or -1 for unlimited connections.
116
+ # * +min_connections+: minimum number of connections the pool will open and maintain (default 0).
117
+ # * +pool_jitter+: maximum reduction factor to apply to +max_age+ and
118
+ # +keepalive+ intervals (default 0.2; range 0.0-1.0).
112
119
  #
113
120
  #--
114
121
  # Synchronization policy:
@@ -116,6 +123,7 @@ module ActiveRecord
116
123
  # * access to these instance variables needs to be in +synchronize+:
117
124
  # * @connections
118
125
  # * @now_connecting
126
+ # * @maintaining
119
127
  # * private methods that require being called in a +synchronize+ blocks
120
128
  # are now explicitly documented
121
129
  class ConnectionPool
@@ -126,9 +134,9 @@ module ActiveRecord
126
134
  else
127
135
  class WeakThreadKeyMap # :nodoc:
128
136
  # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
129
- # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
137
+ # but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
130
138
  def initialize
131
- @map = {}
139
+ @map = Concurrent::Map.new
132
140
  end
133
141
 
134
142
  def clear
@@ -140,7 +148,9 @@ module ActiveRecord
140
148
  end
141
149
 
142
150
  def []=(key, value)
143
- @map.select! { |c, _| c&.alive? }
151
+ @map.each_pair do |thread, _|
152
+ @map.delete(thread) unless thread&.alive?
153
+ end
144
154
  @map[key] = value
145
155
  end
146
156
  end
@@ -172,21 +182,30 @@ module ActiveRecord
172
182
  end
173
183
  end
174
184
 
175
- class LeaseRegistry # :nodoc:
176
- def initialize
177
- @mutex = Mutex.new
178
- @map = WeakThreadKeyMap.new
185
+ if RUBY_ENGINE == "ruby"
186
+ # Thanks to the GVL, the LeaseRegistry doesn't need to be synchronized on MRI
187
+ class LeaseRegistry < WeakThreadKeyMap # :nodoc:
188
+ def [](context)
189
+ super || (self[context] = Lease.new)
190
+ end
179
191
  end
192
+ else
193
+ class LeaseRegistry # :nodoc:
194
+ def initialize
195
+ @mutex = Mutex.new
196
+ @map = WeakThreadKeyMap.new
197
+ end
180
198
 
181
- def [](context)
182
- @mutex.synchronize do
183
- @map[context] ||= Lease.new
199
+ def [](context)
200
+ @mutex.synchronize do
201
+ @map[context] ||= Lease.new
202
+ end
184
203
  end
185
- end
186
204
 
187
- def clear
188
- @mutex.synchronize do
189
- @map.clear
205
+ def clear
206
+ @mutex.synchronize do
207
+ @map.clear
208
+ end
190
209
  end
191
210
  end
192
211
  end
@@ -218,10 +237,10 @@ module ActiveRecord
218
237
 
219
238
  include MonitorMixin
220
239
  prepend QueryCache::ConnectionPoolConfiguration
221
- include ConnectionAdapters::AbstractPool
222
240
 
223
241
  attr_accessor :automatic_reconnect, :checkout_timeout
224
- attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
242
+ attr_reader :db_config, :max_connections, :min_connections, :max_age, :keepalive, :reaper, :pool_config, :async_executor, :role, :shard
243
+ alias :size :max_connections
225
244
 
226
245
  delegate :schema_reflection, :server_version, to: :pool_config
227
246
 
@@ -241,7 +260,10 @@ module ActiveRecord
241
260
 
242
261
  @checkout_timeout = db_config.checkout_timeout
243
262
  @idle_timeout = db_config.idle_timeout
244
- @size = db_config.pool
263
+ @max_connections = db_config.max_connections
264
+ @min_connections = db_config.min_connections
265
+ @max_age = db_config.max_age
266
+ @keepalive = db_config.keepalive
245
267
 
246
268
  # This variable tracks the cache of threads mapped to reserved connections, with the
247
269
  # sole purpose of speeding up the +connection+ method. It is not the authoritative
@@ -263,6 +285,12 @@ module ActiveRecord
263
285
  # currently in the process of independently establishing connections to the DB.
264
286
  @now_connecting = 0
265
287
 
288
+ # Sometimes otherwise-idle connections are temporarily held by the Reaper for
289
+ # maintenance. This variable tracks the number of connections currently in that
290
+ # state -- if a thread requests a connection and there are none available, it
291
+ # will await any in-maintenance connections in preference to creating a new one.
292
+ @maintaining = 0
293
+
266
294
  @threads_blocking_new_connections = 0
267
295
 
268
296
  @available = ConnectionLeasingQueue.new self
@@ -273,13 +301,17 @@ module ActiveRecord
273
301
 
274
302
  @schema_cache = nil
275
303
 
304
+ @activated = false
305
+ @original_context = ActiveSupport::IsolatedExecutionState.context
306
+
307
+ @reaper_lock = Monitor.new
276
308
  @reaper = Reaper.new(self, db_config.reaping_frequency)
277
309
  @reaper.run
278
310
  end
279
311
 
280
312
  def inspect # :nodoc:
281
- name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
282
- shard_field = " shard=#{@shard.inspect}" unless @shard == :default
313
+ name_field = " name=#{name_inspect}" if name_inspect
314
+ shard_field = " shard=#{shard_inspect}" if shard_inspect
283
315
 
284
316
  "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
285
317
  end
@@ -309,6 +341,14 @@ module ActiveRecord
309
341
  InternalMetadata.new(self)
310
342
  end
311
343
 
344
+ def activate
345
+ @activated = true
346
+ end
347
+
348
+ def activated?
349
+ @activated
350
+ end
351
+
312
352
  # Retrieve the connection associated with the current thread, or call
313
353
  # #checkout to obtain one if necessary.
314
354
  #
@@ -325,14 +365,6 @@ module ActiveRecord
325
365
  connection_lease.sticky.nil?
326
366
  end
327
367
 
328
- def connection
329
- ActiveRecord.deprecator.warn(<<~MSG)
330
- ActiveRecord::ConnectionAdapters::ConnectionPool#connection is deprecated
331
- and will be removed in Rails 8.0. Use #lease_connection instead.
332
- MSG
333
- lease_connection
334
- end
335
-
336
368
  def pin_connection!(lock_thread) # :nodoc:
337
369
  @pinned_connection ||= (connection_lease&.connection || checkout)
338
370
  @pinned_connections_depth += 1
@@ -377,8 +409,8 @@ module ActiveRecord
377
409
  clean
378
410
  end
379
411
 
380
- def connection_class # :nodoc:
381
- pool_config.connection_class
412
+ def connection_descriptor # :nodoc:
413
+ pool_config.connection_descriptor
382
414
  end
383
415
 
384
416
  # Returns true if there is an open connection being used for the current thread.
@@ -399,6 +431,8 @@ module ActiveRecord
399
431
  # #lease_connection or #with_connection methods, connections obtained through
400
432
  # #checkout will not be automatically released.
401
433
  def release_connection(existing_lease = nil)
434
+ return if self.discarded?
435
+
402
436
  if conn = connection_lease.release
403
437
  checkin conn
404
438
  return true
@@ -424,18 +458,36 @@ module ActiveRecord
424
458
  begin
425
459
  yield lease.connection
426
460
  ensure
427
- lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
461
+ lease.sticky = sticky_was if prevent_permanent_checkout
428
462
  end
429
463
  else
430
464
  begin
431
465
  yield lease.connection = checkout
432
466
  ensure
433
- lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
467
+ lease.sticky = sticky_was if prevent_permanent_checkout
434
468
  release_connection(lease) unless lease.sticky
435
469
  end
436
470
  end
437
471
  end
438
472
 
473
+ def with_pool_transaction_isolation_level(isolation_level, transaction_open, &block) # :nodoc:
474
+ if !ActiveRecord.default_transaction_isolation_level.nil?
475
+ begin
476
+ if transaction_open && self.pool_transaction_isolation_level != ActiveRecord.default_transaction_isolation_level
477
+ raise ActiveRecord::TransactionIsolationError, "cannot set default isolation level while transaction is open"
478
+ end
479
+
480
+ old_level = self.pool_transaction_isolation_level
481
+ self.pool_transaction_isolation_level = isolation_level
482
+ yield
483
+ ensure
484
+ self.pool_transaction_isolation_level = old_level
485
+ end
486
+ else
487
+ yield
488
+ end
489
+ end
490
+
439
491
  # Returns true if a connection has already been opened.
440
492
  def connected?
441
493
  synchronize { @connections.any?(&:connected?) }
@@ -463,18 +515,32 @@ module ActiveRecord
463
515
  # connections in the pool within a timeout interval (default duration is
464
516
  # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
465
517
  def disconnect(raise_on_acquisition_timeout = true)
466
- with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
467
- synchronize do
468
- @connections.each do |conn|
469
- if conn.in_use?
470
- conn.steal!
471
- checkin conn
518
+ @reaper_lock.synchronize do
519
+ return if self.discarded?
520
+
521
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
522
+ synchronize do
523
+ return if self.discarded?
524
+ @connections.each do |conn|
525
+ if conn.in_use?
526
+ conn.steal!
527
+
528
+ ActiveSupport.error_reporter.handle(source: "active_record.connection_pool") do
529
+ checkin conn
530
+ end
531
+ end
532
+
533
+ ActiveSupport.error_reporter.handle(source: "active_record.connection_pool") do
534
+ conn.disconnect!
535
+ end
472
536
  end
473
- conn.disconnect!
537
+ @connections = @pinned_connection ? [@pinned_connection] : []
538
+ @leases.clear
539
+ @available.clear
540
+
541
+ # Stop maintaining the minimum size until reactivated
542
+ @activated = false
474
543
  end
475
- @connections = []
476
- @leases.clear
477
- @available.clear
478
544
  end
479
545
  end
480
546
  end
@@ -495,12 +561,15 @@ module ActiveRecord
495
561
  #
496
562
  # See AbstractAdapter#discard!
497
563
  def discard! # :nodoc:
498
- synchronize do
499
- return if self.discarded?
500
- @connections.each do |conn|
501
- conn.discard!
564
+ @reaper_lock.synchronize do
565
+ synchronize do
566
+ return if self.discarded?
567
+ Reaper.discard_pool(self)
568
+ @connections.each do |conn|
569
+ conn.discard!
570
+ end
571
+ @connections = @available = @leases = nil
502
572
  end
503
- @connections = @available = @leases = nil
504
573
  end
505
574
  end
506
575
 
@@ -508,7 +577,17 @@ module ActiveRecord
508
577
  @connections.nil?
509
578
  end
510
579
 
511
- # Clears the cache which maps classes and re-connects connections that
580
+ def maintainable? # :nodoc:
581
+ synchronize do
582
+ @connections&.size&.> 0 || (activated? && @min_connections > 0)
583
+ end
584
+ end
585
+
586
+ def reaper_lock(&block) # :nodoc:
587
+ @reaper_lock.synchronize(&block)
588
+ end
589
+
590
+ # Clears reloadable connections from the pool and re-connects connections that
512
591
  # require reloading.
513
592
  #
514
593
  # Raises:
@@ -531,7 +610,7 @@ module ActiveRecord
531
610
  end
532
611
  end
533
612
 
534
- # Clears the cache which maps classes and re-connects connections that
613
+ # Clears reloadable connections from the pool and re-connects connections that
535
614
  # require reloading.
536
615
  #
537
616
  # The pool first tries to gain ownership of all connections. If unable to
@@ -591,11 +670,7 @@ module ActiveRecord
591
670
  conn.lock.synchronize do
592
671
  synchronize do
593
672
  connection_lease.clear(conn)
594
-
595
- conn._run_checkin_callbacks do
596
- conn.expire
597
- end
598
-
673
+ conn.expire
599
674
  @available.add conn
600
675
  end
601
676
  end
@@ -613,7 +688,7 @@ module ActiveRecord
613
688
  @available.delete conn
614
689
 
615
690
  # @available.any_waiting? => true means that prior to removing this
616
- # conn, the pool was at its max size (@connections.size == @size).
691
+ # conn, the pool was at its max size (@connections.size == @max_connections).
617
692
  # This would mean that any threads stuck waiting in the queue wouldn't
618
693
  # know they could checkout_new_connection, so let's do it for them.
619
694
  # Because condition-wait loop is encapsulated in the Queue class
@@ -621,14 +696,14 @@ module ActiveRecord
621
696
  # that are "stuck" there are helpless. They have no way of creating
622
697
  # new connections and are completely reliant on us feeding available
623
698
  # connections into the Queue.
624
- needs_new_connection = @available.any_waiting?
699
+ needs_new_connection = @available.num_waiting > @maintaining
625
700
  end
626
701
 
627
702
  # This is intentionally done outside of the synchronized section as we
628
703
  # would like not to hold the main mutex while checking out new connections.
629
704
  # Thus there is some chance that needs_new_connection information is now
630
705
  # stale, we can live with that (bulk_make_new_connections will make
631
- # sure not to exceed the pool's @size limit).
706
+ # sure not to exceed the pool's @max_connections limit).
632
707
  bulk_make_new_connections(1) if needs_new_connection
633
708
  end
634
709
 
@@ -661,11 +736,27 @@ module ActiveRecord
661
736
  def flush(minimum_idle = @idle_timeout)
662
737
  return if minimum_idle.nil?
663
738
 
664
- idle_connections = synchronize do
739
+ removed_connections = synchronize do
665
740
  return if self.discarded?
666
- @connections.select do |conn|
741
+
742
+ idle_connections = @connections.select do |conn|
667
743
  !conn.in_use? && conn.seconds_idle >= minimum_idle
668
- end.each do |conn|
744
+ end.sort_by { |conn| -conn.seconds_idle } # sort longest idle first
745
+
746
+ # Don't go below our configured pool minimum unless we're flushing
747
+ # everything
748
+ idles_to_retain =
749
+ if minimum_idle > 0
750
+ @min_connections - (@connections.size - idle_connections.size)
751
+ else
752
+ 0
753
+ end
754
+
755
+ if idles_to_retain > 0
756
+ idle_connections.pop idles_to_retain
757
+ end
758
+
759
+ idle_connections.each do |conn|
669
760
  conn.lease
670
761
 
671
762
  @available.delete conn
@@ -673,22 +764,111 @@ module ActiveRecord
673
764
  end
674
765
  end
675
766
 
676
- idle_connections.each do |conn|
767
+ removed_connections.each do |conn|
677
768
  conn.disconnect!
678
769
  end
679
770
  end
680
771
 
681
772
  # Disconnect all currently idle connections. Connections currently checked
682
- # out are unaffected.
773
+ # out are unaffected. The pool will stop maintaining its minimum size until
774
+ # it is reactivated (such as by a subsequent checkout).
683
775
  def flush!
684
776
  reap
685
777
  flush(-1)
778
+
779
+ # Stop maintaining the minimum size until reactivated
780
+ @activated = false
781
+ end
782
+
783
+ # Ensure that the pool contains at least the configured minimum number of
784
+ # connections.
785
+ def prepopulate
786
+ need_new_connections = nil
787
+
788
+ synchronize do
789
+ return if self.discarded?
790
+
791
+ # We don't want to start prepopulating until we know the pool is wanted,
792
+ # so we can avoid maintaining full pools in one-off scripts etc.
793
+ return unless @activated
794
+
795
+ need_new_connections = @connections.size < @min_connections
796
+ end
797
+
798
+ if need_new_connections
799
+ while new_conn = try_to_checkout_new_connection { @connections.size < @min_connections }
800
+ new_conn.allow_preconnect = true
801
+ checkin(new_conn)
802
+ end
803
+ end
804
+ end
805
+
806
+ def retire_old_connections(max_age = @max_age)
807
+ max_age ||= Float::INFINITY
808
+
809
+ sequential_maintenance -> c { c.connection_age&.>= c.pool_jitter(max_age) } do |conn|
810
+ # Disconnect, then return the adapter to the pool. Preconnect will
811
+ # handle the rest.
812
+ conn.disconnect!
813
+ end
814
+ end
815
+
816
+ # Preconnect all connections in the pool. This saves pool users from
817
+ # having to wait for a connection to be established when first using it
818
+ # after checkout.
819
+ def preconnect
820
+ reaper_lock do
821
+ sequential_maintenance -> c { (!c.connected? || !c.verified?) && c.allow_preconnect } do |conn|
822
+ conn.connect!
823
+ rescue
824
+ # Wholesale rescue: there's nothing we can do but move on. The
825
+ # connection will go back to the pool, and the next consumer will
826
+ # presumably try to connect again -- which will either work, or
827
+ # fail and they'll be able to report the exception.
828
+ end
829
+ end
830
+ end
831
+
832
+ # Prod any connections that have been idle for longer than the configured
833
+ # keepalive time. This will incidentally verify the connection is still
834
+ # alive, but the main purpose is to show the server (and any intermediate
835
+ # network hops) that we're still here and using the connection.
836
+ def keep_alive(threshold = @keepalive)
837
+ return if threshold.nil?
838
+
839
+ sequential_maintenance -> c { (c.seconds_since_last_activity || 0) > c.pool_jitter(threshold) } do |conn|
840
+ # conn.active? will cause some amount of network activity, which is all
841
+ # we need to provide a keepalive signal.
842
+ #
843
+ # If it returns false, the connection is already broken; disconnect,
844
+ # so it can be found and repaired.
845
+ conn.disconnect! unless conn.active?
846
+ end
847
+ end
848
+
849
+ # Immediately mark all current connections as due for replacement,
850
+ # equivalent to them having reached +max_age+ -- even if there is
851
+ # no +max_age+ configured.
852
+ def recycle!
853
+ synchronize do
854
+ return if self.discarded?
855
+
856
+ @connections.each do |conn|
857
+ conn.force_retirement
858
+ end
859
+ end
860
+
861
+ retire_old_connections
686
862
  end
687
863
 
688
864
  def num_waiting_in_queue # :nodoc:
689
865
  @available.num_waiting
690
866
  end
691
867
 
868
+ def num_available_in_queue # :nodoc:
869
+ @available.size
870
+ end
871
+
692
872
  # Returns the connection pool's usage statistic.
693
873
  #
694
874
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
@@ -719,6 +899,16 @@ module ActiveRecord
719
899
  raise ex.set_pool(self)
720
900
  end
721
901
 
902
+ def pool_transaction_isolation_level
903
+ isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
904
+ ActiveSupport::IsolatedExecutionState[isolation_level_key]
905
+ end
906
+
907
+ def pool_transaction_isolation_level=(isolation_level)
908
+ isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
909
+ ActiveSupport::IsolatedExecutionState[isolation_level_key] = isolation_level
910
+ end
911
+
722
912
  private
723
913
  def connection_lease
724
914
  @leases[ActiveSupport::IsolatedExecutionState.context]
@@ -728,7 +918,9 @@ module ActiveRecord
728
918
  case ActiveRecord.async_query_executor
729
919
  when :multi_thread_pool
730
920
  if @db_config.max_threads > 0
921
+ name_with_shard = [name_inspect, shard_inspect].join("-").tr("_", "-")
731
922
  Concurrent::ThreadPoolExecutor.new(
923
+ name: "ActiveRecord-#{name_with_shard}-async-query-executor",
732
924
  min_threads: @db_config.min_threads,
733
925
  max_threads: @db_config.max_threads,
734
926
  max_queue: @db_config.max_queue,
@@ -740,11 +932,109 @@ module ActiveRecord
740
932
  end
741
933
  end
742
934
 
935
+ # Perform maintenance work on pool connections. This method will
936
+ # select a connection to work on by calling the +candidate_selector+
937
+ # proc while holding the pool lock. If a connection is selected, it
938
+ # will be checked out for maintenance and passed to the
939
+ # +maintenance_work+ proc. The connection will always be returned to
940
+ # the pool after the proc completes.
941
+ #
942
+ # If the pool has async threads, all work will be scheduled there.
943
+ # Otherwise, this method will block until all work is complete.
944
+ #
945
+ # Each connection will only be processed once per call to this method,
946
+ # but (particularly in the async case) there is no protection against
947
+ # a second call to this method starting to work through the list
948
+ # before the first call has completed. (Though regular pool behavior
949
+ # will prevent two instances from working on the same specific
950
+ # connection at the same time.)
951
+ def sequential_maintenance(candidate_selector, &maintenance_work)
952
+ # This hash doesn't need to be synchronized, because it's only
953
+ # used by one thread at a time: the +perform_work+ block gives
954
+ # up its right to +connections_visited+ when it schedules the
955
+ # next iteration.
956
+ connections_visited = Hash.new(false)
957
+ connections_visited.compare_by_identity
958
+
959
+ perform_work = lambda do
960
+ connection_to_maintain = nil
961
+
962
+ synchronize do
963
+ unless self.discarded?
964
+ if connection_to_maintain = @connections.select { |conn| !conn.in_use? }.select(&candidate_selector).sort_by(&:seconds_idle).find { |conn| !connections_visited[conn] }
965
+ checkout_for_maintenance connection_to_maintain
966
+ end
967
+ end
968
+ end
969
+
970
+ if connection_to_maintain
971
+ connections_visited[connection_to_maintain] = true
972
+
973
+ # If we're running async, we can schedule the next round of work
974
+ # as soon as we've grabbed a connection to work on.
975
+ @async_executor&.post(&perform_work)
976
+
977
+ begin
978
+ maintenance_work.call connection_to_maintain
979
+ ensure
980
+ return_from_maintenance connection_to_maintain
981
+ end
982
+
983
+ true
984
+ end
985
+ end
986
+
987
+ if @async_executor
988
+ @async_executor.post(&perform_work)
989
+ else
990
+ nil while perform_work.call
991
+ end
992
+ end
993
+
994
+ # Directly check a specific connection out of the pool. Skips callbacks.
995
+ #
996
+ # The connection must later either #return_from_maintenance or
997
+ # #remove_from_maintenance, or the pool will hang.
998
+ def checkout_for_maintenance(conn)
999
+ synchronize do
1000
+ @maintaining += 1
1001
+ @available.delete(conn)
1002
+ conn.lease
1003
+ conn
1004
+ end
1005
+ end
1006
+
1007
+ # Return a connection to the pool after it has been checked out for
1008
+ # maintenance. Does not update the connection's idle time, and skips
1009
+ # callbacks.
1010
+ #--
1011
+ # We assume that a connection that has required maintenance is less
1012
+ # desirable (either it's been idle for a long time, or it was just
1013
+ # created and hasn't been used yet). We'll put it at the back of the
1014
+ # queue.
1015
+ def return_from_maintenance(conn)
1016
+ synchronize do
1017
+ conn.expire(false)
1018
+ @available.add_back(conn)
1019
+ @maintaining -= 1
1020
+ end
1021
+ end
1022
+
1023
+ # Remove a connection from the pool after it has been checked out for
1024
+ # maintenance. It will be automatically replaced with a new connection if
1025
+ # necessary.
1026
+ def remove_from_maintenance(conn)
1027
+ synchronize do
1028
+ @maintaining -= 1
1029
+ remove conn
1030
+ end
1031
+ end
1032
+
743
1033
  #--
744
1034
  # this is unfortunately not concurrent
745
1035
  def bulk_make_new_connections(num_new_conns_needed)
746
1036
  num_new_conns_needed.times do
747
- # try_to_checkout_new_connection will not exceed pool's @size limit
1037
+ # try_to_checkout_new_connection will not exceed pool's @max_connections limit
748
1038
  if new_conn = try_to_checkout_new_connection
749
1039
  # make the new_conn available to the starving threads stuck @available Queue
750
1040
  checkin(new_conn)
@@ -757,9 +1047,11 @@ module ActiveRecord
757
1047
  # wrap it in +synchronize+ because some pool's actions are allowed
758
1048
  # to be performed outside of the main +synchronize+ block.
759
1049
  def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
760
- with_new_connections_blocked do
761
- attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
762
- yield
1050
+ @reaper_lock.synchronize do
1051
+ with_new_connections_blocked do
1052
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
1053
+ yield
1054
+ end
763
1055
  end
764
1056
  end
765
1057
 
@@ -879,13 +1171,13 @@ module ActiveRecord
879
1171
  # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
880
1172
  # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
881
1173
  # of the said methods and avoid an additional +synchronize+ overhead.
882
- if conn = @available.poll || try_to_checkout_new_connection
1174
+ if conn = @available.poll || try_to_queue_for_background_connection(checkout_timeout) || try_to_checkout_new_connection
883
1175
  conn
884
1176
  else
885
1177
  reap
886
1178
  # Retry after reaping, which may return an available connection,
887
1179
  # remove an inactive connection, or both
888
- if conn = @available.poll || try_to_checkout_new_connection
1180
+ if conn = @available.poll || try_to_queue_for_background_connection(checkout_timeout) || try_to_checkout_new_connection
889
1181
  conn
890
1182
  else
891
1183
  @available.poll(checkout_timeout)
@@ -895,6 +1187,32 @@ module ActiveRecord
895
1187
  raise ex.set_pool(self)
896
1188
  end
897
1189
 
1190
+ #--
1191
+ # If new connections are already being established in the background,
1192
+ # and there are fewer threads already waiting than the number of
1193
+ # upcoming connections, we can just get in queue and wait to be handed a
1194
+ # connection. This avoids us overshooting the required connection count
1195
+ # by starting a new connection ourselves, and is likely to be faster
1196
+ # too (because at least some of the time it takes to establish a new
1197
+ # connection must have already passed).
1198
+ #
1199
+ # If background connections are available, this method will block and
1200
+ # return a connection. If no background connections are available, it
1201
+ # will immediately return +nil+.
1202
+ def try_to_queue_for_background_connection(checkout_timeout)
1203
+ return unless @maintaining > 0
1204
+
1205
+ synchronize do
1206
+ return unless @maintaining > @available.num_waiting
1207
+
1208
+ # Maintenance work (e.g. a keepalive ping) can stall for reasons outside
1209
+ # our control, such as a slow or unresponsive database server, so this
1210
+ # wait must stay bounded by the caller's own checkout_timeout rather than
1211
+ # assume maintenance always finishes quickly.
1212
+ @available.poll(checkout_timeout)
1213
+ end
1214
+ end
1215
+
898
1216
  #--
899
1217
  # if owner_thread param is omitted, this must be called in synchronize block
900
1218
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
@@ -904,17 +1222,26 @@ module ActiveRecord
904
1222
  end
905
1223
  alias_method :release, :remove_connection_from_thread_cache
906
1224
 
907
- # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
1225
+ # If the pool is not at a <tt>@max_connections</tt> limit, establish new connection. Connecting
908
1226
  # to the DB is done outside main synchronized section.
1227
+ #
1228
+ # If a block is supplied, it is an additional constraint (checked while holding the
1229
+ # pool lock) on whether a new connection should be established.
909
1230
  #--
910
1231
  # Implementation constraint: a newly established connection returned by this
911
1232
  # method must be in the +.leased+ state.
912
1233
  def try_to_checkout_new_connection
913
1234
  # first in synchronized section check if establishing new conns is allowed
914
- # and increment @now_connecting, to prevent overstepping this pool's @size
1235
+ # and increment @now_connecting, to prevent overstepping this pool's @max_connections
915
1236
  # constraint
916
1237
  do_checkout = synchronize do
917
- if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
1238
+ return if self.discarded?
1239
+
1240
+ if @threads_blocking_new_connections.zero? && (@max_connections.nil? || (@connections.size + @now_connecting) < @max_connections) && (!block_given? || yield)
1241
+ if @connections.size > 0 || @original_context != ActiveSupport::IsolatedExecutionState.context
1242
+ @activated = true
1243
+ end
1244
+
918
1245
  @now_connecting += 1
919
1246
  end
920
1247
  end
@@ -925,12 +1252,16 @@ module ActiveRecord
925
1252
  conn = checkout_new_connection
926
1253
  ensure
927
1254
  synchronize do
1255
+ @now_connecting -= 1
928
1256
  if conn
929
- adopt_connection(conn)
930
- # returned conn needs to be already leased
931
- conn.lease
1257
+ if self.discarded?
1258
+ conn.discard!
1259
+ else
1260
+ adopt_connection(conn)
1261
+ # returned conn needs to be already leased
1262
+ conn.lease
1263
+ end
932
1264
  end
933
- @now_connecting -= 1
934
1265
  end
935
1266
  end
936
1267
  end
@@ -953,15 +1284,20 @@ module ActiveRecord
953
1284
  end
954
1285
 
955
1286
  def checkout_and_verify(c)
956
- c._run_checkout_callbacks do
957
- c.clean!
958
- end
959
- c
1287
+ c.clean!
960
1288
  rescue Exception
961
1289
  remove c
962
1290
  c.disconnect!
963
1291
  raise
964
1292
  end
1293
+
1294
+ def name_inspect
1295
+ db_config.name.inspect unless db_config.name == "primary"
1296
+ end
1297
+
1298
+ def shard_inspect
1299
+ shard.inspect unless shard == :default
1300
+ end
965
1301
  end
966
1302
  end
967
1303
  end