activerecord-bogacs 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/workflows/test.yml +82 -0
- data/.travis.yml +32 -34
- data/Gemfile +2 -15
- data/LICENSE.txt +1 -1
- data/README.md +45 -21
- data/Rakefile +8 -6
- data/activerecord-bogacs.gemspec +6 -6
- data/lib/active_record/bogacs/autoload.rb +10 -0
- data/lib/active_record/bogacs/connection_handler.rb +36 -0
- data/lib/active_record/bogacs/default_pool.rb +278 -129
- data/lib/active_record/bogacs/false_pool.rb +95 -73
- data/lib/active_record/bogacs/pool_support.rb +26 -9
- data/lib/active_record/bogacs/railtie.rb +17 -0
- data/lib/active_record/bogacs/reaper.rb +4 -6
- data/lib/active_record/bogacs/shareable_pool.rb +12 -16
- data/lib/active_record/bogacs/thread_safe/synchronized.rb +18 -22
- data/lib/active_record/bogacs/thread_safe.rb +3 -67
- data/lib/active_record/bogacs/validator.rb +21 -26
- data/lib/active_record/bogacs/version.rb +2 -2
- data/lib/active_record/bogacs.rb +7 -54
- data/lib/active_record/connection_adapters/adapter_compat.rb +63 -17
- data/lib/active_record/connection_adapters/pool_class.rb +75 -0
- data/lib/activerecord-bogacs.rb +1 -0
- data/test/active_record/bogacs/false_pool_test.rb +66 -78
- data/test/active_record/bogacs/shareable_pool/connection_pool_test.rb +6 -3
- data/test/active_record/bogacs/shareable_pool/connection_sharing_test.rb +3 -2
- data/test/active_record/bogacs/shareable_pool_helper.rb +1 -1
- data/test/active_record/bogacs/validator_test.rb +22 -28
- data/test/active_record/connection_pool_test_methods.rb +24 -20
- data/test/test_helper.rb +42 -25
- metadata +35 -17
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'active_record/version'
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
require 'monitor'
|
5
|
+
require 'concurrent/atomic/atomic_boolean'
|
3
6
|
|
4
7
|
require 'active_record/connection_adapters/adapter_compat'
|
5
8
|
require 'active_record/bogacs/pool_support'
|
@@ -8,49 +11,17 @@ require 'active_record/bogacs/thread_safe'
|
|
8
11
|
module ActiveRecord
|
9
12
|
module Bogacs
|
10
13
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# Connections can be obtained and used from a connection pool in several
|
14
|
-
# ways:
|
15
|
-
#
|
16
|
-
# 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
|
17
|
-
# earlier (pre-connection-pooling). Eventually, when you're done with
|
18
|
-
# the connection(s) and wish it to be returned to the pool, you call
|
19
|
-
# ActiveRecord::Base.clear_active_connections!.
|
20
|
-
# 2. Manually check out a connection from the pool with
|
21
|
-
# ActiveRecord::Base.connection_pool.checkout. You are responsible for
|
22
|
-
# returning this connection to the pool when finished by calling
|
23
|
-
# ActiveRecord::Base.connection_pool.checkin(connection).
|
24
|
-
# 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
|
25
|
-
# obtains a connection, yields it as the sole argument to the block,
|
26
|
-
# and returns it to the pool after the block completes.
|
27
|
-
#
|
28
|
-
# Connections in the pool are actually AbstractAdapter objects (or objects
|
29
|
-
# compatible with AbstractAdapter's interface).
|
14
|
+
# A "default" `ActiveRecord::ConnectionAdapters::ConnectionPool`-like pool
|
15
|
+
# implementation with compatibility across (older) Rails versions.
|
30
16
|
#
|
31
|
-
#
|
17
|
+
# Currently, mostly, based on ActiveRecord **4.2**.
|
32
18
|
#
|
33
|
-
#
|
34
|
-
# your database connection configuration:
|
19
|
+
# http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html
|
35
20
|
#
|
36
|
-
# * **pool**: number indicating size of connection pool (default 5)
|
37
|
-
# * **checkout_timeout**: number of seconds to block and wait for a connection
|
38
|
-
# before giving up and raising a timeout error (default 5 seconds).
|
39
|
-
# * **pool_initial**: number of connections to pre-initialize when the pool
|
40
|
-
# is created (default 0).
|
41
|
-
# * **reaping_frequency**: frequency in seconds to periodically run a reaper,
|
42
|
-
# which attempts to find and close "dead" connections (can occur if a caller
|
43
|
-
# forgets to close a connection at the end of a thread or a thread dies unexpectedly)
|
44
|
-
# Default is `nil`, which means don't run the periodical Reaper at all (reaping
|
45
|
-
# will still happen occasionally).
|
46
21
|
class DefaultPool
|
47
|
-
|
48
|
-
# with which it shares a Monitor. But could be a generic Queue.
|
49
|
-
#
|
50
|
-
# The Queue in stdlib's 'thread' could replace this class except
|
51
|
-
# stdlib's doesn't support waiting with a timeout.
|
22
|
+
|
52
23
|
# @private
|
53
|
-
class Queue
|
24
|
+
class Queue # ConnectionLeasingQueue
|
54
25
|
def initialize(lock)
|
55
26
|
@lock = lock
|
56
27
|
@cond = @lock.new_cond
|
@@ -106,21 +77,27 @@ module ActiveRecord
|
|
106
77
|
# available, waiting up to +timeout+ seconds for an element to
|
107
78
|
# become available.
|
108
79
|
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
synchronize do
|
114
|
-
if timeout
|
115
|
-
no_wait_poll || wait_poll(timeout, &block)
|
116
|
-
else
|
117
|
-
no_wait_poll
|
118
|
-
end
|
119
|
-
end
|
80
|
+
# @raise [ActiveRecord::ConnectionTimeoutError] if +timeout+ given and no element
|
81
|
+
# becomes available after +timeout+ seconds
|
82
|
+
def poll(timeout = nil)
|
83
|
+
synchronize { internal_poll(timeout) }
|
120
84
|
end
|
121
85
|
|
122
86
|
private
|
123
87
|
|
88
|
+
def internal_poll(timeout)
|
89
|
+
conn = no_wait_poll || (timeout && wait_poll(timeout))
|
90
|
+
# Connections must be leased while holding the main pool mutex. This is
|
91
|
+
# an internal subclass that also +.leases+ returned connections while
|
92
|
+
# still in queue's critical section (queue synchronizes with the same
|
93
|
+
# <tt>@lock</tt> as the main pool) so that a returned connection is already
|
94
|
+
# leased and there is no need to re-enter synchronized block.
|
95
|
+
#
|
96
|
+
# NOTE: avoid the need for ConnectionLeasingQueue, since BiasableQueue is not implemented
|
97
|
+
conn.lease if conn
|
98
|
+
conn
|
99
|
+
end
|
100
|
+
|
124
101
|
def synchronize(&block)
|
125
102
|
@lock.synchronize(&block)
|
126
103
|
end
|
@@ -131,21 +108,21 @@ module ActiveRecord
|
|
131
108
|
end
|
132
109
|
|
133
110
|
# A thread can remove an element from the queue without
|
134
|
-
# waiting if
|
111
|
+
# waiting if and only if the number of currently available
|
135
112
|
# connections is strictly greater than the number of waiting
|
136
113
|
# threads.
|
137
114
|
def can_remove_no_wait?
|
138
115
|
@queue.size > @num_waiting
|
139
116
|
end
|
140
117
|
|
141
|
-
# Removes and returns the head of the queue if possible, or nil
|
118
|
+
# Removes and returns the head of the queue if possible, or +nil+.
|
142
119
|
def remove
|
143
|
-
@queue.
|
120
|
+
@queue.pop
|
144
121
|
end
|
145
122
|
|
146
123
|
# Remove and return the head the queue if the number of
|
147
124
|
# available elements is strictly greater than the number of
|
148
|
-
# threads currently waiting.
|
125
|
+
# threads currently waiting. Otherwise, return +nil+.
|
149
126
|
def no_wait_poll
|
150
127
|
remove if can_remove_no_wait?
|
151
128
|
end
|
@@ -153,13 +130,10 @@ module ActiveRecord
|
|
153
130
|
# Waits on the queue up to +timeout+ seconds, then removes and
|
154
131
|
# returns the head of the queue.
|
155
132
|
def wait_poll(timeout)
|
156
|
-
t0 = Time.now
|
157
|
-
elapsed = 0
|
158
|
-
|
159
133
|
@num_waiting += 1
|
160
134
|
|
161
|
-
|
162
|
-
|
135
|
+
t0 = Time.now
|
136
|
+
elapsed = 0
|
163
137
|
while true
|
164
138
|
@cond.wait(timeout - elapsed)
|
165
139
|
|
@@ -183,22 +157,42 @@ module ActiveRecord
|
|
183
157
|
require 'active_record/bogacs/reaper.rb'
|
184
158
|
|
185
159
|
attr_accessor :automatic_reconnect, :checkout_timeout
|
186
|
-
attr_reader :spec, :
|
187
|
-
attr_reader :initial_size
|
160
|
+
attr_reader :spec, :size, :reaper
|
161
|
+
attr_reader :validator, :initial_size
|
188
162
|
|
189
|
-
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
163
|
+
# Creates a new `ConnectionPool` object. +spec+ is a ConnectionSpecification
|
190
164
|
# object which describes database connection information (e.g. adapter,
|
191
165
|
# host name, username, password, etc), as well as the maximum size for
|
192
166
|
# this ConnectionPool.
|
193
167
|
#
|
194
|
-
# The default ConnectionPool maximum size is 5
|
168
|
+
# @note The default ConnectionPool maximum size is **5**.
|
169
|
+
#
|
170
|
+
# @param [Hash] spec a `ConnectionSpecification`
|
171
|
+
#
|
172
|
+
# @option spec.config [Integer] :pool number indicating size of connection pool (default 5)
|
173
|
+
# @option spec.config [Float] :checkout_timeout number of seconds to block and
|
174
|
+
# wait for a connection before giving up raising a timeout (default 5 seconds).
|
175
|
+
# @option spec.config [Integer] :pool_initial number of connections to pre-initialize
|
176
|
+
# when the pool is created (default 0).
|
177
|
+
# @option spec.config [Float] :reaping_frequency frequency in seconds to periodically
|
178
|
+
# run a reaper, which attempts to find and close "dead" connections (can occur
|
179
|
+
# if a caller forgets to close a connection at the end of a thread or a thread
|
180
|
+
# dies unexpectedly) default is `nil` - don't run the periodical Reaper (reaping
|
181
|
+
# will still happen occasionally).
|
182
|
+
# @option spec.config [Float] :validate_frequency frequency in seconds to periodically
|
183
|
+
# run a connection validation (in a separate thread), to avoid potentially stale
|
184
|
+
# sockets when connections stay open (pooled but unused) for longer periods.
|
195
185
|
def initialize(spec)
|
196
186
|
super()
|
197
187
|
|
198
188
|
@spec = spec
|
199
189
|
|
200
|
-
@checkout_timeout = ( spec.config[:checkout_timeout] ||
|
201
|
-
|
190
|
+
@checkout_timeout = ( spec.config[:checkout_timeout] || 5 ).to_f
|
191
|
+
if @idle_timeout = spec.config.fetch(:idle_timeout, 300)
|
192
|
+
@idle_timeout = @idle_timeout.to_f
|
193
|
+
@idle_timeout = nil if @idle_timeout <= 0
|
194
|
+
end
|
195
|
+
|
202
196
|
@reaper = Reaper.new self, spec.config[:reaping_frequency]
|
203
197
|
@reaping = !! @reaper.run
|
204
198
|
|
@@ -213,13 +207,24 @@ module ActiveRecord
|
|
213
207
|
end
|
214
208
|
|
215
209
|
# The cache of reserved connections mapped to threads
|
216
|
-
@
|
210
|
+
@thread_cached_conns = ThreadSafe::Map.new(initial_capacity: @size)
|
217
211
|
|
218
212
|
@connections = []
|
219
213
|
@automatic_reconnect = true
|
220
214
|
|
215
|
+
# Connection pool allows for concurrent (outside the main +synchronize+ section)
|
216
|
+
# establishment of new connections. This variable tracks the number of threads
|
217
|
+
# currently in the process of independently establishing connections to the DB.
|
218
|
+
@now_connecting = 0
|
219
|
+
|
220
|
+
@threads_blocking_new_connections = 0 # TODO: dummy for now
|
221
|
+
|
221
222
|
@available = Queue.new self
|
222
223
|
|
224
|
+
@lock_thread = false
|
225
|
+
|
226
|
+
@connected = ::Concurrent::AtomicBoolean.new
|
227
|
+
|
223
228
|
initial_size = spec.config[:pool_initial] || 0
|
224
229
|
initial_size = @size if initial_size == true
|
225
230
|
initial_size = (@size * initial_size).to_i if initial_size <= 1.0
|
@@ -230,7 +235,7 @@ module ActiveRecord
|
|
230
235
|
require 'active_record/bogacs/validator' unless self.class.const_defined?(:Validator)
|
231
236
|
@validator = Validator.new self, frequency, spec.config[:validate_timeout]
|
232
237
|
if @validator.run && @reaping
|
233
|
-
logger && logger.
|
238
|
+
logger && logger.warn(":validate_frequency configured alongside with :reaping_frequency")
|
234
239
|
end
|
235
240
|
end
|
236
241
|
end
|
@@ -243,12 +248,9 @@ module ActiveRecord
|
|
243
248
|
#
|
244
249
|
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
245
250
|
def connection
|
246
|
-
connection_id = current_connection_id
|
247
|
-
|
248
|
-
|
249
|
-
conn = ( @reserved_connections[connection_id] ||= checkout )
|
250
|
-
end
|
251
|
-
end
|
251
|
+
connection_id = current_connection_id(current_thread)
|
252
|
+
conn = @thread_cached_conns.fetch(connection_id, nil)
|
253
|
+
conn = ( @thread_cached_conns[connection_id] ||= checkout ) unless conn
|
252
254
|
conn
|
253
255
|
end
|
254
256
|
|
@@ -256,22 +258,16 @@ module ActiveRecord
|
|
256
258
|
#
|
257
259
|
# @return [true, false]
|
258
260
|
def active_connection?
|
259
|
-
connection_id = current_connection_id
|
260
|
-
|
261
|
-
!! conn.in_use? # synchronize { conn.in_use? }
|
262
|
-
else
|
263
|
-
false
|
264
|
-
end
|
261
|
+
connection_id = current_connection_id(current_thread)
|
262
|
+
@thread_cached_conns.fetch(connection_id, nil)
|
265
263
|
end
|
266
264
|
|
267
265
|
# Signal that the thread is finished with the current connection.
|
268
266
|
# #release_connection releases the connection-thread association
|
269
267
|
# and returns the connection to the pool.
|
270
|
-
def release_connection(
|
271
|
-
|
272
|
-
|
273
|
-
checkin conn, true if conn
|
274
|
-
#end
|
268
|
+
def release_connection(owner_thread = Thread.current)
|
269
|
+
conn = @thread_cached_conns.delete(current_connection_id(owner_thread))
|
270
|
+
checkin conn if conn
|
275
271
|
end
|
276
272
|
|
277
273
|
# If a connection already exists yield it to the block. If no connection
|
@@ -281,25 +277,48 @@ module ActiveRecord
|
|
281
277
|
# @yield [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
282
278
|
def with_connection
|
283
279
|
connection_id = current_connection_id
|
284
|
-
|
285
|
-
|
280
|
+
unless conn = @thread_cached_conns[connection_id]
|
281
|
+
conn = connection
|
282
|
+
fresh_connection = true
|
283
|
+
end
|
284
|
+
yield conn
|
286
285
|
ensure
|
287
|
-
release_connection
|
286
|
+
release_connection if fresh_connection
|
288
287
|
end
|
289
288
|
|
290
289
|
# Returns true if a connection has already been opened.
|
291
290
|
#
|
292
291
|
# @return [true, false]
|
293
292
|
def connected?
|
294
|
-
@
|
293
|
+
@connected.true? # synchronize { @connections.any? }
|
294
|
+
end
|
295
|
+
|
296
|
+
# Returns an array containing the connections currently in the pool.
|
297
|
+
# Access to the array does not require synchronization on the pool because
|
298
|
+
# the array is newly created and not retained by the pool.
|
299
|
+
#
|
300
|
+
# However; this method bypasses the ConnectionPool's thread-safe connection
|
301
|
+
# access pattern. A returned connection may be owned by another thread,
|
302
|
+
# unowned, or by happen-stance owned by the calling thread.
|
303
|
+
#
|
304
|
+
# Calling methods on a connection without ownership is subject to the
|
305
|
+
# thread-safety guarantees of the underlying method. Many of the methods
|
306
|
+
# on connection adapter classes are inherently multi-thread unsafe.
|
307
|
+
def connections
|
308
|
+
synchronize { @connections.dup }
|
295
309
|
end
|
296
310
|
|
297
311
|
# Disconnects all connections in the pool, and clears the pool.
|
298
312
|
def disconnect!
|
299
313
|
synchronize do
|
300
|
-
@
|
314
|
+
@connected.make_false
|
315
|
+
|
316
|
+
@thread_cached_conns.clear
|
301
317
|
@connections.each do |conn|
|
302
|
-
|
318
|
+
if conn.in_use?
|
319
|
+
conn.steal!
|
320
|
+
checkin conn
|
321
|
+
end
|
303
322
|
conn.disconnect!
|
304
323
|
end
|
305
324
|
@connections.clear
|
@@ -307,21 +326,39 @@ module ActiveRecord
|
|
307
326
|
end
|
308
327
|
end
|
309
328
|
|
310
|
-
#
|
329
|
+
# Discards all connections in the pool (even if they're currently
|
330
|
+
# leased!), along with the pool itself. Any further interaction with the
|
331
|
+
# pool (except #spec and #schema_cache) is undefined.
|
332
|
+
#
|
333
|
+
# See AbstractAdapter#discard!
|
334
|
+
def discard! # :nodoc:
|
335
|
+
synchronize do
|
336
|
+
return if @connections.nil? # already discarded
|
337
|
+
@connected.make_false
|
338
|
+
|
339
|
+
@connections.each do |conn|
|
340
|
+
conn.discard!
|
341
|
+
end
|
342
|
+
@connections = @available = @thread_cached_conns = nil
|
343
|
+
end
|
344
|
+
end
|
311
345
|
def clear_reloadable_connections!
|
312
346
|
synchronize do
|
313
|
-
@
|
347
|
+
@thread_cached_conns.clear
|
314
348
|
@connections.each do |conn|
|
315
|
-
|
349
|
+
if conn.in_use?
|
350
|
+
conn.steal!
|
351
|
+
checkin conn
|
352
|
+
end
|
316
353
|
conn.disconnect! if conn.requires_reloading?
|
317
354
|
end
|
318
|
-
@connections.delete_if
|
319
|
-
conn.requires_reloading?
|
320
|
-
end
|
355
|
+
@connections.delete_if(&:requires_reloading?)
|
321
356
|
@available.clear
|
322
357
|
@connections.each do |conn|
|
323
358
|
@available.add conn
|
324
359
|
end
|
360
|
+
|
361
|
+
@connected.value = @connections.any?
|
325
362
|
end
|
326
363
|
end
|
327
364
|
|
@@ -342,11 +379,11 @@ module ActiveRecord
|
|
342
379
|
# @private AR 3.2 compatibility
|
343
380
|
def clear_stale_cached_connections!
|
344
381
|
keys = Thread.list.find_all { |t| t.alive? }.map(&:object_id)
|
345
|
-
keys = @
|
382
|
+
keys = @thread_cached_conns.keys - keys
|
346
383
|
keys.each do |key|
|
347
|
-
conn = @
|
384
|
+
conn = @thread_cached_conns[key]
|
348
385
|
checkin conn
|
349
|
-
@
|
386
|
+
@thread_cached_conns.delete(key)
|
350
387
|
end
|
351
388
|
end if ActiveRecord::VERSION::MAJOR < 4
|
352
389
|
|
@@ -361,28 +398,25 @@ module ActiveRecord
|
|
361
398
|
# @raise [ActiveRecord::ConnectionTimeoutError] if all connections are leased
|
362
399
|
# and the pool is at capacity (meaning the number of currently leased
|
363
400
|
# connections is greater than or equal to the size limit set)
|
364
|
-
def checkout
|
365
|
-
|
366
|
-
synchronize do
|
367
|
-
conn = acquire_connection
|
368
|
-
conn.lease
|
369
|
-
end
|
370
|
-
checkout_and_verify(conn)
|
401
|
+
def checkout(checkout_timeout = @checkout_timeout)
|
402
|
+
checkout_and_verify(acquire_connection(checkout_timeout))
|
371
403
|
end
|
372
404
|
|
373
|
-
# Check-in a database connection back into the pool
|
405
|
+
# Check-in a database connection back into the pool, indicating that you
|
406
|
+
# no longer need this connection.
|
374
407
|
#
|
375
|
-
#
|
376
|
-
#
|
377
|
-
|
378
|
-
|
408
|
+
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
409
|
+
# calling #checkout on this pool.
|
410
|
+
def checkin(conn)
|
411
|
+
#conn.lock.synchronize do
|
379
412
|
synchronize do
|
380
|
-
|
413
|
+
remove_connection_from_thread_cache conn
|
381
414
|
|
382
|
-
|
415
|
+
_run_checkin_callbacks(conn)
|
383
416
|
|
384
417
|
@available.add conn
|
385
418
|
end
|
419
|
+
#end
|
386
420
|
end
|
387
421
|
|
388
422
|
# Remove a connection from the connection pool. The returned connection
|
@@ -390,14 +424,25 @@ module ActiveRecord
|
|
390
424
|
#
|
391
425
|
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
392
426
|
def remove(conn)
|
427
|
+
needs_new_connection = false
|
428
|
+
|
393
429
|
synchronize do
|
430
|
+
remove_connection_from_thread_cache conn
|
431
|
+
|
394
432
|
@connections.delete conn
|
395
433
|
@available.delete conn
|
396
434
|
|
397
|
-
|
435
|
+
@connected.value = @connections.any?
|
398
436
|
|
399
|
-
|
437
|
+
needs_new_connection = @available.any_waiting?
|
400
438
|
end
|
439
|
+
|
440
|
+
# This is intentionally done outside of the synchronized section as we
|
441
|
+
# would like not to hold the main mutex while checking out new connections.
|
442
|
+
# Thus there is some chance that needs_new_connection information is now
|
443
|
+
# stale, we can live with that (bulk_make_new_connections will make
|
444
|
+
# sure not to exceed the pool's @size limit).
|
445
|
+
bulk_make_new_connections(1) if needs_new_connection
|
401
446
|
end
|
402
447
|
|
403
448
|
# Recover lost connections for the pool. A lost connection can occur if
|
@@ -407,6 +452,8 @@ module ActiveRecord
|
|
407
452
|
stale_connections = synchronize do
|
408
453
|
@connections.select do |conn|
|
409
454
|
conn.in_use? && !conn.owner.alive?
|
455
|
+
end.each do |conn|
|
456
|
+
conn.steal!
|
410
457
|
end
|
411
458
|
end
|
412
459
|
|
@@ -421,6 +468,61 @@ module ActiveRecord
|
|
421
468
|
end
|
422
469
|
end
|
423
470
|
end
|
471
|
+
|
472
|
+
# Disconnect all connections that have been idle for at least
|
473
|
+
# +minimum_idle+ seconds. Connections currently checked out, or that were
|
474
|
+
# checked in less than +minimum_idle+ seconds ago, are unaffected.
|
475
|
+
def flush(minimum_idle = @idle_timeout)
|
476
|
+
return if minimum_idle.nil?
|
477
|
+
|
478
|
+
idle_connections = synchronize do
|
479
|
+
@connections.select do |conn|
|
480
|
+
!conn.in_use? && conn.seconds_idle >= minimum_idle
|
481
|
+
end.each do |conn|
|
482
|
+
conn.lease
|
483
|
+
|
484
|
+
@available.delete conn
|
485
|
+
@connections.delete conn
|
486
|
+
|
487
|
+
@connected.value = @connections.any?
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
idle_connections.each do |conn|
|
492
|
+
conn.disconnect!
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
# Disconnect all currently idle connections. Connections currently checked
|
497
|
+
# out are unaffected.
|
498
|
+
def flush!
|
499
|
+
reap
|
500
|
+
flush(-1)
|
501
|
+
end
|
502
|
+
|
503
|
+
def num_waiting_in_queue # :nodoc:
|
504
|
+
@available.num_waiting
|
505
|
+
end
|
506
|
+
private :num_waiting_in_queue
|
507
|
+
|
508
|
+
# Return connection pool's usage statistic
|
509
|
+
# Example:
|
510
|
+
#
|
511
|
+
# ActiveRecord::Base.connection_pool.stat # => { size: 15, connections: 1, busy: 1, dead: 0, idle: 0, waiting: 0, checkout_timeout: 5 }
|
512
|
+
def stat
|
513
|
+
synchronize do
|
514
|
+
{
|
515
|
+
size: size,
|
516
|
+
connections: @connections.size,
|
517
|
+
busy: @connections.count { |c| c.in_use? && c.owner.alive? },
|
518
|
+
dead: @connections.count { |c| c.in_use? && !c.owner.alive? },
|
519
|
+
idle: @connections.count { |c| !c.in_use? },
|
520
|
+
waiting: num_waiting_in_queue,
|
521
|
+
checkout_timeout: checkout_timeout
|
522
|
+
}
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
424
526
|
# NOTE: active? and reset! are >= AR 2.3
|
425
527
|
|
426
528
|
def reaper?; (@reaper ||= nil) && @reaper.frequency end
|
@@ -435,43 +537,90 @@ module ActiveRecord
|
|
435
537
|
|
436
538
|
private
|
437
539
|
|
540
|
+
def bulk_make_new_connections(num_new_conns_needed)
|
541
|
+
num_new_conns_needed.times do
|
542
|
+
# try_to_checkout_new_connection will not exceed pool's @size limit
|
543
|
+
if new_conn = try_to_checkout_new_connection
|
544
|
+
# make the new_conn available to the starving threads stuck @available Queue
|
545
|
+
checkin(new_conn)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
438
550
|
# Acquire a connection by one of 1) immediately removing one
|
439
551
|
# from the queue of available connections, 2) creating a new
|
440
552
|
# connection if the pool is not at capacity, 3) waiting on the
|
441
553
|
# queue for a connection to become available.
|
442
554
|
#
|
443
|
-
#
|
444
|
-
#
|
445
|
-
def acquire_connection
|
446
|
-
if conn = @available.poll
|
555
|
+
# @raise [ActiveRecord::ConnectionTimeoutError]
|
556
|
+
# @raise [ActiveRecord::ConnectionNotEstablished]
|
557
|
+
def acquire_connection(checkout_timeout)
|
558
|
+
if conn = @available.poll || try_to_checkout_new_connection
|
447
559
|
conn
|
448
|
-
elsif @connections.size < @size
|
449
|
-
checkout_new_connection
|
450
560
|
else
|
451
561
|
reap unless @reaping
|
452
|
-
@available.poll(
|
562
|
+
@available.poll(checkout_timeout)
|
453
563
|
end
|
454
564
|
end
|
455
565
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
566
|
+
#--
|
567
|
+
# if owner_thread param is omitted, this must be called in synchronize block
|
568
|
+
def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
|
569
|
+
@thread_cached_conns.delete_pair(current_connection_id(owner_thread), conn)
|
570
|
+
end
|
571
|
+
alias_method :release, :remove_connection_from_thread_cache
|
572
|
+
|
573
|
+
# If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
|
574
|
+
# to the DB is done outside main synchronized section.
|
575
|
+
#--
|
576
|
+
# Implementation constraint: a newly established connection returned by this
|
577
|
+
# method must be in the +.leased+ state.
|
578
|
+
def try_to_checkout_new_connection
|
579
|
+
# first in synchronized section check if establishing new conns is allowed
|
580
|
+
# and increment @now_connecting, to prevent overstepping this pool's @size
|
581
|
+
# constraint
|
582
|
+
do_checkout = synchronize do
|
583
|
+
if @threads_blocking_new_connections.zero? && (@connections.size + @now_connecting) < @size
|
584
|
+
@now_connecting += 1
|
585
|
+
end
|
586
|
+
end
|
587
|
+
if do_checkout
|
588
|
+
begin
|
589
|
+
# if successfully incremented @now_connecting establish new connection
|
590
|
+
# outside of synchronized section
|
591
|
+
conn = checkout_new_connection
|
592
|
+
ensure
|
593
|
+
synchronize do
|
594
|
+
if conn
|
595
|
+
adopt_connection(conn)
|
596
|
+
# returned conn needs to be already leased
|
597
|
+
conn.lease
|
598
|
+
end
|
599
|
+
@now_connecting -= 1
|
600
|
+
end
|
601
|
+
end
|
460
602
|
end
|
461
603
|
end
|
462
604
|
|
605
|
+
def adopt_connection(conn)
|
606
|
+
conn.pool = self
|
607
|
+
@connections << conn
|
608
|
+
end
|
609
|
+
|
463
610
|
def checkout_new_connection
|
464
611
|
raise ConnectionNotEstablished unless @automatic_reconnect
|
465
|
-
|
466
612
|
conn = new_connection
|
467
|
-
|
468
|
-
@connections << conn
|
613
|
+
@connected.make_true
|
469
614
|
conn
|
470
615
|
end
|
471
616
|
|
472
617
|
def checkout_and_verify(conn)
|
473
618
|
_run_checkout_callbacks(conn)
|
474
619
|
conn
|
620
|
+
rescue => e
|
621
|
+
remove conn
|
622
|
+
conn.disconnect!
|
623
|
+
raise e
|
475
624
|
end
|
476
625
|
|
477
626
|
def prefill_initial_connections
|
@@ -488,4 +637,4 @@ module ActiveRecord
|
|
488
637
|
end
|
489
638
|
|
490
639
|
end
|
491
|
-
end
|
640
|
+
end
|