activerecord 8.0.2 → 8.1.0.beta1

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 (159) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +459 -413
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +1 -1
  5. data/lib/active_record/associations/association.rb +1 -1
  6. data/lib/active_record/associations/belongs_to_association.rb +9 -1
  7. data/lib/active_record/associations/builder/association.rb +16 -5
  8. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  9. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  10. data/lib/active_record/associations/builder/has_one.rb +1 -1
  11. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  12. data/lib/active_record/associations/collection_association.rb +3 -3
  13. data/lib/active_record/associations/collection_proxy.rb +22 -4
  14. data/lib/active_record/associations/deprecation.rb +88 -0
  15. data/lib/active_record/associations/errors.rb +3 -0
  16. data/lib/active_record/associations/join_dependency.rb +2 -0
  17. data/lib/active_record/associations/preloader/branch.rb +1 -0
  18. data/lib/active_record/associations.rb +159 -21
  19. data/lib/active_record/attribute_methods/query.rb +34 -0
  20. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  21. data/lib/active_record/attributes.rb +38 -24
  22. data/lib/active_record/base.rb +0 -1
  23. data/lib/active_record/coders/json.rb +14 -5
  24. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
  25. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
  26. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
  27. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
  28. data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
  29. data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
  30. data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
  31. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
  32. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
  33. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  34. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
  35. data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
  36. data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
  37. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
  38. data/lib/active_record/connection_adapters/column.rb +17 -4
  39. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  40. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  41. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
  43. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
  44. data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
  45. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
  46. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
  47. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  48. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  49. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
  50. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
  51. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
  52. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
  53. data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
  54. data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
  55. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
  56. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
  57. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
  58. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
  59. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
  60. data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
  61. data/lib/active_record/connection_adapters.rb +1 -0
  62. data/lib/active_record/connection_handling.rb +1 -1
  63. data/lib/active_record/core.rb +13 -10
  64. data/lib/active_record/counter_cache.rb +33 -8
  65. data/lib/active_record/database_configurations/database_config.rb +5 -1
  66. data/lib/active_record/database_configurations/hash_config.rb +56 -9
  67. data/lib/active_record/database_configurations/url_config.rb +13 -3
  68. data/lib/active_record/database_configurations.rb +7 -3
  69. data/lib/active_record/delegated_type.rb +2 -2
  70. data/lib/active_record/dynamic_matchers.rb +54 -69
  71. data/lib/active_record/encryption/encryptable_record.rb +5 -5
  72. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  73. data/lib/active_record/encryption/encryptor.rb +27 -25
  74. data/lib/active_record/encryption/scheme.rb +1 -1
  75. data/lib/active_record/enum.rb +37 -20
  76. data/lib/active_record/errors.rb +20 -4
  77. data/lib/active_record/explain_registry.rb +0 -1
  78. data/lib/active_record/filter_attribute_handler.rb +73 -0
  79. data/lib/active_record/fixture_set/table_row.rb +19 -2
  80. data/lib/active_record/fixtures.rb +2 -2
  81. data/lib/active_record/gem_version.rb +3 -3
  82. data/lib/active_record/inheritance.rb +1 -1
  83. data/lib/active_record/insert_all.rb +12 -7
  84. data/lib/active_record/locking/optimistic.rb +7 -0
  85. data/lib/active_record/locking/pessimistic.rb +5 -0
  86. data/lib/active_record/log_subscriber.rb +1 -5
  87. data/lib/active_record/middleware/shard_selector.rb +34 -17
  88. data/lib/active_record/migration/command_recorder.rb +14 -1
  89. data/lib/active_record/migration/compatibility.rb +34 -24
  90. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  91. data/lib/active_record/migration.rb +31 -21
  92. data/lib/active_record/model_schema.rb +10 -7
  93. data/lib/active_record/nested_attributes.rb +2 -0
  94. data/lib/active_record/persistence.rb +34 -3
  95. data/lib/active_record/query_cache.rb +22 -15
  96. data/lib/active_record/query_logs.rb +7 -7
  97. data/lib/active_record/querying.rb +4 -4
  98. data/lib/active_record/railtie.rb +34 -5
  99. data/lib/active_record/railties/databases.rake +23 -19
  100. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  101. data/lib/active_record/railties/job_runtime.rb +10 -11
  102. data/lib/active_record/reflection.rb +42 -3
  103. data/lib/active_record/relation/batches.rb +26 -12
  104. data/lib/active_record/relation/calculations.rb +35 -25
  105. data/lib/active_record/relation/delegation.rb +0 -1
  106. data/lib/active_record/relation/finder_methods.rb +41 -24
  107. data/lib/active_record/relation/merger.rb +2 -2
  108. data/lib/active_record/relation/predicate_builder.rb +2 -2
  109. data/lib/active_record/relation/query_attribute.rb +3 -1
  110. data/lib/active_record/relation/query_methods.rb +43 -33
  111. data/lib/active_record/relation/spawn_methods.rb +6 -6
  112. data/lib/active_record/relation/where_clause.rb +7 -10
  113. data/lib/active_record/relation.rb +37 -15
  114. data/lib/active_record/result.rb +44 -21
  115. data/lib/active_record/sanitization.rb +2 -0
  116. data/lib/active_record/schema_dumper.rb +12 -10
  117. data/lib/active_record/scoping.rb +0 -1
  118. data/lib/active_record/secure_token.rb +3 -3
  119. data/lib/active_record/signed_id.rb +46 -18
  120. data/lib/active_record/statement_cache.rb +13 -9
  121. data/lib/active_record/store.rb +44 -19
  122. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  123. data/lib/active_record/tasks/database_tasks.rb +24 -35
  124. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
  125. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  126. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
  127. data/lib/active_record/test_databases.rb +11 -3
  128. data/lib/active_record/test_fixtures.rb +27 -2
  129. data/lib/active_record/testing/query_assertions.rb +8 -2
  130. data/lib/active_record/timestamp.rb +4 -2
  131. data/lib/active_record/transaction.rb +2 -5
  132. data/lib/active_record/transactions.rb +34 -10
  133. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  134. data/lib/active_record/type/internal/timezone.rb +7 -0
  135. data/lib/active_record/type/json.rb +15 -2
  136. data/lib/active_record/type/serialized.rb +11 -4
  137. data/lib/active_record/type/type_map.rb +1 -1
  138. data/lib/active_record/type_caster/connection.rb +2 -1
  139. data/lib/active_record/validations/associated.rb +1 -1
  140. data/lib/active_record.rb +68 -5
  141. data/lib/arel/alias_predication.rb +2 -0
  142. data/lib/arel/crud.rb +8 -11
  143. data/lib/arel/delete_manager.rb +5 -0
  144. data/lib/arel/nodes/count.rb +2 -2
  145. data/lib/arel/nodes/delete_statement.rb +4 -2
  146. data/lib/arel/nodes/function.rb +4 -10
  147. data/lib/arel/nodes/named_function.rb +2 -2
  148. data/lib/arel/nodes/node.rb +1 -1
  149. data/lib/arel/nodes/update_statement.rb +4 -2
  150. data/lib/arel/nodes.rb +0 -2
  151. data/lib/arel/select_manager.rb +13 -4
  152. data/lib/arel/update_manager.rb +5 -0
  153. data/lib/arel/visitors/dot.rb +2 -3
  154. data/lib/arel/visitors/postgresql.rb +55 -0
  155. data/lib/arel/visitors/sqlite.rb +55 -8
  156. data/lib/arel/visitors/to_sql.rb +5 -21
  157. data/lib/arel.rb +3 -1
  158. metadata +15 -11
  159. data/lib/active_record/normalization.rb +0 -163
