activerecord 3.2.22.5 → 4.2.11.3
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 +1632 -609
- data/MIT-LICENSE +1 -1
- data/README.rdoc +37 -41
- data/examples/performance.rb +31 -19
- data/examples/simple.rb +4 -4
- data/lib/active_record/aggregations.rb +56 -42
- data/lib/active_record/association_relation.rb +35 -0
- data/lib/active_record/associations/alias_tracker.rb +47 -36
- data/lib/active_record/associations/association.rb +73 -55
- data/lib/active_record/associations/association_scope.rb +143 -82
- data/lib/active_record/associations/belongs_to_association.rb +65 -25
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
- data/lib/active_record/associations/builder/association.rb +125 -31
- data/lib/active_record/associations/builder/belongs_to.rb +89 -61
- data/lib/active_record/associations/builder/collection_association.rb +69 -49
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
- data/lib/active_record/associations/builder/has_many.rb +8 -64
- data/lib/active_record/associations/builder/has_one.rb +12 -51
- data/lib/active_record/associations/builder/singular_association.rb +23 -17
- data/lib/active_record/associations/collection_association.rb +251 -177
- data/lib/active_record/associations/collection_proxy.rb +963 -63
- data/lib/active_record/associations/foreign_association.rb +11 -0
- data/lib/active_record/associations/has_many_association.rb +113 -22
- data/lib/active_record/associations/has_many_through_association.rb +99 -39
- data/lib/active_record/associations/has_one_association.rb +43 -20
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +76 -107
- 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 +230 -156
- data/lib/active_record/associations/preloader/association.rb +96 -55
- data/lib/active_record/associations/preloader/collection_association.rb +3 -3
- data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
- data/lib/active_record/associations/preloader/has_one.rb +1 -1
- data/lib/active_record/associations/preloader/singular_association.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +62 -33
- data/lib/active_record/associations/preloader.rb +101 -79
- data/lib/active_record/associations/singular_association.rb +29 -13
- data/lib/active_record/associations/through_association.rb +30 -16
- data/lib/active_record/associations.rb +463 -345
- data/lib/active_record/attribute.rb +163 -0
- data/lib/active_record/attribute_assignment.rb +142 -151
- data/lib/active_record/attribute_decorators.rb +66 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
- data/lib/active_record/attribute_methods/dirty.rb +137 -57
- data/lib/active_record/attribute_methods/primary_key.rb +50 -36
- data/lib/active_record/attribute_methods/query.rb +5 -4
- data/lib/active_record/attribute_methods/read.rb +73 -106
- data/lib/active_record/attribute_methods/serialization.rb +44 -94
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -45
- data/lib/active_record/attribute_methods/write.rb +57 -44
- data/lib/active_record/attribute_methods.rb +301 -141
- data/lib/active_record/attribute_set/builder.rb +106 -0
- data/lib/active_record/attribute_set.rb +81 -0
- data/lib/active_record/attributes.rb +147 -0
- data/lib/active_record/autosave_association.rb +246 -217
- data/lib/active_record/base.rb +70 -474
- data/lib/active_record/callbacks.rb +66 -28
- data/lib/active_record/coders/json.rb +13 -0
- data/lib/active_record/coders/yaml_column.rb +18 -21
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +396 -219
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -164
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +29 -24
- data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -55
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +261 -169
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +707 -259
- data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +298 -89
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +466 -196
- data/lib/active_record/connection_adapters/column.rb +31 -245
- data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +45 -57
- data/lib/active_record/connection_adapters/mysql_adapter.rb +180 -123
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -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 +46 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -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/float.rb +21 -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/infinity.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
- data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
- data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
- data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
- data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -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 +36 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
- data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +430 -999
- data/lib/active_record/connection_adapters/schema_cache.rb +52 -27
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +579 -22
- data/lib/active_record/connection_handling.rb +132 -0
- data/lib/active_record/core.rb +579 -0
- data/lib/active_record/counter_cache.rb +157 -105
- data/lib/active_record/dynamic_matchers.rb +119 -63
- data/lib/active_record/enum.rb +197 -0
- data/lib/active_record/errors.rb +94 -36
- data/lib/active_record/explain.rb +15 -63
- data/lib/active_record/explain_registry.rb +30 -0
- data/lib/active_record/explain_subscriber.rb +9 -5
- data/lib/active_record/fixture_set/file.rb +56 -0
- data/lib/active_record/fixtures.rb +302 -215
- data/lib/active_record/gem_version.rb +15 -0
- data/lib/active_record/inheritance.rb +143 -70
- data/lib/active_record/integration.rb +65 -12
- data/lib/active_record/legacy_yaml_adapter.rb +30 -0
- data/lib/active_record/locale/en.yml +8 -1
- data/lib/active_record/locking/optimistic.rb +73 -52
- data/lib/active_record/locking/pessimistic.rb +5 -5
- data/lib/active_record/log_subscriber.rb +24 -21
- data/lib/active_record/migration/command_recorder.rb +124 -32
- data/lib/active_record/migration/join_table.rb +15 -0
- data/lib/active_record/migration.rb +511 -213
- data/lib/active_record/model_schema.rb +91 -117
- data/lib/active_record/nested_attributes.rb +184 -130
- data/lib/active_record/no_touching.rb +52 -0
- data/lib/active_record/null_relation.rb +81 -0
- data/lib/active_record/persistence.rb +276 -117
- data/lib/active_record/query_cache.rb +19 -37
- data/lib/active_record/querying.rb +28 -18
- data/lib/active_record/railtie.rb +73 -40
- data/lib/active_record/railties/console_sandbox.rb +3 -4
- data/lib/active_record/railties/controller_runtime.rb +4 -3
- data/lib/active_record/railties/databases.rake +141 -416
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +1 -4
- data/lib/active_record/reflection.rb +513 -154
- data/lib/active_record/relation/batches.rb +91 -43
- data/lib/active_record/relation/calculations.rb +199 -161
- data/lib/active_record/relation/delegation.rb +116 -25
- data/lib/active_record/relation/finder_methods.rb +362 -248
- data/lib/active_record/relation/merger.rb +193 -0
- data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
- data/lib/active_record/relation/predicate_builder.rb +135 -43
- data/lib/active_record/relation/query_methods.rb +928 -167
- data/lib/active_record/relation/spawn_methods.rb +48 -149
- data/lib/active_record/relation.rb +352 -207
- data/lib/active_record/result.rb +101 -10
- data/lib/active_record/runtime_registry.rb +22 -0
- data/lib/active_record/sanitization.rb +56 -59
- data/lib/active_record/schema.rb +19 -13
- data/lib/active_record/schema_dumper.rb +106 -63
- data/lib/active_record/schema_migration.rb +53 -0
- data/lib/active_record/scoping/default.rb +50 -57
- data/lib/active_record/scoping/named.rb +73 -109
- data/lib/active_record/scoping.rb +58 -123
- data/lib/active_record/serialization.rb +6 -2
- data/lib/active_record/serializers/xml_serializer.rb +12 -22
- data/lib/active_record/statement_cache.rb +111 -0
- data/lib/active_record/store.rb +168 -15
- data/lib/active_record/tasks/database_tasks.rb +299 -0
- data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
- data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
- data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
- data/lib/active_record/timestamp.rb +23 -16
- data/lib/active_record/transactions.rb +125 -79
- data/lib/active_record/type/big_integer.rb +13 -0
- data/lib/active_record/type/binary.rb +50 -0
- data/lib/active_record/type/boolean.rb +31 -0
- data/lib/active_record/type/date.rb +50 -0
- data/lib/active_record/type/date_time.rb +54 -0
- data/lib/active_record/type/decimal.rb +64 -0
- data/lib/active_record/type/decimal_without_scale.rb +11 -0
- data/lib/active_record/type/decorator.rb +14 -0
- data/lib/active_record/type/float.rb +19 -0
- data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
- data/lib/active_record/type/integer.rb +59 -0
- data/lib/active_record/type/mutable.rb +16 -0
- data/lib/active_record/type/numeric.rb +36 -0
- data/lib/active_record/type/serialized.rb +62 -0
- data/lib/active_record/type/string.rb +40 -0
- data/lib/active_record/type/text.rb +11 -0
- data/lib/active_record/type/time.rb +26 -0
- data/lib/active_record/type/time_value.rb +38 -0
- data/lib/active_record/type/type_map.rb +64 -0
- data/lib/active_record/type/unsigned_integer.rb +15 -0
- data/lib/active_record/type/value.rb +110 -0
- data/lib/active_record/type.rb +23 -0
- data/lib/active_record/validations/associated.rb +24 -16
- data/lib/active_record/validations/presence.rb +67 -0
- data/lib/active_record/validations/uniqueness.rb +123 -64
- data/lib/active_record/validations.rb +36 -29
- data/lib/active_record/version.rb +5 -7
- data/lib/active_record.rb +66 -46
- data/lib/rails/generators/active_record/migration/migration_generator.rb +53 -8
- data/lib/rails/generators/active_record/{model/templates/migration.rb → migration/templates/create_table_migration.rb} +5 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +20 -15
- data/lib/rails/generators/active_record/migration.rb +11 -8
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -4
- data/lib/rails/generators/active_record/model/templates/model.rb +4 -6
- data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
- data/lib/rails/generators/active_record.rb +3 -11
- metadata +101 -45
- 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/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/session_store.rb +0 -360
- data/lib/active_record/test_case.rb +0 -73
- 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,11 +1,13 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'thread_safe'
|
2
3
|
require 'monitor'
|
3
4
|
require 'set'
|
4
|
-
require 'active_support/core_ext/
|
5
|
+
require 'active_support/core_ext/string/filters'
|
5
6
|
|
6
7
|
module ActiveRecord
|
7
8
|
# Raised when a connection could not be obtained within the connection
|
8
|
-
# acquisition timeout period
|
9
|
+
# acquisition timeout period: because max connections in pool
|
10
|
+
# are in use.
|
9
11
|
class ConnectionTimeoutError < ConnectionNotEstablished
|
10
12
|
end
|
11
13
|
|
@@ -50,20 +52,177 @@ module ActiveRecord
|
|
50
52
|
#
|
51
53
|
# == Options
|
52
54
|
#
|
53
|
-
# There are
|
55
|
+
# There are several connection-pooling-related options that you can add to
|
54
56
|
# your database connection configuration:
|
55
57
|
#
|
56
58
|
# * +pool+: number indicating size of connection pool (default 5)
|
57
|
-
# * +
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
59
|
+
# * +checkout_timeout+: number of seconds to block and wait for a connection
|
60
|
+
# before giving up and raising a timeout error (default 5 seconds).
|
61
|
+
# * +reaping_frequency+: frequency in seconds to periodically run the
|
62
|
+
# Reaper, which attempts to find and recover connections from dead
|
63
|
+
# threads, which can occur if a programmer forgets to close a
|
64
|
+
# connection at the end of a thread or a thread dies unexpectedly.
|
65
|
+
# Regardless of this setting, the Reaper will be invoked before every
|
66
|
+
# blocking wait. (Default nil, which means don't schedule the Reaper).
|
62
67
|
class ConnectionPool
|
68
|
+
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
|
69
|
+
# with which it shares a Monitor. But could be a generic Queue.
|
70
|
+
#
|
71
|
+
# The Queue in stdlib's 'thread' could replace this class except
|
72
|
+
# stdlib's doesn't support waiting with a timeout.
|
73
|
+
class Queue
|
74
|
+
def initialize(lock = Monitor.new)
|
75
|
+
@lock = lock
|
76
|
+
@cond = @lock.new_cond
|
77
|
+
@num_waiting = 0
|
78
|
+
@queue = []
|
79
|
+
end
|
80
|
+
|
81
|
+
# Test if any threads are currently waiting on the queue.
|
82
|
+
def any_waiting?
|
83
|
+
synchronize do
|
84
|
+
@num_waiting > 0
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns the number of threads currently waiting on this
|
89
|
+
# queue.
|
90
|
+
def num_waiting
|
91
|
+
synchronize do
|
92
|
+
@num_waiting
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Add +element+ to the queue. Never blocks.
|
97
|
+
def add(element)
|
98
|
+
synchronize do
|
99
|
+
@queue.push element
|
100
|
+
@cond.signal
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# If +element+ is in the queue, remove and return it, or nil.
|
105
|
+
def delete(element)
|
106
|
+
synchronize do
|
107
|
+
@queue.delete(element)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Remove all elements from the queue.
|
112
|
+
def clear
|
113
|
+
synchronize do
|
114
|
+
@queue.clear
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Remove the head of the queue.
|
119
|
+
#
|
120
|
+
# If +timeout+ is not given, remove and return the head the
|
121
|
+
# queue if the number of available elements is strictly
|
122
|
+
# greater than the number of threads currently waiting (that
|
123
|
+
# is, don't jump ahead in line). Otherwise, return nil.
|
124
|
+
#
|
125
|
+
# If +timeout+ is given, block if it there is no element
|
126
|
+
# available, waiting up to +timeout+ seconds for an element to
|
127
|
+
# become available.
|
128
|
+
#
|
129
|
+
# Raises:
|
130
|
+
# - ConnectionTimeoutError if +timeout+ is given and no element
|
131
|
+
# becomes available after +timeout+ seconds,
|
132
|
+
def poll(timeout = nil)
|
133
|
+
synchronize do
|
134
|
+
if timeout
|
135
|
+
no_wait_poll || wait_poll(timeout)
|
136
|
+
else
|
137
|
+
no_wait_poll
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def synchronize(&block)
|
145
|
+
@lock.synchronize(&block)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Test if the queue currently contains any elements.
|
149
|
+
def any?
|
150
|
+
!@queue.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
# A thread can remove an element from the queue without
|
154
|
+
# waiting if an only if the number of currently available
|
155
|
+
# connections is strictly greater than the number of waiting
|
156
|
+
# threads.
|
157
|
+
def can_remove_no_wait?
|
158
|
+
@queue.size > @num_waiting
|
159
|
+
end
|
160
|
+
|
161
|
+
# Removes and returns the head of the queue if possible, or nil.
|
162
|
+
def remove
|
163
|
+
@queue.shift
|
164
|
+
end
|
165
|
+
|
166
|
+
# Remove and return the head the queue if the number of
|
167
|
+
# available elements is strictly greater than the number of
|
168
|
+
# threads currently waiting. Otherwise, return nil.
|
169
|
+
def no_wait_poll
|
170
|
+
remove if can_remove_no_wait?
|
171
|
+
end
|
172
|
+
|
173
|
+
# Waits on the queue up to +timeout+ seconds, then removes and
|
174
|
+
# returns the head of the queue.
|
175
|
+
def wait_poll(timeout)
|
176
|
+
@num_waiting += 1
|
177
|
+
|
178
|
+
t0 = Time.now
|
179
|
+
elapsed = 0
|
180
|
+
loop do
|
181
|
+
@cond.wait(timeout - elapsed)
|
182
|
+
|
183
|
+
return remove if any?
|
184
|
+
|
185
|
+
elapsed = Time.now - t0
|
186
|
+
if elapsed >= timeout
|
187
|
+
msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
|
188
|
+
[timeout, elapsed]
|
189
|
+
raise ConnectionTimeoutError, msg
|
190
|
+
end
|
191
|
+
end
|
192
|
+
ensure
|
193
|
+
@num_waiting -= 1
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
|
198
|
+
# A reaper instantiated with a nil frequency will never reap the
|
199
|
+
# connection pool.
|
200
|
+
#
|
201
|
+
# Configure the frequency by setting "reaping_frequency" in your
|
202
|
+
# database yaml file.
|
203
|
+
class Reaper
|
204
|
+
attr_reader :pool, :frequency
|
205
|
+
|
206
|
+
def initialize(pool, frequency)
|
207
|
+
@pool = pool
|
208
|
+
@frequency = frequency
|
209
|
+
end
|
210
|
+
|
211
|
+
def run
|
212
|
+
return unless frequency
|
213
|
+
Thread.new(frequency, pool) { |t, p|
|
214
|
+
while true
|
215
|
+
sleep t
|
216
|
+
p.reap
|
217
|
+
end
|
218
|
+
}
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
63
222
|
include MonitorMixin
|
64
223
|
|
65
|
-
attr_accessor :automatic_reconnect
|
66
|
-
attr_reader :spec, :connections
|
224
|
+
attr_accessor :automatic_reconnect, :checkout_timeout
|
225
|
+
attr_reader :spec, :connections, :size, :reaper
|
67
226
|
|
68
227
|
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
69
228
|
# object which describes database connection information (e.g. adapter,
|
@@ -76,20 +235,20 @@ module ActiveRecord
|
|
76
235
|
|
77
236
|
@spec = spec
|
78
237
|
|
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
|
238
|
+
@checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5
|
239
|
+
@reaper = Reaper.new(self, (spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f))
|
240
|
+
@reaper.run
|
87
241
|
|
88
242
|
# default max pool size to 5
|
89
243
|
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
90
244
|
|
245
|
+
# The cache of reserved connections mapped to threads
|
246
|
+
@reserved_connections = ThreadSafe::Cache.new(:initial_capacity => @size)
|
247
|
+
|
91
248
|
@connections = []
|
92
249
|
@automatic_reconnect = true
|
250
|
+
|
251
|
+
@available = Queue.new self
|
93
252
|
end
|
94
253
|
|
95
254
|
# Retrieve the connection associated with the current thread, or call
|
@@ -98,7 +257,9 @@ module ActiveRecord
|
|
98
257
|
# #connection can be called any number of times; the connection is
|
99
258
|
# held in a hash keyed by the thread id.
|
100
259
|
def connection
|
101
|
-
|
260
|
+
# this is correctly done double-checked locking
|
261
|
+
# (ThreadSafe::Cache's lookups have volatile semantics)
|
262
|
+
@reserved_connections[current_connection_id] || synchronize do
|
102
263
|
@reserved_connections[current_connection_id] ||= checkout
|
103
264
|
end
|
104
265
|
end
|
@@ -116,8 +277,10 @@ module ActiveRecord
|
|
116
277
|
# #release_connection releases the connection-thread association
|
117
278
|
# and returns the connection to the pool.
|
118
279
|
def release_connection(with_id = current_connection_id)
|
119
|
-
|
120
|
-
|
280
|
+
synchronize do
|
281
|
+
conn = @reserved_connections.delete(with_id)
|
282
|
+
checkin conn if conn
|
283
|
+
end
|
121
284
|
end
|
122
285
|
|
123
286
|
# If a connection already exists yield it to the block. If no connection
|
@@ -139,19 +302,20 @@ module ActiveRecord
|
|
139
302
|
# Disconnects all connections in the pool, and clears the pool.
|
140
303
|
def disconnect!
|
141
304
|
synchronize do
|
142
|
-
@reserved_connections
|
305
|
+
@reserved_connections.clear
|
143
306
|
@connections.each do |conn|
|
144
307
|
checkin conn
|
145
308
|
conn.disconnect!
|
146
309
|
end
|
147
310
|
@connections = []
|
311
|
+
@available.clear
|
148
312
|
end
|
149
313
|
end
|
150
314
|
|
151
315
|
# Clears the cache which maps classes.
|
152
316
|
def clear_reloadable_connections!
|
153
317
|
synchronize do
|
154
|
-
@reserved_connections
|
318
|
+
@reserved_connections.clear
|
155
319
|
@connections.each do |conn|
|
156
320
|
checkin conn
|
157
321
|
conn.disconnect! if conn.requires_reloading?
|
@@ -159,121 +323,32 @@ module ActiveRecord
|
|
159
323
|
@connections.delete_if do |conn|
|
160
324
|
conn.requires_reloading?
|
161
325
|
end
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
# Verify active connections and remove and disconnect connections
|
166
|
-
# associated with stale threads.
|
167
|
-
def verify_active_connections! #:nodoc:
|
168
|
-
synchronize do
|
169
|
-
clear_stale_cached_connections!
|
170
|
-
@connections.each do |connection|
|
171
|
-
connection.verify!
|
326
|
+
@available.clear
|
327
|
+
@connections.each do |conn|
|
328
|
+
@available.add conn
|
172
329
|
end
|
173
330
|
end
|
174
331
|
end
|
175
332
|
|
176
|
-
def columns
|
177
|
-
with_connection do |c|
|
178
|
-
c.schema_cache.columns
|
179
|
-
end
|
180
|
-
end
|
181
|
-
deprecate :columns
|
182
|
-
|
183
|
-
def columns_hash
|
184
|
-
with_connection do |c|
|
185
|
-
c.schema_cache.columns_hash
|
186
|
-
end
|
187
|
-
end
|
188
|
-
deprecate :columns_hash
|
189
|
-
|
190
|
-
def primary_keys
|
191
|
-
with_connection do |c|
|
192
|
-
c.schema_cache.primary_keys
|
193
|
-
end
|
194
|
-
end
|
195
|
-
deprecate :primary_keys
|
196
|
-
|
197
|
-
def clear_cache!
|
198
|
-
with_connection do |c|
|
199
|
-
c.schema_cache.clear!
|
200
|
-
end
|
201
|
-
end
|
202
|
-
deprecate :clear_cache!
|
203
|
-
|
204
|
-
# Return any checked-out connections back to the pool by threads that
|
205
|
-
# are no longer alive.
|
206
|
-
def clear_stale_cached_connections!
|
207
|
-
keys = @reserved_connections.keys - Thread.list.find_all { |t|
|
208
|
-
t.alive?
|
209
|
-
}.map { |thread| thread.object_id }
|
210
|
-
keys.each do |key|
|
211
|
-
conn = @reserved_connections[key]
|
212
|
-
ActiveSupport::Deprecation.warn(<<-eowarn) if conn.in_use?
|
213
|
-
Database connections will not be closed automatically, please close your
|
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
|
220
|
-
end
|
221
|
-
|
222
333
|
# Check-out a database connection from the pool, indicating that you want
|
223
334
|
# to use it. You should call #checkin when you no longer need this.
|
224
335
|
#
|
225
|
-
# This is done by either returning
|
226
|
-
# a new connection
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
#
|
231
|
-
# exception will be raised.
|
336
|
+
# This is done by either returning and leasing existing connection, or by
|
337
|
+
# creating a new connection and leasing it.
|
338
|
+
#
|
339
|
+
# If all connections are leased and the pool is at capacity (meaning the
|
340
|
+
# number of currently leased connections is greater than or equal to the
|
341
|
+
# size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
|
232
342
|
#
|
233
343
|
# Returns: an AbstractAdapter object.
|
234
344
|
#
|
235
345
|
# Raises:
|
236
|
-
# - ConnectionTimeoutError: no connection can be obtained from the pool
|
237
|
-
# within the timeout period.
|
346
|
+
# - ConnectionTimeoutError: no connection can be obtained from the pool.
|
238
347
|
def checkout
|
239
348
|
synchronize do
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
conn = @connections.find { |c| c.lease }
|
244
|
-
|
245
|
-
unless conn
|
246
|
-
if @connections.size < @size
|
247
|
-
conn = checkout_new_connection
|
248
|
-
conn.lease
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
if conn
|
253
|
-
checkout_and_verify conn
|
254
|
-
return conn
|
255
|
-
end
|
256
|
-
|
257
|
-
if waited_time >= @timeout
|
258
|
-
raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout} (waited #{waited_time} seconds). The max pool size is currently #{@size}; consider increasing it."
|
259
|
-
end
|
260
|
-
|
261
|
-
# Sometimes our wait can end because a connection is available,
|
262
|
-
# but another thread can snatch it up first. If timeout hasn't
|
263
|
-
# passed but no connection is avail, looks like that happened --
|
264
|
-
# loop and wait again, for the time remaining on our timeout.
|
265
|
-
before_wait = Time.now
|
266
|
-
@queue.wait( [@timeout - waited_time, 0].max )
|
267
|
-
waited_time += (Time.now - before_wait)
|
268
|
-
|
269
|
-
# Will go away in Rails 4, when we don't clean up
|
270
|
-
# after leaked connections automatically anymore. Right now, clean
|
271
|
-
# up after we've returned from a 'wait' if it looks like it's
|
272
|
-
# needed, then loop and try again.
|
273
|
-
if(active_connections.size >= @connections.size)
|
274
|
-
clear_stale_cached_connections!
|
275
|
-
end
|
276
|
-
end
|
349
|
+
conn = acquire_connection
|
350
|
+
conn.lease
|
351
|
+
checkout_and_verify(conn)
|
277
352
|
end
|
278
353
|
end
|
279
354
|
|
@@ -284,39 +359,87 @@ connection. For example: ActiveRecord::Base.connection.close
|
|
284
359
|
# calling +checkout+ on this pool.
|
285
360
|
def checkin(conn)
|
286
361
|
synchronize do
|
287
|
-
conn.
|
362
|
+
owner = conn.owner
|
363
|
+
|
364
|
+
conn._run_checkin_callbacks do
|
288
365
|
conn.expire
|
289
|
-
@queue.signal
|
290
366
|
end
|
291
367
|
|
292
|
-
release conn
|
368
|
+
release conn, owner
|
369
|
+
|
370
|
+
@available.add conn
|
293
371
|
end
|
294
372
|
end
|
295
373
|
|
296
|
-
|
297
|
-
|
298
|
-
def
|
374
|
+
# Remove a connection from the connection pool. The connection will
|
375
|
+
# remain open and active but will no longer be managed by this pool.
|
376
|
+
def remove(conn)
|
299
377
|
synchronize do
|
300
|
-
|
378
|
+
@connections.delete conn
|
379
|
+
@available.delete conn
|
301
380
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
381
|
+
release conn, conn.owner
|
382
|
+
|
383
|
+
@available.add checkout_new_connection if @available.any_waiting?
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Recover lost connections for the pool. A lost connection can occur if
|
388
|
+
# a programmer forgets to checkin a connection at the end of a thread
|
389
|
+
# or a thread dies unexpectedly.
|
390
|
+
def reap
|
391
|
+
stale_connections = synchronize do
|
392
|
+
@connections.select do |conn|
|
393
|
+
conn.in_use? && !conn.owner.alive?
|
308
394
|
end
|
395
|
+
end
|
309
396
|
|
310
|
-
|
397
|
+
stale_connections.each do |conn|
|
398
|
+
synchronize do
|
399
|
+
if conn.active?
|
400
|
+
conn.reset!
|
401
|
+
checkin conn
|
402
|
+
else
|
403
|
+
remove conn
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
private
|
410
|
+
|
411
|
+
# Acquire a connection by one of 1) immediately removing one
|
412
|
+
# from the queue of available connections, 2) creating a new
|
413
|
+
# connection if the pool is not at capacity, 3) waiting on the
|
414
|
+
# queue for a connection to become available.
|
415
|
+
#
|
416
|
+
# Raises:
|
417
|
+
# - ConnectionTimeoutError if a connection could not be acquired
|
418
|
+
def acquire_connection
|
419
|
+
if conn = @available.poll
|
420
|
+
conn
|
421
|
+
elsif @connections.size < @size
|
422
|
+
checkout_new_connection
|
423
|
+
else
|
424
|
+
reap
|
425
|
+
@available.poll(@checkout_timeout)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def release(conn, owner)
|
430
|
+
thread_id = owner.object_id
|
431
|
+
|
432
|
+
if @reserved_connections[thread_id] == conn
|
433
|
+
@reserved_connections.delete thread_id
|
311
434
|
end
|
312
435
|
end
|
313
436
|
|
314
437
|
def new_connection
|
315
|
-
|
438
|
+
Base.send(spec.adapter_method, spec.config)
|
316
439
|
end
|
317
440
|
|
318
441
|
def current_connection_id #:nodoc:
|
319
|
-
|
442
|
+
Base.connection_id ||= Thread.current.object_id
|
320
443
|
end
|
321
444
|
|
322
445
|
def checkout_new_connection
|
@@ -329,14 +452,14 @@ connection. For example: ActiveRecord::Base.connection.close
|
|
329
452
|
end
|
330
453
|
|
331
454
|
def checkout_and_verify(c)
|
332
|
-
c.
|
455
|
+
c._run_checkout_callbacks do
|
333
456
|
c.verify!
|
334
457
|
end
|
335
458
|
c
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
459
|
+
rescue
|
460
|
+
remove c
|
461
|
+
c.disconnect!
|
462
|
+
raise
|
340
463
|
end
|
341
464
|
end
|
342
465
|
|
@@ -346,59 +469,96 @@ connection. For example: ActiveRecord::Base.connection.close
|
|
346
469
|
#
|
347
470
|
# For example, suppose that you have 5 models, with the following hierarchy:
|
348
471
|
#
|
349
|
-
#
|
350
|
-
#
|
351
|
-
#
|
352
|
-
#
|
353
|
-
#
|
354
|
-
#
|
355
|
-
#
|
472
|
+
# class Author < ActiveRecord::Base
|
473
|
+
# end
|
474
|
+
#
|
475
|
+
# class BankAccount < ActiveRecord::Base
|
476
|
+
# end
|
477
|
+
#
|
478
|
+
# class Book < ActiveRecord::Base
|
479
|
+
# establish_connection "library_db"
|
480
|
+
# end
|
356
481
|
#
|
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.
|
482
|
+
# class ScaryBook < Book
|
483
|
+
# end
|
362
484
|
#
|
363
|
-
#
|
364
|
-
#
|
365
|
-
#
|
485
|
+
# class GoodBook < Book
|
486
|
+
# end
|
487
|
+
#
|
488
|
+
# And a database.yml that looked like this:
|
489
|
+
#
|
490
|
+
# development:
|
491
|
+
# database: my_application
|
492
|
+
# host: localhost
|
493
|
+
#
|
494
|
+
# library_db:
|
495
|
+
# database: library
|
496
|
+
# host: some.library.org
|
497
|
+
#
|
498
|
+
# Your primary database in the development environment is "my_application"
|
499
|
+
# but the Book model connects to a separate database called "library_db"
|
500
|
+
# (this can even be a database on a different machine).
|
501
|
+
#
|
502
|
+
# Book, ScaryBook and GoodBook will all use the same connection pool to
|
503
|
+
# "library_db" while Author, BankAccount, and any other models you create
|
504
|
+
# will use the default connection pool to "my_application".
|
505
|
+
#
|
506
|
+
# The various connection pools are managed by a single instance of
|
507
|
+
# ConnectionHandler accessible via ActiveRecord::Base.connection_handler.
|
508
|
+
# All Active Record models use this handler to determine the connection pool that they
|
509
|
+
# should use.
|
366
510
|
class ConnectionHandler
|
367
|
-
|
511
|
+
def initialize
|
512
|
+
# These caches are keyed by klass.name, NOT klass. Keying them by klass
|
513
|
+
# alone would lead to memory leaks in development mode as all previous
|
514
|
+
# instances of the class would stay in memory.
|
515
|
+
@owner_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
|
516
|
+
h[k] = ThreadSafe::Cache.new(:initial_capacity => 2)
|
517
|
+
end
|
518
|
+
@class_to_pool = ThreadSafe::Cache.new(:initial_capacity => 2) do |h,k|
|
519
|
+
h[k] = ThreadSafe::Cache.new
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
def connection_pool_list
|
524
|
+
owner_to_pool.values.compact
|
525
|
+
end
|
526
|
+
|
527
|
+
def connection_pools
|
528
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
529
|
+
In the next release, this will return the same as `#connection_pool_list`.
|
530
|
+
(An array of pools, rather than a hash mapping specs to pools.)
|
531
|
+
MSG
|
368
532
|
|
369
|
-
|
370
|
-
@connection_pools = pools
|
371
|
-
@class_to_pool = {}
|
533
|
+
Hash[connection_pool_list.map { |pool| [pool.spec, pool] }]
|
372
534
|
end
|
373
535
|
|
374
|
-
def establish_connection(
|
375
|
-
@
|
376
|
-
|
536
|
+
def establish_connection(owner, spec)
|
537
|
+
@class_to_pool.clear
|
538
|
+
raise RuntimeError, "Anonymous class is not allowed." unless owner.name
|
539
|
+
owner_to_pool[owner.name] = ConnectionAdapters::ConnectionPool.new(spec)
|
377
540
|
end
|
378
541
|
|
379
542
|
# Returns true if there are any active connections among the connection
|
380
543
|
# pools that the ConnectionHandler is managing.
|
381
544
|
def active_connections?
|
382
|
-
|
545
|
+
connection_pool_list.any?(&:active_connection?)
|
383
546
|
end
|
384
547
|
|
385
|
-
# Returns any connections in use by the current thread back to the pool
|
548
|
+
# Returns any connections in use by the current thread back to the pool,
|
549
|
+
# and also returns connections to the pool cached by threads that are no
|
550
|
+
# longer alive.
|
386
551
|
def clear_active_connections!
|
387
|
-
|
552
|
+
connection_pool_list.each(&:release_connection)
|
388
553
|
end
|
389
554
|
|
390
555
|
# Clears the cache which maps classes.
|
391
556
|
def clear_reloadable_connections!
|
392
|
-
|
557
|
+
connection_pool_list.each(&:clear_reloadable_connections!)
|
393
558
|
end
|
394
559
|
|
395
560
|
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! }
|
561
|
+
connection_pool_list.each(&:disconnect!)
|
402
562
|
end
|
403
563
|
|
404
564
|
# Locate the connection of the nearest super class. This can be an
|
@@ -407,7 +567,10 @@ connection. For example: ActiveRecord::Base.connection.close
|
|
407
567
|
# for (not necessarily the current class).
|
408
568
|
def retrieve_connection(klass) #:nodoc:
|
409
569
|
pool = retrieve_connection_pool(klass)
|
410
|
-
|
570
|
+
raise ConnectionNotEstablished, "No connection pool for #{klass}" unless pool
|
571
|
+
conn = pool.connection
|
572
|
+
raise ConnectionNotEstablished, "No connection for #{klass} in connection pool" unless conn
|
573
|
+
conn
|
411
574
|
end
|
412
575
|
|
413
576
|
# Returns true if a connection that's accessible to this class has
|
@@ -421,65 +584,79 @@ connection. For example: ActiveRecord::Base.connection.close
|
|
421
584
|
# connection and the defined connection (if they exist). The result
|
422
585
|
# can be used as an argument for establish_connection, for easily
|
423
586
|
# re-establishing the connection.
|
424
|
-
def remove_connection(
|
425
|
-
pool =
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
pool.spec.config
|
587
|
+
def remove_connection(owner)
|
588
|
+
if pool = owner_to_pool.delete(owner.name)
|
589
|
+
@class_to_pool.clear
|
590
|
+
pool.automatic_reconnect = false
|
591
|
+
pool.disconnect!
|
592
|
+
pool.spec.config
|
593
|
+
end
|
432
594
|
end
|
433
595
|
|
596
|
+
# Retrieving the connection pool happens a lot so we cache it in @class_to_pool.
|
597
|
+
# This makes retrieving the connection pool O(1) once the process is warm.
|
598
|
+
# When a connection is established or removed, we invalidate the cache.
|
599
|
+
#
|
600
|
+
# Ideally we would use #fetch here, as class_to_pool[klass] may sometimes be nil.
|
601
|
+
# However, benchmarking (https://gist.github.com/jonleighton/3552829) showed that
|
602
|
+
# #fetch is significantly slower than #[]. So in the nil case, no caching will
|
603
|
+
# take place, but that's ok since the nil case is not the common one that we wish
|
604
|
+
# to optimise for.
|
434
605
|
def retrieve_connection_pool(klass)
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
end
|
441
|
-
|
442
|
-
class ConnectionManagement
|
443
|
-
class Proxy # :nodoc:
|
444
|
-
attr_reader :body, :testing
|
606
|
+
class_to_pool[klass.name] ||= begin
|
607
|
+
until pool = pool_for(klass)
|
608
|
+
klass = klass.superclass
|
609
|
+
break unless klass <= Base
|
610
|
+
end
|
445
611
|
|
446
|
-
|
447
|
-
@body = body
|
448
|
-
@testing = testing
|
612
|
+
class_to_pool[klass.name] = pool
|
449
613
|
end
|
614
|
+
end
|
450
615
|
|
451
|
-
|
452
|
-
@body.send(method_sym, *arguments, &block)
|
453
|
-
end
|
616
|
+
private
|
454
617
|
|
455
|
-
|
456
|
-
|
457
|
-
|
618
|
+
def owner_to_pool
|
619
|
+
@owner_to_pool[Process.pid]
|
620
|
+
end
|
458
621
|
|
459
|
-
|
460
|
-
|
461
|
-
|
622
|
+
def class_to_pool
|
623
|
+
@class_to_pool[Process.pid]
|
624
|
+
end
|
462
625
|
|
463
|
-
|
464
|
-
|
626
|
+
def pool_for(owner)
|
627
|
+
owner_to_pool.fetch(owner.name) {
|
628
|
+
if ancestor_pool = pool_from_any_process_for(owner)
|
629
|
+
# A connection was established in an ancestor process that must have
|
630
|
+
# subsequently forked. We can't reuse the connection, but we can copy
|
631
|
+
# the specification and establish a new connection with it.
|
632
|
+
establish_connection owner, ancestor_pool.spec
|
633
|
+
else
|
634
|
+
owner_to_pool[owner.name] = nil
|
635
|
+
end
|
636
|
+
}
|
637
|
+
end
|
465
638
|
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
end
|
639
|
+
def pool_from_any_process_for(owner)
|
640
|
+
owner_to_pool = @owner_to_pool.values.reverse.find { |v| v[owner.name] }
|
641
|
+
owner_to_pool && owner_to_pool[owner.name]
|
470
642
|
end
|
643
|
+
end
|
471
644
|
|
645
|
+
class ConnectionManagement
|
472
646
|
def initialize(app)
|
473
647
|
@app = app
|
474
648
|
end
|
475
649
|
|
476
650
|
def call(env)
|
477
|
-
testing = env
|
651
|
+
testing = env['rack.test']
|
478
652
|
|
479
|
-
|
653
|
+
response = @app.call(env)
|
654
|
+
response[2] = ::Rack::BodyProxy.new(response[2]) do
|
655
|
+
ActiveRecord::Base.clear_active_connections! unless testing
|
656
|
+
end
|
480
657
|
|
481
|
-
|
482
|
-
rescue
|
658
|
+
response
|
659
|
+
rescue Exception
|
483
660
|
ActiveRecord::Base.clear_active_connections! unless testing
|
484
661
|
raise
|
485
662
|
end
|