activerecord 3.2.19 → 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 +7 -0
- data/CHANGELOG.md +1715 -604
- data/MIT-LICENSE +2 -2
- data/README.rdoc +40 -45
- data/examples/performance.rb +33 -22
- data/examples/simple.rb +3 -4
- data/lib/active_record/aggregations.rb +76 -51
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +54 -40
- data/lib/active_record/associations/association.rb +76 -56
- data/lib/active_record/associations/association_scope.rb +125 -93
- data/lib/active_record/associations/belongs_to_association.rb +57 -28
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +120 -32
- data/lib/active_record/associations/builder/belongs_to.rb +115 -62
- data/lib/active_record/associations/builder/collection_association.rb +61 -53
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
- data/lib/active_record/associations/builder/has_many.rb +9 -65
- data/lib/active_record/associations/builder/has_one.rb +18 -52
- data/lib/active_record/associations/builder/singular_association.rb +18 -19
- data/lib/active_record/associations/collection_association.rb +268 -186
- data/lib/active_record/associations/collection_proxy.rb +1003 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +81 -41
- data/lib/active_record/associations/has_many_through_association.rb +76 -55
- data/lib/active_record/associations/has_one_association.rb +51 -21
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
- data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
- data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
- data/lib/active_record/associations/join_dependency.rb +239 -155
- data/lib/active_record/associations/preloader/association.rb +97 -62
- data/lib/active_record/associations/preloader/collection_association.rb +2 -8
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +0 -8
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +75 -33
- data/lib/active_record/associations/preloader.rb +111 -79
- data/lib/active_record/associations/singular_association.rb +35 -13
- data/lib/active_record/associations/through_association.rb +41 -19
- data/lib/active_record/associations.rb +727 -501
- data/lib/active_record/attribute/user_provided_default.rb +28 -0
- data/lib/active_record/attribute.rb +213 -0
- data/lib/active_record/attribute_assignment.rb +32 -162
- data/lib/active_record/attribute_decorators.rb +67 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +101 -61
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +7 -6
- data/lib/active_record/attribute_methods/read.rb +56 -117
- data/lib/active_record/attribute_methods/serialization.rb +43 -96
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
- data/lib/active_record/attribute_methods/write.rb +34 -45
- data/lib/active_record/attribute_methods.rb +333 -144
- data/lib/active_record/attribute_mutation_tracker.rb +70 -0
- data/lib/active_record/attribute_set/builder.rb +108 -0
- data/lib/active_record/attribute_set.rb +108 -0
- data/lib/active_record/attributes.rb +265 -0
- data/lib/active_record/autosave_association.rb +285 -223
- data/lib/active_record/base.rb +95 -490
- data/lib/active_record/callbacks.rb +95 -61
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +28 -19
- data/lib/active_record/collection_cache_key.rb +40 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
- data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
- data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
- data/lib/active_record/connection_adapters/column.rb +30 -259
- data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
- 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 +47 -196
- data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
- data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- 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 +93 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
- data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
- data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
- data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
- 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 +538 -24
- data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
- data/lib/active_record/connection_handling.rb +155 -0
- data/lib/active_record/core.rb +561 -0
- data/lib/active_record/counter_cache.rb +146 -105
- data/lib/active_record/dynamic_matchers.rb +101 -64
- data/lib/active_record/enum.rb +234 -0
- data/lib/active_record/errors.rb +153 -56
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +10 -6
- data/lib/active_record/fixture_set/file.rb +77 -0
- data/lib/active_record/fixtures.rb +355 -232
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +144 -79
- data/lib/active_record/integration.rb +66 -13
- 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 +9 -1
- data/lib/active_record/locking/optimistic.rb +77 -56
- data/lib/active_record/locking/pessimistic.rb +6 -6
- data/lib/active_record/log_subscriber.rb +53 -28
- data/lib/active_record/migration/command_recorder.rb +166 -33
- data/lib/active_record/migration/compatibility.rb +126 -0
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +792 -264
- data/lib/active_record/model_schema.rb +192 -130
- data/lib/active_record/nested_attributes.rb +238 -145
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +89 -0
- data/lib/active_record/persistence.rb +357 -157
- data/lib/active_record/query_cache.rb +22 -43
- data/lib/active_record/querying.rb +34 -23
- data/lib/active_record/railtie.rb +88 -48
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +5 -4
- data/lib/active_record/railties/databases.rake +170 -422
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -5
- data/lib/active_record/reflection.rb +715 -189
- data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
- data/lib/active_record/relation/batches.rb +203 -50
- data/lib/active_record/relation/calculations.rb +203 -194
- data/lib/active_record/relation/delegation.rb +103 -25
- data/lib/active_record/relation/finder_methods.rb +457 -261
- data/lib/active_record/relation/from_clause.rb +32 -0
- data/lib/active_record/relation/merger.rb +167 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
- 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 +13 -0
- data/lib/active_record/relation/predicate_builder.rb +153 -48
- data/lib/active_record/relation/query_attribute.rb +19 -0
- data/lib/active_record/relation/query_methods.rb +1019 -194
- data/lib/active_record/relation/record_fetch_warning.rb +49 -0
- data/lib/active_record/relation/spawn_methods.rb +46 -150
- 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 +450 -245
- data/lib/active_record/result.rb +104 -12
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +120 -94
- data/lib/active_record/schema.rb +28 -18
- data/lib/active_record/schema_dumper.rb +141 -74
- data/lib/active_record/schema_migration.rb +50 -0
- data/lib/active_record/scoping/default.rb +64 -57
- data/lib/active_record/scoping/named.rb +93 -108
- data/lib/active_record/scoping.rb +73 -121
- data/lib/active_record/secure_token.rb +38 -0
- data/lib/active_record/serialization.rb +7 -5
- data/lib/active_record/statement_cache.rb +113 -0
- data/lib/active_record/store.rb +173 -15
- 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 +313 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
- data/lib/active_record/timestamp.rb +42 -24
- data/lib/active_record/touch_later.rb +58 -0
- data/lib/active_record/transactions.rb +233 -105
- data/lib/active_record/type/adapter_specific_registry.rb +130 -0
- data/lib/active_record/type/date.rb +7 -0
- data/lib/active_record/type/date_time.rb +7 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- 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 +63 -0
- data/lib/active_record/type/time.rb +20 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type.rb +72 -0
- 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 +33 -18
- data/lib/active_record/validations/length.rb +24 -0
- data/lib/active_record/validations/presence.rb +66 -0
- data/lib/active_record/validations/uniqueness.rb +128 -68
- data/lib/active_record/validations.rb +48 -40
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +71 -47
- data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
- data/lib/rails/generators/active_record/migration.rb +18 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
- data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +188 -134
- data/examples/associations.png +0 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
- data/lib/active_record/associations/join_helper.rb +0 -55
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
- data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
- data/lib/active_record/dynamic_finder_match.rb +0 -68
- data/lib/active_record/dynamic_scope_match.rb +0 -23
- data/lib/active_record/fixtures/file.rb +0 -65
- data/lib/active_record/identity_map.rb +0 -162
- data/lib/active_record/observer.rb +0 -121
- data/lib/active_record/serializers/xml_serializer.rb +0 -203
- data/lib/active_record/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
- data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
- data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -1,14 +1,21 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'concurrent/map'
|
2
3
|
require 'monitor'
|
3
|
-
require 'set'
|
4
|
-
require 'active_support/core_ext/module/deprecation'
|
5
4
|
|
6
5
|
module ActiveRecord
|
7
6
|
# Raised when a connection could not be obtained within the connection
|
8
|
-
# acquisition timeout period
|
7
|
+
# acquisition timeout period: because max connections in pool
|
8
|
+
# are in use.
|
9
9
|
class ConnectionTimeoutError < ConnectionNotEstablished
|
10
10
|
end
|
11
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
|
+
|
12
19
|
module ConnectionAdapters
|
13
20
|
# Connection pool base class for managing Active Record database
|
14
21
|
# connections.
|
@@ -31,17 +38,18 @@ module ActiveRecord
|
|
31
38
|
# Connections can be obtained and used from a connection pool in several
|
32
39
|
# ways:
|
33
40
|
#
|
34
|
-
# 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
|
35
43
|
# earlier (pre-connection-pooling). Eventually, when you're done with
|
36
44
|
# the connection(s) and wish it to be returned to the pool, you call
|
37
|
-
# ActiveRecord::Base.clear_active_connections
|
38
|
-
# 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
|
39
47
|
# Action Pack's request handling cycle.
|
40
48
|
# 2. Manually check out a connection from the pool with
|
41
|
-
# ActiveRecord::Base.connection_pool.checkout. You are responsible for
|
49
|
+
# {ActiveRecord::Base.connection_pool.checkout}[rdoc-ref:#checkout]. You are responsible for
|
42
50
|
# returning this connection to the pool when finished by calling
|
43
|
-
# ActiveRecord::Base.connection_pool.checkin(connection).
|
44
|
-
# 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
|
45
53
|
# obtains a connection, yields it as the sole argument to the block,
|
46
54
|
# and returns it to the pool after the block completes.
|
47
55
|
#
|
@@ -50,20 +58,258 @@ module ActiveRecord
|
|
50
58
|
#
|
51
59
|
# == Options
|
52
60
|
#
|
53
|
-
# There are
|
61
|
+
# There are several connection-pooling-related options that you can add to
|
54
62
|
# your database connection configuration:
|
55
63
|
#
|
56
64
|
# * +pool+: number indicating size of connection pool (default 5)
|
57
|
-
# * +
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
65
|
+
# * +checkout_timeout+: number of seconds to block and wait for a connection
|
66
|
+
# before giving up and raising a timeout error (default 5 seconds).
|
67
|
+
# * +reaping_frequency+: frequency in seconds to periodically run the
|
68
|
+
# Reaper, which attempts to find and recover connections from dead
|
69
|
+
# threads, which can occur if a programmer forgets to close a
|
70
|
+
# connection at the end of a thread or a thread dies unexpectedly.
|
71
|
+
# Regardless of this setting, the Reaper will be invoked before every
|
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
|
62
82
|
class ConnectionPool
|
83
|
+
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
|
84
|
+
# with which it shares a Monitor. But could be a generic Queue.
|
85
|
+
#
|
86
|
+
# The Queue in stdlib's 'thread' could replace this class except
|
87
|
+
# stdlib's doesn't support waiting with a timeout.
|
88
|
+
class Queue
|
89
|
+
def initialize(lock = Monitor.new)
|
90
|
+
@lock = lock
|
91
|
+
@cond = @lock.new_cond
|
92
|
+
@num_waiting = 0
|
93
|
+
@queue = []
|
94
|
+
end
|
95
|
+
|
96
|
+
# Test if any threads are currently waiting on the queue.
|
97
|
+
def any_waiting?
|
98
|
+
synchronize do
|
99
|
+
@num_waiting > 0
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the number of threads currently waiting on this
|
104
|
+
# queue.
|
105
|
+
def num_waiting
|
106
|
+
synchronize do
|
107
|
+
@num_waiting
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Add +element+ to the queue. Never blocks.
|
112
|
+
def add(element)
|
113
|
+
synchronize do
|
114
|
+
@queue.push element
|
115
|
+
@cond.signal
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# If +element+ is in the queue, remove and return it, or nil.
|
120
|
+
def delete(element)
|
121
|
+
synchronize do
|
122
|
+
@queue.delete(element)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Remove all elements from the queue.
|
127
|
+
def clear
|
128
|
+
synchronize do
|
129
|
+
@queue.clear
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Remove the head of the queue.
|
134
|
+
#
|
135
|
+
# If +timeout+ is not given, remove and return the head the
|
136
|
+
# queue if the number of available elements is strictly
|
137
|
+
# greater than the number of threads currently waiting (that
|
138
|
+
# is, don't jump ahead in line). Otherwise, return nil.
|
139
|
+
#
|
140
|
+
# If +timeout+ is given, block if there is no element
|
141
|
+
# available, waiting up to +timeout+ seconds for an element to
|
142
|
+
# become available.
|
143
|
+
#
|
144
|
+
# Raises:
|
145
|
+
# - ActiveRecord::ConnectionTimeoutError if +timeout+ is given and no element
|
146
|
+
# becomes available within +timeout+ seconds,
|
147
|
+
def poll(timeout = nil)
|
148
|
+
synchronize { internal_poll(timeout) }
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def internal_poll(timeout)
|
154
|
+
no_wait_poll || (timeout && wait_poll(timeout))
|
155
|
+
end
|
156
|
+
|
157
|
+
def synchronize(&block)
|
158
|
+
@lock.synchronize(&block)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Test if the queue currently contains any elements.
|
162
|
+
def any?
|
163
|
+
!@queue.empty?
|
164
|
+
end
|
165
|
+
|
166
|
+
# A thread can remove an element from the queue without
|
167
|
+
# waiting if and only if the number of currently available
|
168
|
+
# connections is strictly greater than the number of waiting
|
169
|
+
# threads.
|
170
|
+
def can_remove_no_wait?
|
171
|
+
@queue.size > @num_waiting
|
172
|
+
end
|
173
|
+
|
174
|
+
# Removes and returns the head of the queue if possible, or nil.
|
175
|
+
def remove
|
176
|
+
@queue.shift
|
177
|
+
end
|
178
|
+
|
179
|
+
# Remove and return the head the queue if the number of
|
180
|
+
# available elements is strictly greater than the number of
|
181
|
+
# threads currently waiting. Otherwise, return nil.
|
182
|
+
def no_wait_poll
|
183
|
+
remove if can_remove_no_wait?
|
184
|
+
end
|
185
|
+
|
186
|
+
# Waits on the queue up to +timeout+ seconds, then removes and
|
187
|
+
# returns the head of the queue.
|
188
|
+
def wait_poll(timeout)
|
189
|
+
@num_waiting += 1
|
190
|
+
|
191
|
+
t0 = Time.now
|
192
|
+
elapsed = 0
|
193
|
+
loop do
|
194
|
+
@cond.wait(timeout - elapsed)
|
195
|
+
|
196
|
+
return remove if any?
|
197
|
+
|
198
|
+
elapsed = Time.now - t0
|
199
|
+
if elapsed >= timeout
|
200
|
+
msg = 'could not obtain a connection from the pool within %0.3f seconds (waited %0.3f seconds); all pooled connections were in use' %
|
201
|
+
[timeout, elapsed]
|
202
|
+
raise ConnectionTimeoutError, msg
|
203
|
+
end
|
204
|
+
end
|
205
|
+
ensure
|
206
|
+
@num_waiting -= 1
|
207
|
+
end
|
208
|
+
end
|
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
|
+
|
284
|
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
|
285
|
+
# A reaper instantiated with a nil frequency will never reap the
|
286
|
+
# connection pool.
|
287
|
+
#
|
288
|
+
# Configure the frequency by setting "reaping_frequency" in your
|
289
|
+
# database yaml file.
|
290
|
+
class Reaper
|
291
|
+
attr_reader :pool, :frequency
|
292
|
+
|
293
|
+
def initialize(pool, frequency)
|
294
|
+
@pool = pool
|
295
|
+
@frequency = frequency
|
296
|
+
end
|
297
|
+
|
298
|
+
def run
|
299
|
+
return unless frequency
|
300
|
+
Thread.new(frequency, pool) { |t, p|
|
301
|
+
while true
|
302
|
+
sleep t
|
303
|
+
p.reap
|
304
|
+
end
|
305
|
+
}
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
63
309
|
include MonitorMixin
|
64
310
|
|
65
|
-
attr_accessor :automatic_reconnect
|
66
|
-
attr_reader :spec, :connections
|
311
|
+
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
|
312
|
+
attr_reader :spec, :connections, :size, :reaper
|
67
313
|
|
68
314
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
69
315
|
# object which describes database connection information (e.g. adapter,
|
@@ -76,59 +322,82 @@ module ActiveRecord
|
|
76
322
|
|
77
323
|
@spec = spec
|
78
324
|
|
79
|
-
|
80
|
-
@
|
81
|
-
|
82
|
-
@queue = new_cond
|
83
|
-
# 'wait_timeout', the backward-compatible key, conflicts with spec key
|
84
|
-
# used by mysql2 for something entirely different, checkout_timeout
|
85
|
-
# preferred to avoid conflict and allow independent values.
|
86
|
-
@timeout = spec.config[:checkout_timeout] || spec.config[:wait_timeout] || 5
|
325
|
+
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
|
326
|
+
@reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
|
327
|
+
@reaper.run
|
87
328
|
|
88
329
|
# default max pool size to 5
|
89
330
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
90
331
|
|
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)
|
343
|
+
|
91
344
|
@connections = []
|
92
345
|
@automatic_reconnect = true
|
346
|
+
|
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
|
93
356
|
end
|
94
357
|
|
95
358
|
# Retrieve the connection associated with the current thread, or call
|
96
359
|
# #checkout to obtain one if necessary.
|
97
360
|
#
|
98
361
|
# #connection can be called any number of times; the connection is
|
99
|
-
# held in a
|
362
|
+
# held in a cache keyed by a thread.
|
100
363
|
def connection
|
101
|
-
|
102
|
-
@reserved_connections[current_connection_id] ||= checkout
|
103
|
-
end
|
364
|
+
@thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
|
104
365
|
end
|
105
366
|
|
106
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?
|
107
372
|
def active_connection?
|
108
|
-
|
109
|
-
@reserved_connections.fetch(current_connection_id) {
|
110
|
-
return false
|
111
|
-
}.in_use?
|
112
|
-
end
|
373
|
+
@thread_cached_conns[connection_cache_key(Thread.current)]
|
113
374
|
end
|
114
375
|
|
115
376
|
# Signal that the thread is finished with the current connection.
|
116
377
|
# #release_connection releases the connection-thread association
|
117
378
|
# and returns the connection to the pool.
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
386
|
+
end
|
121
387
|
end
|
122
388
|
|
123
|
-
# 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
|
124
391
|
# exists checkout a connection, yield it to the block, and checkin the
|
125
392
|
# connection when finished.
|
126
393
|
def with_connection
|
127
|
-
|
128
|
-
|
129
|
-
|
394
|
+
unless conn = @thread_cached_conns[connection_cache_key(Thread.current)]
|
395
|
+
conn = connection
|
396
|
+
fresh_connection = true
|
397
|
+
end
|
398
|
+
yield conn
|
130
399
|
ensure
|
131
|
-
release_connection
|
400
|
+
release_connection if fresh_connection
|
132
401
|
end
|
133
402
|
|
134
403
|
# Returns true if a connection has already been opened.
|
@@ -137,283 +406,481 @@ module ActiveRecord
|
|
137
406
|
end
|
138
407
|
|
139
408
|
# Disconnects all connections in the pool, and clears the pool.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
|
146
423
|
end
|
147
|
-
@connections = []
|
148
424
|
end
|
149
425
|
end
|
150
426
|
|
151
|
-
#
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
@connections.delete_if do |conn|
|
160
|
-
conn.requires_reloading?
|
161
|
-
end
|
162
|
-
end
|
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)
|
163
435
|
end
|
164
436
|
|
165
|
-
#
|
166
|
-
#
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
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?)
|
175
454
|
|
176
|
-
|
177
|
-
with_connection do |c|
|
178
|
-
c.schema_cache.columns
|
179
|
-
end
|
180
|
-
end
|
181
|
-
deprecate :columns
|
455
|
+
@available.clear
|
182
456
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
189
464
|
|
190
|
-
|
191
|
-
|
192
|
-
|
465
|
+
@connections.each do |conn|
|
466
|
+
@available.add conn
|
467
|
+
end
|
468
|
+
end
|
193
469
|
end
|
194
|
-
end
|
195
|
-
deprecate :primary_keys
|
196
470
|
|
197
|
-
|
198
|
-
with_connection do |c|
|
199
|
-
c.schema_cache.clear!
|
200
|
-
end
|
471
|
+
bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
|
201
472
|
end
|
202
|
-
deprecate :clear_cache!
|
203
473
|
|
204
|
-
#
|
205
|
-
#
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
database connection at the end of the thread by calling `close` on your
|
215
|
-
connection. For example: ActiveRecord::Base.connection.close
|
216
|
-
eowarn
|
217
|
-
checkin conn
|
218
|
-
@reserved_connections.delete(key)
|
219
|
-
end
|
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)
|
220
484
|
end
|
221
485
|
|
222
486
|
# Check-out a database connection from the pool, indicating that you want
|
223
487
|
# to use it. You should call #checkin when you no longer need this.
|
224
488
|
#
|
225
|
-
# This is done by either returning
|
226
|
-
# a new connection
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
# exception will be raised.
|
489
|
+
# This is done by either returning and leasing existing connection, or by
|
490
|
+
# creating a new connection and leasing it.
|
491
|
+
#
|
492
|
+
# If all connections are leased and the pool is at capacity (meaning the
|
493
|
+
# number of currently leased connections is greater than or equal to the
|
494
|
+
# size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
|
232
495
|
#
|
233
496
|
# Returns: an AbstractAdapter object.
|
234
497
|
#
|
235
498
|
# Raises:
|
236
|
-
# - ConnectionTimeoutError
|
237
|
-
|
238
|
-
|
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))
|
502
|
+
end
|
503
|
+
|
504
|
+
# Check-in a database connection back into the pool, indicating that you
|
505
|
+
# no longer need this connection.
|
506
|
+
#
|
507
|
+
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
508
|
+
# calling #checkout on this pool.
|
509
|
+
def checkin(conn)
|
239
510
|
synchronize do
|
240
|
-
|
511
|
+
remove_connection_from_thread_cache conn
|
241
512
|
|
242
|
-
|
243
|
-
conn
|
513
|
+
conn._run_checkin_callbacks do
|
514
|
+
conn.expire
|
515
|
+
end
|
244
516
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
conn.lease
|
249
|
-
end
|
250
|
-
end
|
517
|
+
@available.add conn
|
518
|
+
end
|
519
|
+
end
|
251
520
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
521
|
+
# Remove a connection from the connection pool. The connection will
|
522
|
+
# remain open and active but will no longer be managed by this pool.
|
523
|
+
def remove(conn)
|
524
|
+
needs_new_connection = false
|
256
525
|
|
257
|
-
|
258
|
-
|
259
|
-
|
526
|
+
synchronize do
|
527
|
+
remove_connection_from_thread_cache conn
|
528
|
+
|
529
|
+
@connections.delete conn
|
530
|
+
@available.delete conn
|
531
|
+
|
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?
|
542
|
+
end
|
260
543
|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
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
|
550
|
+
end
|
551
|
+
|
552
|
+
# Recover lost connections for the pool. A lost connection can occur if
|
553
|
+
# a programmer forgets to checkin a connection at the end of a thread
|
554
|
+
# or a thread dies unexpectedly.
|
555
|
+
def reap
|
556
|
+
stale_connections = synchronize do
|
557
|
+
@connections.select do |conn|
|
558
|
+
conn.in_use? && !conn.owner.alive?
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
stale_connections.each do |conn|
|
563
|
+
synchronize do
|
564
|
+
if conn.active?
|
565
|
+
conn.reset!
|
566
|
+
checkin conn
|
567
|
+
else
|
568
|
+
remove conn
|
275
569
|
end
|
276
570
|
end
|
277
571
|
end
|
278
572
|
end
|
279
573
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
574
|
+
def num_waiting_in_queue # :nodoc:
|
575
|
+
@available.num_waiting
|
576
|
+
end
|
577
|
+
|
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)
|
290
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
|
291
599
|
|
292
|
-
|
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
|
293
608
|
end
|
294
609
|
end
|
295
610
|
|
296
|
-
|
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
|
297
616
|
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
301
654
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
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}"
|
308
669
|
end
|
670
|
+
end
|
309
671
|
|
310
|
-
|
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
|
311
681
|
end
|
682
|
+
yield
|
683
|
+
ensure
|
684
|
+
synchronize { @new_cons_enabled = previous_value }
|
312
685
|
end
|
313
686
|
|
687
|
+
# Acquire a connection by one of 1) immediately removing one
|
688
|
+
# from the queue of available connections, 2) creating a new
|
689
|
+
# connection if the pool is not at capacity, 3) waiting on the
|
690
|
+
# queue for a connection to become available.
|
691
|
+
#
|
692
|
+
# Raises:
|
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
|
706
|
+
conn
|
707
|
+
else
|
708
|
+
reap
|
709
|
+
@available.poll(checkout_timeout)
|
710
|
+
end
|
711
|
+
end
|
712
|
+
|
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)
|
717
|
+
end
|
718
|
+
alias_method :release, :remove_connection_from_thread_cache
|
719
|
+
|
314
720
|
def new_connection
|
315
|
-
|
721
|
+
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
722
|
+
conn.schema_cache = schema_cache.dup if schema_cache
|
723
|
+
end
|
316
724
|
end
|
317
725
|
|
318
|
-
|
319
|
-
|
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
|
756
|
+
end
|
757
|
+
|
758
|
+
def adopt_connection(conn)
|
759
|
+
conn.pool = self
|
760
|
+
@connections << conn
|
320
761
|
end
|
321
762
|
|
322
763
|
def checkout_new_connection
|
323
764
|
raise ConnectionNotEstablished unless @automatic_reconnect
|
324
|
-
|
325
|
-
c = new_connection
|
326
|
-
c.pool = self
|
327
|
-
@connections << c
|
328
|
-
c
|
765
|
+
new_connection
|
329
766
|
end
|
330
767
|
|
331
768
|
def checkout_and_verify(c)
|
332
|
-
c.
|
769
|
+
c._run_checkout_callbacks do
|
333
770
|
c.verify!
|
334
771
|
end
|
335
772
|
c
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
773
|
+
rescue
|
774
|
+
remove c
|
775
|
+
c.disconnect!
|
776
|
+
raise
|
340
777
|
end
|
341
778
|
end
|
342
779
|
|
343
780
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
344
|
-
# for keeping separate connection pools
|
345
|
-
# to different databases.
|
781
|
+
# for keeping separate connection pools that connect to different databases.
|
346
782
|
#
|
347
783
|
# For example, suppose that you have 5 models, with the following hierarchy:
|
348
784
|
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
355
|
-
#
|
785
|
+
# class Author < ActiveRecord::Base
|
786
|
+
# end
|
787
|
+
#
|
788
|
+
# class BankAccount < ActiveRecord::Base
|
789
|
+
# end
|
790
|
+
#
|
791
|
+
# class Book < ActiveRecord::Base
|
792
|
+
# establish_connection "library_db"
|
793
|
+
# end
|
794
|
+
#
|
795
|
+
# class ScaryBook < Book
|
796
|
+
# end
|
356
797
|
#
|
357
|
-
#
|
358
|
-
#
|
359
|
-
# the same connection pool. Likewise, Author and BankAccount will use the
|
360
|
-
# same connection pool. However, the connection pool used by Author/BankAccount
|
361
|
-
# is not the same as the one used by Book/ScaryBook/GoodBook.
|
798
|
+
# class GoodBook < Book
|
799
|
+
# end
|
362
800
|
#
|
363
|
-
#
|
364
|
-
#
|
365
|
-
#
|
801
|
+
# And a database.yml that looked like this:
|
802
|
+
#
|
803
|
+
# development:
|
804
|
+
# database: my_application
|
805
|
+
# host: localhost
|
806
|
+
#
|
807
|
+
# library_db:
|
808
|
+
# database: library
|
809
|
+
# host: some.library.org
|
810
|
+
#
|
811
|
+
# Your primary database in the development environment is "my_application"
|
812
|
+
# but the Book model connects to a separate database called "library_db"
|
813
|
+
# (this can even be a database on a different machine).
|
814
|
+
#
|
815
|
+
# Book, ScaryBook and GoodBook will all use the same connection pool to
|
816
|
+
# "library_db" while Author, BankAccount, and any other models you create
|
817
|
+
# will use the default connection pool to "my_application".
|
818
|
+
#
|
819
|
+
# The various connection pools are managed by a single instance of
|
820
|
+
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
|
821
|
+
# All Active Record models use this handler to determine the connection pool that they
|
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.
|
366
827
|
class ConnectionHandler
|
367
|
-
|
828
|
+
def initialize
|
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)
|
832
|
+
end
|
833
|
+
end
|
368
834
|
|
369
|
-
def
|
370
|
-
|
371
|
-
@class_to_pool = {}
|
835
|
+
def connection_pool_list
|
836
|
+
owner_to_pool.values.compact
|
372
837
|
end
|
838
|
+
alias :connection_pools :connection_pool_list
|
373
839
|
|
374
|
-
def establish_connection(
|
375
|
-
|
376
|
-
@class_to_pool[name] = @connection_pools[spec]
|
840
|
+
def establish_connection(spec)
|
841
|
+
owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
377
842
|
end
|
378
843
|
|
379
844
|
# Returns true if there are any active connections among the connection
|
380
845
|
# pools that the ConnectionHandler is managing.
|
381
846
|
def active_connections?
|
382
|
-
|
847
|
+
connection_pool_list.any?(&:active_connection?)
|
383
848
|
end
|
384
849
|
|
385
|
-
# Returns any connections in use by the current thread back to the pool
|
850
|
+
# Returns any connections in use by the current thread back to the pool,
|
851
|
+
# and also returns connections to the pool cached by threads that are no
|
852
|
+
# longer alive.
|
386
853
|
def clear_active_connections!
|
387
|
-
|
854
|
+
connection_pool_list.each(&:release_connection)
|
388
855
|
end
|
389
856
|
|
390
857
|
# Clears the cache which maps classes.
|
858
|
+
#
|
859
|
+
# See ConnectionPool#clear_reloadable_connections! for details.
|
391
860
|
def clear_reloadable_connections!
|
392
|
-
|
861
|
+
connection_pool_list.each(&:clear_reloadable_connections!)
|
393
862
|
end
|
394
863
|
|
395
864
|
def clear_all_connections!
|
396
|
-
|
397
|
-
end
|
398
|
-
|
399
|
-
# Verify active connections.
|
400
|
-
def verify_active_connections! #:nodoc:
|
401
|
-
@connection_pools.each_value {|pool| pool.verify_active_connections! }
|
865
|
+
connection_pool_list.each(&:disconnect!)
|
402
866
|
end
|
403
867
|
|
404
868
|
# Locate the connection of the nearest super class. This can be an
|
405
869
|
# active or defined connection: if it is the latter, it will be
|
406
870
|
# opened and set as the active connection for the class it was defined
|
407
871
|
# for (not necessarily the current class).
|
408
|
-
def retrieve_connection(
|
409
|
-
pool = retrieve_connection_pool(
|
410
|
-
|
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
|
875
|
+
conn = pool.connection
|
876
|
+
raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
|
877
|
+
conn
|
411
878
|
end
|
412
879
|
|
413
880
|
# Returns true if a connection that's accessible to this class has
|
414
881
|
# already been opened.
|
415
|
-
def connected?(
|
416
|
-
conn = retrieve_connection_pool(
|
882
|
+
def connected?(spec_name)
|
883
|
+
conn = retrieve_connection_pool(spec_name)
|
417
884
|
conn && conn.connected?
|
418
885
|
end
|
419
886
|
|
@@ -421,67 +888,47 @@ connection. For example: ActiveRecord::Base.connection.close
|
|
421
888
|
# connection and the defined connection (if they exist). The result
|
422
889
|
# can be used as an argument for establish_connection, for easily
|
423
890
|
# re-establishing the connection.
|
424
|
-
def remove_connection(
|
425
|
-
pool =
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
pool.automatic_reconnect = false
|
430
|
-
pool.disconnect!
|
431
|
-
pool.spec.config
|
432
|
-
end
|
433
|
-
|
434
|
-
def retrieve_connection_pool(klass)
|
435
|
-
pool = @class_to_pool[klass.name]
|
436
|
-
return pool if pool
|
437
|
-
return nil if ActiveRecord::Base == klass
|
438
|
-
retrieve_connection_pool klass.superclass
|
439
|
-
end
|
440
|
-
end
|
441
|
-
|
442
|
-
class ConnectionManagement
|
443
|
-
class Proxy # :nodoc:
|
444
|
-
attr_reader :body, :testing
|
445
|
-
|
446
|
-
def initialize(body, testing = false)
|
447
|
-
@body = body
|
448
|
-
@testing = testing
|
449
|
-
end
|
450
|
-
|
451
|
-
def method_missing(method_sym, *arguments, &block)
|
452
|
-
@body.send(method_sym, *arguments, &block)
|
453
|
-
end
|
454
|
-
|
455
|
-
def respond_to?(method_sym, include_private = false)
|
456
|
-
super || @body.respond_to?(method_sym)
|
457
|
-
end
|
458
|
-
|
459
|
-
def each(&block)
|
460
|
-
body.each(&block)
|
461
|
-
end
|
462
|
-
|
463
|
-
def close
|
464
|
-
body.close if body.respond_to?(:close)
|
465
|
-
|
466
|
-
# Don't return connection (and perform implicit rollback) if
|
467
|
-
# this request is a part of integration test
|
468
|
-
ActiveRecord::Base.clear_active_connections! unless testing
|
891
|
+
def remove_connection(spec_name)
|
892
|
+
if pool = owner_to_pool.delete(spec_name)
|
893
|
+
pool.automatic_reconnect = false
|
894
|
+
pool.disconnect!
|
895
|
+
pool.spec.config
|
469
896
|
end
|
470
897
|
end
|
471
898
|
|
472
|
-
|
473
|
-
|
899
|
+
# Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
|
900
|
+
# This makes retrieving the connection pool O(1) once the process is warm.
|
901
|
+
# When a connection is established or removed, we invalidate the cache.
|
902
|
+
#
|
903
|
+
# Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
|
904
|
+
# However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
|
905
|
+
# #fetch is significantly slower than #[]. So in the nil case, no caching will
|
906
|
+
# take place, but that's ok since the nil case is not the common one that we wish
|
907
|
+
# to optimise for.
|
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)
|
911
|
+
# A connection was established in an ancestor process that must have
|
912
|
+
# subsequently forked. We can't reuse the connection, but we can copy
|
913
|
+
# the specification and establish a new connection with it.
|
914
|
+
establish_connection(ancestor_pool.spec).tap do |pool|
|
915
|
+
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
|
916
|
+
end
|
917
|
+
else
|
918
|
+
owner_to_pool[spec_name] = nil
|
919
|
+
end
|
920
|
+
end
|
474
921
|
end
|
475
922
|
|
476
|
-
|
477
|
-
testing = env.key?('rack.test')
|
923
|
+
private
|
478
924
|
|
479
|
-
|
925
|
+
def owner_to_pool
|
926
|
+
@owner_to_pool[Process.pid]
|
927
|
+
end
|
480
928
|
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
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]
|
485
932
|
end
|
486
933
|
end
|
487
934
|
end
|