@@ -14,12 +14,12 @@ module ActiveRecord
14
14
  class NullPool # :nodoc:
15
15
  include ConnectionAdapters::AbstractPool
16
16
 
17
- class NullConfig # :nodoc:
17
+ class NullConfig
18
18
  def method_missing(...)
19
19
  nil
20
20
  end
21
21
  end
22
- NULL_CONFIG = NullConfig.new # :nodoc:
22
+ NULL_CONFIG = NullConfig.new
23
23
 
24
24
  def initialize
25
25
  super()
@@ -48,6 +48,11 @@ module ActiveRecord
48
48
  def dirties_query_cache
49
49
  true
50
50
  end
51
+
52
+ def pool_transaction_isolation_level; end
53
+ def pool_transaction_isolation_level=(isolation_level)
54
+ raise NotImplementedError, "This method should never be called"
55
+ end
51
56
  end
52
57
 
53
58
  # = Active Record Connection Pool
@@ -100,13 +105,20 @@ module ActiveRecord
100
105
  # There are several connection-pooling-related options that you can add to
101
106
  # your database connection configuration:
102
107
  #
103
- # * +pool+: maximum number of connections the pool may manage (default 5).
104
- # * +idle_timeout+: number of seconds that a connection will be kept
105
- # unused in the pool before it is automatically disconnected (default
106
- # 300 seconds). Set this to zero to keep connections forever.
107
108
  # * +checkout_timeout+: number of seconds to wait for a connection to
