activerecord 4.2.0 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1537 -789
- data/MIT-LICENSE +2 -2
- data/README.rdoc +7 -8
- data/examples/performance.rb +2 -3
- data/examples/simple.rb +0 -1
- data/lib/active_record/aggregations.rb +37 -23
- data/lib/active_record/association_relation.rb +16 -3
- data/lib/active_record/associations/alias_tracker.rb +19 -16
- data/lib/active_record/associations/association.rb +23 -9
- data/lib/active_record/associations/association_scope.rb +74 -102
- data/lib/active_record/associations/belongs_to_association.rb +26 -29
- data/lib/active_record/associations/builder/association.rb +28 -34
- data/lib/active_record/associations/builder/belongs_to.rb +43 -18
- data/lib/active_record/associations/builder/collection_association.rb +12 -20
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +11 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -10
- data/lib/active_record/associations/collection_association.rb +61 -33
- data/lib/active_record/associations/collection_proxy.rb +81 -35
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +21 -57
- data/lib/active_record/associations/has_many_through_association.rb +15 -45
- data/lib/active_record/associations/has_one_association.rb +13 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
- data/lib/active_record/associations/join_dependency.rb +37 -21
- data/lib/active_record/associations/preloader/association.rb +51 -53
- data/lib/active_record/associations/preloader/collection_association.rb +0 -6
- data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/through_association.rb +27 -14
- data/lib/active_record/associations/preloader.rb +18 -8
- data/lib/active_record/associations/singular_association.rb +8 -8
- data/lib/active_record/associations/through_association.rb +22 -9
- data/lib/active_record/associations.rb +321 -212
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +79 -15
- data/lib/active_record/attribute_assignment.rb +20 -141
- data/lib/active_record/attribute_decorators.rb +6 -5
- data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
- data/lib/active_record/attribute_methods/dirty.rb +51 -81
- data/lib/active_record/attribute_methods/primary_key.rb +2 -2
- data/lib/active_record/attribute_methods/query.rb +2 -2
- data/lib/active_record/attribute_methods/read.rb +31 -59
- data/lib/active_record/attribute_methods/serialization.rb +13 -16
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -14
- data/lib/active_record/attribute_methods/write.rb +14 -38
- data/lib/active_record/attribute_methods.rb +70 -45
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +37 -15
- data/lib/active_record/attribute_set.rb +34 -3
- data/lib/active_record/attributes.rb +199 -73
- data/lib/active_record/autosave_association.rb +73 -25
- data/lib/active_record/base.rb +35 -27
- data/lib/active_record/callbacks.rb +39 -43
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +20 -8
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +457 -181
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
- data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
- data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
- data/lib/active_record/connection_adapters/column.rb +28 -43
- data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
- data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
- data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -177
- data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -13
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -154
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
- data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +38 -15
- data/lib/active_record/core.rb +109 -114
- data/lib/active_record/counter_cache.rb +14 -25
- data/lib/active_record/dynamic_matchers.rb +1 -20
- data/lib/active_record/enum.rb +115 -79
- data/lib/active_record/errors.rb +88 -48
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +2 -2
- data/lib/active_record/fixture_set/file.rb +26 -5
- data/lib/active_record/fixtures.rb +84 -46
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +32 -40
- data/lib/active_record/integration.rb +4 -4
- data/lib/active_record/internal_metadata.rb +56 -0
- data/lib/active_record/legacy_yaml_adapter.rb +46 -0
- data/lib/active_record/locale/en.yml +3 -2
- data/lib/active_record/locking/optimistic.rb +27 -25
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +43 -21
- data/lib/active_record/migration/command_recorder.rb +59 -18
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration.rb +372 -114
- data/lib/active_record/model_schema.rb +128 -38
- data/lib/active_record/nested_attributes.rb +71 -32
- data/lib/active_record/no_touching.rb +1 -1
- data/lib/active_record/null_relation.rb +16 -8
- data/lib/active_record/persistence.rb +124 -80
- data/lib/active_record/query_cache.rb +15 -18
- data/lib/active_record/querying.rb +10 -9
- data/lib/active_record/railtie.rb +28 -19
- data/lib/active_record/railties/controller_runtime.rb +1 -1
- data/lib/active_record/railties/databases.rake +67 -51
- data/lib/active_record/readonly_attributes.rb +1 -1
- data/lib/active_record/reflection.rb +318 -139
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +139 -34
- data/lib/active_record/relation/calculations.rb +80 -102
- data/lib/active_record/relation/delegation.rb +7 -20
- data/lib/active_record/relation/finder_methods.rb +167 -97
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +38 -41
- data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
- data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
- data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
- data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
- data/lib/active_record/relation/predicate_builder.rb +124 -82
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +323 -257
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +11 -10
- data/lib/active_record/relation/where_clause.rb +174 -0
- data/lib/active_record/relation/where_clause_factory.rb +38 -0
- data/lib/active_record/relation.rb +176 -115
- data/lib/active_record/result.rb +4 -3
- data/lib/active_record/runtime_registry.rb +1 -1
- data/lib/active_record/sanitization.rb +95 -66
- data/lib/active_record/schema.rb +26 -22
- data/lib/active_record/schema_dumper.rb +62 -38
- data/lib/active_record/schema_migration.rb +11 -17
- data/lib/active_record/scoping/default.rb +24 -9
- data/lib/active_record/scoping/named.rb +49 -28
- data/lib/active_record/scoping.rb +32 -15
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +2 -4
- data/lib/active_record/statement_cache.rb +16 -14
- data/lib/active_record/store.rb +8 -3
- data/lib/active_record/suppressor.rb +58 -0
- data/lib/active_record/table_metadata.rb +68 -0
- data/lib/active_record/tasks/database_tasks.rb +59 -42
- data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
- data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
- data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
- data/lib/active_record/timestamp.rb +20 -9
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +159 -67
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +2 -41
- data/lib/active_record/type/date_time.rb +2 -38
- data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
- data/lib/active_record/type/internal/abstract_json.rb +29 -0
- data/lib/active_record/type/internal/timezone.rb +15 -0
- data/lib/active_record/type/serialized.rb +21 -14
- data/lib/active_record/type/time.rb +10 -16
- data/lib/active_record/type/type_map.rb +4 -4
- data/lib/active_record/type.rb +66 -17
- data/lib/active_record/type_caster/connection.rb +29 -0
- data/lib/active_record/type_caster/map.rb +19 -0
- data/lib/active_record/type_caster.rb +7 -0
- data/lib/active_record/validations/absence.rb +23 -0
- data/lib/active_record/validations/associated.rb +10 -3
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +11 -12
- data/lib/active_record/validations/uniqueness.rb +29 -18
- data/lib/active_record/validations.rb +33 -32
- data/lib/active_record.rb +9 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
- data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
- data/lib/rails/generators/active_record/migration.rb +7 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
- metadata +60 -34
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
- data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
- data/lib/active_record/serializers/xml_serializer.rb +0 -193
- data/lib/active_record/type/big_integer.rb +0 -13
- data/lib/active_record/type/binary.rb +0 -50
- data/lib/active_record/type/boolean.rb +0 -30
- data/lib/active_record/type/decimal.rb +0 -40
- data/lib/active_record/type/decimal_without_scale.rb +0 -11
- data/lib/active_record/type/decorator.rb +0 -14
- data/lib/active_record/type/float.rb +0 -19
- data/lib/active_record/type/integer.rb +0 -55
- data/lib/active_record/type/mutable.rb +0 -16
- data/lib/active_record/type/numeric.rb +0 -36
- data/lib/active_record/type/string.rb +0 -36
- data/lib/active_record/type/text.rb +0 -11
- data/lib/active_record/type/time_value.rb +0 -38
- data/lib/active_record/type/unsigned_integer.rb +0 -15
- data/lib/active_record/type/value.rb +0 -101
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'thread'
|
2
|
-
require '
|
2
|
+
require 'concurrent/map'
|
3
3
|
require 'monitor'
|
4
|
-
require 'set'
|
5
|
-
require 'active_support/core_ext/string/filters'
|
6
4
|
|
7
5
|
module ActiveRecord
|
8
6
|
# Raised when a connection could not be obtained within the connection
|
@@ -11,6 +9,13 @@ module ActiveRecord
|
|
11
9
|
class ConnectionTimeoutError < ConnectionNotEstablished
|
12
10
|
end
|
13
11
|
|
12
|
+
# Raised when a pool was unable to get ahold of all its connections
|
13
|
+
# to perform a "group" action such as
|
14
|
+
# {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
|
15
|
+
# or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
|
16
|
+
class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
|
17
|
+
end
|
18
|
+
|
14
19
|
module ConnectionAdapters
|
15
20
|
# Connection pool base class for managing Active Record database
|
16
21
|
# connections.
|
@@ -33,17 +38,18 @@ module ActiveRecord
|
|
33
38
|
# Connections can be obtained and used from a connection pool in several
|
34
39
|
# ways:
|
35
40
|
#
|
36
|
-
# 1. Simply use ActiveRecord::Base.connection
|
41
|
+
# 1. Simply use {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling.connection]
|
42
|
+
# as with Active Record 2.1 and
|
37
43
|
# earlier (pre-connection-pooling). Eventually, when you're done with
|
38
44
|
# the connection(s) and wish it to be returned to the pool, you call
|
39
|
-
# ActiveRecord::Base.clear_active_connections
|
40
|
-
# default behavior for Active Record when used in conjunction with
|
45
|
+
# {ActiveRecord::Base.clear_active_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_active_connections!].
|
46
|
+
# This will be the default behavior for Active Record when used in conjunction with
|
41
47
|
# Action Pack's request handling cycle.
|
42
48
|
# 2. Manually check out a connection from the pool with
|
43
|
-
# ActiveRecord::Base.connection_pool.checkout. You are responsible for
|
49
|
+
# {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
|
44
50
|
# returning this connection to the pool when finished by calling
|
45
|
-
# ActiveRecord::Base.connection_pool.checkin(connection).
|
46
|
-
# 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
|
51
|
+
# {ActiveRecord::Base.connection_pool.checkin(connection)}[rdoc-ref:#checkin].
|
52
|
+
# 3. Use {ActiveRecord::Base.connection_pool.with_connection(&block)}[rdoc-ref:#with_connection], which
|
47
53
|
# obtains a connection, yields it as the sole argument to the block,
|
48
54
|
# and returns it to the pool after the block completes.
|
49
55
|
#
|
@@ -64,6 +70,15 @@ module ActiveRecord
|
|
64
70
|
# connection at the end of a thread or a thread dies unexpectedly.
|
65
71
|
# Regardless of this setting, the Reaper will be invoked before every
|
66
72
|
# blocking wait. (Default nil, which means don't schedule the Reaper).
|
73
|
+
#
|
74
|
+
#--
|
75
|
+
# Synchronization policy:
|
76
|
+
# * all public methods can be called outside +synchronize+
|
77
|
+
# * access to these i-vars needs to be in +synchronize+:
|
78
|
+
# * @connections
|
79
|
+
# * @now_connecting
|
80
|
+
# * private methods that require being called in a +synchronize+ blocks
|
81
|
+
# are now explicitly documented
|
67
82
|
class ConnectionPool
|
68
83
|
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
|
69
84
|
# with which it shares a Monitor. But could be a generic Queue.
|
@@ -122,25 +137,23 @@ module ActiveRecord
|
|
122
137
|
# greater than the number of threads currently waiting (that
|
123
138
|
# is, don't jump ahead in line). Otherwise, return nil.
|
124
139
|
#
|
125
|
-
# If +timeout+ is given, block if
|
140
|
+
# If +timeout+ is given, block if there is no element
|
126
141
|
# available, waiting up to +timeout+ seconds for an element to
|
127
142
|
# become available.
|
128
143
|
#
|
129
144
|
# Raises:
|
130
|
-
# - ConnectionTimeoutError if +timeout+ is given and no element
|
131
|
-
# becomes available
|
145
|
+
# - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
|
146
|
+
# becomes available within +timeout+ seconds,
|
132
147
|
def poll(timeout = nil)
|
133
|
-
synchronize
|
134
|
-
if timeout
|
135
|
-
no_wait_poll || wait_poll(timeout)
|
136
|
-
else
|
137
|
-
no_wait_poll
|
138
|
-
end
|
139
|
-
end
|
148
|
+
synchronize { internal_poll(timeout) }
|
140
149
|
end
|
141
150
|
|
142
151
|
private
|
143
152
|
|
153
|
+
def internal_poll(timeout)
|
154
|
+
no_wait_poll || (timeout && wait_poll(timeout))
|
155
|
+
end
|
156
|
+
|
144
157
|
def synchronize(&block)
|
145
158
|
@lock.synchronize(&block)
|
146
159
|
end
|
@@ -151,7 +164,7 @@ module ActiveRecord
|
|
151
164
|
end
|
152
165
|
|
153
166
|
# A thread can remove an element from the queue without
|
154
|
-
# waiting if
|
167
|
+
# waiting if and only if the number of currently available
|
155
168
|
# connections is strictly greater than the number of waiting
|
156
169
|
# threads.
|
157
170
|
def can_remove_no_wait?
|
@@ -184,7 +197,7 @@ module ActiveRecord
|
|
184
197
|
|
185
198
|
elapsed = Time.now - t0
|
186
199
|
if elapsed >= timeout
|
187
|
-
msg = 'could not obtain a
|
200
|
+
msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
|
188
201
|
[timeout, elapsed]
|
189
202
|
raise ConnectionTimeoutError, msg
|
190
203
|
end
|
@@ -194,6 +207,80 @@ module ActiveRecord
|
|
194
207
|
end
|
195
208
|
end
|
196
209
|
|
210
|
+
# Adds the ability to turn a basic fair FIFO queue into one
|
211
|
+
# biased to some thread.
|
212
|
+
module BiasableQueue # :nodoc:
|
213
|
+
class BiasedConditionVariable # :nodoc:
|
214
|
+
# semantics of condition variables guarantee that +broadcast+, +broadcast_on_biased+,
|
215
|
+
# +signal+ and +wait+ methods are only called while holding a lock
|
216
|
+
def initialize(lock, other_cond, preferred_thread)
|
217
|
+
@real_cond = lock.new_cond
|
218
|
+
@other_cond = other_cond
|
219
|
+
@preferred_thread = preferred_thread
|
220
|
+
@num_waiting_on_real_cond = 0
|
221
|
+
end
|
222
|
+
|
223
|
+
def broadcast
|
224
|
+
broadcast_on_biased
|
225
|
+
@other_cond.broadcast
|
226
|
+
end
|
227
|
+
|
228
|
+
def broadcast_on_biased
|
229
|
+
@num_waiting_on_real_cond = 0
|
230
|
+
@real_cond.broadcast
|
231
|
+
end
|
232
|
+
|
233
|
+
def signal
|
234
|
+
if @num_waiting_on_real_cond > 0
|
235
|
+
@num_waiting_on_real_cond -= 1
|
236
|
+
@real_cond
|
237
|
+
else
|
238
|
+
@other_cond
|
239
|
+
end.signal
|
240
|
+
end
|
241
|
+
|
242
|
+
def wait(timeout)
|
243
|
+
if Thread.current == @preferred_thread
|
244
|
+
@num_waiting_on_real_cond += 1
|
245
|
+
@real_cond
|
246
|
+
else
|
247
|
+
@other_cond
|
248
|
+
end.wait(timeout)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def with_a_bias_for(thread)
|
253
|
+
previous_cond = nil
|
254
|
+
new_cond = nil
|
255
|
+
synchronize do
|
256
|
+
previous_cond = @cond
|
257
|
+
@cond = new_cond = BiasedConditionVariable.new(@lock, @cond, thread)
|
258
|
+
end
|
259
|
+
yield
|
260
|
+
ensure
|
261
|
+
synchronize do
|
262
|
+
@cond = previous_cond if previous_cond
|
263
|
+
new_cond.broadcast_on_biased if new_cond # wake up any remaining sleepers
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
# Connections must be leased while holding the main pool mutex. This is
|
269
|
+
# an internal subclass that also +.leases+ returned connections while
|
270
|
+
# still in queue's critical section (queue synchronizes with the same
|
271
|
+
# +@lock+ as the main pool) so that a returned connection is already
|
272
|
+
# leased and there is no need to re-enter synchronized block.
|
273
|
+
class ConnectionLeasingQueue < Queue # :nodoc:
|
274
|
+
include BiasableQueue
|
275
|
+
|
276
|
+
private
|
277
|
+
def internal_poll(timeout)
|
278
|
+
conn = super
|
279
|
+
conn.lease if conn
|
280
|
+
conn
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
197
284
|
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
|
198
285
|
# A reaper instantiated with a nil frequency will never reap the
|
199
286
|
# connection pool.
|
@@ -221,7 +308,7 @@ module ActiveRecord
|
|
221
308
|
|
222
309
|
include MonitorMixin
|
223
310
|
|
224
|
-
attr_accessor :automatic_reconnect, :checkout_timeout
|
311
|
+
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
225
312
|
attr_reader :spec, :connections, :size, :reaper
|
226
313
|
|
227
314
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
@@ -236,62 +323,81 @@ module ActiveRecord
|
|
236
323
|
@spec = spec
|
237
324
|
|
238
325
|
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
|
239
|
-
@reaper
|
326
|
+
@reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
|
240
327
|
@reaper.run
|
241
328
|
|
242
329
|
# default max pool size to 5
|
243
330
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
244
331
|
|
245
|
-
# The cache of reserved connections
|
246
|
-
|
332
|
+
# The cache of threads mapped to reserved connections, the sole purpose
|
333
|
+
# of the cache is to speed-up +connection+ method, it is not the authoritative
|
334
|
+
# registry of which thread owns which connection, that is tracked by
|
335
|
+
# +connection.owner+ attr on each +connection+ instance.
|
336
|
+
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
|
337
|
+
# then that +thread+ does indeed own that +conn+, however an absence of a such
|
338
|
+
# mapping does not mean that the +thread+ doesn't own the said connection, in
|
339
|
+
# that case +conn.owner+ attr should be consulted.
|
340
|
+
# Access and modification of +@thread_cached_conns+ does not require
|
341
|
+
# synchronization.
|
342
|
+
@thread_cached_conns = Concurrent::Map.new(:initial_capacity => @size)
|
247
343
|
|
248
344
|
@connections = []
|
249
345
|
@automatic_reconnect = true
|
250
346
|
|
251
|
-
|
347
|
+
# Connection pool allows for concurrent (outside the main +synchronize+ section)
|
348
|
+
# establishment of new connections. This variable tracks the number of threads
|
349
|
+
# currently in the process of independently establishing connections to the DB.
|
350
|
+
@now_connecting = 0
|
351
|
+
|
352
|
+
# A boolean toggle that allows/disallows new connections.
|
353
|
+
@new_cons_enabled = true
|
354
|
+
|
355
|
+
@available = ConnectionLeasingQueue.new self
|
252
356
|
end
|
253
357
|
|
254
358
|
# Retrieve the connection associated with the current thread, or call
|
255
359
|
# #checkout to obtain one if necessary.
|
256
360
|
#
|
257
361
|
# #connection can be called any number of times; the connection is
|
258
|
-
# held in a
|
362
|
+
# held in a cache keyed by a thread.
|
259
363
|
def connection
|
260
|
-
|
261
|
-
# (ThreadSafe::Cache's lookups have volatile semantics)
|
262
|
-
@reserved_connections[current_connection_id] || synchronize do
|
263
|
-
@reserved_connections[current_connection_id] ||= checkout
|
264
|
-
end
|
364
|
+
@thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
|
265
365
|
end
|
266
366
|
|
267
367
|
# Is there an open connection that is being used for the current thread?
|
368
|
+
#
|
369
|
+
# This method only works for connections that have been obtained through
|
370
|
+
# #connection or #with_connection methods, connections obtained through
|
371
|
+
# #checkout will not be detected by #active_connection?
|
268
372
|
def active_connection?
|
269
|
-
|
270
|
-
@reserved_connections.fetch(current_connection_id) {
|
271
|
-
return false
|
272
|
-
}.in_use?
|
273
|
-
end
|
373
|
+
@thread_cached_conns[connection_cache_key(Thread.current)]
|
274
374
|
end
|
275
375
|
|
276
376
|
# Signal that the thread is finished with the current connection.
|
277
377
|
# #release_connection releases the connection-thread association
|
278
378
|
# and returns the connection to the pool.
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
379
|
+
#
|
380
|
+
# This method only works for connections that have been obtained through
|
381
|
+
# #connection or #with_connection methods, connections obtained through
|
382
|
+
# #checkout will not be automatically released.
|
383
|
+
def release_connection(owner_thread = Thread.current)
|
384
|
+
if conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
|
385
|
+
checkin conn
|
283
386
|
end
|
284
387
|
end
|
285
388
|
|
286
|
-
# If a connection
|
389
|
+
# If a connection obtained through #connection or #with_connection methods
|
390
|
+
# already exists yield it to the block. If no such connection
|
287
391
|
# exists checkout a connection, yield it to the block, and checkin the
|
288
392
|
# connection when finished.
|
289
393
|
def with_connection
|
290
|
-
|
291
|
-
|
292
|
-
|
394
|
+
unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
|
395
|
+
conn = connection
|
396
|
+
fresh_connection = true
|
397
|
+
end
|
398
|
+
yield conn
|
293
399
|
ensure
|
294
|
-
release_connection
|
400
|
+
release_connection if fresh_connection
|
295
401
|
end
|
296
402
|
|
297
403
|
# Returns true if a connection has already been opened.
|
@@ -300,34 +406,81 @@ module ActiveRecord
|
|
300
406
|
end
|
301
407
|
|
302
408
|
# Disconnects all connections in the pool, and clears the pool.
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
409
|
+
#
|
410
|
+
# Raises:
|
411
|
+
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
412
|
+
# connections in the pool within a timeout interval (default duration is
|
413
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
|
414
|
+
def disconnect(raise_on_acquisition_timeout = true)
|
415
|
+
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
416
|
+
synchronize do
|
417
|
+
@connections.each do |conn|
|
418
|
+
checkin conn
|
419
|
+
conn.disconnect!
|
420
|
+
end
|
421
|
+
@connections = []
|
422
|
+
@available.clear
|
309
423
|
end
|
310
|
-
@connections = []
|
311
|
-
@available.clear
|
312
424
|
end
|
313
425
|
end
|
314
426
|
|
315
|
-
#
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
427
|
+
# Disconnects all connections in the pool, and clears the pool.
|
428
|
+
#
|
429
|
+
# The pool first tries to gain ownership of all connections, if unable to
|
430
|
+
# do so within a timeout interval (default duration is
|
431
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
|
432
|
+
# disconnected without any regard for other connection owning threads.
|
433
|
+
def disconnect!
|
434
|
+
disconnect(false)
|
435
|
+
end
|
436
|
+
|
437
|
+
# Clears the cache which maps classes and re-connects connections that
|
438
|
+
# require reloading.
|
439
|
+
#
|
440
|
+
# Raises:
|
441
|
+
# - ActiveRecord::ExclusiveConnectionTimeoutError if unable to gain ownership of all
|
442
|
+
# connections in the pool within a timeout interval (default duration is
|
443
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds).
|
444
|
+
def clear_reloadable_connections(raise_on_acquisition_timeout = true)
|
445
|
+
num_new_conns_required = 0
|
446
|
+
|
447
|
+
with_exclusively_acquired_all_connections(raise_on_acquisition_timeout) do
|
448
|
+
synchronize do
|
449
|
+
@connections.each do |conn|
|
450
|
+
checkin conn
|
451
|
+
conn.disconnect! if conn.requires_reloading?
|
452
|
+
end
|
453
|
+
@connections.delete_if(&:requires_reloading?)
|
454
|
+
|
455
|
+
@available.clear
|
456
|
+
|
457
|
+
if @connections.size < @size
|
458
|
+
# because of the pruning done by this method, we might be running
|
459
|
+
# low on connections, while threads stuck in queue are helpless
|
460
|
+
# (not being able to establish new connections for themselves),
|
461
|
+
# see also more detailed explanation in +remove+
|
462
|
+
num_new_conns_required = num_waiting_in_queue - @connections.size
|
463
|
+
end
|
464
|
+
|
465
|
+
@connections.each do |conn|
|
466
|
+
@available.add conn
|
467
|
+
end
|
329
468
|
end
|
330
469
|
end
|
470
|
+
|
471
|
+
bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
|
472
|
+
end
|
473
|
+
|
474
|
+
# Clears the cache which maps classes and re-connects connections that
|
475
|
+
# require reloading.
|
476
|
+
#
|
477
|
+
# The pool first tries to gain ownership of all connections, if unable to
|
478
|
+
# do so within a timeout interval (default duration is
|
479
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
|
480
|
+
# clears the cache and reloads connections without any regard for other
|
481
|
+
# connection owning threads.
|
482
|
+
def clear_reloadable_connections!
|
483
|
+
clear_reloadable_connections(false)
|
331
484
|
end
|
332
485
|
|
333
486
|
# Check-out a database connection from the pool, indicating that you want
|
@@ -343,48 +496,60 @@ module ActiveRecord
|
|
343
496
|
# Returns: an AbstractAdapter object.
|
344
497
|
#
|
345
498
|
# Raises:
|
346
|
-
# - ConnectionTimeoutError
|
347
|
-
def checkout
|
348
|
-
|
349
|
-
conn = acquire_connection
|
350
|
-
conn.lease
|
351
|
-
checkout_and_verify(conn)
|
352
|
-
end
|
499
|
+
# - ActiveRecord::ConnectionTimeoutError no connection can be obtained from the pool.
|
500
|
+
def checkout(checkout_timeout = @checkout_timeout)
|
501
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
353
502
|
end
|
354
503
|
|
355
504
|
# Check-in a database connection back into the pool, indicating that you
|
356
505
|
# no longer need this connection.
|
357
506
|
#
|
358
507
|
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
359
|
-
# calling
|
508
|
+
# calling #checkout on this pool.
|
360
509
|
def checkin(conn)
|
361
510
|
synchronize do
|
362
|
-
|
511
|
+
remove_connection_from_thread_cache conn
|
363
512
|
|
364
513
|
conn._run_checkin_callbacks do
|
365
514
|
conn.expire
|
366
515
|
end
|
367
516
|
|
368
|
-
release owner
|
369
|
-
|
370
517
|
@available.add conn
|
371
518
|
end
|
372
519
|
end
|
373
520
|
|
374
|
-
# Remove a connection from the connection pool.
|
521
|
+
# Remove a connection from the connection pool. The connection will
|
375
522
|
# remain open and active but will no longer be managed by this pool.
|
376
523
|
def remove(conn)
|
524
|
+
needs_new_connection = false
|
525
|
+
|
377
526
|
synchronize do
|
527
|
+
remove_connection_from_thread_cache conn
|
528
|
+
|
378
529
|
@connections.delete conn
|
379
530
|
@available.delete conn
|
380
531
|
|
381
|
-
|
382
|
-
|
383
|
-
|
532
|
+
# @available.any_waiting? => true means that prior to removing this
|
533
|
+
# conn, the pool was at its max size (@connections.size == @size)
|
534
|
+
# this would mean that any threads stuck waiting in the queue wouldn't
|
535
|
+
# know they could checkout_new_connection, so let's do it for them.
|
536
|
+
# Because condition-wait loop is encapsulated in the Queue class
|
537
|
+
# (that in turn is oblivious to ConnectionPool implementation), threads
|
538
|
+
# that are "stuck" there are helpless, they have no way of creating
|
539
|
+
# new connections and are completely reliant on us feeding available
|
540
|
+
# connections into the Queue.
|
541
|
+
needs_new_connection = @available.any_waiting?
|
384
542
|
end
|
543
|
+
|
544
|
+
# This is intentionally done outside of the synchronized section as we
|
545
|
+
# would like not to hold the main mutex while checking out new connections,
|
546
|
+
# thus there is some chance that needs_new_connection information is now
|
547
|
+
# stale, we can live with that (bulk_make_new_connections will make
|
548
|
+
# sure not to exceed the pool's @size limit).
|
549
|
+
bulk_make_new_connections(1) if needs_new_connection
|
385
550
|
end
|
386
551
|
|
387
|
-
# Recover lost connections for the pool.
|
552
|
+
# Recover lost connections for the pool. A lost connection can occur if
|
388
553
|
# a programmer forgets to checkin a connection at the end of a thread
|
389
554
|
# or a thread dies unexpectedly.
|
390
555
|
def reap
|
@@ -406,7 +571,118 @@ module ActiveRecord
|
|
406
571
|
end
|
407
572
|
end
|
408
573
|
|
574
|
+
def num_waiting_in_queue # :nodoc:
|
575
|
+
@available.num_waiting
|
576
|
+
end
|
577
|
+
|
409
578
|
private
|
579
|
+
#--
|
580
|
+
# this is unfortunately not concurrent
|
581
|
+
def bulk_make_new_connections(num_new_conns_needed)
|
582
|
+
num_new_conns_needed.times do
|
583
|
+
# try_to_checkout_new_connection will not exceed pool's @size limit
|
584
|
+
if new_conn = try_to_checkout_new_connection
|
585
|
+
# make the new_conn available to the starving threads stuck @available Queue
|
586
|
+
checkin(new_conn)
|
587
|
+
end
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
#--
|
592
|
+
# From the discussion on GitHub:
|
593
|
+
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
594
|
+
# This hook-in method allows for easier monkey-patching fixes needed by
|
595
|
+
# JRuby users that use Fibers.
|
596
|
+
def connection_cache_key(thread)
|
597
|
+
thread
|
598
|
+
end
|
599
|
+
|
600
|
+
# Take control of all existing connections so a "group" action such as
|
601
|
+
# reload/disconnect can be performed safely. It is no longer enough to
|
602
|
+
# wrap it in +synchronize+ because some pool's actions are allowed
|
603
|
+
# to be performed outside of the main +synchronize+ block.
|
604
|
+
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
605
|
+
with_new_connections_blocked do
|
606
|
+
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
607
|
+
yield
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
612
|
+
collected_conns = synchronize do
|
613
|
+
# account for our own connections
|
614
|
+
@connections.select {|conn| conn.owner == Thread.current}
|
615
|
+
end
|
616
|
+
|
617
|
+
newly_checked_out = []
|
618
|
+
timeout_time = Time.now + (@checkout_timeout * 2)
|
619
|
+
|
620
|
+
@available.with_a_bias_for(Thread.current) do
|
621
|
+
while true
|
622
|
+
synchronize do
|
623
|
+
return if collected_conns.size == @connections.size && @now_connecting == 0
|
624
|
+
remaining_timeout = timeout_time - Time.now
|
625
|
+
remaining_timeout = 0 if remaining_timeout < 0
|
626
|
+
conn = checkout_for_exclusive_access(remaining_timeout)
|
627
|
+
collected_conns << conn
|
628
|
+
newly_checked_out << conn
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
rescue ExclusiveConnectionTimeoutError
|
633
|
+
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
|
634
|
+
# timeouts and are expected to just give up: we've obtained as many connections
|
635
|
+
# as possible, note that in a case like that we don't return any of the
|
636
|
+
# +newly_checked_out+ connections.
|
637
|
+
|
638
|
+
if raise_on_acquisition_timeout
|
639
|
+
release_newly_checked_out = true
|
640
|
+
raise
|
641
|
+
end
|
642
|
+
rescue Exception # if something else went wrong
|
643
|
+
# this can't be a "naked" rescue, because we have should return conns
|
644
|
+
# even for non-StandardErrors
|
645
|
+
release_newly_checked_out = true
|
646
|
+
raise
|
647
|
+
ensure
|
648
|
+
if release_newly_checked_out && newly_checked_out
|
649
|
+
# releasing only those conns that were checked out in this method, conns
|
650
|
+
# checked outside this method (before it was called) are not for us to release
|
651
|
+
newly_checked_out.each {|conn| checkin(conn)}
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
#--
|
656
|
+
# Must be called in a synchronize block.
|
657
|
+
def checkout_for_exclusive_access(checkout_timeout)
|
658
|
+
checkout(checkout_timeout)
|
659
|
+
rescue ConnectionTimeoutError
|
660
|
+
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
661
|
+
# rescue block, because doing so would put it outside of synchronize section, without
|
662
|
+
# being in a critical section thread_report might become inaccurate
|
663
|
+
msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
664
|
+
|
665
|
+
thread_report = []
|
666
|
+
@connections.each do |conn|
|
667
|
+
unless conn.owner == Thread.current
|
668
|
+
thread_report << "#{conn} is owned by #{conn.owner}"
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
673
|
+
|
674
|
+
raise ExclusiveConnectionTimeoutError, msg
|
675
|
+
end
|
676
|
+
|
677
|
+
def with_new_connections_blocked
|
678
|
+
previous_value = nil
|
679
|
+
synchronize do
|
680
|
+
previous_value, @new_cons_enabled = @new_cons_enabled, false
|
681
|
+
end
|
682
|
+
yield
|
683
|
+
ensure
|
684
|
+
synchronize { @new_cons_enabled = previous_value }
|
685
|
+
end
|
410
686
|
|
411
687
|
# Acquire a connection by one of 1) immediately removing one
|
412
688
|
# from the queue of available connections, 2) creating a new
|
@@ -414,39 +690,79 @@ module ActiveRecord
|
|
414
690
|
# queue for a connection to become available.
|
415
691
|
#
|
416
692
|
# Raises:
|
417
|
-
# - ConnectionTimeoutError if a connection could not be acquired
|
418
|
-
|
419
|
-
|
693
|
+
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
|
694
|
+
#
|
695
|
+
#--
|
696
|
+
# Implementation detail: the connection returned by +acquire_connection+
|
697
|
+
# will already be "+connection.lease+ -ed" to the current thread.
|
698
|
+
def acquire_connection(checkout_timeout)
|
699
|
+
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
|
700
|
+
# +conn.lease+ the returned connection (and to do this in a +synchronized+
|
701
|
+
# section), this is not the cleanest implementation, as ideally we would
|
702
|
+
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
|
703
|
+
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
704
|
+
# of the said methods and avoid an additional +synchronize+ overhead.
|
705
|
+
if conn = @available.poll || try_to_checkout_new_connection
|
420
706
|
conn
|
421
|
-
elsif @connections.size < @size
|
422
|
-
checkout_new_connection
|
423
707
|
else
|
424
708
|
reap
|
425
|
-
@available.poll(
|
709
|
+
@available.poll(checkout_timeout)
|
426
710
|
end
|
427
711
|
end
|
428
712
|
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
@
|
713
|
+
#--
|
714
|
+
# if owner_thread param is omitted, this must be called in synchronize block
|
715
|
+
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
716
|
+
@thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
|
433
717
|
end
|
718
|
+
alias_method :release, :remove_connection_from_thread_cache
|
434
719
|
|
435
720
|
def new_connection
|
436
|
-
Base.send(spec.adapter_method, spec.config)
|
721
|
+
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
722
|
+
conn.schema_cache = schema_cache.dup if schema_cache
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
# If the pool is not at a +@size+ limit, establish new connection. Connecting
|
727
|
+
# to the DB is done outside main synchronized section.
|
728
|
+
#--
|
729
|
+
# Implementation constraint: a newly established connection returned by this
|
730
|
+
# method must be in the +.leased+ state.
|
731
|
+
def try_to_checkout_new_connection
|
732
|
+
# first in synchronized section check if establishing new conns is allowed
|
733
|
+
# and increment @now_connecting, to prevent overstepping this pool's @size
|
734
|
+
# constraint
|
735
|
+
do_checkout = synchronize do
|
736
|
+
if @new_cons_enabled && (@connections.size + @now_connecting) < @size
|
737
|
+
@now_connecting += 1
|
738
|
+
end
|
739
|
+
end
|
740
|
+
if do_checkout
|
741
|
+
begin
|
742
|
+
# if successfully incremented @now_connecting establish new connection
|
743
|
+
# outside of synchronized section
|
744
|
+
conn = checkout_new_connection
|
745
|
+
ensure
|
746
|
+
synchronize do
|
747
|
+
if conn
|
748
|
+
adopt_connection(conn)
|
749
|
+
# returned conn needs to be already leased
|
750
|
+
conn.lease
|
751
|
+
end
|
752
|
+
@now_connecting -= 1
|
753
|
+
end
|
754
|
+
end
|
755
|
+
end
|
437
756
|
end
|
438
757
|
|
439
|
-
def
|
440
|
-
|
758
|
+
def adopt_connection(conn)
|
759
|
+
conn.pool = self
|
760
|
+
@connections << conn
|
441
761
|
end
|
442
762
|
|
443
763
|
def checkout_new_connection
|
444
764
|
raise ConnectionNotEstablished unless @automatic_reconnect
|
445
|
-
|
446
|
-
c = new_connection
|
447
|
-
c.pool = self
|
448
|
-
@connections << c
|
449
|
-
c
|
765
|
+
new_connection
|
450
766
|
end
|
451
767
|
|
452
768
|
def checkout_and_verify(c)
|
@@ -454,12 +770,15 @@ module ActiveRecord
|
|
454
770
|
c.verify!
|
455
771
|
end
|
456
772
|
c
|
773
|
+
rescue
|
774
|
+
remove c
|
775
|
+
c.disconnect!
|
776
|
+
raise
|
457
777
|
end
|
458
778
|
end
|
459
779
|
|
460
780
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
461
|
-
# for keeping separate connection pools
|
462
|
-
# to different databases.
|
781
|
+
# for keeping separate connection pools that connect to different databases.
|
463
782
|
#
|
464
783
|
# For example, suppose that you have 5 models, with the following hierarchy:
|
465
784
|
#
|
@@ -501,36 +820,25 @@ module ActiveRecord
|
|
501
820
|
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
|
502
821
|
# All Active Record models use this handler to determine the connection pool that they
|
503
822
|
# should use.
|
823
|
+
#
|
824
|
+
# The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
|
825
|
+
# about the model. The model, needs to pass a specification name to the handler,
|
826
|
+
# in order to lookup the correct connection pool.
|
504
827
|
class ConnectionHandler
|
505
828
|
def initialize
|
506
|
-
# These caches are keyed by
|
507
|
-
|
508
|
-
|
509
|
-
@owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
|
510
|
-
h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
|
511
|
-
end
|
512
|
-
@class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
|
513
|
-
h[k] = ThreadSafe::Cache.new
|
829
|
+
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
830
|
+
@owner_to_pool = Concurrent::Map.new(:initial_capacity => 2) do |h,k|
|
831
|
+
h[k] = Concurrent::Map.new(:initial_capacity => 2)
|
514
832
|
end
|
515
833
|
end
|
516
834
|
|
517
835
|
def connection_pool_list
|
518
836
|
owner_to_pool.values.compact
|
519
837
|
end
|
838
|
+
alias :connection_pools :connection_pool_list
|
520
839
|
|
521
|
-
def
|
522
|
-
|
523
|
-
In the next release, this will return the same as `#connection_pool_list`.
|
524
|
-
(An array of pools, rather than a hash mapping specs to pools.)
|
525
|
-
MSG
|
526
|
-
|
527
|
-
Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
|
528
|
-
end
|
529
|
-
|
530
|
-
def establish_connection(owner, spec)
|
531
|
-
@class_to_pool.clear
|
532
|
-
raise RuntimeError, "Anonymous class is not allowed." unless owner.name
|
533
|
-
owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
840
|
+
def establish_connection(spec)
|
841
|
+
owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
534
842
|
end
|
535
843
|
|
536
844
|
# Returns true if there are any active connections among the connection
|
@@ -547,6 +855,8 @@ module ActiveRecord
|
|
547
855
|
end
|
548
856
|
|
549
857
|
# Clears the cache which maps classes.
|
858
|
+
#
|
859
|
+
# See ConnectionPool#clear_reloadable_connections! for details.
|
550
860
|
def clear_reloadable_connections!
|
551
861
|
connection_pool_list.each(&:clear_reloadable_connections!)
|
552
862
|
end
|
@@ -559,18 +869,18 @@ module ActiveRecord
|
|
559
869
|
# active or defined connection: if it is the latter, it will be
|
560
870
|
# opened and set as the active connection for the class it was defined
|
561
871
|
# for (not necessarily the current class).
|
562
|
-
def retrieve_connection(
|
563
|
-
pool = retrieve_connection_pool(
|
564
|
-
raise ConnectionNotEstablished, "No connection pool
|
872
|
+
def retrieve_connection(spec_name) #:nodoc:
|
873
|
+
pool = retrieve_connection_pool(spec_name)
|
874
|
+
raise ConnectionNotEstablished, "No connection pool with id #{spec_name} found." unless pool
|
565
875
|
conn = pool.connection
|
566
|
-
raise ConnectionNotEstablished, "No connection for #{
|
876
|
+
raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
|
567
877
|
conn
|
568
878
|
end
|
569
879
|
|
570
880
|
# Returns true if a connection that's accessible to this class has
|
571
881
|
# already been opened.
|
572
|
-
def connected?(
|
573
|
-
conn = retrieve_connection_pool(
|
882
|
+
def connected?(spec_name)
|
883
|
+
conn = retrieve_connection_pool(spec_name)
|
574
884
|
conn && conn.connected?
|
575
885
|
end
|
576
886
|
|
@@ -578,9 +888,8 @@ module ActiveRecord
|
|
578
888
|
# connection and the defined connection (if they exist). The result
|
579
889
|
# can be used as an argument for establish_connection, for easily
|
580
890
|
# re-establishing the connection.
|
581
|
-
def remove_connection(
|
582
|
-
if pool = owner_to_pool.delete(
|
583
|
-
@class_to_pool.clear
|
891
|
+
def remove_connection(spec_name)
|
892
|
+
if pool = owner_to_pool.delete(spec_name)
|
584
893
|
pool.automatic_reconnect = false
|
585
894
|
pool.disconnect!
|
586
895
|
pool.spec.config
|
@@ -596,63 +905,30 @@ module ActiveRecord
|
|
596
905
|
# #fetch is significantly slower than #[]. So in the nil case, no caching will
|
597
906
|
# take place, but that's ok since the nil case is not the common one that we wish
|
598
907
|
# to optimise for.
|
599
|
-
def retrieve_connection_pool(
|
600
|
-
|
601
|
-
|
602
|
-
klass = klass.superclass
|
603
|
-
break unless klass <= Base
|
604
|
-
end
|
605
|
-
|
606
|
-
class_to_pool[klass.name] = pool
|
607
|
-
end
|
608
|
-
end
|
609
|
-
|
610
|
-
private
|
611
|
-
|
612
|
-
def owner_to_pool
|
613
|
-
@owner_to_pool[Process.pid]
|
614
|
-
end
|
615
|
-
|
616
|
-
def class_to_pool
|
617
|
-
@class_to_pool[Process.pid]
|
618
|
-
end
|
619
|
-
|
620
|
-
def pool_for(owner)
|
621
|
-
owner_to_pool.fetch(owner.name) {
|
622
|
-
if ancestor_pool = pool_from_any_process_for(owner)
|
908
|
+
def retrieve_connection_pool(spec_name)
|
909
|
+
owner_to_pool.fetch(spec_name) do
|
910
|
+
if ancestor_pool = pool_from_any_process_for(spec_name)
|
623
911
|
# A connection was established in an ancestor process that must have
|
624
912
|
# subsequently forked. We can't reuse the connection, but we can copy
|
625
913
|
# the specification and establish a new connection with it.
|
626
|
-
establish_connection
|
914
|
+
establish_connection(ancestor_pool.spec).tap do |pool|
|
915
|
+
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
|
916
|
+
end
|
627
917
|
else
|
628
|
-
owner_to_pool[
|
918
|
+
owner_to_pool[spec_name] = nil
|
629
919
|
end
|
630
|
-
|
920
|
+
end
|
631
921
|
end
|
632
922
|
|
633
|
-
|
634
|
-
owner_to_pool = @owner_to_pool.values.find { |v| v[owner.name] }
|
635
|
-
owner_to_pool && owner_to_pool[owner.name]
|
636
|
-
end
|
637
|
-
end
|
923
|
+
private
|
638
924
|
|
639
|
-
|
640
|
-
|
641
|
-
@app = app
|
925
|
+
def owner_to_pool
|
926
|
+
@owner_to_pool[Process.pid]
|
642
927
|
end
|
643
928
|
|
644
|
-
def
|
645
|
-
|
646
|
-
|
647
|
-
response = @app.call(env)
|
648
|
-
response[2] = ::Rack::BodyProxy.new(response[2]) do
|
649
|
-
ActiveRecord::Base.clear_active_connections! unless testing
|
650
|
-
end
|
651
|
-
|
652
|
-
response
|
653
|
-
rescue Exception
|
654
|
-
ActiveRecord::Base.clear_active_connections! unless testing
|
655
|
-
raise
|
929
|
+
def pool_from_any_process_for(spec_name)
|
930
|
+
owner_to_pool = @owner_to_pool.values.find { |v| v[spec_name] }
|
931
|
+
owner_to_pool && owner_to_pool[spec_name]
|
656
932
|
end
|
657
933
|
end
|
658
934
|
end
|