activerecord 8.0.2.1 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +459 -421
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +9 -1
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +3 -3
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +2 -0
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +17 -4
- data/lib/active_record/attributes.rb +38 -24
- data/lib/active_record/base.rb +0 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +2 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +384 -49
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +26 -30
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +89 -23
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -13
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -16
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +12 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -27
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +56 -32
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +1 -1
- data/lib/active_record/core.rb +12 -9
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +56 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +2 -2
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +5 -5
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +27 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +12 -7
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +31 -21
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +7 -7
- data/lib/active_record/querying.rb +4 -4
- data/lib/active_record/railtie.rb +34 -5
- data/lib/active_record/railties/databases.rake +23 -19
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +35 -25
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +37 -21
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +43 -33
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +7 -10
- data/lib/active_record/relation.rb +37 -15
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +46 -18
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +24 -35
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +11 -3
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +34 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -4
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +68 -5
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/select_manager.rb +13 -4
- data/lib/arel/update_manager.rb +5 -0
- data/lib/arel/visitors/dot.rb +2 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +5 -21
- data/lib/arel.rb +3 -1
- metadata +13 -9
- 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
|
17
|
+
class NullConfig
|
18
18
|
def method_missing(...)
|
19
19
|
nil
|
20
20
|
end
|
21
21
|
end
|
22
|
-
NULL_CONFIG = NullConfig.new
|
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
|
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, :
|
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
|
-
@
|
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=#{
|
280
|
-
shard_field = " shard=#{
|
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
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
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
|
-
|
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
|
-
|
487
|
-
|
488
|
-
conn
|
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
|
-
|
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
|
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 == @
|
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.
|
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 @
|
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
|
-
|
726
|
+
removed_connections = synchronize do
|
652
727
|
return if self.discarded?
|
653
|
-
|
728
|
+
|
729
|
+
idle_connections = @connections.select do |conn|
|
654
730
|
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
655
|
-
end.
|
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
|
-
|
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 @
|
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
|
-
|
748
|
-
|
749
|
-
|
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>@
|
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 @
|
1218
|
+
# and increment @now_connecting, to prevent overstepping this pool's @max_connections
|
902
1219
|
# constraint
|
903
1220
|
do_checkout = synchronize do
|
904
|
-
if
|
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
|
-
|
917
|
-
|
918
|
-
|
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
|