108
109
  # become available before giving up and raising a timeout error (default
109
110
  # 5 seconds).
111
+ # * +idle_timeout+: number of seconds that a connection will be kept
112
+ # unused in the pool before it is automatically disconnected (default
113
+ # 300 seconds). Set this to zero to keep connections forever.
114
+ # * +keepalive+: number of seconds between keepalive checks if the
115
+ # connection has been idle (default 600 seconds).
116
+ # * +max_age+: number of seconds the pool will allow the connection to
117
+ # exist before retiring it at next checkin. (default Float::INFINITY).
118
+ # * +max_connections+: maximum number of connections the pool may manage (default 5).
119
+ # * +min_connections+: minimum number of connections the pool will open and maintain (default 0).
120
+ # * +pool_jitter+: maximum reduction factor to apply to +max_age+ and
121
+ # +keepalive+ intervals (default 0.2; range 0.0-1.0).
110
122
  #
111
123
  #--
112
124
  # Synchronization policy:
@@ -114,6 +126,7 @@ module ActiveRecord
114
126
  # * access to these instance variables needs to be in +synchronize+:
115
127
  # * @connections
116
128
  # * @now_connecting
129
+ # * @maintaining
117
130
  # * private methods that require being called in a +synchronize+ blocks
118
131
  # are now explicitly documented
119
132
  class ConnectionPool
@@ -124,7 +137,7 @@ module ActiveRecord
124
137
  else
125
138
  class WeakThreadKeyMap # :nodoc:
126
139
  # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
127
- # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
140
+ # but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
128
141
  def initialize
129
142
  @map = {}
130
143
  end
@@ -219,7 +232,8 @@ module ActiveRecord
219
232
  include ConnectionAdapters::AbstractPool
220
233
 
221
234
  attr_accessor :automatic_reconnect, :checkout_timeout
222
- attr_reader :db_config, :size, :reaper, :pool_config, :async_executor, :role, :shard
235
+ attr_reader :db_config, :max_connections, :min_connections, :max_age, :keepalive, :reaper, :pool_config, :async_executor, :role, :shard
236
+ alias :size :max_connections
223
237
 
224
238
  delegate :schema_reflection, :server_version, to: :pool_config
225
239
 
@@ -239,7 +253,10 @@ module ActiveRecord
239
253
 
240
254
  @checkout_timeout = db_config.checkout_timeout
241
255
  @idle_timeout = db_config.idle_timeout
242
- @size = db_config.pool
256
+ @max_connections = db_config.max_connections
257
+ @min_connections = db_config.min_connections
258
+ @max_age = db_config.max_age
259
+ @keepalive = db_config.keepalive
243
260
 
244
261
  # This variable tracks the cache of threads mapped to reserved connections, with the
245
262
  # sole purpose of speeding up the +connection+ method. It is not the authoritative
@@ -261,6 +278,12 @@ module ActiveRecord
261
278
  # currently in the process of independently establishing connections to the DB.
262
279
  @now_connecting = 0
263
280
 
281
+ # Sometimes otherwise-idle connections are temporarily held by the Reaper for
282
+ # maintenance. This variable tracks the number of connections currently in that
283
+ # state -- if a thread requests a connection and there are none available, it
284
+ # will await any in-maintenance connections in preference to creating a new one.
285
+ @maintaining = 0
286
+
264
287
  @threads_blocking_new_connections = 0
265
288
 
266
289
  @available = ConnectionLeasingQueue.new self
@@ -271,13 +294,17 @@ module ActiveRecord
271
294
 
272
295
  @schema_cache = nil
273
296
 
297
+ @activated = false
298
+ @original_context = ActiveSupport::IsolatedExecutionState.context
299
+
300
+ @reaper_lock = Monitor.new
274
301
  @reaper = Reaper.new(self, db_config.reaping_frequency)
275
302
  @reaper.run
276
303
  end
277
304
 
278
305
  def inspect # :nodoc:
