activerecord 7.1.5.2 → 8.1.2.1
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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/key_provider.rb +1 -1
- data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- 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 +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- 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 +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- 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 +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- 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/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "thread"
|
|
4
3
|
require "concurrent/map"
|
|
5
4
|
require "monitor"
|
|
6
5
|
|
|
@@ -9,30 +8,47 @@ require "active_record/connection_adapters/abstract/connection_pool/reaper"
|
|
|
9
8
|
|
|
10
9
|
module ActiveRecord
|
|
11
10
|
module ConnectionAdapters
|
|
12
|
-
module AbstractPool # :nodoc:
|
|
13
|
-
end
|
|
14
|
-
|
|
15
11
|
class NullPool # :nodoc:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class NullConfig # :nodoc:
|
|
19
|
-
def method_missing(*)
|
|
12
|
+
class NullConfig
|
|
13
|
+
def method_missing(...)
|
|
20
14
|
nil
|
|
21
15
|
end
|
|
22
16
|
end
|
|
23
|
-
NULL_CONFIG = NullConfig.new
|
|
17
|
+
NULL_CONFIG = NullConfig.new
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
super()
|
|
21
|
+
@mutex = Mutex.new
|
|
22
|
+
@server_version = nil
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def server_version(connection) # :nodoc:
|
|
26
|
+
@server_version || @mutex.synchronize { @server_version ||= connection.get_database_version }
|
|
27
|
+
end
|
|
24
28
|
|
|
25
29
|
def schema_reflection
|
|
26
30
|
SchemaReflection.new(nil)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
|
-
def
|
|
33
|
+
def schema_cache; end
|
|
34
|
+
def query_cache; end
|
|
35
|
+
def connection_descriptor; end
|
|
30
36
|
def checkin(_); end
|
|
31
37
|
def remove(_); end
|
|
32
38
|
def async_executor; end
|
|
39
|
+
|
|
33
40
|
def db_config
|
|
34
41
|
NULL_CONFIG
|
|
35
42
|
end
|
|
43
|
+
|
|
44
|
+
def dirties_query_cache
|
|
45
|
+
true
|
|
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
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
# = Active Record Connection Pool
|
|
@@ -58,7 +74,7 @@ module ActiveRecord
|
|
|
58
74
|
# Connections can be obtained and used from a connection pool in several
|
|
59
75
|
# ways:
|
|
60
76
|
#
|
|
61
|
-
# 1. Simply use {ActiveRecord::Base.
|
|
77
|
+
# 1. Simply use {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection].
|
|
62
78
|
# When you're done with the connection(s) and wish it to be returned to the pool, you call
|
|
63
79
|
# {ActiveRecord::Base.connection_handler.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
|
64
80
|
# This is the default behavior for Active Record when used in conjunction with
|
|
@@ -85,13 +101,21 @@ module ActiveRecord
|
|
|
85
101
|
# There are several connection-pooling-related options that you can add to
|
|
86
102
|
# your database connection configuration:
|
|
87
103
|
#
|
|
88
|
-
# * +pool+: maximum number of connections the pool may manage (default 5).
|
|
89
|
-
# * +idle_timeout+: number of seconds that a connection will be kept
|
|
90
|
-
# unused in the pool before it is automatically disconnected (default
|
|
91
|
-
# 300 seconds). Set this to zero to keep connections forever.
|
|
92
104
|
# * +checkout_timeout+: number of seconds to wait for a connection to
|
|
93
105
|
# become available before giving up and raising a timeout error (default
|
|
94
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).
|
|
95
119
|
#
|
|
96
120
|
#--
|
|
97
121
|
# Synchronization policy:
|
|
@@ -99,17 +123,124 @@ module ActiveRecord
|
|
|
99
123
|
# * access to these instance variables needs to be in +synchronize+:
|
|
100
124
|
# * @connections
|
|
101
125
|
# * @now_connecting
|
|
126
|
+
# * @maintaining
|
|
102
127
|
# * private methods that require being called in a +synchronize+ blocks
|
|
103
128
|
# are now explicitly documented
|
|
104
129
|
class ConnectionPool
|
|
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) && RUBY_VERSION >= "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 = {}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def clear
|
|
143
|
+
@map.clear
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def [](key)
|
|
147
|
+
@map[key]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def []=(key, value)
|
|
151
|
+
@map.select! { |c, _| c&.alive? }
|
|
152
|
+
@map[key] = value
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class Lease # :nodoc:
|
|
158
|
+
attr_accessor :connection, :sticky
|
|
159
|
+
|
|
160
|
+
def initialize
|
|
161
|
+
@connection = nil
|
|
162
|
+
@sticky = nil
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def release
|
|
166
|
+
conn = @connection
|
|
167
|
+
@connection = nil
|
|
168
|
+
@sticky = nil
|
|
169
|
+
conn
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def clear(connection)
|
|
173
|
+
if @connection == connection
|
|
174
|
+
@connection = nil
|
|
175
|
+
@sticky = nil
|
|
176
|
+
true
|
|
177
|
+
else
|
|
178
|
+
false
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
if RUBY_ENGINE == "ruby"
|
|
184
|
+
# Thanks to the GVL, the LeaseRegistry doesn't need to be synchronized on MRI
|
|
185
|
+
class LeaseRegistry < WeakThreadKeyMap # :nodoc:
|
|
186
|
+
def [](context)
|
|
187
|
+
super || (self[context] = Lease.new)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
class LeaseRegistry # :nodoc:
|
|
192
|
+
def initialize
|
|
193
|
+
@mutex = Mutex.new
|
|
194
|
+
@map = WeakThreadKeyMap.new
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def [](context)
|
|
198
|
+
@mutex.synchronize do
|
|
199
|
+
@map[context] ||= Lease.new
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def clear
|
|
204
|
+
@mutex.synchronize do
|
|
205
|
+
@map.clear
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
module ExecutorHooks # :nodoc:
|
|
212
|
+
class << self
|
|
213
|
+
def run
|
|
214
|
+
# noop
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def complete(_)
|
|
218
|
+
ActiveRecord::Base.connection_handler.each_connection_pool do |pool|
|
|
219
|
+
if (connection = pool.active_connection?)
|
|
220
|
+
transaction = connection.current_transaction
|
|
221
|
+
if transaction.closed? || !transaction.joinable?
|
|
222
|
+
pool.release_connection
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
class << self
|
|
231
|
+
def install_executor_hooks(executor = ActiveSupport::Executor)
|
|
232
|
+
executor.register_hook(ExecutorHooks)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
105
236
|
include MonitorMixin
|
|
106
|
-
|
|
107
|
-
include ConnectionAdapters::AbstractPool
|
|
237
|
+
prepend QueryCache::ConnectionPoolConfiguration
|
|
108
238
|
|
|
109
239
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
|
110
|
-
attr_reader :db_config, :
|
|
240
|
+
attr_reader :db_config, :max_connections, :min_connections, :max_age, :keepalive, :reaper, :pool_config, :async_executor, :role, :shard
|
|
241
|
+
alias :size :max_connections
|
|
111
242
|
|
|
112
|
-
delegate :schema_reflection, :
|
|
243
|
+
delegate :schema_reflection, :server_version, to: :pool_config
|
|
113
244
|
|
|
114
245
|
# Creates a new ConnectionPool object. +pool_config+ is a PoolConfig
|
|
115
246
|
# object which describes database connection information (e.g. adapter,
|
|
@@ -127,7 +258,10 @@ module ActiveRecord
|
|
|
127
258
|
|
|
128
259
|
@checkout_timeout = db_config.checkout_timeout
|
|
129
260
|
@idle_timeout = db_config.idle_timeout
|
|
130
|
-
@
|
|
261
|
+
@max_connections = db_config.max_connections
|
|
262
|
+
@min_connections = db_config.min_connections
|
|
263
|
+
@max_age = db_config.max_age
|
|
264
|
+
@keepalive = db_config.keepalive
|
|
131
265
|
|
|
132
266
|
# This variable tracks the cache of threads mapped to reserved connections, with the
|
|
133
267
|
# sole purpose of speeding up the +connection+ method. It is not the authoritative
|
|
@@ -137,9 +271,9 @@ module ActiveRecord
|
|
|
137
271
|
# then that +thread+ does indeed own that +conn+. However, an absence of such
|
|
138
272
|
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
|
139
273
|
# that case +conn.owner+ attr should be consulted.
|
|
140
|
-
# Access and modification of <tt>@
|
|
274
|
+
# Access and modification of <tt>@leases</tt> does not require
|
|
141
275
|
# synchronization.
|
|
142
|
-
@
|
|
276
|
+
@leases = LeaseRegistry.new
|
|
143
277
|
|
|
144
278
|
@connections = []
|
|
145
279
|
@automatic_reconnect = true
|
|
@@ -149,89 +283,212 @@ module ActiveRecord
|
|
|
149
283
|
# currently in the process of independently establishing connections to the DB.
|
|
150
284
|
@now_connecting = 0
|
|
151
285
|
|
|
286
|
+
# Sometimes otherwise-idle connections are temporarily held by the Reaper for
|
|
287
|
+
# maintenance. This variable tracks the number of connections currently in that
|
|
288
|
+
# state -- if a thread requests a connection and there are none available, it
|
|
289
|
+
# will await any in-maintenance connections in preference to creating a new one.
|
|
290
|
+
@maintaining = 0
|
|
291
|
+
|
|
152
292
|
@threads_blocking_new_connections = 0
|
|
153
293
|
|
|
154
294
|
@available = ConnectionLeasingQueue.new self
|
|
155
|
-
|
|
156
|
-
@
|
|
295
|
+
@pinned_connection = nil
|
|
296
|
+
@pinned_connections_depth = 0
|
|
157
297
|
|
|
158
298
|
@async_executor = build_async_executor
|
|
159
299
|
|
|
300
|
+
@schema_cache = nil
|
|
301
|
+
|
|
302
|
+
@activated = false
|
|
303
|
+
@original_context = ActiveSupport::IsolatedExecutionState.context
|
|
304
|
+
|
|
305
|
+
@reaper_lock = Monitor.new
|
|
160
306
|
@reaper = Reaper.new(self, db_config.reaping_frequency)
|
|
161
307
|
@reaper.run
|
|
162
308
|
end
|
|
163
309
|
|
|
164
|
-
def
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
else
|
|
168
|
-
@lock_thread = nil
|
|
169
|
-
end
|
|
310
|
+
def inspect # :nodoc:
|
|
311
|
+
name_field = " name=#{name_inspect}" if name_inspect
|
|
312
|
+
shard_field = " shard=#{shard_inspect}" if shard_inspect
|
|
170
313
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
314
|
+
"#<#{self.class.name} env_name=#{db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def schema_cache
|
|
318
|
+
@schema_cache ||= BoundSchemaReflection.new(schema_reflection, self)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def schema_reflection=(schema_reflection)
|
|
322
|
+
pool_config.schema_reflection = schema_reflection
|
|
323
|
+
@schema_cache = nil
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def migration_context # :nodoc:
|
|
327
|
+
MigrationContext.new(migrations_paths, schema_migration, internal_metadata)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def migrations_paths # :nodoc:
|
|
331
|
+
db_config.migrations_paths || Migrator.migrations_paths
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def schema_migration # :nodoc:
|
|
335
|
+
SchemaMigration.new(self)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def internal_metadata # :nodoc:
|
|
339
|
+
InternalMetadata.new(self)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def activate
|
|
343
|
+
@activated = true
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def activated?
|
|
347
|
+
@activated
|
|
174
348
|
end
|
|
175
349
|
|
|
176
350
|
# Retrieve the connection associated with the current thread, or call
|
|
177
351
|
# #checkout to obtain one if necessary.
|
|
178
352
|
#
|
|
179
|
-
# #
|
|
353
|
+
# #lease_connection can be called any number of times; the connection is
|
|
180
354
|
# held in a cache keyed by a thread.
|
|
181
|
-
def
|
|
182
|
-
|
|
355
|
+
def lease_connection
|
|
356
|
+
lease = connection_lease
|
|
357
|
+
lease.connection ||= checkout
|
|
358
|
+
lease.sticky = true
|
|
359
|
+
lease.connection
|
|
183
360
|
end
|
|
184
361
|
|
|
185
|
-
def
|
|
186
|
-
|
|
362
|
+
def permanent_lease? # :nodoc:
|
|
363
|
+
connection_lease.sticky.nil?
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def pin_connection!(lock_thread) # :nodoc:
|
|
367
|
+
@pinned_connection ||= (connection_lease&.connection || checkout)
|
|
368
|
+
@pinned_connections_depth += 1
|
|
369
|
+
|
|
370
|
+
# Any leased connection must be in @connections otherwise
|
|
371
|
+
# some methods like #connected? won't behave correctly
|
|
372
|
+
unless @connections.include?(@pinned_connection)
|
|
373
|
+
@connections << @pinned_connection
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
@pinned_connection.lock_thread = ActiveSupport::IsolatedExecutionState.context if lock_thread
|
|
377
|
+
@pinned_connection.pinned = true
|
|
378
|
+
@pinned_connection.verify! # eagerly validate the connection
|
|
379
|
+
@pinned_connection.begin_transaction joinable: false, _lazy: false
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def unpin_connection! # :nodoc:
|
|
383
|
+
raise "There isn't a pinned connection #{object_id}" unless @pinned_connection
|
|
384
|
+
|
|
385
|
+
clean = true
|
|
386
|
+
@pinned_connection.lock.synchronize do
|
|
387
|
+
@pinned_connections_depth -= 1
|
|
388
|
+
connection = @pinned_connection
|
|
389
|
+
@pinned_connection = nil if @pinned_connections_depth.zero?
|
|
390
|
+
|
|
391
|
+
if connection.transaction_open?
|
|
392
|
+
connection.rollback_transaction
|
|
393
|
+
else
|
|
394
|
+
# Something committed or rolled back the transaction
|
|
395
|
+
clean = false
|
|
396
|
+
connection.reset!
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
if @pinned_connection.nil?
|
|
400
|
+
connection.pinned = false
|
|
401
|
+
connection.steal!
|
|
402
|
+
connection.lock_thread = nil
|
|
403
|
+
checkin(connection)
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
clean
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def connection_descriptor # :nodoc:
|
|
411
|
+
pool_config.connection_descriptor
|
|
187
412
|
end
|
|
188
|
-
alias :connection_klass :connection_class
|
|
189
|
-
deprecate :connection_klass, deprecator: ActiveRecord.deprecator
|
|
190
413
|
|
|
191
414
|
# Returns true if there is an open connection being used for the current thread.
|
|
192
415
|
#
|
|
193
416
|
# This method only works for connections that have been obtained through
|
|
194
|
-
# #
|
|
417
|
+
# #lease_connection or #with_connection methods. Connections obtained through
|
|
195
418
|
# #checkout will not be detected by #active_connection?
|
|
196
419
|
def active_connection?
|
|
197
|
-
|
|
420
|
+
connection_lease.connection
|
|
198
421
|
end
|
|
422
|
+
alias_method :active_connection, :active_connection? # :nodoc:
|
|
199
423
|
|
|
200
424
|
# Signal that the thread is finished with the current connection.
|
|
201
425
|
# #release_connection releases the connection-thread association
|
|
202
426
|
# and returns the connection to the pool.
|
|
203
427
|
#
|
|
204
428
|
# This method only works for connections that have been obtained through
|
|
205
|
-
# #
|
|
429
|
+
# #lease_connection or #with_connection methods, connections obtained through
|
|
206
430
|
# #checkout will not be automatically released.
|
|
207
|
-
def release_connection(
|
|
208
|
-
if
|
|
431
|
+
def release_connection(existing_lease = nil)
|
|
432
|
+
return if self.discarded?
|
|
433
|
+
|
|
434
|
+
if conn = connection_lease.release
|
|
209
435
|
checkin conn
|
|
436
|
+
return true
|
|
210
437
|
end
|
|
438
|
+
false
|
|
211
439
|
end
|
|
212
440
|
|
|
213
441
|
# Yields a connection from the connection pool to the block. If no connection
|
|
214
442
|
# is already checked out by the current thread, a connection will be checked
|
|
215
443
|
# out from the pool, yielded to the block, and then returned to the pool when
|
|
216
444
|
# the block is finished. If a connection has already been checked out on the
|
|
217
|
-
# current thread, such as via #
|
|
445
|
+
# current thread, such as via #lease_connection or #with_connection, that existing
|
|
218
446
|
# connection will be the one yielded and it will not be returned to the pool
|
|
219
447
|
# automatically at the end of the block; it is expected that such an existing
|
|
220
448
|
# connection will be properly returned to the pool by the code that checked
|
|
221
449
|
# it out.
|
|
222
|
-
def with_connection
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
450
|
+
def with_connection(prevent_permanent_checkout: false)
|
|
451
|
+
lease = connection_lease
|
|
452
|
+
sticky_was = lease.sticky
|
|
453
|
+
lease.sticky = false if prevent_permanent_checkout
|
|
454
|
+
|
|
455
|
+
if lease.connection
|
|
456
|
+
begin
|
|
457
|
+
yield lease.connection
|
|
458
|
+
ensure
|
|
459
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
|
460
|
+
end
|
|
461
|
+
else
|
|
462
|
+
begin
|
|
463
|
+
yield lease.connection = checkout
|
|
464
|
+
ensure
|
|
465
|
+
lease.sticky = sticky_was if prevent_permanent_checkout && !sticky_was
|
|
466
|
+
release_connection(lease) unless lease.sticky
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def with_pool_transaction_isolation_level(isolation_level, transaction_open, &block) # :nodoc:
|
|
472
|
+
if !ActiveRecord.default_transaction_isolation_level.nil?
|
|
473
|
+
begin
|
|
474
|
+
if transaction_open && self.pool_transaction_isolation_level != ActiveRecord.default_transaction_isolation_level
|
|
475
|
+
raise ActiveRecord::TransactionIsolationError, "cannot set default isolation level while transaction is open"
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
old_level = self.pool_transaction_isolation_level
|
|
479
|
+
self.pool_transaction_isolation_level = isolation_level
|
|
480
|
+
yield
|
|
481
|
+
ensure
|
|
482
|
+
self.pool_transaction_isolation_level = old_level
|
|
483
|
+
end
|
|
484
|
+
else
|
|
485
|
+
yield
|
|
226
486
|
end
|
|
227
|
-
yield conn
|
|
228
|
-
ensure
|
|
229
|
-
release_connection if fresh_connection
|
|
230
487
|
end
|
|
231
488
|
|
|
232
489
|
# Returns true if a connection has already been opened.
|
|
233
490
|
def connected?
|
|
234
|
-
synchronize { @connections.any? }
|
|
491
|
+
synchronize { @connections.any?(&:connected?) }
|
|
235
492
|
end
|
|
236
493
|
|
|
237
494
|
# Returns an array containing the connections currently in the pool.
|
|
@@ -256,17 +513,26 @@ module ActiveRecord
|
|
|
256
513
|
# connections in the pool within a timeout interval (default duration is
|
|
257
514
|
# <tt>spec.db_config.checkout_timeout * 2</tt> seconds).
|
|
258
515
|
def disconnect(raise_on_acquisition_timeout = true)
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
516
|
+
@reaper_lock.synchronize do
|
|
517
|
+
return if self.discarded?
|
|
518
|
+
|
|
519
|
+
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
|
520
|
+
synchronize do
|
|
521
|
+
return if self.discarded?
|
|
522
|
+
@connections.each do |conn|
|
|
523
|
+
if conn.in_use?
|
|
524
|
+
conn.steal!
|
|
525
|
+
checkin conn
|
|
526
|
+
end
|
|
527
|
+
conn.disconnect!
|
|
265
528
|
end
|
|
266
|
-
|
|
529
|
+
@connections = []
|
|
530
|
+
@leases.clear
|
|
531
|
+
@available.clear
|
|
532
|
+
|
|
533
|
+
# Stop maintaining the minimum size until reactivated
|
|
534
|
+
@activated = false
|
|
267
535
|
end
|
|
268
|
-
@connections = []
|
|
269
|
-
@available.clear
|
|
270
536
|
end
|
|
271
537
|
end
|
|
272
538
|
end
|
|
@@ -287,12 +553,14 @@ module ActiveRecord
|
|
|
287
553
|
#
|
|
288
554
|
# See AbstractAdapter#discard!
|
|
289
555
|
def discard! # :nodoc:
|
|
290
|
-
synchronize do
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
conn
|
|
556
|
+
@reaper_lock.synchronize do
|
|
557
|
+
synchronize do
|
|
558
|
+
return if self.discarded?
|
|
559
|
+
@connections.each do |conn|
|
|
560
|
+
conn.discard!
|
|
561
|
+
end
|
|
562
|
+
@connections = @available = @leases = nil
|
|
294
563
|
end
|
|
295
|
-
@connections = @available = @thread_cached_conns = nil
|
|
296
564
|
end
|
|
297
565
|
end
|
|
298
566
|
|
|
@@ -300,7 +568,17 @@ module ActiveRecord
|
|
|
300
568
|
@connections.nil?
|
|
301
569
|
end
|
|
302
570
|
|
|
303
|
-
|
|
571
|
+
def maintainable? # :nodoc:
|
|
572
|
+
synchronize do
|
|
573
|
+
@connections&.size&.> 0 || (activated? && @min_connections > 0)
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
|
|
577
|
+
def reaper_lock(&block) # :nodoc:
|
|
578
|
+
@reaper_lock.synchronize(&block)
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
304
582
|
# require reloading.
|
|
305
583
|
#
|
|
306
584
|
# Raises:
|
|
@@ -323,7 +601,7 @@ module ActiveRecord
|
|
|
323
601
|
end
|
|
324
602
|
end
|
|
325
603
|
|
|
326
|
-
# Clears
|
|
604
|
+
# Clears reloadable connections from the pool and re-connects connections that
|
|
327
605
|
# require reloading.
|
|
328
606
|
#
|
|
329
607
|
# The pool first tries to gain ownership of all connections. If unable to
|
|
@@ -350,9 +628,26 @@ module ActiveRecord
|
|
|
350
628
|
# Raises:
|
|
351
629
|
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
|
352
630
|
def checkout(checkout_timeout = @checkout_timeout)
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
631
|
+
return checkout_and_verify(acquire_connection(checkout_timeout)) unless @pinned_connection
|
|
632
|
+
|
|
633
|
+
@pinned_connection.lock.synchronize do
|
|
634
|
+
synchronize do
|
|
635
|
+
# The pinned connection may have been cleaned up before we synchronized, so check if it is still present
|
|
636
|
+
if @pinned_connection
|
|
637
|
+
@pinned_connection.verify!
|
|
638
|
+
|
|
639
|
+
# Any leased connection must be in @connections otherwise
|
|
640
|
+
# some methods like #connected? won't behave correctly
|
|
641
|
+
unless @connections.include?(@pinned_connection)
|
|
642
|
+
@connections << @pinned_connection
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
@pinned_connection
|
|
646
|
+
else
|
|
647
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
356
651
|
end
|
|
357
652
|
|
|
358
653
|
# Check-in a database connection back into the pool, indicating that you
|
|
@@ -361,15 +656,12 @@ module ActiveRecord
|
|
|
361
656
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
|
362
657
|
# calling #checkout on this pool.
|
|
363
658
|
def checkin(conn)
|
|
659
|
+
return if @pinned_connection.equal?(conn)
|
|
660
|
+
|
|
364
661
|
conn.lock.synchronize do
|
|
365
662
|
synchronize do
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
conn._run_checkin_callbacks do
|
|
369
|
-
conn.expire
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
conn.lock_thread = nil
|
|
663
|
+
connection_lease.clear(conn)
|
|
664
|
+
conn.expire
|
|
373
665
|
@available.add conn
|
|
374
666
|
end
|
|
375
667
|
end
|
|
@@ -387,7 +679,7 @@ module ActiveRecord
|
|
|
387
679
|
@available.delete conn
|
|
388
680
|
|
|
389
681
|
# @available.any_waiting? => true means that prior to removing this
|
|
390
|
-
# conn, the pool was at its max size (@connections.size == @
|
|
682
|
+
# conn, the pool was at its max size (@connections.size == @max_connections).
|
|
391
683
|
# This would mean that any threads stuck waiting in the queue wouldn't
|
|
392
684
|
# know they could checkout_new_connection, so let's do it for them.
|
|
393
685
|
# Because condition-wait loop is encapsulated in the Queue class
|
|
@@ -395,14 +687,14 @@ module ActiveRecord
|
|
|
395
687
|
# that are "stuck" there are helpless. They have no way of creating
|
|
396
688
|
# new connections and are completely reliant on us feeding available
|
|
397
689
|
# connections into the Queue.
|
|
398
|
-
needs_new_connection = @available.
|
|
690
|
+
needs_new_connection = @available.num_waiting > @maintaining
|
|
399
691
|
end
|
|
400
692
|
|
|
401
693
|
# This is intentionally done outside of the synchronized section as we
|
|
402
694
|
# would like not to hold the main mutex while checking out new connections.
|
|
403
695
|
# Thus there is some chance that needs_new_connection information is now
|
|
404
696
|
# stale, we can live with that (bulk_make_new_connections will make
|
|
405
|
-
# sure not to exceed the pool's @
|
|
697
|
+
# sure not to exceed the pool's @max_connections limit).
|
|
406
698
|
bulk_make_new_connections(1) if needs_new_connection
|
|
407
699
|
end
|
|
408
700
|
|
|
@@ -435,11 +727,27 @@ module ActiveRecord
|
|
|
435
727
|
def flush(minimum_idle = @idle_timeout)
|
|
436
728
|
return if minimum_idle.nil?
|
|
437
729
|
|
|
438
|
-
|
|
730
|
+
removed_connections = synchronize do
|
|
439
731
|
return if self.discarded?
|
|
440
|
-
|
|
732
|
+
|
|
733
|
+
idle_connections = @connections.select do |conn|
|
|
441
734
|
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
|
442
|
-
end.
|
|
735
|
+
end.sort_by { |conn| -conn.seconds_idle } # sort longest idle first
|
|
736
|
+
|
|
737
|
+
# Don't go below our configured pool minimum unless we're flushing
|
|
738
|
+
# everything
|
|
739
|
+
idles_to_retain =
|
|
740
|
+
if minimum_idle > 0
|
|
741
|
+
@min_connections - (@connections.size - idle_connections.size)
|
|
742
|
+
else
|
|
743
|
+
0
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
if idles_to_retain > 0
|
|
747
|
+
idle_connections.pop idles_to_retain
|
|
748
|
+
end
|
|
749
|
+
|
|
750
|
+
idle_connections.each do |conn|
|
|
443
751
|
conn.lease
|
|
444
752
|
|
|
445
753
|
@available.delete conn
|
|
@@ -447,22 +755,109 @@ module ActiveRecord
|
|
|
447
755
|
end
|
|
448
756
|
end
|
|
449
757
|
|
|
450
|
-
|
|
758
|
+
removed_connections.each do |conn|
|
|
451
759
|
conn.disconnect!
|
|
452
760
|
end
|
|
453
761
|
end
|
|
454
762
|
|
|
455
763
|
# Disconnect all currently idle connections. Connections currently checked
|
|
456
|
-
# out are unaffected.
|
|
764
|
+
# out are unaffected. The pool will stop maintaining its minimum size until
|
|
765
|
+
# it is reactivated (such as by a subsequent checkout).
|
|
457
766
|
def flush!
|
|
458
767
|
reap
|
|
459
768
|
flush(-1)
|
|
769
|
+
|
|
770
|
+
# Stop maintaining the minimum size until reactivated
|
|
771
|
+
@activated = false
|
|
772
|
+
end
|
|
773
|
+
|
|
774
|
+
# Ensure that the pool contains at least the configured minimum number of
|
|
775
|
+
# connections.
|
|
776
|
+
def prepopulate
|
|
777
|
+
need_new_connections = nil
|
|
778
|
+
|
|
779
|
+
synchronize do
|
|
780
|
+
return if self.discarded?
|
|
781
|
+
|
|
782
|
+
# We don't want to start prepopulating until we know the pool is wanted,
|
|
783
|
+
# so we can avoid maintaining full pools in one-off scripts etc.
|
|
784
|
+
return unless @activated
|
|
785
|
+
|
|
786
|
+
need_new_connections = @connections.size < @min_connections
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
if need_new_connections
|
|
790
|
+
while new_conn = try_to_checkout_new_connection { @connections.size < @min_connections }
|
|
791
|
+
new_conn.allow_preconnect = true
|
|
792
|
+
checkin(new_conn)
|
|
793
|
+
end
|
|
794
|
+
end
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
def retire_old_connections(max_age = @max_age)
|
|
798
|
+
max_age ||= Float::INFINITY
|
|
799
|
+
|
|
800
|
+
sequential_maintenance -> c { c.connection_age&.>= c.pool_jitter(max_age) } do |conn|
|
|
801
|
+
# Disconnect, then return the adapter to the pool. Preconnect will
|
|
802
|
+
# handle the rest.
|
|
803
|
+
conn.disconnect!
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
# Preconnect all connections in the pool. This saves pool users from
|
|
808
|
+
# having to wait for a connection to be established when first using it
|
|
809
|
+
# after checkout.
|
|
810
|
+
def preconnect
|
|
811
|
+
sequential_maintenance -> c { (!c.connected? || !c.verified?) && c.allow_preconnect } do |conn|
|
|
812
|
+
conn.connect!
|
|
813
|
+
rescue
|
|
814
|
+
# Wholesale rescue: there's nothing we can do but move on. The
|
|
815
|
+
# connection will go back to the pool, and the next consumer will
|
|
816
|
+
# presumably try to connect again -- which will either work, or
|
|
817
|
+
# fail and they'll be able to report the exception.
|
|
818
|
+
end
|
|
819
|
+
end
|
|
820
|
+
|
|
821
|
+
# Prod any connections that have been idle for longer than the configured
|
|
822
|
+
# keepalive time. This will incidentally verify the connection is still
|
|
823
|
+
# alive, but the main purpose is to show the server (and any intermediate
|
|
824
|
+
# network hops) that we're still here and using the connection.
|
|
825
|
+
def keep_alive(threshold = @keepalive)
|
|
826
|
+
return if threshold.nil?
|
|
827
|
+
|
|
828
|
+
sequential_maintenance -> c { (c.seconds_since_last_activity || 0) > c.pool_jitter(threshold) } do |conn|
|
|
829
|
+
# conn.active? will cause some amount of network activity, which is all
|
|
830
|
+
# we need to provide a keepalive signal.
|
|
831
|
+
#
|
|
832
|
+
# If it returns false, the connection is already broken; disconnect,
|
|
833
|
+
# so it can be found and repaired.
|
|
834
|
+
conn.disconnect! unless conn.active?
|
|
835
|
+
end
|
|
836
|
+
end
|
|
837
|
+
|
|
838
|
+
# Immediately mark all current connections as due for replacement,
|
|
839
|
+
# equivalent to them having reached +max_age+ -- even if there is
|
|
840
|
+
# no +max_age+ configured.
|
|
841
|
+
def recycle!
|
|
842
|
+
synchronize do
|
|
843
|
+
return if self.discarded?
|
|
844
|
+
|
|
845
|
+
@connections.each do |conn|
|
|
846
|
+
conn.force_retirement
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
retire_old_connections
|
|
460
851
|
end
|
|
461
852
|
|
|
462
853
|
def num_waiting_in_queue # :nodoc:
|
|
463
854
|
@available.num_waiting
|
|
464
855
|
end
|
|
465
856
|
|
|
857
|
+
def num_available_in_queue # :nodoc:
|
|
858
|
+
@available.size
|
|
859
|
+
end
|
|
860
|
+
|
|
466
861
|
# Returns the connection pool's usage statistic.
|
|
467
862
|
#
|
|
468
863
|
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
|
|
@@ -485,12 +880,36 @@ module ActiveRecord
|
|
|
485
880
|
Thread.pass
|
|
486
881
|
end
|
|
487
882
|
|
|
883
|
+
def new_connection # :nodoc:
|
|
884
|
+
connection = db_config.new_connection
|
|
885
|
+
connection.pool = self
|
|
886
|
+
connection
|
|
887
|
+
rescue ConnectionNotEstablished => ex
|
|
888
|
+
raise ex.set_pool(self)
|
|
889
|
+
end
|
|
890
|
+
|
|
891
|
+
def pool_transaction_isolation_level
|
|
892
|
+
isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
|
|
893
|
+
ActiveSupport::IsolatedExecutionState[isolation_level_key]
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
def pool_transaction_isolation_level=(isolation_level)
|
|
897
|
+
isolation_level_key = "activerecord_pool_transaction_isolation_level_#{db_config.name}"
|
|
898
|
+
ActiveSupport::IsolatedExecutionState[isolation_level_key] = isolation_level
|
|
899
|
+
end
|
|
900
|
+
|
|
488
901
|
private
|
|
902
|
+
def connection_lease
|
|
903
|
+
@leases[ActiveSupport::IsolatedExecutionState.context]
|
|
904
|
+
end
|
|
905
|
+
|
|
489
906
|
def build_async_executor
|
|
490
907
|
case ActiveRecord.async_query_executor
|
|
491
908
|
when :multi_thread_pool
|
|
492
909
|
if @db_config.max_threads > 0
|
|
910
|
+
name_with_shard = [name_inspect, shard_inspect].join("-").tr("_", "-")
|
|
493
911
|
Concurrent::ThreadPoolExecutor.new(
|
|
912
|
+
name: "ActiveRecord-#{name_with_shard}-async-query-executor",
|
|
494
913
|
min_threads: @db_config.min_threads,
|
|
495
914
|
max_threads: @db_config.max_threads,
|
|
496
915
|
max_queue: @db_config.max_queue,
|
|
@@ -502,11 +921,109 @@ module ActiveRecord
|
|
|
502
921
|
end
|
|
503
922
|
end
|
|
504
923
|
|
|
924
|
+
# Perform maintenance work on pool connections. This method will
|
|
925
|
+
# select a connection to work on by calling the +candidate_selector+
|
|
926
|
+
# proc while holding the pool lock. If a connection is selected, it
|
|
927
|
+
# will be checked out for maintenance and passed to the
|
|
928
|
+
# +maintenance_work+ proc. The connection will always be returned to
|
|
929
|
+
# the pool after the proc completes.
|
|
930
|
+
#
|
|
931
|
+
# If the pool has async threads, all work will be scheduled there.
|
|
932
|
+
# Otherwise, this method will block until all work is complete.
|
|
933
|
+
#
|
|
934
|
+
# Each connection will only be processed once per call to this method,
|
|
935
|
+
# but (particularly in the async case) there is no protection against
|
|
936
|
+
# a second call to this method starting to work through the list
|
|
937
|
+
# before the first call has completed. (Though regular pool behavior
|
|
938
|
+
# will prevent two instances from working on the same specific
|
|
939
|
+
# connection at the same time.)
|
|
940
|
+
def sequential_maintenance(candidate_selector, &maintenance_work)
|
|
941
|
+
# This hash doesn't need to be synchronized, because it's only
|
|
942
|
+
# used by one thread at a time: the +perform_work+ block gives
|
|
943
|
+
# up its right to +connections_visited+ when it schedules the
|
|
944
|
+
# next iteration.
|
|
945
|
+
connections_visited = Hash.new(false)
|
|
946
|
+
connections_visited.compare_by_identity
|
|
947
|
+
|
|
948
|
+
perform_work = lambda do
|
|
949
|
+
connection_to_maintain = nil
|
|
950
|
+
|
|
951
|
+
synchronize do
|
|
952
|
+
unless self.discarded?
|
|
953
|
+
if connection_to_maintain = @connections.select { |conn| !conn.in_use? }.select(&candidate_selector).sort_by(&:seconds_idle).find { |conn| !connections_visited[conn] }
|
|
954
|
+
checkout_for_maintenance connection_to_maintain
|
|
955
|
+
end
|
|
956
|
+
end
|
|
957
|
+
end
|
|
958
|
+
|
|
959
|
+
if connection_to_maintain
|
|
960
|
+
connections_visited[connection_to_maintain] = true
|
|
961
|
+
|
|
962
|
+
# If we're running async, we can schedule the next round of work
|
|
963
|
+
# as soon as we've grabbed a connection to work on.
|
|
964
|
+
@async_executor&.post(&perform_work)
|
|
965
|
+
|
|
966
|
+
begin
|
|
967
|
+
maintenance_work.call connection_to_maintain
|
|
968
|
+
ensure
|
|
969
|
+
return_from_maintenance connection_to_maintain
|
|
970
|
+
end
|
|
971
|
+
|
|
972
|
+
true
|
|
973
|
+
end
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
if @async_executor
|
|
977
|
+
@async_executor.post(&perform_work)
|
|
978
|
+
else
|
|
979
|
+
nil while perform_work.call
|
|
980
|
+
end
|
|
981
|
+
end
|
|
982
|
+
|
|
983
|
+
# Directly check a specific connection out of the pool. Skips callbacks.
|
|
984
|
+
#
|
|
985
|
+
# The connection must later either #return_from_maintenance or
|
|
986
|
+
# #remove_from_maintenance, or the pool will hang.
|
|
987
|
+
def checkout_for_maintenance(conn)
|
|
988
|
+
synchronize do
|
|
989
|
+
@maintaining += 1
|
|
990
|
+
@available.delete(conn)
|
|
991
|
+
conn.lease
|
|
992
|
+
conn
|
|
993
|
+
end
|
|
994
|
+
end
|
|
995
|
+
|
|
996
|
+
# Return a connection to the pool after it has been checked out for
|
|
997
|
+
# maintenance. Does not update the connection's idle time, and skips
|
|
998
|
+
# callbacks.
|
|
999
|
+
#--
|
|
1000
|
+
# We assume that a connection that has required maintenance is less
|
|
1001
|
+
# desirable (either it's been idle for a long time, or it was just
|
|
1002
|
+
# created and hasn't been used yet). We'll put it at the back of the
|
|
1003
|
+
# queue.
|
|
1004
|
+
def return_from_maintenance(conn)
|
|
1005
|
+
synchronize do
|
|
1006
|
+
conn.expire(false)
|
|
1007
|
+
@available.add_back(conn)
|
|
1008
|
+
@maintaining -= 1
|
|
1009
|
+
end
|
|
1010
|
+
end
|
|
1011
|
+
|
|
1012
|
+
# Remove a connection from the pool after it has been checked out for
|
|
1013
|
+
# maintenance. It will be automatically replaced with a new connection if
|
|
1014
|
+
# necessary.
|
|
1015
|
+
def remove_from_maintenance(conn)
|
|
1016
|
+
synchronize do
|
|
1017
|
+
@maintaining -= 1
|
|
1018
|
+
remove conn
|
|
1019
|
+
end
|
|
1020
|
+
end
|
|
1021
|
+
|
|
505
1022
|
#--
|
|
506
1023
|
# this is unfortunately not concurrent
|
|
507
1024
|
def bulk_make_new_connections(num_new_conns_needed)
|
|
508
1025
|
num_new_conns_needed.times do
|
|
509
|
-
# try_to_checkout_new_connection will not exceed pool's @
|
|
1026
|
+
# try_to_checkout_new_connection will not exceed pool's @max_connections limit
|
|
510
1027
|
if new_conn = try_to_checkout_new_connection
|
|
511
1028
|
# make the new_conn available to the starving threads stuck @available Queue
|
|
512
1029
|
checkin(new_conn)
|
|
@@ -514,32 +1031,23 @@ module ActiveRecord
|
|
|
514
1031
|
end
|
|
515
1032
|
end
|
|
516
1033
|
|
|
517
|
-
#--
|
|
518
|
-
# From the discussion on GitHub:
|
|
519
|
-
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
|
520
|
-
# This hook-in method allows for easier monkey-patching fixes needed by
|
|
521
|
-
# JRuby users that use Fibers.
|
|
522
|
-
def connection_cache_key(thread)
|
|
523
|
-
thread
|
|
524
|
-
end
|
|
525
|
-
|
|
526
|
-
def current_thread
|
|
527
|
-
@lock_thread || ActiveSupport::IsolatedExecutionState.context
|
|
528
|
-
end
|
|
529
|
-
|
|
530
1034
|
# Take control of all existing connections so a "group" action such as
|
|
531
1035
|
# reload/disconnect can be performed safely. It is no longer enough to
|
|
532
1036
|
# wrap it in +synchronize+ because some pool's actions are allowed
|
|
533
1037
|
# to be performed outside of the main +synchronize+ block.
|
|
534
1038
|
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1039
|
+
@reaper_lock.synchronize do
|
|
1040
|
+
with_new_connections_blocked do
|
|
1041
|
+
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
|
1042
|
+
yield
|
|
1043
|
+
end
|
|
538
1044
|
end
|
|
539
1045
|
end
|
|
540
1046
|
|
|
541
1047
|
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
|
542
1048
|
collected_conns = synchronize do
|
|
1049
|
+
reap # No need to wait for dead owners
|
|
1050
|
+
|
|
543
1051
|
# account for our own connections
|
|
544
1052
|
@connections.select { |conn| conn.owner == ActiveSupport::IsolatedExecutionState.context }
|
|
545
1053
|
end
|
|
@@ -551,6 +1059,7 @@ module ActiveRecord
|
|
|
551
1059
|
loop do
|
|
552
1060
|
synchronize do
|
|
553
1061
|
return if collected_conns.size == @connections.size && @now_connecting == 0
|
|
1062
|
+
|
|
554
1063
|
remaining_timeout = timeout_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
555
1064
|
remaining_timeout = 0 if remaining_timeout < 0
|
|
556
1065
|
conn = checkout_for_exclusive_access(remaining_timeout)
|
|
@@ -601,7 +1110,7 @@ module ActiveRecord
|
|
|
601
1110
|
|
|
602
1111
|
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
|
603
1112
|
|
|
604
|
-
raise ExclusiveConnectionTimeoutError,
|
|
1113
|
+
raise ExclusiveConnectionTimeoutError.new(msg, connection_pool: self)
|
|
605
1114
|
end
|
|
606
1115
|
|
|
607
1116
|
def with_new_connections_blocked
|
|
@@ -651,54 +1160,76 @@ module ActiveRecord
|
|
|
651
1160
|
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to <tt>@available.poll</tt>
|
|
652
1161
|
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
|
653
1162
|
# of the said methods and avoid an additional +synchronize+ overhead.
|
|
654
|
-
if conn = @available.poll || try_to_checkout_new_connection
|
|
1163
|
+
if conn = @available.poll || try_to_queue_for_background_connection(checkout_timeout) || try_to_checkout_new_connection
|
|
655
1164
|
conn
|
|
656
1165
|
else
|
|
657
1166
|
reap
|
|
658
1167
|
# Retry after reaping, which may return an available connection,
|
|
659
1168
|
# remove an inactive connection, or both
|
|
660
|
-
if conn = @available.poll || try_to_checkout_new_connection
|
|
1169
|
+
if conn = @available.poll || try_to_queue_for_background_connection(checkout_timeout) || try_to_checkout_new_connection
|
|
661
1170
|
conn
|
|
662
1171
|
else
|
|
663
1172
|
@available.poll(checkout_timeout)
|
|
664
1173
|
end
|
|
665
1174
|
end
|
|
1175
|
+
rescue ConnectionTimeoutError => ex
|
|
1176
|
+
raise ex.set_pool(self)
|
|
666
1177
|
end
|
|
667
1178
|
|
|
668
1179
|
#--
|
|
669
|
-
#
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1180
|
+
# If new connections are already being established in the background,
|
|
1181
|
+
# and there are fewer threads already waiting than the number of
|
|
1182
|
+
# upcoming connections, we can just get in queue and wait to be handed a
|
|
1183
|
+
# connection. This avoids us overshooting the required connection count
|
|
1184
|
+
# by starting a new connection ourselves, and is likely to be faster
|
|
1185
|
+
# too (because at least some of the time it takes to establish a new
|
|
1186
|
+
# connection must have already passed).
|
|
1187
|
+
#
|
|
1188
|
+
# If background connections are available, this method will block and
|
|
1189
|
+
# return a connection. If no background connections are available, it
|
|
1190
|
+
# will immediately return +nil+.
|
|
1191
|
+
def try_to_queue_for_background_connection(checkout_timeout)
|
|
1192
|
+
return unless @maintaining > 0
|
|
674
1193
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
connection.pool = self
|
|
1194
|
+
synchronize do
|
|
1195
|
+
return unless @maintaining > @available.num_waiting
|
|
678
1196
|
|
|
679
|
-
|
|
680
|
-
connection.
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
raise
|
|
1197
|
+
# We are guaranteed the "maintaining" thread will return its promised
|
|
1198
|
+
# connection within one maintenance-unit of time. Thus we can safely
|
|
1199
|
+
# do a blocking wait with (functionally) no timeout.
|
|
1200
|
+
@available.poll(100)
|
|
684
1201
|
end
|
|
1202
|
+
end
|
|
685
1203
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
1204
|
+
#--
|
|
1205
|
+
# if owner_thread param is omitted, this must be called in synchronize block
|
|
1206
|
+
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
|
1207
|
+
if owner_thread
|
|
1208
|
+
@leases[owner_thread].clear(conn)
|
|
1209
|
+
end
|
|
689
1210
|
end
|
|
1211
|
+
alias_method :release, :remove_connection_from_thread_cache
|
|
690
1212
|
|
|
691
|
-
# If the pool is not at a <tt>@
|
|
1213
|
+
# If the pool is not at a <tt>@max_connections</tt> limit, establish new connection. Connecting
|
|
692
1214
|
# to the DB is done outside main synchronized section.
|
|
1215
|
+
#
|
|
1216
|
+
# If a block is supplied, it is an additional constraint (checked while holding the
|
|
1217
|
+
# pool lock) on whether a new connection should be established.
|
|
693
1218
|
#--
|
|
694
1219
|
# Implementation constraint: a newly established connection returned by this
|
|
695
1220
|
# method must be in the +.leased+ state.
|
|
696
1221
|
def try_to_checkout_new_connection
|
|
697
1222
|
# first in synchronized section check if establishing new conns is allowed
|
|
698
|
-
# and increment @now_connecting, to prevent overstepping this pool's @
|
|
1223
|
+
# and increment @now_connecting, to prevent overstepping this pool's @max_connections
|
|
699
1224
|
# constraint
|
|
700
1225
|
do_checkout = synchronize do
|
|
701
|
-
if
|
|
1226
|
+
return if self.discarded?
|
|
1227
|
+
|
|
1228
|
+
if @threads_blocking_new_connections.zero? && (@max_connections.nil? || (@connections.size + @now_connecting) < @max_connections) && (!block_given? || yield)
|
|
1229
|
+
if @connections.size > 0 || @original_context != ActiveSupport::IsolatedExecutionState.context
|
|
1230
|
+
@activated = true
|
|
1231
|
+
end
|
|
1232
|
+
|
|
702
1233
|
@now_connecting += 1
|
|
703
1234
|
end
|
|
704
1235
|
end
|
|
@@ -709,12 +1240,16 @@ module ActiveRecord
|
|
|
709
1240
|
conn = checkout_new_connection
|
|
710
1241
|
ensure
|
|
711
1242
|
synchronize do
|
|
1243
|
+
@now_connecting -= 1
|
|
712
1244
|
if conn
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1245
|
+
if self.discarded?
|
|
1246
|
+
conn.discard!
|
|
1247
|
+
else
|
|
1248
|
+
adopt_connection(conn)
|
|
1249
|
+
# returned conn needs to be already leased
|
|
1250
|
+
conn.lease
|
|
1251
|
+
end
|
|
716
1252
|
end
|
|
717
|
-
@now_connecting -= 1
|
|
718
1253
|
end
|
|
719
1254
|
end
|
|
720
1255
|
end
|
|
@@ -723,6 +1258,12 @@ module ActiveRecord
|
|
|
723
1258
|
def adopt_connection(conn)
|
|
724
1259
|
conn.pool = self
|
|
725
1260
|
@connections << conn
|
|
1261
|
+
|
|
1262
|
+
# We just created the first connection, it's time to load the schema
|
|
1263
|
+
# cache if that wasn't eagerly done before
|
|
1264
|
+
if @schema_cache.nil? && ActiveRecord.lazily_load_schema_cache
|
|
1265
|
+
schema_cache.load!
|
|
1266
|
+
end
|
|
726
1267
|
end
|
|
727
1268
|
|
|
728
1269
|
def checkout_new_connection
|
|
@@ -731,15 +1272,20 @@ module ActiveRecord
|
|
|
731
1272
|
end
|
|
732
1273
|
|
|
733
1274
|
def checkout_and_verify(c)
|
|
734
|
-
c.
|
|
735
|
-
c.clean!
|
|
736
|
-
end
|
|
737
|
-
c
|
|
1275
|
+
c.clean!
|
|
738
1276
|
rescue Exception
|
|
739
1277
|
remove c
|
|
740
1278
|
c.disconnect!
|
|
741
1279
|
raise
|
|
742
1280
|
end
|
|
1281
|
+
|
|
1282
|
+
def name_inspect
|
|
1283
|
+
db_config.name.inspect unless db_config.name == "primary"
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
def shard_inspect
|
|
1287
|
+
shard.inspect unless shard == :default
|
|
1288
|
+
end
|
|
743
1289
|
end
|
|
744
1290
|
end
|
|
745
1291
|
end
|