activerecord 5.0.7.2 → 5.1.0.beta1
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 +5 -5
- data/CHANGELOG.md +389 -2252
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/examples/performance.rb +28 -28
- data/examples/simple.rb +3 -3
- data/lib/active_record.rb +20 -20
- data/lib/active_record/aggregations.rb +244 -244
- data/lib/active_record/association_relation.rb +5 -5
- data/lib/active_record/associations.rb +1579 -1569
- data/lib/active_record/associations/alias_tracker.rb +1 -1
- data/lib/active_record/associations/association.rb +23 -15
- data/lib/active_record/associations/association_scope.rb +83 -81
- data/lib/active_record/associations/belongs_to_association.rb +0 -1
- data/lib/active_record/associations/builder/belongs_to.rb +16 -14
- data/lib/active_record/associations/builder/collection_association.rb +1 -2
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
- data/lib/active_record/associations/collection_association.rb +74 -241
- data/lib/active_record/associations/collection_proxy.rb +144 -70
- data/lib/active_record/associations/has_many_association.rb +15 -19
- data/lib/active_record/associations/has_many_through_association.rb +12 -5
- data/lib/active_record/associations/has_one_association.rb +22 -28
- data/lib/active_record/associations/has_one_through_association.rb +5 -1
- data/lib/active_record/associations/join_dependency.rb +117 -115
- data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
- data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
- data/lib/active_record/associations/preloader.rb +94 -94
- data/lib/active_record/associations/preloader/association.rb +87 -64
- data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
- data/lib/active_record/associations/preloader/collection_association.rb +6 -6
- data/lib/active_record/associations/preloader/has_many.rb +0 -2
- data/lib/active_record/associations/preloader/singular_association.rb +6 -8
- data/lib/active_record/associations/preloader/through_association.rb +34 -41
- data/lib/active_record/associations/singular_association.rb +8 -25
- data/lib/active_record/associations/through_association.rb +3 -6
- data/lib/active_record/attribute.rb +98 -71
- data/lib/active_record/attribute/user_provided_default.rb +4 -2
- data/lib/active_record/attribute_assignment.rb +61 -61
- data/lib/active_record/attribute_decorators.rb +35 -13
- data/lib/active_record/attribute_methods.rb +56 -65
- data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
- data/lib/active_record/attribute_methods/dirty.rb +216 -34
- data/lib/active_record/attribute_methods/primary_key.rb +78 -73
- data/lib/active_record/attribute_methods/read.rb +39 -35
- data/lib/active_record/attribute_methods/serialization.rb +7 -7
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
- data/lib/active_record/attribute_methods/write.rb +36 -30
- data/lib/active_record/attribute_mutation_tracker.rb +53 -10
- data/lib/active_record/attribute_set.rb +9 -6
- data/lib/active_record/attribute_set/builder.rb +41 -49
- data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
- data/lib/active_record/attributes.rb +21 -21
- data/lib/active_record/autosave_association.rb +13 -13
- data/lib/active_record/base.rb +24 -22
- data/lib/active_record/callbacks.rb +52 -14
- data/lib/active_record/coders/yaml_column.rb +9 -11
- data/lib/active_record/collection_cache_key.rb +6 -17
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
- data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
- data/lib/active_record/connection_adapters/column.rb +27 -5
- data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
- data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
- data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
- data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
- data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
- data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
- data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
- data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
- data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +161 -170
- data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
- data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
- data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
- data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -20
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +187 -130
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
- data/lib/active_record/connection_handling.rb +14 -26
- data/lib/active_record/core.rb +110 -93
- data/lib/active_record/counter_cache.rb +62 -13
- data/lib/active_record/define_callbacks.rb +20 -0
- data/lib/active_record/dynamic_matchers.rb +80 -79
- data/lib/active_record/enum.rb +8 -6
- data/lib/active_record/errors.rb +58 -15
- data/lib/active_record/explain.rb +1 -2
- data/lib/active_record/explain_registry.rb +1 -1
- data/lib/active_record/explain_subscriber.rb +7 -4
- data/lib/active_record/fixture_set/file.rb +11 -8
- data/lib/active_record/fixtures.rb +66 -53
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +93 -79
- data/lib/active_record/integration.rb +7 -7
- data/lib/active_record/internal_metadata.rb +3 -16
- data/lib/active_record/legacy_yaml_adapter.rb +1 -1
- data/lib/active_record/locking/optimistic.rb +64 -56
- data/lib/active_record/locking/pessimistic.rb +10 -1
- data/lib/active_record/log_subscriber.rb +29 -29
- data/lib/active_record/migration.rb +155 -172
- data/lib/active_record/migration/command_recorder.rb +94 -94
- data/lib/active_record/migration/compatibility.rb +76 -37
- data/lib/active_record/migration/join_table.rb +6 -6
- data/lib/active_record/model_schema.rb +85 -119
- data/lib/active_record/nested_attributes.rb +200 -199
- data/lib/active_record/null_relation.rb +10 -33
- data/lib/active_record/persistence.rb +45 -38
- data/lib/active_record/query_cache.rb +4 -8
- data/lib/active_record/querying.rb +2 -3
- data/lib/active_record/railtie.rb +16 -17
- data/lib/active_record/railties/controller_runtime.rb +6 -2
- data/lib/active_record/railties/databases.rake +125 -140
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +2 -2
- data/lib/active_record/reflection.rb +79 -96
- data/lib/active_record/relation.rb +72 -115
- data/lib/active_record/relation/batches.rb +87 -58
- data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
- data/lib/active_record/relation/calculations.rb +154 -160
- data/lib/active_record/relation/delegation.rb +30 -29
- data/lib/active_record/relation/finder_methods.rb +195 -226
- data/lib/active_record/relation/merger.rb +58 -62
- data/lib/active_record/relation/predicate_builder.rb +92 -89
- data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
- data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
- data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
- data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
- data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
- data/lib/active_record/relation/query_attribute.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +247 -295
- data/lib/active_record/relation/record_fetch_warning.rb +3 -3
- data/lib/active_record/relation/spawn_methods.rb +4 -5
- data/lib/active_record/relation/where_clause.rb +79 -65
- data/lib/active_record/relation/where_clause_factory.rb +47 -8
- data/lib/active_record/result.rb +29 -31
- data/lib/active_record/runtime_registry.rb +3 -3
- data/lib/active_record/sanitization.rb +182 -197
- data/lib/active_record/schema.rb +3 -3
- data/lib/active_record/schema_dumper.rb +14 -37
- data/lib/active_record/schema_migration.rb +3 -3
- data/lib/active_record/scoping.rb +9 -10
- data/lib/active_record/scoping/default.rb +87 -91
- data/lib/active_record/scoping/named.rb +16 -28
- data/lib/active_record/secure_token.rb +2 -2
- data/lib/active_record/statement_cache.rb +13 -15
- data/lib/active_record/store.rb +31 -32
- data/lib/active_record/suppressor.rb +2 -1
- data/lib/active_record/table_metadata.rb +9 -5
- data/lib/active_record/tasks/database_tasks.rb +72 -65
- data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
- data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
- data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
- data/lib/active_record/timestamp.rb +39 -25
- data/lib/active_record/touch_later.rb +1 -2
- data/lib/active_record/transactions.rb +98 -110
- data/lib/active_record/type.rb +17 -13
- data/lib/active_record/type/adapter_specific_registry.rb +46 -42
- data/lib/active_record/type/decimal_without_scale.rb +9 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
- data/lib/active_record/type/serialized.rb +8 -8
- data/lib/active_record/type/text.rb +9 -0
- data/lib/active_record/type/time.rb +0 -1
- data/lib/active_record/type/type_map.rb +11 -15
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type_caster.rb +2 -2
- data/lib/active_record/type_caster/connection.rb +8 -6
- data/lib/active_record/type_caster/map.rb +3 -1
- data/lib/active_record/validations.rb +4 -4
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +8 -39
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record.rb +4 -4
- data/lib/rails/generators/active_record/migration.rb +2 -2
- data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
- metadata +22 -13
- data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
data/lib/active_record/base.rb
CHANGED
@@ -1,25 +1,26 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require
|
20
|
-
require
|
21
|
-
require
|
22
|
-
require
|
1
|
+
require "yaml"
|
2
|
+
require "active_support/benchmarkable"
|
3
|
+
require "active_support/dependencies"
|
4
|
+
require "active_support/descendants_tracker"
|
5
|
+
require "active_support/time"
|
6
|
+
require "active_support/core_ext/module/attribute_accessors"
|
7
|
+
require "active_support/core_ext/array/extract_options"
|
8
|
+
require "active_support/core_ext/hash/deep_merge"
|
9
|
+
require "active_support/core_ext/hash/slice"
|
10
|
+
require "active_support/core_ext/hash/transform_values"
|
11
|
+
require "active_support/core_ext/string/behavior"
|
12
|
+
require "active_support/core_ext/kernel/singleton_class"
|
13
|
+
require "active_support/core_ext/module/introspection"
|
14
|
+
require "active_support/core_ext/object/duplicable"
|
15
|
+
require "active_support/core_ext/class/subclasses"
|
16
|
+
require "active_record/attribute_decorators"
|
17
|
+
require "active_record/define_callbacks"
|
18
|
+
require "active_record/errors"
|
19
|
+
require "active_record/log_subscriber"
|
20
|
+
require "active_record/explain_subscriber"
|
21
|
+
require "active_record/relation/delegation"
|
22
|
+
require "active_record/attributes"
|
23
|
+
require "active_record/type_caster"
|
23
24
|
|
24
25
|
module ActiveRecord #:nodoc:
|
25
26
|
# = Active Record
|
@@ -303,6 +304,7 @@ module ActiveRecord #:nodoc:
|
|
303
304
|
include AttributeDecorators
|
304
305
|
include Locking::Optimistic
|
305
306
|
include Locking::Pessimistic
|
307
|
+
include DefineCallbacks
|
306
308
|
include AttributeMethods
|
307
309
|
include Callbacks
|
308
310
|
include Timestamp
|
@@ -225,6 +225,55 @@ module ActiveRecord
|
|
225
225
|
#
|
226
226
|
# This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
|
227
227
|
#
|
228
|
+
# Also, there are cases when you want several callbacks of the same type to
|
229
|
+
# be executed in order.
|
230
|
+
#
|
231
|
+
# For example:
|
232
|
+
#
|
233
|
+
# class Topic
|
234
|
+
# has_many :children
|
235
|
+
#
|
236
|
+
# after_save :log_children
|
237
|
+
# after_save :do_something_else
|
238
|
+
#
|
239
|
+
# private
|
240
|
+
#
|
241
|
+
# def log_chidren
|
242
|
+
# # Child processing
|
243
|
+
# end
|
244
|
+
#
|
245
|
+
# def do_something_else
|
246
|
+
# # Something else
|
247
|
+
# end
|
248
|
+
# end
|
249
|
+
#
|
250
|
+
# In this case the +log_children+ gets executed before +do_something_else+.
|
251
|
+
# The same applies to all non-transactional callbacks.
|
252
|
+
#
|
253
|
+
# In case there are multiple transactional callbacks as seen below, the order
|
254
|
+
# is reversed.
|
255
|
+
#
|
256
|
+
# For example:
|
257
|
+
#
|
258
|
+
# class Topic
|
259
|
+
# has_many :children
|
260
|
+
#
|
261
|
+
# after_commit :log_children
|
262
|
+
# after_commit :do_something_else
|
263
|
+
#
|
264
|
+
# private
|
265
|
+
#
|
266
|
+
# def log_chidren
|
267
|
+
# # Child processing
|
268
|
+
# end
|
269
|
+
#
|
270
|
+
# def do_something_else
|
271
|
+
# # Something else
|
272
|
+
# end
|
273
|
+
# end
|
274
|
+
#
|
275
|
+
# In this case the +do_something_else+ gets executed before +log_children+.
|
276
|
+
#
|
228
277
|
# == \Transactions
|
229
278
|
#
|
230
279
|
# The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
|
@@ -265,17 +314,6 @@ module ActiveRecord
|
|
265
314
|
:before_destroy, :around_destroy, :after_destroy, :after_commit, :after_rollback
|
266
315
|
]
|
267
316
|
|
268
|
-
module ClassMethods # :nodoc:
|
269
|
-
include ActiveModel::Callbacks
|
270
|
-
end
|
271
|
-
|
272
|
-
included do
|
273
|
-
include ActiveModel::Validations::Callbacks
|
274
|
-
|
275
|
-
define_model_callbacks :initialize, :find, :touch, :only => :after
|
276
|
-
define_model_callbacks :save, :create, :update, :destroy
|
277
|
-
end
|
278
|
-
|
279
317
|
def destroy #:nodoc:
|
280
318
|
@_destroy_callback_already_called ||= false
|
281
319
|
return if @_destroy_callback_already_called
|
@@ -294,15 +332,15 @@ module ActiveRecord
|
|
294
332
|
|
295
333
|
private
|
296
334
|
|
297
|
-
def create_or_update(*)
|
335
|
+
def create_or_update(*)
|
298
336
|
_run_save_callbacks { super }
|
299
337
|
end
|
300
338
|
|
301
|
-
def _create_record
|
339
|
+
def _create_record
|
302
340
|
_run_create_callbacks { super }
|
303
341
|
end
|
304
342
|
|
305
|
-
def _update_record(*)
|
343
|
+
def _update_record(*)
|
306
344
|
_run_update_callbacks { super }
|
307
345
|
end
|
308
346
|
end
|
@@ -1,12 +1,12 @@
|
|
1
|
-
require
|
1
|
+
require "yaml"
|
2
2
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Coders # :nodoc:
|
5
5
|
class YAMLColumn # :nodoc:
|
6
|
-
|
7
6
|
attr_accessor :object_class
|
8
7
|
|
9
|
-
def initialize(object_class = Object)
|
8
|
+
def initialize(attr_name, object_class = Object)
|
9
|
+
@attr_name = attr_name
|
10
10
|
@object_class = object_class
|
11
11
|
check_arity_of_constructor
|
12
12
|
end
|
@@ -14,37 +14,35 @@ module ActiveRecord
|
|
14
14
|
def dump(obj)
|
15
15
|
return if obj.nil?
|
16
16
|
|
17
|
-
assert_valid_value(obj)
|
17
|
+
assert_valid_value(obj, action: "dump")
|
18
18
|
YAML.dump obj
|
19
19
|
end
|
20
20
|
|
21
21
|
def load(yaml)
|
22
22
|
return object_class.new if object_class != Object && yaml.nil?
|
23
|
-
return yaml unless yaml.is_a?(String) && yaml
|
23
|
+
return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)
|
24
24
|
obj = YAML.load(yaml)
|
25
25
|
|
26
|
-
assert_valid_value(obj)
|
26
|
+
assert_valid_value(obj, action: "load")
|
27
27
|
obj ||= object_class.new if object_class != Object
|
28
28
|
|
29
29
|
obj
|
30
30
|
end
|
31
31
|
|
32
|
-
def assert_valid_value(obj)
|
32
|
+
def assert_valid_value(obj, action:)
|
33
33
|
unless obj.nil? || obj.is_a?(object_class)
|
34
34
|
raise SerializationTypeMismatch,
|
35
|
-
"
|
35
|
+
"can't #{action} `#{@attr_name}`: was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
|
42
|
-
begin
|
41
|
+
def check_arity_of_constructor
|
43
42
|
load(nil)
|
44
43
|
rescue ArgumentError
|
45
44
|
raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
|
46
45
|
end
|
47
|
-
end
|
48
46
|
end
|
49
47
|
end
|
50
48
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module CollectionCacheKey
|
3
|
-
|
4
3
|
def collection_cache_key(collection = all, timestamp_column = :updated_at) # :nodoc:
|
5
4
|
query_signature = Digest::MD5.hexdigest(collection.to_sql)
|
6
5
|
key = "#{collection.model_name.cache_key}/query-#{query_signature}"
|
@@ -8,27 +7,17 @@ module ActiveRecord
|
|
8
7
|
if collection.loaded?
|
9
8
|
size = collection.size
|
10
9
|
if size > 0
|
11
|
-
timestamp = collection.max_by(×tamp_column).
|
10
|
+
timestamp = collection.max_by(×tamp_column).public_send(timestamp_column)
|
12
11
|
end
|
13
12
|
else
|
14
13
|
column_type = type_for_attribute(timestamp_column.to_s)
|
15
14
|
column = "#{connection.quote_table_name(collection.table_name)}.#{connection.quote_column_name(timestamp_column)}"
|
16
|
-
select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
|
17
|
-
|
18
|
-
if collection.limit_value || collection.offset_value
|
19
|
-
query = collection.spawn
|
20
|
-
query.select_values = [column]
|
21
|
-
subquery_alias = "subquery_for_cache_key"
|
22
|
-
subquery_column = "#{subquery_alias}.#{timestamp_column}"
|
23
|
-
subquery = query.arel.as(subquery_alias)
|
24
|
-
arel = Arel::SelectManager.new(query.engine).project(select_values % subquery_column).from(subquery)
|
25
|
-
else
|
26
|
-
query = collection.unscope(:order)
|
27
|
-
query.select_values = [select_values % column]
|
28
|
-
arel = query.arel
|
29
|
-
end
|
30
15
|
|
31
|
-
|
16
|
+
query = collection
|
17
|
+
.unscope(:select)
|
18
|
+
.select("COUNT(*) AS #{connection.quote_column_name("size")}", "MAX(#{column}) AS timestamp")
|
19
|
+
.unscope(:order)
|
20
|
+
result = connection.select_one(query)
|
32
21
|
|
33
22
|
if result.blank?
|
34
23
|
size = 0
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "thread"
|
2
|
+
require "concurrent/map"
|
3
|
+
require "monitor"
|
4
4
|
|
5
5
|
module ActiveRecord
|
6
6
|
# Raised when a connection could not be obtained within the connection
|
@@ -69,12 +69,12 @@ module ActiveRecord
|
|
69
69
|
# threads, which can occur if a programmer forgets to close a
|
70
70
|
# connection at the end of a thread or a thread dies unexpectedly.
|
71
71
|
# Regardless of this setting, the Reaper will be invoked before every
|
72
|
-
# blocking wait. (Default nil
|
72
|
+
# blocking wait. (Default +nil+, which means don't schedule the Reaper).
|
73
73
|
#
|
74
74
|
#--
|
75
75
|
# Synchronization policy:
|
76
76
|
# * all public methods can be called outside +synchronize+
|
77
|
-
# * access to these
|
77
|
+
# * access to these instance variables needs to be in +synchronize+:
|
78
78
|
# * @connections
|
79
79
|
# * @now_connecting
|
80
80
|
# * private methods that require being called in a +synchronize+ blocks
|
@@ -116,7 +116,7 @@ module ActiveRecord
|
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
-
# If +element+ is in the queue, remove and return it, or nil
|
119
|
+
# If +element+ is in the queue, remove and return it, or +nil+.
|
120
120
|
def delete(element)
|
121
121
|
synchronize do
|
122
122
|
@queue.delete(element)
|
@@ -135,7 +135,7 @@ module ActiveRecord
|
|
135
135
|
# If +timeout+ is not given, remove and return the head the
|
136
136
|
# queue if the number of available elements is strictly
|
137
137
|
# greater than the number of threads currently waiting (that
|
138
|
-
# is, don't jump ahead in line). Otherwise, return nil
|
138
|
+
# is, don't jump ahead in line). Otherwise, return +nil+.
|
139
139
|
#
|
140
140
|
# If +timeout+ is given, block if there is no element
|
141
141
|
# available, waiting up to +timeout+ seconds for an element to
|
@@ -150,61 +150,61 @@ module ActiveRecord
|
|
150
150
|
|
151
151
|
private
|
152
152
|
|
153
|
-
|
154
|
-
|
155
|
-
|
153
|
+
def internal_poll(timeout)
|
154
|
+
no_wait_poll || (timeout && wait_poll(timeout))
|
155
|
+
end
|
156
156
|
|
157
|
-
|
158
|
-
|
159
|
-
|
157
|
+
def synchronize(&block)
|
158
|
+
@lock.synchronize(&block)
|
159
|
+
end
|
160
160
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
161
|
+
# Test if the queue currently contains any elements.
|
162
|
+
def any?
|
163
|
+
!@queue.empty?
|
164
|
+
end
|
165
165
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
173
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
174
|
+
# Removes and returns the head of the queue if possible, or +nil+.
|
175
|
+
def remove
|
176
|
+
@queue.shift
|
177
|
+
end
|
178
178
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
185
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
190
|
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
191
|
+
t0 = Time.now
|
192
|
+
elapsed = 0
|
193
|
+
loop do
|
194
|
+
@cond.wait(timeout - elapsed)
|
195
195
|
|
196
|
-
|
196
|
+
return remove if any?
|
197
197
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
203
204
|
end
|
205
|
+
ensure
|
206
|
+
@num_waiting -= 1
|
204
207
|
end
|
205
|
-
ensure
|
206
|
-
@num_waiting -= 1
|
207
|
-
end
|
208
208
|
end
|
209
209
|
|
210
210
|
# Adds the ability to turn a basic fair FIFO queue into one
|
@@ -274,15 +274,15 @@ module ActiveRecord
|
|
274
274
|
include BiasableQueue
|
275
275
|
|
276
276
|
private
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
277
|
+
def internal_poll(timeout)
|
278
|
+
conn = super
|
279
|
+
conn.lease if conn
|
280
|
+
conn
|
281
|
+
end
|
282
282
|
end
|
283
283
|
|
284
284
|
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
|
285
|
-
# A reaper instantiated with a nil frequency will never reap the
|
285
|
+
# A reaper instantiated with a +nil+ frequency will never reap the
|
286
286
|
# connection pool.
|
287
287
|
#
|
288
288
|
# Configure the frequency by setting "reaping_frequency" in your
|
@@ -298,7 +298,7 @@ module ActiveRecord
|
|
298
298
|
def run
|
299
299
|
return unless frequency
|
300
300
|
Thread.new(frequency, pool) { |t, p|
|
301
|
-
|
301
|
+
loop do
|
302
302
|
sleep t
|
303
303
|
p.reap
|
304
304
|
end
|
@@ -330,17 +330,17 @@ module ActiveRecord
|
|
330
330
|
# default max pool size to 5
|
331
331
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
332
332
|
|
333
|
-
#
|
334
|
-
#
|
335
|
-
# registry of which thread owns which connection
|
336
|
-
# +connection.owner+ attr on each +connection+ instance.
|
333
|
+
# This variable tracks the cache of threads mapped to reserved connections, with the
|
334
|
+
# sole purpose of speeding up the +connection+ method. It is not the authoritative
|
335
|
+
# registry of which thread owns which connection. Connection ownership is tracked by
|
336
|
+
# the +connection.owner+ attr on each +connection+ instance.
|
337
337
|
# The invariant works like this: if there is mapping of <tt>thread => conn</tt>,
|
338
|
-
# then that +thread+ does indeed own that +conn
|
339
|
-
# mapping does not mean that the +thread+ doesn't own the said connection
|
338
|
+
# then that +thread+ does indeed own that +conn+. However, an absence of a such
|
339
|
+
# mapping does not mean that the +thread+ doesn't own the said connection. In
|
340
340
|
# that case +conn.owner+ attr should be consulted.
|
341
341
|
# Access and modification of +@thread_cached_conns+ does not require
|
342
342
|
# synchronization.
|
343
|
-
@thread_cached_conns = Concurrent::Map.new(:
|
343
|
+
@thread_cached_conns = Concurrent::Map.new(initial_capacity: @size)
|
344
344
|
|
345
345
|
@connections = []
|
346
346
|
@automatic_reconnect = true
|
@@ -353,6 +353,16 @@ module ActiveRecord
|
|
353
353
|
@threads_blocking_new_connections = 0
|
354
354
|
|
355
355
|
@available = ConnectionLeasingQueue.new self
|
356
|
+
|
357
|
+
@lock_thread = false
|
358
|
+
end
|
359
|
+
|
360
|
+
def lock_thread=(lock_thread)
|
361
|
+
if lock_thread
|
362
|
+
@lock_thread = Thread.current
|
363
|
+
else
|
364
|
+
@lock_thread = nil
|
365
|
+
end
|
356
366
|
end
|
357
367
|
|
358
368
|
# Retrieve the connection associated with the current thread, or call
|
@@ -361,13 +371,13 @@ module ActiveRecord
|
|
361
371
|
# #connection can be called any number of times; the connection is
|
362
372
|
# held in a cache keyed by a thread.
|
363
373
|
def connection
|
364
|
-
@thread_cached_conns[connection_cache_key(Thread.current)] ||= checkout
|
374
|
+
@thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
|
365
375
|
end
|
366
376
|
|
367
|
-
#
|
377
|
+
# Returns true if there is an open connection being used for the current thread.
|
368
378
|
#
|
369
379
|
# This method only works for connections that have been obtained through
|
370
|
-
# #connection or #with_connection methods
|
380
|
+
# #connection or #with_connection methods. Connections obtained through
|
371
381
|
# #checkout will not be detected by #active_connection?
|
372
382
|
def active_connection?
|
373
383
|
@thread_cached_conns[connection_cache_key(Thread.current)]
|
@@ -429,9 +439,9 @@ module ActiveRecord
|
|
429
439
|
|
430
440
|
# Disconnects all connections in the pool, and clears the pool.
|
431
441
|
#
|
432
|
-
# The pool first tries to gain ownership of all connections
|
442
|
+
# The pool first tries to gain ownership of all connections. If unable to
|
433
443
|
# do so within a timeout interval (default duration is
|
434
|
-
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool is forcefully
|
444
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool is forcefully
|
435
445
|
# disconnected without any regard for other connection owning threads.
|
436
446
|
def disconnect!
|
437
447
|
disconnect(false)
|
@@ -463,9 +473,9 @@ module ActiveRecord
|
|
463
473
|
# Clears the cache which maps classes and re-connects connections that
|
464
474
|
# require reloading.
|
465
475
|
#
|
466
|
-
# The pool first tries to gain ownership of all connections
|
476
|
+
# The pool first tries to gain ownership of all connections. If unable to
|
467
477
|
# do so within a timeout interval (default duration is
|
468
|
-
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), the pool forcefully
|
478
|
+
# <tt>spec.config[:checkout_timeout] * 2</tt> seconds), then the pool forcefully
|
469
479
|
# clears the cache and reloads connections without any regard for other
|
470
480
|
# connection owning threads.
|
471
481
|
def clear_reloadable_connections!
|
@@ -519,20 +529,20 @@ module ActiveRecord
|
|
519
529
|
@available.delete conn
|
520
530
|
|
521
531
|
# @available.any_waiting? => true means that prior to removing this
|
522
|
-
# conn, the pool was at its max size (@connections.size == @size)
|
523
|
-
#
|
532
|
+
# conn, the pool was at its max size (@connections.size == @size).
|
533
|
+
# This would mean that any threads stuck waiting in the queue wouldn't
|
524
534
|
# know they could checkout_new_connection, so let's do it for them.
|
525
535
|
# Because condition-wait loop is encapsulated in the Queue class
|
526
536
|
# (that in turn is oblivious to ConnectionPool implementation), threads
|
527
|
-
# that are "stuck" there are helpless
|
537
|
+
# that are "stuck" there are helpless. They have no way of creating
|
528
538
|
# new connections and are completely reliant on us feeding available
|
529
539
|
# connections into the Queue.
|
530
540
|
needs_new_connection = @available.any_waiting?
|
531
541
|
end
|
532
542
|
|
533
543
|
# This is intentionally done outside of the synchronized section as we
|
534
|
-
# would like not to hold the main mutex while checking out new connections
|
535
|
-
#
|
544
|
+
# would like not to hold the main mutex while checking out new connections.
|
545
|
+
# Thus there is some chance that needs_new_connection information is now
|
536
546
|
# stale, we can live with that (bulk_make_new_connections will make
|
537
547
|
# sure not to exceed the pool's @size limit).
|
538
548
|
bulk_make_new_connections(1) if needs_new_connection
|
@@ -564,225 +574,243 @@ module ActiveRecord
|
|
564
574
|
@available.num_waiting
|
565
575
|
end
|
566
576
|
|
567
|
-
|
568
|
-
|
569
|
-
#
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
583
|
-
# This hook-in method allows for easier monkey-patching fixes needed by
|
584
|
-
# JRuby users that use Fibers.
|
585
|
-
def connection_cache_key(thread)
|
586
|
-
thread
|
587
|
-
end
|
588
|
-
|
589
|
-
# Take control of all existing connections so a "group" action such as
|
590
|
-
# reload/disconnect can be performed safely. It is no longer enough to
|
591
|
-
# wrap it in +synchronize+ because some pool's actions are allowed
|
592
|
-
# to be performed outside of the main +synchronize+ block.
|
593
|
-
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
594
|
-
with_new_connections_blocked do
|
595
|
-
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
596
|
-
yield
|
577
|
+
# Return connection pool's usage statistic
|
578
|
+
# Example:
|
579
|
+
#
|
580
|
+
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
|
581
|
+
def stat
|
582
|
+
synchronize do
|
583
|
+
{
|
584
|
+
size: size,
|
585
|
+
connections: @connections.size,
|
586
|
+
busy: @connections.count { |c| c.in_use? && c.owner.alive? },
|
587
|
+
dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
|
588
|
+
idle: @connections.count { |c| !c.in_use? },
|
589
|
+
waiting: num_waiting_in_queue,
|
590
|
+
checkout_timeout: checkout_timeout
|
591
|
+
}
|
597
592
|
end
|
598
593
|
end
|
599
594
|
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
@available.with_a_bias_for(Thread.current) do
|
610
|
-
while true
|
611
|
-
synchronize do
|
612
|
-
return if collected_conns.size == @connections.size && @now_connecting == 0
|
613
|
-
remaining_timeout = timeout_time - Time.now
|
614
|
-
remaining_timeout = 0 if remaining_timeout < 0
|
615
|
-
conn = checkout_for_exclusive_access(remaining_timeout)
|
616
|
-
collected_conns << conn
|
617
|
-
newly_checked_out << conn
|
595
|
+
private
|
596
|
+
#--
|
597
|
+
# this is unfortunately not concurrent
|
598
|
+
def bulk_make_new_connections(num_new_conns_needed)
|
599
|
+
num_new_conns_needed.times do
|
600
|
+
# try_to_checkout_new_connection will not exceed pool's @size limit
|
601
|
+
if new_conn = try_to_checkout_new_connection
|
602
|
+
# make the new_conn available to the starving threads stuck @available Queue
|
603
|
+
checkin(new_conn)
|
618
604
|
end
|
619
605
|
end
|
620
606
|
end
|
621
|
-
rescue ExclusiveConnectionTimeoutError
|
622
|
-
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
|
623
|
-
# timeouts and are expected to just give up: we've obtained as many connections
|
624
|
-
# as possible, note that in a case like that we don't return any of the
|
625
|
-
# +newly_checked_out+ connections.
|
626
607
|
|
627
|
-
|
628
|
-
|
629
|
-
|
608
|
+
#--
|
609
|
+
# From the discussion on GitHub:
|
610
|
+
# https://github.com/rails/rails/pull/14938#commitcomment-6601951
|
611
|
+
# This hook-in method allows for easier monkey-patching fixes needed by
|
612
|
+
# JRuby users that use Fibers.
|
613
|
+
def connection_cache_key(thread)
|
614
|
+
thread
|
630
615
|
end
|
631
|
-
|
632
|
-
#
|
633
|
-
#
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
616
|
+
|
617
|
+
# Take control of all existing connections so a "group" action such as
|
618
|
+
# reload/disconnect can be performed safely. It is no longer enough to
|
619
|
+
# wrap it in +synchronize+ because some pool's actions are allowed
|
620
|
+
# to be performed outside of the main +synchronize+ block.
|
621
|
+
def with_exclusively_acquired_all_connections(raise_on_acquisition_timeout = true)
|
622
|
+
with_new_connections_blocked do
|
623
|
+
attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout)
|
624
|
+
yield
|
625
|
+
end
|
641
626
|
end
|
642
|
-
end
|
643
627
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
650
|
-
# rescue block, because doing so would put it outside of synchronize section, without
|
651
|
-
# being in a critical section thread_report might become inaccurate
|
652
|
-
msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
628
|
+
def attempt_to_checkout_all_existing_connections(raise_on_acquisition_timeout = true)
|
629
|
+
collected_conns = synchronize do
|
630
|
+
# account for our own connections
|
631
|
+
@connections.select { |conn| conn.owner == Thread.current }
|
632
|
+
end
|
653
633
|
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
634
|
+
newly_checked_out = []
|
635
|
+
timeout_time = Time.now + (@checkout_timeout * 2)
|
636
|
+
|
637
|
+
@available.with_a_bias_for(Thread.current) do
|
638
|
+
loop do
|
639
|
+
synchronize do
|
640
|
+
return if collected_conns.size == @connections.size && @now_connecting == 0
|
641
|
+
remaining_timeout = timeout_time - Time.now
|
642
|
+
remaining_timeout = 0 if remaining_timeout < 0
|
643
|
+
conn = checkout_for_exclusive_access(remaining_timeout)
|
644
|
+
collected_conns << conn
|
645
|
+
newly_checked_out << conn
|
646
|
+
end
|
647
|
+
end
|
648
|
+
end
|
649
|
+
rescue ExclusiveConnectionTimeoutError
|
650
|
+
# <tt>raise_on_acquisition_timeout == false</tt> means we are directed to ignore any
|
651
|
+
# timeouts and are expected to just give up: we've obtained as many connections
|
652
|
+
# as possible, note that in a case like that we don't return any of the
|
653
|
+
# +newly_checked_out+ connections.
|
654
|
+
|
655
|
+
if raise_on_acquisition_timeout
|
656
|
+
release_newly_checked_out = true
|
657
|
+
raise
|
658
|
+
end
|
659
|
+
rescue Exception # if something else went wrong
|
660
|
+
# this can't be a "naked" rescue, because we have should return conns
|
661
|
+
# even for non-StandardErrors
|
662
|
+
release_newly_checked_out = true
|
663
|
+
raise
|
664
|
+
ensure
|
665
|
+
if release_newly_checked_out && newly_checked_out
|
666
|
+
# releasing only those conns that were checked out in this method, conns
|
667
|
+
# checked outside this method (before it was called) are not for us to release
|
668
|
+
newly_checked_out.each { |conn| checkin(conn) }
|
658
669
|
end
|
659
670
|
end
|
660
671
|
|
661
|
-
|
672
|
+
#--
|
673
|
+
# Must be called in a synchronize block.
|
674
|
+
def checkout_for_exclusive_access(checkout_timeout)
|
675
|
+
checkout(checkout_timeout)
|
676
|
+
rescue ConnectionTimeoutError
|
677
|
+
# this block can't be easily moved into attempt_to_checkout_all_existing_connections's
|
678
|
+
# rescue block, because doing so would put it outside of synchronize section, without
|
679
|
+
# being in a critical section thread_report might become inaccurate
|
680
|
+
msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds"
|
681
|
+
|
682
|
+
thread_report = []
|
683
|
+
@connections.each do |conn|
|
684
|
+
unless conn.owner == Thread.current
|
685
|
+
thread_report << "#{conn} is owned by #{conn.owner}"
|
686
|
+
end
|
687
|
+
end
|
662
688
|
|
663
|
-
|
664
|
-
end
|
689
|
+
msg << " (#{thread_report.join(', ')})" if thread_report.any?
|
665
690
|
|
666
|
-
|
667
|
-
synchronize do
|
668
|
-
@threads_blocking_new_connections += 1
|
691
|
+
raise ExclusiveConnectionTimeoutError, msg
|
669
692
|
end
|
670
693
|
|
671
|
-
|
672
|
-
|
673
|
-
|
694
|
+
def with_new_connections_blocked
|
695
|
+
synchronize do
|
696
|
+
@threads_blocking_new_connections += 1
|
697
|
+
end
|
674
698
|
|
675
|
-
|
676
|
-
|
699
|
+
yield
|
700
|
+
ensure
|
701
|
+
num_new_conns_required = 0
|
677
702
|
|
678
|
-
|
679
|
-
@
|
703
|
+
synchronize do
|
704
|
+
@threads_blocking_new_connections -= 1
|
680
705
|
|
681
|
-
|
706
|
+
if @threads_blocking_new_connections.zero?
|
707
|
+
@available.clear
|
682
708
|
|
683
|
-
|
684
|
-
|
709
|
+
num_new_conns_required = num_waiting_in_queue
|
710
|
+
|
711
|
+
@connections.each do |conn|
|
712
|
+
next if conn.in_use?
|
685
713
|
|
686
|
-
|
687
|
-
|
714
|
+
@available.add conn
|
715
|
+
num_new_conns_required -= 1
|
716
|
+
end
|
688
717
|
end
|
689
718
|
end
|
690
|
-
end
|
691
719
|
|
692
|
-
|
693
|
-
end
|
694
|
-
|
695
|
-
# Acquire a connection by one of 1) immediately removing one
|
696
|
-
# from the queue of available connections, 2) creating a new
|
697
|
-
# connection if the pool is not at capacity, 3) waiting on the
|
698
|
-
# queue for a connection to become available.
|
699
|
-
#
|
700
|
-
# Raises:
|
701
|
-
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
|
702
|
-
#
|
703
|
-
#--
|
704
|
-
# Implementation detail: the connection returned by +acquire_connection+
|
705
|
-
# will already be "+connection.lease+ -ed" to the current thread.
|
706
|
-
def acquire_connection(checkout_timeout)
|
707
|
-
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
|
708
|
-
# +conn.lease+ the returned connection (and to do this in a +synchronized+
|
709
|
-
# section), this is not the cleanest implementation, as ideally we would
|
710
|
-
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
|
711
|
-
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
712
|
-
# of the said methods and avoid an additional +synchronize+ overhead.
|
713
|
-
if conn = @available.poll || try_to_checkout_new_connection
|
714
|
-
conn
|
715
|
-
else
|
716
|
-
reap
|
717
|
-
@available.poll(checkout_timeout)
|
720
|
+
bulk_make_new_connections(num_new_conns_required) if num_new_conns_required > 0
|
718
721
|
end
|
719
|
-
end
|
720
722
|
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
723
|
+
# Acquire a connection by one of 1) immediately removing one
|
724
|
+
# from the queue of available connections, 2) creating a new
|
725
|
+
# connection if the pool is not at capacity, 3) waiting on the
|
726
|
+
# queue for a connection to become available.
|
727
|
+
#
|
728
|
+
# Raises:
|
729
|
+
# - ActiveRecord::ConnectionTimeoutError if a connection could not be acquired
|
730
|
+
#
|
731
|
+
#--
|
732
|
+
# Implementation detail: the connection returned by +acquire_connection+
|
733
|
+
# will already be "+connection.lease+ -ed" to the current thread.
|
734
|
+
def acquire_connection(checkout_timeout)
|
735
|
+
# NOTE: we rely on +@available.poll+ and +try_to_checkout_new_connection+ to
|
736
|
+
# +conn.lease+ the returned connection (and to do this in a +synchronized+
|
737
|
+
# section). This is not the cleanest implementation, as ideally we would
|
738
|
+
# <tt>synchronize { conn.lease }</tt> in this method, but by leaving it to +@available.poll+
|
739
|
+
# and +try_to_checkout_new_connection+ we can piggyback on +synchronize+ sections
|
740
|
+
# of the said methods and avoid an additional +synchronize+ overhead.
|
741
|
+
if conn = @available.poll || try_to_checkout_new_connection
|
742
|
+
conn
|
743
|
+
else
|
744
|
+
reap
|
745
|
+
@available.poll(checkout_timeout)
|
746
|
+
end
|
747
|
+
end
|
727
748
|
|
728
|
-
|
729
|
-
|
730
|
-
|
749
|
+
#--
|
750
|
+
# if owner_thread param is omitted, this must be called in synchronize block
|
751
|
+
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
752
|
+
@thread_cached_conns.delete_pair(connection_cache_key(owner_thread), conn)
|
731
753
|
end
|
732
|
-
|
754
|
+
alias_method :release, :remove_connection_from_thread_cache
|
733
755
|
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
# Implementation constraint: a newly established connection returned by this
|
738
|
-
# method must be in the +.leased+ state.
|
739
|
-
def try_to_checkout_new_connection
|
740
|
-
# first in synchronized section check if establishing new conns is allowed
|
741
|
-
# and increment @now_connecting, to prevent overstepping this pool's @size
|
742
|
-
# constraint
|
743
|
-
do_checkout = synchronize do
|
744
|
-
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
|
745
|
-
@now_connecting += 1
|
756
|
+
def new_connection
|
757
|
+
Base.send(spec.adapter_method, spec.config).tap do |conn|
|
758
|
+
conn.schema_cache = schema_cache.dup if schema_cache
|
746
759
|
end
|
747
760
|
end
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
761
|
+
|
762
|
+
# If the pool is not at a +@size+ limit, establish new connection. Connecting
|
763
|
+
# to the DB is done outside main synchronized section.
|
764
|
+
#--
|
765
|
+
# Implementation constraint: a newly established connection returned by this
|
766
|
+
# method must be in the +.leased+ state.
|
767
|
+
def try_to_checkout_new_connection
|
768
|
+
# first in synchronized section check if establishing new conns is allowed
|
769
|
+
# and increment @now_connecting, to prevent overstepping this pool's @size
|
770
|
+
# constraint
|
771
|
+
do_checkout = synchronize do
|
772
|
+
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
|
773
|
+
@now_connecting += 1
|
774
|
+
end
|
775
|
+
end
|
776
|
+
if do_checkout
|
777
|
+
begin
|
778
|
+
# if successfully incremented @now_connecting establish new connection
|
779
|
+
# outside of synchronized section
|
780
|
+
conn = checkout_new_connection
|
781
|
+
ensure
|
782
|
+
synchronize do
|
783
|
+
if conn
|
784
|
+
adopt_connection(conn)
|
785
|
+
# returned conn needs to be already leased
|
786
|
+
conn.lease
|
787
|
+
end
|
788
|
+
@now_connecting -= 1
|
759
789
|
end
|
760
|
-
@now_connecting -= 1
|
761
790
|
end
|
762
791
|
end
|
763
792
|
end
|
764
|
-
end
|
765
793
|
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
794
|
+
def adopt_connection(conn)
|
795
|
+
conn.pool = self
|
796
|
+
@connections << conn
|
797
|
+
end
|
770
798
|
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
799
|
+
def checkout_new_connection
|
800
|
+
raise ConnectionNotEstablished unless @automatic_reconnect
|
801
|
+
new_connection
|
802
|
+
end
|
775
803
|
|
776
|
-
|
777
|
-
|
778
|
-
|
804
|
+
def checkout_and_verify(c)
|
805
|
+
c._run_checkout_callbacks do
|
806
|
+
c.verify!
|
807
|
+
end
|
808
|
+
c
|
809
|
+
rescue
|
810
|
+
remove c
|
811
|
+
c.disconnect!
|
812
|
+
raise
|
779
813
|
end
|
780
|
-
c
|
781
|
-
rescue
|
782
|
-
remove c
|
783
|
-
c.disconnect!
|
784
|
-
raise
|
785
|
-
end
|
786
814
|
end
|
787
815
|
|
788
816
|
# ConnectionHandler is a collection of ConnectionPool objects. It is used
|
@@ -830,13 +858,13 @@ module ActiveRecord
|
|
830
858
|
# should use.
|
831
859
|
#
|
832
860
|
# The ConnectionHandler class is not coupled with the Active models, as it has no knowlodge
|
833
|
-
# about the model. The model
|
861
|
+
# about the model. The model needs to pass a specification name to the handler,
|
834
862
|
# in order to lookup the correct connection pool.
|
835
863
|
class ConnectionHandler
|
836
864
|
def initialize
|
837
865
|
# These caches are keyed by spec.name (ConnectionSpecification#name).
|
838
|
-
@owner_to_pool = Concurrent::Map.new(:
|
839
|
-
h[k] = Concurrent::Map.new(:
|
866
|
+
@owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
|
867
|
+
h[k] = Concurrent::Map.new(initial_capacity: 2)
|
840
868
|
end
|
841
869
|
end
|
842
870
|
|
@@ -845,8 +873,26 @@ module ActiveRecord
|
|
845
873
|
end
|
846
874
|
alias :connection_pools :connection_pool_list
|
847
875
|
|
848
|
-
def establish_connection(
|
849
|
-
|
876
|
+
def establish_connection(config)
|
877
|
+
resolver = ConnectionSpecification::Resolver.new(Base.configurations)
|
878
|
+
spec = resolver.spec(config)
|
879
|
+
|
880
|
+
remove_connection(spec.name)
|
881
|
+
|
882
|
+
message_bus = ActiveSupport::Notifications.instrumenter
|
883
|
+
payload = {
|
884
|
+
connection_id: object_id
|
885
|
+
}
|
886
|
+
if spec
|
887
|
+
payload[:spec_name] = spec.name
|
888
|
+
payload[:config] = spec.config
|
889
|
+
end
|
890
|
+
|
891
|
+
message_bus.instrument("!connection.active_record", payload) do
|
892
|
+
owner_to_pool[spec.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
893
|
+
end
|
894
|
+
|
895
|
+
owner_to_pool[spec.name]
|
850
896
|
end
|
851
897
|
|
852
898
|
# Returns true if there are any active connections among the connection
|
@@ -879,9 +925,9 @@ module ActiveRecord
|
|
879
925
|
# for (not necessarily the current class).
|
880
926
|
def retrieve_connection(spec_name) #:nodoc:
|
881
927
|
pool = retrieve_connection_pool(spec_name)
|
882
|
-
raise ConnectionNotEstablished, "No connection pool with
|
928
|
+
raise ConnectionNotEstablished, "No connection pool with '#{spec_name}' found." unless pool
|
883
929
|
conn = pool.connection
|
884
|
-
raise ConnectionNotEstablished, "No connection for #{spec_name} in connection pool" unless conn
|
930
|
+
raise ConnectionNotEstablished, "No connection for '#{spec_name}' in connection pool" unless conn
|
885
931
|
conn
|
886
932
|
end
|
887
933
|
|
@@ -894,7 +940,7 @@ module ActiveRecord
|
|
894
940
|
|
895
941
|
# Remove the connection for this class. This will close the active
|
896
942
|
# connection and the defined connection (if they exist). The result
|
897
|
-
# can be used as an argument for establish_connection, for easily
|
943
|
+
# can be used as an argument for #establish_connection, for easily
|
898
944
|
# re-establishing the connection.
|
899
945
|
def remove_connection(spec_name)
|
900
946
|
if pool = owner_to_pool.delete(spec_name)
|
@@ -904,22 +950,18 @@ module ActiveRecord
|
|
904
950
|
end
|
905
951
|
end
|
906
952
|
|
907
|
-
# Retrieving the connection pool happens a lot so we cache it in @
|
953
|
+
# Retrieving the connection pool happens a lot, so we cache it in @owner_to_pool.
|
908
954
|
# This makes retrieving the connection pool O(1) once the process is warm.
|
909
955
|
# When a connection is established or removed, we invalidate the cache.
|
910
|
-
#
|
911
|
-
# Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
|
912
|
-
# However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
|
913
|
-
# #fetch is significantly slower than #[]. So in the nil case, no caching will
|
914
|
-
# take place, but that's ok since the nil case is not the common one that we wish
|
915
|
-
# to optimise for.
|
916
956
|
def retrieve_connection_pool(spec_name)
|
917
957
|
owner_to_pool.fetch(spec_name) do
|
958
|
+
# Check if a connection was previously established in an ancestor process,
|
959
|
+
# which may have been forked.
|
918
960
|
if ancestor_pool = pool_from_any_process_for(spec_name)
|
919
961
|
# A connection was established in an ancestor process that must have
|
920
962
|
# subsequently forked. We can't reuse the connection, but we can copy
|
921
963
|
# the specification and establish a new connection with it.
|
922
|
-
establish_connection(ancestor_pool.spec).tap do |pool|
|
964
|
+
establish_connection(ancestor_pool.spec.to_hash).tap do |pool|
|
923
965
|
pool.schema_cache = ancestor_pool.schema_cache if ancestor_pool.schema_cache
|
924
966
|
end
|
925
967
|
else
|
@@ -930,14 +972,14 @@ module ActiveRecord
|
|
930
972
|
|
931
973
|
private
|
932
974
|
|
933
|
-
|
934
|
-
|
935
|
-
|
975
|
+
def owner_to_pool
|
976
|
+
@owner_to_pool[Process.pid]
|
977
|
+
end
|
936
978
|
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
979
|
+
def pool_from_any_process_for(spec_name)
|
980
|
+
owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[spec_name] }
|
981
|
+
owner_to_pool && owner_to_pool[spec_name]
|
982
|
+
end
|
941
983
|
end
|
942
984
|
end
|
943
985
|
end
|