279
- name_field = " name=#{db_config.name.inspect}" unless db_config.name == "primary"
280
- shard_field = " shard=#{@shard.inspect}" unless @shard == :default
306
+ name_field = " name=#{name_inspect}" if name_inspect
307
+ shard_field = " shard=#{shard_inspect}" if shard_inspect
281
308
 
282
309
  "#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
283
310
  end
@@ -307,6 +334,14 @@ module ActiveRecord
307
334
  InternalMetadata.new(self)
308
335
  end
309
336
 
337
+ def activate
338
+ @activated = true
339
+ end
340
+
341
+ def activated?
342
+ @activated
343
+ end
344
+
310
345
  # Retrieve the connection associated with the current thread, or call
311
346
  # #checkout to obtain one if necessary.
312
347
  #
@@ -386,6 +421,8 @@ module ActiveRecord
386
421
  # #lease_connection or #with_connection methods, connections obtained through
387
422
  # #checkout will not be automatically released.
388
423
  def release_connection(existing_lease = nil)
424
+ return if self.discarded?
425
+
389
426
  if conn = connection_lease.release
390
427
  checkin conn
391
428
  return true
@@ -423,6 +460,24 @@ module ActiveRecord
423
460
  end
424
461
  end
425
462
 
463
+ def with_pool_transaction_isolation_level(isolation_level, transaction_open, &block) # :nodoc:
464
+ if !ActiveRecord.default_transaction_isolation_level.nil?
465
+ begin
466
+ if transaction_open && self.pool_transaction_isolation_level != ActiveRecord.default_transaction_isolation_level
467
+ raise ActiveRecord::TransactionIsolationError, "cannot set default isolation level while transaction is open"
468
+ end
469
+
470
+ old_level = self.pool_transaction_isolation_level
471
+ self.pool_transaction_isolation_level = isolation_level
472
+ yield
473
+ ensure
474
+ self.pool_transaction_isolation_level = old_level
475
+ end
476
+ else
477
+ yield
478
+ end
479
+ end
480
+
426
481
  # Returns true if a connection has already been opened.
427
482
  def connected?
428
483
  synchronize { @connections.any?(&:connected?) }
@@ -450,18 +505,26 @@ module ActiveRecord
450
505
  # connections in the pool within a timeout interval (default duration is
451
506
  # <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
452
507
  def disconnect(raise_on_acquisition_timeout = true)
453
- with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
454
- synchronize do
455
- @connections.each do |conn|
456
- if conn.in_use?
457
- conn.steal!
458
- checkin conn
508
+ @reaper_lock.synchronize do
509
+ return if self.discarded?
510
+
511
+ with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
512
+ synchronize do
513
+ return if self.discarded?
514
+ @connections.each do |conn|
515
+ if conn.in_use?
516
+ conn.steal!
517
+ checkin conn
518
+ end
519
+ conn.disconnect!
459
520
  end
460
- conn.disconnect!
521
+ @connections = []
522
+ @leases.clear
523
+ @available.clear
524
+
525
+ # Stop maintaining the minimum size until reactivated
526
+ @activated = false
461
527
  end
462
- @connections = []
463
- @leases.clear
464
- @available.clear
465
528
  end
466
529
  end
467
530
  end
@@ -482,12 +545,14 @@ module ActiveRecord
482
545
  #
483
546
  # See AbstractAdapter#discard!
484
547
  def discard! # :nodoc:
485
- synchronize do
486
- return if self.discarded?
487
- @connections.each do |conn|
488
- conn.discard!
548
+ @reaper_lock.synchronize do
549
+ synchronize do
550
+ return if self.discarded?
551
+ @connections.each do |conn|
552
+ conn.discard!
553
+ end
554
+ @connections = @available = @leases = nil
489
555
  end
490
- @connections = @available = @leases = nil
491
556
  end
492
557
  end
493
558
 
@@ -495,7 +560,17 @@ module ActiveRecord
495
560
  @connections.nil?
496
561
  end
497
562
 
498
- # Clears the cache which maps classes and re-connects connections that
563
+ def maintainable? # :nodoc:
564
+ synchronize do
565
+ @connections&.size&.> 0 || (activated? && @min_connections > 0)
566
+ end
567
+ end
568
+
569
+ def reaper_lock(&block) # :nodoc:
570
+ @reaper_lock.synchronize(&block)
571
+ end
572
+
573
+ # Clears reloadable connections from the pool and re-connects connections that
499
574
  # require reloading.
500
575
  #
501
576
  # Raises:
