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.
Files changed (32) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +82 -0
  3. data/.travis.yml +32 -34
  4. data/Gemfile +2 -15
  5. data/LICENSE.txt +1 -1
  6. data/README.md +45 -21
  7. data/Rakefile +8 -6
  8. data/activerecord-bogacs.gemspec +6 -6
  9. data/lib/active_record/bogacs/autoload.rb +10 -0
  10. data/lib/active_record/bogacs/connection_handler.rb +36 -0
  11. data/lib/active_record/bogacs/default_pool.rb +278 -129
  12. data/lib/active_record/bogacs/false_pool.rb +95 -73
  13. data/lib/active_record/bogacs/pool_support.rb +26 -9
  14. data/lib/active_record/bogacs/railtie.rb +17 -0
  15. data/lib/active_record/bogacs/reaper.rb +4 -6
  16. data/lib/active_record/bogacs/shareable_pool.rb +12 -16
  17. data/lib/active_record/bogacs/thread_safe/synchronized.rb +18 -22
  18. data/lib/active_record/bogacs/thread_safe.rb +3 -67
  19. data/lib/active_record/bogacs/validator.rb +21 -26
  20. data/lib/active_record/bogacs/version.rb +2 -2
  21. data/lib/active_record/bogacs.rb +7 -54
  22. data/lib/active_record/connection_adapters/adapter_compat.rb +63 -17
  23. data/lib/active_record/connection_adapters/pool_class.rb +75 -0
  24. data/lib/activerecord-bogacs.rb +1 -0
  25. data/test/active_record/bogacs/false_pool_test.rb +66 -78
  26. data/test/active_record/bogacs/shareable_pool/connection_pool_test.rb +6 -3
  27. data/test/active_record/bogacs/shareable_pool/connection_sharing_test.rb +3 -2
  28. data/test/active_record/bogacs/shareable_pool_helper.rb +1 -1
  29. data/test/active_record/bogacs/validator_test.rb +22 -28
  30. data/test/active_record/connection_pool_test_methods.rb +24 -20
  31. data/test/test_helper.rb +42 -25
  32. 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
- # == Obtaining (checking out) a connection
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
- # == Options
17
+ # Currently, mostly, based on ActiveRecord **4.2**.
32
18
  #
33
- # There are several connection-pooling-related options that you can add to
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
- # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
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
- # Raises:
110
- # - ConnectionTimeoutError if +timeout+ is given and no element
111
- # becomes available after +timeout+ seconds,
112
- def poll(timeout = nil, &block)
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 an only if the number of currently available
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.shift
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. Otherwise, return nil.
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
- yield if block_given?
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, :connections, :size, :reaper, :validator
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
- spec.config[:wait_timeout] || 5.0 ).to_f # <= 3.2 supports wait_timeout
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
- @reserved_connections = ThreadSafe::Map.new(:initial_capacity => @size)
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.info("pool: validator configured alongside with reaper")
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
- unless conn = @reserved_connections.fetch(connection_id, nil)
248
- synchronize do
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
- if conn = @reserved_connections.fetch(connection_id, nil)
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(with_id = current_connection_id)
271
- #synchronize do
272
- conn = @reserved_connections.delete(with_id)
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
- fresh_connection = true unless active_connection?
285
- yield connection
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(connection_id) if fresh_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
- @connections.size > 0 # synchronize { @connections.any? }
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
- @reserved_connections.clear
314
+ @connected.make_false
315
+
316
+ @thread_cached_conns.clear
301
317
  @connections.each do |conn|
302
- checkin conn
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
- # Clears the cache which maps classes.
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
- @reserved_connections.clear
347
+ @thread_cached_conns.clear
314
348
  @connections.each do |conn|
315
- checkin conn
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 do |conn|
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 = @reserved_connections.keys - keys
382
+ keys = @thread_cached_conns.keys - keys
346
383
  keys.each do |key|
347
- conn = @reserved_connections[key]
384
+ conn = @thread_cached_conns[key]
348
385
  checkin conn
349
- @reserved_connections.delete(key)
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
- conn = nil
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
- # @param [ActiveRecord::ConnectionAdapters::AbstractAdapter] connection
376
- # object, which was obtained earlier by calling #checkout on this pool
377
- # @see #checkout
378
- def checkin(conn, released = nil)
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
- _run_checkin_callbacks(conn)
413
+ remove_connection_from_thread_cache conn
381
414
 
382
- release conn, conn.owner unless released
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
- release conn, conn.owner
435
+ @connected.value = @connections.any?
398
436
 
399
- @available.add checkout_new_connection if @available.any_waiting?
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
- # Raises:
444
- # - ConnectionTimeoutError if a connection could not be acquired
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(@checkout_timeout)
562
+ @available.poll(checkout_timeout)
453
563
  end
454
564
  end
455
565
 
456
- def release(conn, owner)
457
- thread_id = owner.object_id
458
- if @reserved_connections[thread_id] == conn
459
- @reserved_connections.delete thread_id
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
- conn.pool = self
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