activerecord 8.0.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1381 -241
- data/README.rdoc +2 -2
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +6 -4
- data/lib/active_record/associations/association.rb +35 -17
- data/lib/active_record/associations/association_scope.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +20 -4
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +36 -8
- 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 +9 -5
- data/lib/active_record/associations/collection_proxy.rb +26 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/foreign_association.rb +3 -1
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
- data/lib/active_record/associations/join_dependency.rb +5 -4
- data/lib/active_record/associations/preloader/association.rb +1 -1
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/primary_key.rb +2 -1
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/serialization.rb +19 -6
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
- data/lib/active_record/attribute_methods.rb +24 -17
- data/lib/active_record/attributes.rb +40 -26
- data/lib/active_record/autosave_association.rb +22 -12
- data/lib/active_record/base.rb +3 -4
- data/lib/active_record/callbacks.rb +1 -2
- data/lib/active_record/coders/column_serializer.rb +1 -1
- data/lib/active_record/coders/json.rb +16 -6
- data/lib/active_record/coders/yaml_column.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +19 -18
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +68 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +474 -112
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +68 -41
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -9
- 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 +31 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +94 -28
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -64
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +133 -40
- data/lib/active_record/connection_adapters/column.rb +14 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
- 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 +40 -25
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +66 -15
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +29 -33
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +46 -3
- 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/referential_integrity.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +22 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +70 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +99 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +40 -12
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +39 -23
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +15 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +7 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +68 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +8 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +16 -2
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +15 -10
- data/lib/active_record/core.rb +44 -12
- data/lib/active_record/counter_cache.rb +36 -9
- data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +66 -9
- data/lib/active_record/database_configurations/url_config.rb +15 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +19 -19
- data/lib/active_record/disable_joins_association_relation.rb +5 -1
- 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/encrypted_fixtures.rb +12 -7
- data/lib/active_record/encryption/encryptor.rb +39 -25
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +37 -20
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +76 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +5 -3
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +9 -4
- data/lib/active_record/insert_all.rb +14 -9
- 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 +30 -22
- data/lib/active_record/message_pack.rb +13 -0
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +32 -6
- 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 +38 -12
- data/lib/active_record/nested_attributes.rb +10 -6
- data/lib/active_record/persistence.rb +35 -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 +17 -5
- data/lib/active_record/railtie.rb +35 -6
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +24 -20
- 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 +36 -1
- data/lib/active_record/relation/batches.rb +25 -11
- data/lib/active_record/relation/calculations.rb +62 -41
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +76 -45
- data/lib/active_record/relation/merger.rb +4 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +10 -8
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +106 -45
- data/lib/active_record/relation/spawn_methods.rb +6 -6
- data/lib/active_record/relation/where_clause.rb +10 -11
- data/lib/active_record/relation.rb +63 -31
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema.rb +1 -1
- data/lib/active_record/schema_dumper.rb +41 -21
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +48 -19
- data/lib/active_record/statement_cache.rb +16 -12
- data/lib/active_record/store.rb +46 -19
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +83 -0
- data/lib/active_record/tasks/database_tasks.rb +44 -45
- data/lib/active_record/tasks/mysql_database_tasks.rb +4 -56
- 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 +18 -4
- data/lib/active_record/test_fixtures.rb +40 -6
- 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 +58 -32
- 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 +13 -2
- data/lib/active_record/type/serialized.rb +16 -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 +72 -7
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/collectors/bind.rb +1 -1
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -1
- 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 +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- 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 +7 -23
- data/lib/arel.rb +3 -1
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +16 -15
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
|
@@ -8,27 +8,22 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
|
|
|
8
8
|
|
|
9
9
|
module ActiveRecord
|
|
10
10
|
module ConnectionAdapters
|
|
11
|
-
module AbstractPool # :nodoc:
|
|
12
|
-
end
|
|
13
|
-
|
|
14
11
|
class NullPool # :nodoc:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class NullConfig # :nodoc:
|
|
12
|
+
class NullConfig
|
|
18
13
|
def method_missing(...)
|
|
19
14
|
nil
|
|
20
15
|
end
|
|
21
16
|
end
|
|
22
|
-
NULL_CONFIG = NullConfig.new
|
|
17
|
+
NULL_CONFIG = NullConfig.new
|
|
23
18
|
|
|
24
19
|
def initialize
|
|
25
20
|
super()
|
|
26
|
-
@
|
|
21
|
+
@monitor = Monitor.new
|
|
27
22
|
@server_version = nil
|
|
28
23
|
end
|
|
29
24
|
|
|
30
25
|
def server_version(connection) # :nodoc:
|
|
31
|
-
@server_version || @
|
|
26
|
+
@server_version || @monitor.synchronize { @server_version ||= connection.get_database_version }
|
|
32
27
|
end
|
|
33
28
|
|
|
34
29
|
def schema_reflection
|
|
@@ -36,7 +31,8 @@ module ActiveRecord
|
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
def schema_cache; end
|
|
39
|
-
def
|
|
34
|
+
def query_cache; end
|
|
35
|
+
def connection_descriptor; end
|
|
40
36
|
def checkin(_); end
|
|
41
37
|
def remove(_); end
|
|
42
38
|
def async_executor; end
|
|
@@ -48,6 +44,11 @@ module ActiveRecord
|
|
|
48
44
|
def dirties_query_cache
|
|
49
45
|
true
|
|
50
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
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
# = Active Record Connection Pool
|
|
@@ -100,13 +101,21 @@ module ActiveRecord
|
|
|
100
101
|
# There are several connection-pooling-related options that you can add to
|
|
101
102
|
# your database connection configuration:
|
|
102
103
|
#
|
|
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
104
|
# * +checkout_timeout+: number of seconds to wait for a connection to
|
|
108
105
|
# become available before giving up and raising a timeout error (default
|
|
109
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).
|
|
110
119
|
#
|
|
111
120
|
#--
|
|
112
121
|
# Synchronization policy:
|
|
@@ -114,27 +123,36 @@ module ActiveRecord
|
|
|
114
123
|
# * access to these instance variables needs to be in +synchronize+:
|
|
115
124
|
# * @connections
|
|
116
125
|
# * @now_connecting
|
|
126
|
+
# * @maintaining
|
|
117
127
|
# * private methods that require being called in a +synchronize+ blocks
|
|
118
128
|
# are now explicitly documented
|
|
119
129
|
class ConnectionPool
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
# Prior to 3.3.5, WeakKeyMap had a use after free bug
|
|
131
|
+
# https://bugs.ruby-lang.org/issues/20688
|
|
132
|
+
if ObjectSpace.const_defined?(:WeakKeyMap) && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.3.5")
|
|
133
|
+
WeakThreadKeyMap = ObjectSpace::WeakKeyMap
|
|
134
|
+
else
|
|
135
|
+
class WeakThreadKeyMap # :nodoc:
|
|
136
|
+
# FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
|
|
137
|
+
# but it currently causes GC crashes: https://github.com/byroot/rails/pull/3
|
|
138
|
+
def initialize
|
|
139
|
+
@map = Concurrent::Map.new
|
|
140
|
+
end
|
|
126
141
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
def clear
|
|
143
|
+
@map.clear
|
|
144
|
+
end
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
146
|
+
def [](key)
|
|
147
|
+
@map[key]
|
|
148
|
+
end
|
|
134
149
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
150
|
+
def []=(key, value)
|
|
151
|
+
@map.each_pair do |thread, _|
|
|
152
|
+
@map.delete(thread) unless thread&.alive?
|
|
153
|
+
end
|
|
154
|
+
@map[key] = value
|
|
155
|
+
end
|
|
138
156
|
end
|
|
139
157
|
end
|
|
140
158
|
|
|
@@ -164,21 +182,30 @@ module ActiveRecord
|
|
|
164
182
|
end
|
|
165
183
|
end
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
171
191
|
end
|
|
192
|
+
else
|
|
193
|
+
class LeaseRegistry # :nodoc:
|
|
194
|
+
def initialize
|
|
195
|
+
@mutex = Mutex.new
|
|
196
|
+
@map = WeakThreadKeyMap.new
|
|
197
|
+
end
|
|
172
198
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
199
|
+
def [](context)
|
|
200
|
+
@mutex.synchronize do
|
|
201
|
+
@map[context] ||= Lease.new
|
|
202
|
+
end
|
|
176
203
|
end
|
|
177
|
-
end
|
|
178
204
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
205
|
+
def clear
|
|
206
|
+
@mutex.synchronize do
|
|
207
|
+
@map.clear
|
|
208
|
+
end
|
|
182
209
|
end
|
|
183
210
|
end
|
|
184
211
|
end
|
|
@@ -210,10 +237,10 @@ module ActiveRecord
|
|
|
210
237
|
|
|
211
238
|
include MonitorMixin
|
|
212
239
|
prepend QueryCache::ConnectionPoolConfiguration
|
|
213
|
-
include ConnectionAdapters::AbstractPool
|
|
214
240
|
|
|
215
241
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
|
216
|
-
attr_reader :db_config, :
|
|
242
|
+
attr_reader :db_config, :max_connections, :min_connections, :max_age, :keepalive, :reaper, :pool_config, :async_executor, :role, :shard
|
|
243
|
+
alias :size :max_connections
|
|
217
244
|
|
|
218
245
|
delegate :schema_reflection, :server_version, to: :pool_config
|
|
219
246
|
|
|
@@ -233,7 +260,10 @@ module ActiveRecord
|
|
|
233
260
|
|
|
234
261
|
@checkout_timeout = db_config.checkout_timeout
|
|
235
262
|
@idle_timeout = db_config.idle_timeout
|
|
236
|
-
@
|
|
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
|
|
237
267
|
|
|
238
268
|
# This variable tracks the cache of threads mapped to reserved connections, with the
|
|
239
269
|
# sole purpose of speeding up the +connection+ method. It is not the authoritative
|
|
@@ -255,6 +285,12 @@ module ActiveRecord
|
|
|
255
285
|
# currently in the process of independently establishing connections to the DB.
|
|
256
286
|
@now_connecting = 0
|
|
257
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
|
+
|
|
258
294
|
@threads_blocking_new_connections = 0
|
|
259
295
|
|
|
260
296
|
@available = ConnectionLeasingQueue.new self
|
|
@@ -265,13 +301,17 @@ module ActiveRecord
|
|
|
265
301
|
|
|
266
302
|
@schema_cache = nil
|
|
267
303
|
|
|
304
|
+
@activated = false
|
|
305
|
+
@original_context = ActiveSupport::IsolatedExecutionState.context
|
|
306
|
+
|
|
307
|
+
@reaper_lock = Monitor.new
|
|
268
308
|
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
|
269
309
|
@reaper.run
|
|
270
310
|
end
|
|
271
311
|
|
|
272
312
|
def inspect # :nodoc:
|
|
273
|
-
name_field = " name=#{
|
|
274
|
-
shard_field = " shard=#{
|
|
313
|
+
name_field = " name=#{name_inspect}" if name_inspect
|
|
314
|
+
shard_field = " shard=#{shard_inspect}" if shard_inspect
|
|
275
315
|
|
|
276
316
|
"#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
|
|
277
317
|
end
|
|
@@ -301,6 +341,14 @@ module ActiveRecord
|
|
|
301
341
|
InternalMetadata.new(self)
|
|
302
342
|
end
|
|
303
343
|
|
|
344
|
+
def activate
|
|
345
|
+
@activated = true
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def activated?
|
|
349
|
+
@activated
|
|
350
|
+
end
|
|
351
|
+
|
|
304
352
|
# Retrieve the connection associated with the current thread, or call
|
|
305
353
|
# #checkout to obtain one if necessary.
|
|
306
354
|
#
|
|
@@ -308,8 +356,9 @@ module ActiveRecord
|
|
|
308
356
|
# held in a cache keyed by a thread.
|
|
309
357
|
def lease_connection
|
|
310
358
|
lease = connection_lease
|
|
311
|
-
lease.sticky = true
|
|
312
359
|
lease.connection ||= checkout
|
|
360
|
+
lease.sticky = true
|
|
361
|
+
lease.connection
|
|
313
362
|
end
|
|
314
363
|
|
|
315
364
|
def permanent_lease? # :nodoc:
|
|
@@ -327,6 +376,7 @@ module ActiveRecord
|
|
|
327
376
|
end
|
|
328
377
|
|
|
329
378
|
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
|
379
|
+
@pinned_connection.pinned = true
|
|
330
380
|
@pinned_connection.verify! # eagerly validate the connection
|
|
331
381
|
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
|
332
382
|
end
|
|
@@ -349,6 +399,7 @@ module ActiveRecord
|
|
|
349
399
|
end
|
|
350
400
|
|
|
351
401
|
if @pinned_connection.nil?
|
|
402
|
+
connection.pinned = false
|
|
352
403
|
connection.steal!
|
|
353
404
|
connection.lock_thread = nil
|
|
354
405
|
checkin(connection)
|
|
@@ -358,8 +409,8 @@ module ActiveRecord
|
|
|
358
409
|
clean
|
|
359
410
|
end
|
|
360
411
|
|
|
361
|
-
def
|
|
362
|
-
pool_config.
|
|
412
|
+
def connection_descriptor # :nodoc:
|
|
413
|
+
pool_config.connection_descriptor
|
|
363
414
|
end
|
|
364
415
|
|
|
365
416
|
# Returns true if there is an open connection being used for the current thread.
|
|
@@ -380,6 +431,8 @@ module ActiveRecord
|
|
|
380
431
|
# #lease_connection or #with_connection methods, connections obtained through
|
|
381
432
|
# #checkout will not be automatically released.
|
|
382
433
|
def release_connection(existing_lease = nil)
|
|
434
|
+
return if self.discarded?
|
|
435
|
+
|
|
383
436
|
if conn = connection_lease.release
|
|
384
437
|
checkin conn
|
|
385
438
|
return true
|
|
@@ -405,18 +458,36 @@ module ActiveRecord
|
|
|
405
458
|
begin
|
|
406
459
|
yield lease.connection
|
|
407
460
|
ensure
|
|
408
|
-
lease.sticky = sticky_was if prevent_permanent_checkout
|
|
461
|
+
lease.sticky = sticky_was if prevent_permanent_checkout
|
|
409
462
|
end
|
|
410
463
|
else
|
|
411
464
|
begin
|
|
412
465
|
yield lease.connection = checkout
|
|
413
466
|
ensure
|
|
414
|
-
lease.sticky = sticky_was if prevent_permanent_checkout
|
|
467
|
+
lease.sticky = sticky_was if prevent_permanent_checkout
|
|
415
468
|
release_connection(lease) unless lease.sticky
|
|
416
469
|
end
|
|
417
470
|
end
|
|
418
471
|
end
|
|
419
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
|
+
|
|
420
491
|
# Returns true if a connection has already been opened.
|
|
421
492
|
def connected?
|
|
422
493
|
synchronize { @connections.any?(&:connected?) }
|
|
@@ -444,18 +515,32 @@ module ActiveRecord
|
|
|
444
515
|
# connections in the pool within a timeout interval (default duration is
|
|
445
516
|
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
|
|
446
517
|
def disconnect(raise_on_acquisition_timeout = true)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
|
453
536
|
end
|
|
454
|
-
|
|
537
|
+
@connections = @pinned_connection ? [@pinned_connection] : []
|
|
538
|
+
@leases.clear
|
|
539
|
+
@available.clear
|
|
540
|
+
|
|
541
|
+
# Stop maintaining the minimum size until reactivated
|
|
542
|
+
@activated = false
|
|
455
543
|
end
|
|
456
|
-
@connections = []
|
|
457
|
-
@leases.clear
|
|
458
|
-
@available.clear
|
|
459
544
|
end
|
|
460
545
|
end
|
|
461
546
|
end
|
|
@@ -476,12 +561,15 @@ module ActiveRecord
|
|
|
476
561
|
#
|
|
477
562
|
# See AbstractAdapter#discard!
|
|
478
563
|
def discard! # :nodoc:
|
|
479
|
-
synchronize do
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
|
483
572
|
end
|
|
484
|
-
@connections = @available = @leases = nil
|
|
485
573
|
end
|
|
486
574
|
end
|
|
487
575
|
|
|
@@ -489,7 +577,17 @@ module ActiveRecord
|
|
|
489
577
|
@connections.nil?
|
|
490
578
|
end
|
|
491
579
|
|
|
492
|
-
|
|
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
|
|
493
591
|
# require reloading.
|
|
494
592
|
#
|
|
495
593
|
# Raises:
|
|
@@ -512,7 +610,7 @@ module ActiveRecord
|
|
|
512
610
|
end
|
|
513
611
|
end
|
|
514
612
|
|
|
515
|
-
# Clears
|
|
613
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
516
614
|
# require reloading.
|
|
517
615
|
#
|
|
518
616
|
# The pool first tries to gain ownership of all connections. If unable to
|
|
@@ -539,20 +637,25 @@ module ActiveRecord
|
|
|
539
637
|
# Raises:
|
|
540
638
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
|
541
639
|
def checkout(checkout_timeout = @checkout_timeout)
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
640
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
|
641
|
+
|
|
642
|
+
@pinned_connection.lock.synchronize do
|
|
643
|
+
synchronize do
|
|
644
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
|
645
|
+
if @pinned_connection
|
|
545
646
|
@pinned_connection.verify!
|
|
647
|
+
|
|
546
648
|
# Any leased connection must be in @connections otherwise
|
|
547
649
|
# some methods like #connected? won't behave correctly
|
|
548
650
|
unless @connections.include?(@pinned_connection)
|
|
549
651
|
@connections << @pinned_connection
|
|
550
652
|
end
|
|
653
|
+
|
|
654
|
+
@pinned_connection
|
|
655
|
+
else
|
|
656
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
551
657
|
end
|
|
552
658
|
end
|
|
553
|
-
@pinned_connection
|
|
554
|
-
else
|
|
555
|
-
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
556
659
|
end
|
|
557
660
|
end
|
|
558
661
|
|
|
@@ -567,11 +670,7 @@ module ActiveRecord
|
|
|
567
670
|
conn.lock.synchronize do
|
|
568
671
|
synchronize do
|
|
569
672
|
connection_lease.clear(conn)
|
|
570
|
-
|
|
571
|
-
conn._run_checkin_callbacks do
|
|
572
|
-
conn.expire
|
|
573
|
-
end
|
|
574
|
-
|
|
673
|
+
conn.expire
|
|
575
674
|
@available.add conn
|
|
576
675
|
end
|
|
577
676
|
end
|
|
@@ -589,7 +688,7 @@ module ActiveRecord
|
|
|
589
688
|
@available.delete conn
|
|
590
689
|
|
|
591
690
|
# @available.any_waiting? => true means that prior to removing this
|
|
592
|
-
# conn, the pool was at its max size (@connections.size == @
|
|
691
|
+
# conn, the pool was at its max size (@connections.size == @max_connections).
|
|
593
692
|
# This would mean that any threads stuck waiting in the queue wouldn't
|
|
594
693
|
# know they could checkout_new_connection, so let's do it for them.
|
|
595
694
|
# Because condition-wait loop is encapsulated in the Queue class
|
|
@@ -597,14 +696,14 @@ module ActiveRecord
|
|
|
597
696
|
# that are "stuck" there are helpless. They have no way of creating
|
|
598
697
|
# new connections and are completely reliant on us feeding available
|
|
599
698
|
# connections into the Queue.
|
|
600
|
-
needs_new_connection = @available.
|
|
699
|
+
needs_new_connection = @available.num_waiting > @maintaining
|
|
601
700
|
end
|
|
602
701
|
|
|
603
702
|
# This is intentionally done outside of the synchronized section as we
|
|
604
703
|
# would like not to hold the main mutex while checking out new connections.
|
|
605
704
|
# Thus there is some chance that needs_new_connection information is now
|
|
606
705
|
# stale, we can live with that (bulk_make_new_connections will make
|
|
607
|
-
# sure not to exceed the pool's @
|
|
706
|
+
# sure not to exceed the pool's @max_connections limit).
|
|
608
707
|
bulk_make_new_connections(1) if needs_new_connection
|
|
609
708
|
end
|
|
610
709
|
|
|
@@ -637,11 +736,27 @@ module ActiveRecord
|
|
|
637
736
|
def flush(minimum_idle = @idle_timeout)
|
|
638
737
|
return if minimum_idle.nil?
|
|
639
738
|
|
|
640
|
-
|
|
739
|
+
removed_connections = synchronize do
|
|
641
740
|
return if self.discarded?
|
|
642
|
-
|
|
741
|
+
|
|
742
|
+
idle_connections = @connections.select do |conn|
|
|
643
743
|
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
|
644
|
-
end.
|
|
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|
|
|
645
760
|
conn.lease
|
|
646
761
|
|
|
647
762
|
@available.delete conn
|
|
@@ -649,22 +764,111 @@ module ActiveRecord
|
|
|
649
764
|
end
|
|
650
765
|
end
|
|
651
766
|
|
|
652
|
-
|
|
767
|
+
removed_connections.each do |conn|
|
|
653
768
|
conn.disconnect!
|
|
654
769
|
end
|
|
655
770
|
end
|
|
656
771
|
|
|
657
772
|
# Disconnect all currently idle connections. Connections currently checked
|
|
658
|
-
# 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).
|
|
659
775
|
def flush!
|
|
660
776
|
reap
|
|
661
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
|
|
662
862
|
end
|
|
663
863
|
|
|
664
864
|
def num_waiting_in_queue # :nodoc:
|
|
665
865
|
@available.num_waiting
|
|
666
866
|
end
|
|
667
867
|
|
|
868
|
+
def num_available_in_queue # :nodoc:
|
|
869
|
+
@available.size
|
|
870
|
+
end
|
|
871
|
+
|
|
668
872
|
# Returns the connection pool's usage statistic.
|
|
669
873
|
#
|
|
670
874
|
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
|
|
@@ -687,6 +891,24 @@ module ActiveRecord
|
|
|
687
891
|
Thread.pass
|
|
688
892
|
end
|
|
689
893
|
|
|
894
|
+
def new_connection # :nodoc:
|
|
895
|
+
connection = db_config.new_connection
|
|
896
|
+
connection.pool = self
|
|
897
|
+
connection
|
|
898
|
+
rescue ConnectionNotEstablished => ex
|
|
899
|
+
raise ex.set_pool(self)
|
|
900
|
+
end
|
|
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
|
+
|
|
690
912
|
private
|
|
691
913
|
def connection_lease
|
|
692
914
|
@leases[ActiveSupport::IsolatedExecutionState.context]
|
|
@@ -696,7 +918,9 @@ module ActiveRecord
|
|
|
696
918
|
case ActiveRecord.async_query_executor
|
|
697
919
|
when :multi_thread_pool
|
|
698
920
|
if @db_config.max_threads > 0
|
|
921
|
+
name_with_shard = [name_inspect, shard_inspect].join("-").tr("_", "-")
|
|
699
922
|
Concurrent::ThreadPoolExecutor.new(
|
|
923
|
+
name: "ActiveRecord-#{name_with_shard}-async-query-executor",
|
|
700
924
|
min_threads: @db_config.min_threads,
|
|
701
925
|
max_threads: @db_config.max_threads,
|
|
702
926
|
max_queue: @db_config.max_queue,
|
|
@@ -708,11 +932,109 @@ module ActiveRecord
|
|
|
708
932
|
end
|
|
709
933
|
end
|
|
710
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
|
+
|
|
711
1033
|
#--
|
|
712
1034
|
# this is unfortunately not concurrent
|
|
713
1035
|
def bulk_make_new_connections(num_new_conns_needed)
|
|
714
1036
|
num_new_conns_needed.times do
|
|
715
|
-
# try_to_checkout_new_connection will not exceed pool's @
|
|
1037
|
+
# try_to_checkout_new_connection will not exceed pool's @max_connections limit
|
|
716
1038
|
if new_conn = try_to_checkout_new_connection
|
|
717
1039
|
# make the new_conn available to the starving threads stuck @available Queue
|
|
718
1040
|
checkin(new_conn)
|
|
@@ -725,9 +1047,11 @@ module ActiveRecord
|
|
|
725
1047
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
|
726
1048
|
# to be performed outside of the main +synchronize+ block.
|
|
727
1049
|
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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
|
|
731
1055
|
end
|
|
732
1056
|
end
|
|
733
1057
|
|
|
@@ -847,13 +1171,13 @@ module ActiveRecord
|
|
|
847
1171
|
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
|
|
848
1172
|
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
|
849
1173
|
# of the said methods and avoid an additional +synchronize+ overhead.
|
|
850
|
-
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
|
|
851
1175
|
conn
|
|
852
1176
|
else
|
|
853
1177
|
reap
|
|
854
1178
|
# Retry after reaping, which may return an available connection,
|
|
855
1179
|
# remove an inactive connection, or both
|
|
856
|
-
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
|
|
857
1181
|
conn
|
|
858
1182
|
else
|
|
859
1183
|
@available.poll(checkout_timeout)
|
|
@@ -863,32 +1187,61 @@ module ActiveRecord
|
|
|
863
1187
|
raise ex.set_pool(self)
|
|
864
1188
|
end
|
|
865
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
|
+
|
|
866
1216
|
#--
|
|
867
1217
|
# if owner_thread param is omitted, this must be called in synchronize block
|
|
868
1218
|
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
|
869
|
-
|
|
1219
|
+
if owner_thread
|
|
1220
|
+
@leases[owner_thread].clear(conn)
|
|
1221
|
+
end
|
|
870
1222
|
end
|
|
871
1223
|
alias_method :release, :remove_connection_from_thread_cache
|
|
872
1224
|
|
|
873
|
-
|
|
874
|
-
connection = db_config.new_connection
|
|
875
|
-
connection.pool = self
|
|
876
|
-
connection
|
|
877
|
-
rescue ConnectionNotEstablished => ex
|
|
878
|
-
raise ex.set_pool(self)
|
|
879
|
-
end
|
|
880
|
-
|
|
881
|
-
# 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
|
|
882
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.
|
|
883
1230
|
#--
|
|
884
1231
|
# Implementation constraint: a newly established connection returned by this
|
|
885
1232
|
# method must be in the +.leased+ state.
|
|
886
1233
|
def try_to_checkout_new_connection
|
|
887
1234
|
# first in synchronized section check if establishing new conns is allowed
|
|
888
|
-
# and increment @now_connecting, to prevent overstepping this pool's @
|
|
1235
|
+
# and increment @now_connecting, to prevent overstepping this pool's @max_connections
|
|
889
1236
|
# constraint
|
|
890
1237
|
do_checkout = synchronize do
|
|
891
|
-
if
|
|
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
|
+
|
|
892
1245
|
@now_connecting += 1
|
|
893
1246
|
end
|
|
894
1247
|
end
|
|
@@ -899,12 +1252,16 @@ module ActiveRecord
|
|
|
899
1252
|
conn = checkout_new_connection
|
|
900
1253
|
ensure
|
|
901
1254
|
synchronize do
|
|
1255
|
+
@now_connecting -= 1
|
|
902
1256
|
if conn
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
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
|
|
906
1264
|
end
|
|
907
|
-
@now_connecting -= 1
|
|
908
1265
|
end
|
|
909
1266
|
end
|
|
910
1267
|
end
|
|
@@ -927,15 +1284,20 @@ module ActiveRecord
|
|
|
927
1284
|
end
|
|
928
1285
|
|
|
929
1286
|
def checkout_and_verify(c)
|
|
930
|
-
c.
|
|
931
|
-
c.clean!
|
|
932
|
-
end
|
|
933
|
-
c
|
|
1287
|
+
c.clean!
|
|
934
1288
|
rescue Exception
|
|
935
1289
|
remove c
|
|
936
1290
|
c.disconnect!
|
|
937
1291
|
raise
|
|
938
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
|
|
939
1301
|
end
|
|
940
1302
|
end
|
|
941
1303
|
end
|