@@ -518,7 +593,7 @@ module ActiveRecord
518
593
  end
519
594
  end
520
595
 
521
- # Clears the cache which maps classes and re-connects connections that
596
+ # Clears reloadable connections from the pool and re-connects connections that
522
597
  # require reloading.
523
598
  #
524
599
  # The pool first tries to gain ownership of all connections. If unable to
@@ -600,7 +675,7 @@ module ActiveRecord
600
675
  @available.delete conn
601
676
 
602
677
  # @available.any_waiting? => true means that prior to removing this
603
- # conn, the pool was at its max size (@connections.size == @size).
678
+ # conn, the pool was at its max size (@connections.size == @max_connections).
604
679
  # This would mean that any threads stuck waiting in the queue wouldn't
605
680
  # know they could checkout_new_connection, so let's do it for them.
606
681
  # Because condition-wait loop is encapsulated in the Queue class
@@ -608,14 +683,14 @@ module ActiveRecord
608
683
  # that are "stuck" there are helpless. They have no way of creating
609
684
  # new connections and are completely reliant on us feeding available
610
685
  # connections into the Queue.
611
- needs_new_connection = @available.any_waiting?
686
+ needs_new_connection = @available.num_waiting > @maintaining
612
687
  end
613
688
 
614
689
  # This is intentionally done outside of the synchronized section as we
615
690
  # would like not to hold the main mutex while checking out new connections.
616
691
  # Thus there is some chance that needs_new_connection information is now
617
692
  # stale, we can live with that (bulk_make_new_connections will make
618
- # sure not to exceed the pool's @size limit).
693
+ # sure not to exceed the pool's @max_connections limit).
619
694
  bulk_make_new_connections(1) if needs_new_connection
620
695
  end
621
696
 
@@ -648,11 +723,27 @@ module ActiveRecord
648
723
  def flush(minimum_idle = @idle_timeout)
649
724
  return if minimum_idle.nil?
650
725
 
651
- idle_connections = synchronize do
726
+ removed_connections = synchronize do
652
727
  return if self.discarded?
653
- @connections.select do |conn|
728
+
729
+ idle_connections = @connections.select do |conn|
654
730
  !conn.in_use? && conn.seconds_idle >= minimum_idle
655
- end.each do |conn|
731
+ end.sort_by { |conn| -conn.seconds_idle } # sort longest idle first
732
+
733
+ # Don't go below our configured pool minimum unless we're flushing
734
+ # everything
735
+ idles_to_retain =
736
+ if minimum_idle > 0
737
+ @min_connections - (@connections.size - idle_connections.size)
738
+ else
739
+ 0
740
+ end
741
+
742
+ if idles_to_retain > 0
743
+ idle_connections.pop idles_to_retain
744
+ end
745
+
746
+ idle_connections.each do |conn|
656
747
  conn.lease
657
748
 
658
749
  @available.delete conn
@@ -660,22 +751,108 @@ module ActiveRecord
660
751
  end
661
752
  end
662
753
 
663
- idle_connections.each do |conn|
754
+ removed_connections.each do |conn|
664
755
  conn.disconnect!
665
756
  end
666
757
  end
667
758
 
668
759
  # Disconnect all currently idle connections. Connections currently checked
669
- # out are unaffected.
760
+ # out are unaffected. The pool will stop maintaining its minimum size until
761
+ # it is reactivated (such as by a subsequent checkout).
670
762
  def flush!
671
763
  reap
672
764
  flush(-1)
765
+
766
+ # Stop maintaining the minimum size until reactivated
767
+ @activated = false
768
+ end
769
+
770
+ # Ensure that the pool contains at least the configured minimum number of
771
+ # connections.
772
+ def prepopulate
773
+ need_new_connections = nil
774
+
775
+ synchronize do
776
+ return if self.discarded?
777
+
778
+ # We don't want to start prepopulating until we know the pool is wanted,
779
+ # so we can avoid maintaining full pools in one-off scripts etc.
780
+ return unless @activated
781
+
782
+ need_new_connections = @connections.size < @min_connections
783
+ end
784
+
785
+ if need_new_connections
786
+ while new_conn = try_to_checkout_new_connection { @connections.size < @min_connections }
787
+ checkin(new_conn)
788
+ end
789
+ end
790
+ end
791
+
792
+ def retire_old_connections(max_age = @max_age)
793
+ max_age ||= Float::INFINITY
794
+
795
+ sequential_maintenance -> c { c.connection_age&.>= c.pool_jitter(max_age) } do |conn|
796
+ # Disconnect, then return the adapter to the pool. Preconnect will
797
+ # handle the rest.
798
+ conn.disconnect!
799
+ end
800
+ end
801
+
802
+ # Preconnect all connections in the pool. This saves pool users from
803
+ # having to wait for a connection to be established when first using it
804
+ # after checkout.
805
+ def preconnect
806
+ sequential_maintenance -> c { (!c.connected? || !c.verified?) && c.allow_preconnect } do |conn|
807
+ conn.connect!
808
+ rescue
809
+ # Wholesale rescue: there's nothing we can do but move on. The
810
+ # connection will go back to the pool, and the next consumer will
811
+ # presumably try to connect again -- which will either work, or
812
+ # fail and they'll be able to report the exception.
813
+ end
814
+ end
815
+
816
+ # Prod any connections that have been idle for longer than the configured
817
+ # keepalive time. This will incidentally verify the connection is still
818
+ # alive, but the main purpose is to show the server (and any intermediate
819
+ # network hops) that we're still here and using the connection.
820
+ def keep_alive(threshold = @keepalive)
821
+ return if threshold.nil?
822
+
823
+ sequential_maintenance -> c { (c.seconds_since_last_activity || 0) > c.pool_jitter(threshold) } do |conn|
824
+ # conn.active? will cause some amount of network activity, which is all
825
+ # we need to provide a keepalive signal.
826
+ #
827
+ # If it returns false, the connection is already broken; disconnect,
828
+ # so it can be found and repaired.
829
+ conn.disconnect! unless conn.active?
830
+ end
831
+ end
832
+
833
+ # Immediately mark all current connections as due for replacement,
834
+ # equivalent to them having reached +max_age+ -- even if there is
835
+ # no +max_age+ configured.
836
+ def recycle!
837
+ synchronize do
838
+ return if self.discarded?
839
+
840
+ @connections.each do |conn|
841
+ conn.force_retirement
842
+ end
843
+ end
844
+
845
+ retire_old_connections
673
846
  end
674
847
 
675
848
  def num_waiting_in_queue # :nodoc:
676
849
  @available.num_waiting
677
850
  end
678
851
 
852
+ def num_available_in_queue # :nodoc:
853
+ @available.size
854
+ end
855
+
679
856
  # Returns the connection pool's usage statistic.
680
857
  #
681
858
  # ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
@@ -706,6 +883,16 @@ module ActiveRecord
706
883
  raise ex.set_pool(self)
707
884
  end
708
885
 
886
+ def pool_transaction_isolation_level
887
+ isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
888
+ ActiveSupport::IsolatedExecutionState[isolation_level_key]
889
+ end
890
+
891
+ def pool_transaction_isolation_level=(isolation_level)
892
+ isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
893
+ ActiveSupport::IsolatedExecutionState[isolation_level_key] = isolation_level
894
+ end
895
+
709
896
  private
710
897
  def connection_lease
711
898
  @leases[ActiveSupport::IsolatedExecutionState.context]
@@ -715,7 +902,9 @@ module ActiveRecord
715
902
  case ActiveRecord.async_query_executor
716
903
  when :multi_thread_pool
717
904
  if @db_config.max_threads > 0
905
+ name_with_shard = [name_inspect, shard_inspect].join("-").tr("_", "-")
718
906
  Concurrent::ThreadPoolExecutor.new(
907
+ name: "ActiveRecord-#{name_with_shard}-async-query-executor",
719
908
  min_threads: @db_config.min_threads,
720
909
  max_threads: @db_config.max_threads,
721
910
  max_queue: @db_config.max_queue,
@@ -727,11 +916,109 @@ module ActiveRecord
727
916
  end
728
917
  end
729
918
 
919
+ # Perform maintenance work on pool connections. This method will
920
+ # select a connection to work on by calling the +candidate_selector+
921
+ # proc while holding the pool lock. If a connection is selected, it
922
+ # will be checked out for maintenance and passed to the
923
+ # +maintenance_work+ proc. The connection will always be returned to
924
+ # the pool after the proc completes.
925
+ #
926
+ # If the pool has async threads, all work will be scheduled there.
927
+ # Otherwise, this method will block until all work is complete.
928
+ #
929
+ # Each connection will only be processed once per call to this method,
930
+ # but (particularly in the async case) there is no protection against
931
+ # a second call to this method starting to work through the list
932
+ # before the first call has completed. (Though regular pool behaviour
933
+ # will prevent two instances from working on the same specific
934
+ # connection at the same time.)
935
+ def sequential_maintenance(candidate_selector, &maintenance_work)
936
+ # This hash doesn't need to be synchronized, because it's only
937
+ # used by one thread at a time: the +perform_work+ block gives
938
+ # up its right to +connections_visited+ when it schedules the
939
+ # next iteration.
940
+ connections_visited = Hash.new(false)
941
+ connections_visited.compare_by_identity
942
+
943
+ perform_work = lambda do
944
+ connection_to_maintain = nil
945
+
946
+ synchronize do
947
+ unless self.discarded?
948
+ if connection_to_maintain = @connections.select { |conn| !conn.in_use? }.select(&candidate_selector).sort_by(&:seconds_idle).find { |conn| !connections_visited[conn] }
949
+ checkout_for_maintenance connection_to_maintain
950
+ end
951
+ end
952
+ end
953
+
954
+ if connection_to_maintain
955
+ connections_visited[connection_to_maintain] = true
956
+
957
+ # If we're running async, we can schedule the next round of work
958
+ # as soon as we've grabbed a connection to work on.
959
+ @async_executor&.post(&perform_work)
960
+
961
+ begin
962
+ maintenance_work.call connection_to_maintain
963
+ ensure
964
+ return_from_maintenance connection_to_maintain
965
+ end
966
+
967
+ true
968
+ end
969
+ end
970
+
971
+ if @async_executor
972
+ @async_executor.post(&perform_work)
973
+ else
974
+ nil while perform_work.call
975
+ end
976
+ end
977
+
978
+ # Directly check a specific connection out of the pool. Skips callbacks.
979
+ #
980
+ # The connection must later either #return_from_maintenance or
981
+ # #remove_from_maintenance, or the pool will hang.
982
+ def checkout_for_maintenance(conn)
983
+ synchronize do
984
+ @maintaining += 1
985
+ @available.delete(conn)
986
+ conn.lease
987
+ conn
988
+ end
989
+ end
990
+
991
+ # Return a connection to the pool after it has been checked out for
992
+ # maintenance. Does not update the connection's idle time, and skips
993
+ # callbacks.
994
+ #--
995
+ # We assume that a connection that has required maintenance is less
996
+ # desirable (either it's been idle for a long time, or it was just
997
+ # created and hasn't been used yet). We'll put it at the back of the
998
+ # queue.
999
+ def return_from_maintenance(conn)
1000
+ synchronize do
1001
+ conn.expire(false)
1002
+ @available.add_back(conn)
1003
+ @maintaining -= 1
1004
+ end
1005
+ end
1006
+
1007
+ # Remove a connection from the pool after it has been checked out for
1008
+ # maintenance. It will be automatically replaced with a new connection if
1009
+ # necessary.
1010
+ def remove_from_maintenance(conn)
1011
+ synchronize do
1012
+ @maintaining -= 1
1013
+ remove conn
1014
+ end
1015
+ end
1016
+
730
1017
  #--
731
1018
  # this is unfortunately not concurrent
732
1019
  def bulk_make_new_connections(num_new_conns_needed)
733
1020
  num_new_conns_needed.times do
734
- # try_to_checkout_new_connection will not exceed pool's @size limit
1021
+ # try_to_checkout_new_connection will not exceed pool's @max_connections limit
735
1022
  if new_conn = try_to_checkout_new_connection
736
1023
  # make the new_conn available to the starving threads stuck @available Queue
737
1024
  checkin(new_conn)
@@ -744,9 +1031,11 @@ module ActiveRecord
744
1031
  # wrap it in +synchronize+ because some pool's actions are allowed
745
1032
  # to be performed outside of the main +synchronize+ block.
746
1033
  def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
747
- with_new_connections_blocked do
748
- attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
749
- yield
1034
+ @reaper_lock.synchronize do
1035
+ with_new_connections_blocked do
1036
+ attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
1037
+ yield
1038
+ end
750
1039
  end
751
1040
  end
752
1041
 
@@ -866,13 +1155,13 @@ module ActiveRecord
866
1155
  # <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
867
1156
  # and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
868
1157
  # of the said methods and avoid an additional +synchronize+ overhead.
869
- if conn = @available.poll || try_to_checkout_new_connection
1158
+ if conn = @available.poll || try_to_queue_for_background_connection(checkout_timeout) || try_to_checkout_new_connection
870
1159
  conn
871
1160
  else
872
1161
  reap
873
1162
  # Retry after reaping, which may return an available connection,
874
1163
  # remove an inactive connection, or both
875
- if conn = @available.poll || try_to_checkout_new_connection
1164
+ if conn = @available.poll || try_to_queue_for_background_connection(checkout_timeout) || try_to_checkout_new_connection
876
1165
  conn
877
1166
  else
878
1167
  @available.poll(checkout_timeout)
@@ -882,6 +1171,31 @@ module ActiveRecord
882
1171
  raise ex.set_pool(self)
883
1172
  end
884
1173
 
1174
+ #--
1175
+ # If new connections are already being established in the background,
1176
+ # and there are fewer threads already waiting than the number of
1177
+ # upcoming connections, we can just get in queue and wait to be handed a
1178
+ # connection. This avoids us overshooting the required connection count
1179
+ # by starting a new connection ourselves, and is likely to be faster
1180
+ # too (because at least some of the time it takes to establish a new
1181
+ # connection must have already passed).
1182
+ #
1183
+ # If background connections are available, this method will block and
1184
+ # return a connection. If no background connections are available, it
1185
+ # will immediately return +nil+.
1186
+ def try_to_queue_for_background_connection(checkout_timeout)
1187
+ return unless @maintaining > 0
1188
+
1189
+ synchronize do
1190
+ return unless @maintaining > @available.num_waiting
1191
+
1192
+ # We are guaranteed the "maintaining" thread will return its promised
1193
+ # connection within one maintenance-unit of time. Thus we can safely
1194
+ # do a blocking wait with (functionally) no timeout.
1195
+ @available.poll(100)
1196
+ end
1197
+ end
1198
+
885
1199
  #--
886
1200
  # if owner_thread param is omitted, this must be called in synchronize block
887
1201
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
@@ -891,17 +1205,26 @@ module ActiveRecord
891
1205
  end
892
1206
  alias_method :release, :remove_connection_from_thread_cache
893
1207
 
894
- # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
1208
+ # If the pool is not at a <tt>@max_connections</tt> limit, establish new connection. Connecting
895
1209
  # to the DB is done outside main synchronized section.
1210
+ #
1211
+ # If a block is supplied, it is an additional constraint (checked while holding the
1212
+ # pool lock) on whether a new connection should be established.
896
1213
  #--
897
1214
  # Implementation constraint: a newly established connection returned by this
898
1215
  # method must be in the +.leased+ state.
899
1216
  def try_to_checkout_new_connection
900
1217
  # first in synchronized section check if establishing new conns is allowed
901
- # and increment @now_connecting, to prevent overstepping this pool's @size
1218
+ # and increment @now_connecting, to prevent overstepping this pool's @max_connections
902
1219
  # constraint
903
1220
  do_checkout = synchronize do
904
- if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
1221
+ return if self.discarded?
1222
+
1223
+ if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @max_connections && (!block_given? || yield)
1224
+ if @connections.size > 0 || @original_context != ActiveSupport::IsolatedExecutionState.context
1225
+ @activated = true
1226
+ end
1227
+
905
1228
  @now_connecting += 1
906
1229
  end
907
1230
  end
@@ -912,12 +1235,16 @@ module ActiveRecord
912
1235
  conn = checkout_new_connection
913
1236
  ensure
914
1237
  synchronize do
1238
+ @now_connecting -= 1
915
1239
  if conn
916
- adopt_connection(conn)
917
- # returned conn needs to be already leased
918
- conn.lease
1240
+ if self.discarded?
1241
+ conn.discard!
1242
+ else
1243
+ adopt_connection(conn)
1244
+ # returned conn needs to be already leased
1245
+ conn.lease
1246
+ end
919
1247
  end
920
- @now_connecting -= 1
921
1248
  end
922
1249
  end
923
1250
  end
@@ -949,6 +1276,14 @@ module ActiveRecord
949
1276
  c.disconnect!
950
1277
  raise
951
1278
  end
1279
+
1280
+ def name_inspect
1281
+ db_config.name.inspect unless db_config.name == "primary"
1282
+ end
1283
+
1284
+ def shard_inspect
1285
+ shard.inspect unless shard == :default
1286
+ end
952
1287
  end
953
1288
  end
954
1289
  end