activerecord-bogacs 0.1.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 +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +26 -0
- data/Gemfile +33 -0
- data/LICENSE.txt +22 -0
- data/README.md +124 -0
- data/Rakefile +167 -0
- data/activerecord-bogacs.gemspec +26 -0
- data/lib/active_record/bogacs.rb +55 -0
- data/lib/active_record/bogacs/default_pool.rb +672 -0
- data/lib/active_record/bogacs/false_pool.rb +259 -0
- data/lib/active_record/bogacs/pool_support.rb +21 -0
- data/lib/active_record/bogacs/shareable_pool.rb +255 -0
- data/lib/active_record/bogacs/version.rb +5 -0
- data/lib/active_record/connection_adapters/adapter_compat.rb +57 -0
- data/lib/active_record/shared_connection.rb +24 -0
- data/test/active_record/bogacs/default_pool_test.rb +34 -0
- data/test/active_record/bogacs/false_pool_test.rb +200 -0
- data/test/active_record/bogacs/shareable_pool/connection_pool_test.rb +186 -0
- data/test/active_record/bogacs/shareable_pool/connection_sharing_test.rb +429 -0
- data/test/active_record/bogacs/shareable_pool_helper.rb +81 -0
- data/test/active_record/builtin_pool_test.rb +18 -0
- data/test/active_record/connection_pool_test_methods.rb +336 -0
- data/test/test_helper.rb +304 -0
- metadata +130 -0
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'thread_safe'
|
2
|
+
require 'active_record/bogacs/pool_support'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
module Bogacs
|
6
|
+
class FalsePool
|
7
|
+
|
8
|
+
include PoolSupport
|
9
|
+
|
10
|
+
include ThreadSafe::Util::CheapLockable
|
11
|
+
alias_method :synchronize, :cheap_synchronize
|
12
|
+
|
13
|
+
attr_accessor :automatic_reconnect
|
14
|
+
|
15
|
+
attr_reader :size, :spec
|
16
|
+
|
17
|
+
def initialize(spec)
|
18
|
+
@connected = nil
|
19
|
+
|
20
|
+
@spec = spec
|
21
|
+
@size = nil
|
22
|
+
#@automatic_reconnect = true
|
23
|
+
|
24
|
+
@reserved_connections = ThreadSafe::Cache.new #:initial_capacity => @size
|
25
|
+
end
|
26
|
+
|
27
|
+
# @private replacement for attr_reader :connections
|
28
|
+
def connections; @reserved_connections.values end
|
29
|
+
|
30
|
+
# @private attr_reader :reaper
|
31
|
+
def reaper; end
|
32
|
+
|
33
|
+
# @private
|
34
|
+
def checkout_timeout; end
|
35
|
+
|
36
|
+
# Retrieve the connection associated with the current thread, or call
|
37
|
+
# #checkout to obtain one if necessary.
|
38
|
+
#
|
39
|
+
# #connection can be called any number of times; the connection is
|
40
|
+
# held in a hash keyed by the thread id.
|
41
|
+
def connection
|
42
|
+
@reserved_connections[current_connection_id] ||= checkout
|
43
|
+
end
|
44
|
+
|
45
|
+
# Is there an open connection that is being used for the current thread?
|
46
|
+
def active_connection?
|
47
|
+
conn = @reserved_connections[current_connection_id]
|
48
|
+
conn ? conn.in_use? : false
|
49
|
+
end
|
50
|
+
|
51
|
+
# Signal that the thread is finished with the current connection.
|
52
|
+
# #release_connection releases the connection-thread association
|
53
|
+
# and returns the connection to the pool.
|
54
|
+
def release_connection(with_id = current_connection_id)
|
55
|
+
conn = @reserved_connections.delete(with_id)
|
56
|
+
checkin conn if conn
|
57
|
+
end
|
58
|
+
|
59
|
+
# If a connection already exists yield it to the block. If no connection
|
60
|
+
# exists checkout a connection, yield it to the block, and checkin the
|
61
|
+
# connection when finished.
|
62
|
+
def with_connection
|
63
|
+
connection_id = current_connection_id
|
64
|
+
fresh_connection = true unless active_connection?
|
65
|
+
yield connection
|
66
|
+
ensure
|
67
|
+
release_connection(connection_id) if fresh_connection
|
68
|
+
end
|
69
|
+
|
70
|
+
# Returns true if a connection has already been opened.
|
71
|
+
def connected?; @connected end
|
72
|
+
|
73
|
+
# Disconnects all connections in the pool, and clears the pool.
|
74
|
+
def disconnect!
|
75
|
+
synchronize do
|
76
|
+
@connected = false
|
77
|
+
|
78
|
+
connections = @reserved_connections.values
|
79
|
+
@reserved_connections.clear
|
80
|
+
|
81
|
+
connections.each do |conn|
|
82
|
+
checkin conn
|
83
|
+
conn.disconnect!
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Clears the cache which maps classes.
|
89
|
+
def clear_reloadable_connections!
|
90
|
+
synchronize do
|
91
|
+
@connected = false
|
92
|
+
|
93
|
+
connections = @reserved_connections.values
|
94
|
+
@reserved_connections.clear
|
95
|
+
|
96
|
+
connections.each do |conn|
|
97
|
+
checkin conn
|
98
|
+
conn.disconnect! if conn.requires_reloading?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Verify active connections and remove and disconnect connections
|
104
|
+
# associated with stale threads.
|
105
|
+
# @private AR 3.2 compatibility
|
106
|
+
def verify_active_connections!
|
107
|
+
synchronize do
|
108
|
+
clear_stale_cached_connections!
|
109
|
+
connections = @reserved_connections.values
|
110
|
+
connections.each do |connection|
|
111
|
+
connection.verify!
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end if ActiveRecord::VERSION::MAJOR < 4
|
115
|
+
|
116
|
+
# Return any checked-out connections back to the pool by threads that
|
117
|
+
# are no longer alive.
|
118
|
+
# @private AR 3.2 compatibility
|
119
|
+
def clear_stale_cached_connections!
|
120
|
+
keys = Thread.list.find_all { |t| t.alive? }.map(&:object_id)
|
121
|
+
keys = @reserved_connections.keys - keys
|
122
|
+
keys.each do |key|
|
123
|
+
if conn = @reserved_connections[key]
|
124
|
+
checkin conn, false # no release
|
125
|
+
@reserved_connections.delete(key)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end if ActiveRecord::VERSION::MAJOR < 4
|
129
|
+
|
130
|
+
# Check-out a database connection from the pool, indicating that you want
|
131
|
+
# to use it. You should call #checkin when you no longer need this.
|
132
|
+
#
|
133
|
+
# This is done by either returning and leasing existing connection, or by
|
134
|
+
# creating a new connection and leasing it.
|
135
|
+
#
|
136
|
+
# If all connections are leased and the pool is at capacity (meaning the
|
137
|
+
# number of currently leased connections is greater than or equal to the
|
138
|
+
# size limit set), an ActiveRecord::ConnectionTimeoutError exception will be raised.
|
139
|
+
#
|
140
|
+
# Returns: an AbstractAdapter object.
|
141
|
+
#
|
142
|
+
# Raises:
|
143
|
+
# - ConnectionTimeoutError: no connection can be obtained from the pool.
|
144
|
+
def checkout
|
145
|
+
#synchronize do
|
146
|
+
conn = checkout_new_connection # acquire_connection
|
147
|
+
conn.lease
|
148
|
+
conn.run_callbacks(:checkout) { conn.verify! } # checkout_and_verify(conn)
|
149
|
+
conn
|
150
|
+
#end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Check-in a database connection back into the pool, indicating that you
|
154
|
+
# no longer need this connection.
|
155
|
+
#
|
156
|
+
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
157
|
+
# calling +checkout+ on this pool.
|
158
|
+
def checkin(conn, do_release = true)
|
159
|
+
release(conn) if do_release
|
160
|
+
#synchronize do
|
161
|
+
conn.run_callbacks(:checkin) { conn.expire }
|
162
|
+
#release conn
|
163
|
+
#@available.add conn
|
164
|
+
#end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Remove a connection from the connection pool. The connection will
|
168
|
+
# remain open and active but will no longer be managed by this pool.
|
169
|
+
def remove(conn)
|
170
|
+
release(conn)
|
171
|
+
end
|
172
|
+
|
173
|
+
# @private
|
174
|
+
def reap
|
175
|
+
# we do not really manage the connection pool - nothing to do ...
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
# Acquire a connection by one of 1) immediately removing one
|
181
|
+
# from the queue of available connections, 2) creating a new
|
182
|
+
# connection if the pool is not at capacity, 3) waiting on the
|
183
|
+
# queue for a connection to become available.
|
184
|
+
#
|
185
|
+
# Raises:
|
186
|
+
# - ConnectionTimeoutError if a connection could not be acquired
|
187
|
+
def acquire_connection
|
188
|
+
# underlying pool will poll and block if "empty" (all checked-out)
|
189
|
+
#if conn = @available.poll
|
190
|
+
#conn
|
191
|
+
#elsif @connections.size < @size
|
192
|
+
#checkout_new_connection
|
193
|
+
#else
|
194
|
+
#reap
|
195
|
+
#@available.poll(@checkout_timeout)
|
196
|
+
#end
|
197
|
+
checkout_new_connection
|
198
|
+
end
|
199
|
+
|
200
|
+
def release(conn, owner = nil)
|
201
|
+
thread_id = owner.object_id unless owner.nil?
|
202
|
+
|
203
|
+
thread_id ||=
|
204
|
+
if @reserved_connections[conn_id = current_connection_id] == conn
|
205
|
+
conn_id
|
206
|
+
else
|
207
|
+
connections = @reserved_connections
|
208
|
+
connections.keys.find { |k| connections[k] == conn }
|
209
|
+
end
|
210
|
+
|
211
|
+
@reserved_connections.delete thread_id if thread_id
|
212
|
+
end
|
213
|
+
|
214
|
+
def checkout_new_connection
|
215
|
+
# NOTE: automatic reconnect seems to make no sense for us!
|
216
|
+
#raise ConnectionNotEstablished unless @automatic_reconnect
|
217
|
+
|
218
|
+
begin
|
219
|
+
conn = new_connection
|
220
|
+
rescue ConnectionTimeoutError => e
|
221
|
+
raise e
|
222
|
+
rescue => e
|
223
|
+
raise ConnectionTimeoutError, e.message if _timeout_error?(e)
|
224
|
+
raise e
|
225
|
+
end
|
226
|
+
conn.pool = self
|
227
|
+
synchronize { @connected = true } if @connected != true
|
228
|
+
conn
|
229
|
+
end
|
230
|
+
|
231
|
+
#def checkout_and_verify(conn)
|
232
|
+
# conn.run_callbacks(:checkout) { conn.verify! }
|
233
|
+
# conn
|
234
|
+
#end
|
235
|
+
|
236
|
+
# sample on JRuby + Tomcat JDBC :
|
237
|
+
# ActiveRecord::JDBCError(<The driver encountered an unknown error:
|
238
|
+
# org.apache.tomcat.jdbc.pool.PoolExhaustedException:
|
239
|
+
# [main] Timeout: Pool empty. Unable to fetch a connection in 2 seconds,
|
240
|
+
# none available[size:10; busy:10; idle:0; lastwait:2500].>
|
241
|
+
# )
|
242
|
+
|
243
|
+
def _timeout_error?(error)
|
244
|
+
error.inspect =~ /timeout/i
|
245
|
+
end
|
246
|
+
|
247
|
+
# def _timeout_error?(error); end # TODO: not sure what to do on MRI and friends
|
248
|
+
#
|
249
|
+
# def _timeout_error?(error)
|
250
|
+
# if error.is_a?(JDBCError)
|
251
|
+
# if sql_exception = error.sql_exception
|
252
|
+
# return true if sql_exception.to_s =~ /timeout/i
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
# end if defined? ArJdbc
|
256
|
+
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
#require 'thread_safe'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Bogacs
|
5
|
+
module PoolSupport
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
#base.send :include, ThreadSafe::Util::CheapLockable
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_connection
|
12
|
+
Base.send(spec.adapter_method, spec.config)
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_connection_id
|
16
|
+
Base.connection_id ||= Thread.current.object_id # TODO
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'active_record/connection_adapters/abstract/connection_pool'
|
2
|
+
require 'thread'
|
3
|
+
require 'thread_safe'
|
4
|
+
require 'atomic'
|
5
|
+
|
6
|
+
# NOTE: needs explicit configuration - before connection gets established e.g.
|
7
|
+
#
|
8
|
+
# pool_class = ActiveRecord::ConnectionAdapters::ShareableConnectionPool
|
9
|
+
# ActiveRecord::ConnectionAdapters::ConnectionHandler.connection_pool_class = pool_class
|
10
|
+
#
|
11
|
+
module ActiveRecord
|
12
|
+
module Bogacs
|
13
|
+
class ShareablePool < ConnectionAdapters::ConnectionPool # TODO do not override?!
|
14
|
+
include ThreadSafe::Util::CheapLockable
|
15
|
+
|
16
|
+
DEFAULT_SHARED_POOL = 0.25 # only allow 25% of the pool size to be shared
|
17
|
+
MAX_THREAD_SHARING = 5 # not really a strict limit but should hold
|
18
|
+
|
19
|
+
attr_reader :shared_size
|
20
|
+
|
21
|
+
# @override
|
22
|
+
def initialize(spec)
|
23
|
+
super(spec) # ConnectionPool#initialize
|
24
|
+
shared_size = spec.config[:shared_pool]
|
25
|
+
shared_size = shared_size ? shared_size.to_f : DEFAULT_SHARED_POOL
|
26
|
+
# size 0.0 - 1.0 assumes percentage of the pool size
|
27
|
+
shared_size = ( @size * shared_size ).round if shared_size <= 1.0
|
28
|
+
@shared_size = shared_size.to_i
|
29
|
+
@shared_connections = ThreadSafe::Cache.new # :initial_capacity => @shared_size, :concurrency_level => 20
|
30
|
+
end
|
31
|
+
|
32
|
+
# @override
|
33
|
+
def connection
|
34
|
+
# TODO we assume here a single pool - multiple pool support not implemented!
|
35
|
+
Thread.current[:shared_pool_connection] || begin # super - simplified :
|
36
|
+
super # @reserved_connections.compute_if_absent(current_connection_id) { checkout }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @override
|
41
|
+
def active_connection?
|
42
|
+
if shared_conn = Thread.current[:shared_pool_connection]
|
43
|
+
return shared_conn.in_use?
|
44
|
+
end
|
45
|
+
super_active_connection? current_connection_id
|
46
|
+
end
|
47
|
+
|
48
|
+
# @override called from ConnectionManagement middle-ware (when finished)
|
49
|
+
def release_connection(with_id = current_connection_id)
|
50
|
+
if reserved_conn = @reserved_connections[with_id]
|
51
|
+
if shared_count = @shared_connections[reserved_conn]
|
52
|
+
cheap_synchronize do # lock due #get_shared_connection ... not needed ?!
|
53
|
+
# NOTE: the other option is to not care about shared here at all ...
|
54
|
+
if shared_count.get == 0 # releasing a shared connection
|
55
|
+
release_shared_connection(reserved_conn)
|
56
|
+
#else return false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
else # check back-in non-shared connections
|
60
|
+
checkin reserved_conn # (what super does)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# @override
|
66
|
+
def disconnect!
|
67
|
+
cheap_synchronize { @shared_connections.clear; super }
|
68
|
+
end
|
69
|
+
|
70
|
+
# @override
|
71
|
+
def clear_reloadable_connections!
|
72
|
+
cheap_synchronize { @shared_connections.clear; super }
|
73
|
+
end
|
74
|
+
|
75
|
+
# @override
|
76
|
+
# @note called from #reap thus the pool should work with reaper
|
77
|
+
def remove(conn)
|
78
|
+
cheap_synchronize { @shared_connections.delete(conn); super }
|
79
|
+
end
|
80
|
+
|
81
|
+
# # Return any checked-out connections back to the pool by threads that
|
82
|
+
# # are no longer alive.
|
83
|
+
# # @private AR 3.2 compatibility
|
84
|
+
# def clear_stale_cached_connections!
|
85
|
+
# keys = Thread.list.find_all { |t| t.alive? }.map(&:object_id)
|
86
|
+
# keys = @reserved_connections.keys - keys
|
87
|
+
# keys.each do |key|
|
88
|
+
# release_connection(key)
|
89
|
+
# @reserved_connections.delete(key)
|
90
|
+
# end
|
91
|
+
# end if ActiveRecord::VERSION::MAJOR < 4
|
92
|
+
|
93
|
+
# TODO take care of explicit connection.close (`pool.checkin self`) ?
|
94
|
+
|
95
|
+
# Custom API :
|
96
|
+
|
97
|
+
def release_shared_connection(connection)
|
98
|
+
if connection == Thread.current[:shared_pool_connection]
|
99
|
+
Thread.current[:shared_pool_connection] = nil
|
100
|
+
end
|
101
|
+
|
102
|
+
@shared_connections.delete(connection)
|
103
|
+
checkin connection
|
104
|
+
end
|
105
|
+
|
106
|
+
def with_shared_connection
|
107
|
+
# with_shared_connection call nested in the same thread
|
108
|
+
if connection = Thread.current[:shared_pool_connection]
|
109
|
+
emulated_checkout(connection)
|
110
|
+
return yield connection
|
111
|
+
end
|
112
|
+
|
113
|
+
start = Time.now if DEBUG
|
114
|
+
begin
|
115
|
+
connection_id = current_connection_id
|
116
|
+
# if there's a 'regular' connection on the thread use it as super
|
117
|
+
if super_active_connection?(connection_id) # for current thread
|
118
|
+
connection = self.connection # do not mark as shared
|
119
|
+
DEBUG && debug("with_shared_conn 10 got active = #{connection.to_s}")
|
120
|
+
# otherwise if we have a shared connection - use that one :
|
121
|
+
elsif connection = get_shared_connection
|
122
|
+
emulated_checkout(connection); shared = true
|
123
|
+
DEBUG && debug("with_shared_conn 20 got shared = #{connection.to_s}")
|
124
|
+
else
|
125
|
+
cheap_synchronize do
|
126
|
+
# check shared again as/if threads end up sync-ing up here :
|
127
|
+
if connection = get_shared_connection
|
128
|
+
emulated_checkout(connection)
|
129
|
+
DEBUG && debug("with_shared_conn 21 got shared = #{connection.to_s}")
|
130
|
+
end # here we acquire but a connection from the pool
|
131
|
+
# TODO the bottle-neck for concurrency doing sync { checkout } :
|
132
|
+
unless connection # here we acquire a connection from the pool
|
133
|
+
connection = self.checkout # might block if pool fully used
|
134
|
+
add_shared_connection(connection)
|
135
|
+
DEBUG && debug("with_shared_conn 30 acq shared = #{connection.to_s}")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
shared = true
|
139
|
+
end
|
140
|
+
|
141
|
+
Thread.current[:shared_pool_connection] = connection if shared
|
142
|
+
|
143
|
+
DEBUG && debug("with_shared_conn obtaining a connection took #{(Time.now - start) * 1000}ms")
|
144
|
+
yield connection
|
145
|
+
ensure
|
146
|
+
Thread.current[:shared_pool_connection] = nil # if shared
|
147
|
+
rem_shared_connection(connection) if shared
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
def super_active_connection?(connection_id = current_connection_id)
|
154
|
+
(@reserved_connections.get(connection_id) || ( return false )).in_use?
|
155
|
+
end
|
156
|
+
|
157
|
+
def super_active_connection?(connection_id = current_connection_id)
|
158
|
+
synchronize do
|
159
|
+
(@reserved_connections[connection_id] || ( return false )).in_use?
|
160
|
+
end
|
161
|
+
end if ActiveRecord::VERSION::MAJOR < 4
|
162
|
+
|
163
|
+
def acquire_connection_no_wait?
|
164
|
+
synchronize do
|
165
|
+
@available.send(:can_remove_no_wait?) || @connections.size < @size
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# get a (shared) connection that is least shared among threads (or nil)
|
170
|
+
# nil gets returned if it's 'better' to checkout a new one to be shared
|
171
|
+
# ... to better utilize shared connection reuse among multiple threads
|
172
|
+
def get_shared_connection # (lock = nil)
|
173
|
+
least_count = MAX_THREAD_SHARING + 1; least_shared = nil
|
174
|
+
shared_connections_size = @shared_connections.size
|
175
|
+
|
176
|
+
@shared_connections.each_pair do |connection, shared_count|
|
177
|
+
next if shared_count.get >= MAX_THREAD_SHARING
|
178
|
+
if ( shared_count = shared_count.get ) < least_count
|
179
|
+
DEBUG && debug(" get_shared_conn loop : #{connection.to_s} shared #{shared_count}-time(s)")
|
180
|
+
# ! DO NOT return connection if shared_count == 0
|
181
|
+
least_count = shared_count; least_shared = connection
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
if least_count > 0
|
186
|
+
if shared_connections_size < @shared_connections.size
|
187
|
+
DEBUG && debug(" get_shared_conn retry (shared connection added)")
|
188
|
+
return get_shared_connection # someone else added something re-try
|
189
|
+
end
|
190
|
+
if ( @shared_connections.size < @shared_size ) && acquire_connection_no_wait?
|
191
|
+
DEBUG && debug(" get_shared_conn return none - acquire from pool")
|
192
|
+
return nil # we should rather 'get' a new shared one from the pool
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# we did as much as could without a lock - now sync due possible release
|
197
|
+
cheap_synchronize do # TODO although this likely might be avoided ...
|
198
|
+
# should try again if possibly the same connection got released :
|
199
|
+
unless least_count = @shared_connections[least_shared]
|
200
|
+
DEBUG && debug(" get_shared_conn retry (connection got released)")
|
201
|
+
return get_shared_connection
|
202
|
+
end
|
203
|
+
least_count.update { |v| v + 1 }
|
204
|
+
end if least_shared
|
205
|
+
|
206
|
+
DEBUG && debug(" get_shared_conn least shared = #{least_shared.to_s}")
|
207
|
+
least_shared # might be nil in that case we'll likely wait (as super)
|
208
|
+
end
|
209
|
+
|
210
|
+
def add_shared_connection(connection)
|
211
|
+
@shared_connections[connection] = Atomic.new(1)
|
212
|
+
end
|
213
|
+
|
214
|
+
def rem_shared_connection(connection)
|
215
|
+
if shared_count = @shared_connections[connection]
|
216
|
+
# shared_count.update { |v| v - 1 } # NOTE: likely fine without lock!
|
217
|
+
cheap_synchronize do # give it back to the pool
|
218
|
+
shared_count.update { |v| v - 1 } # might give it back if :
|
219
|
+
release_shared_connection(connection) if shared_count.get == 0
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def emulated_checkin(connection)
|
225
|
+
# NOTE: not sure we'd like to run `run_callbacks :checkin {}` here ...
|
226
|
+
connection.expire
|
227
|
+
end
|
228
|
+
|
229
|
+
def emulated_checkout(connection)
|
230
|
+
# NOTE: not sure we'd like to run `run_callbacks :checkout {}` here ...
|
231
|
+
connection.lease; # connection.verify! auto-reconnect should do this
|
232
|
+
end
|
233
|
+
|
234
|
+
DEBUG = begin
|
235
|
+
debug = ENV['DB_POOL_DEBUG'].to_s
|
236
|
+
if debug.to_s == 'false' then false
|
237
|
+
elsif ! debug.empty?
|
238
|
+
log_dev = case debug
|
239
|
+
when 'STDOUT', 'stdout' then STDOUT
|
240
|
+
when 'STDERR', 'stderr' then STDERR
|
241
|
+
when 'true' then ActiveRecord::Base.logger
|
242
|
+
else File.expand_path(debug)
|
243
|
+
end
|
244
|
+
require 'logger'; Logger.new log_dev
|
245
|
+
else nil
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def debug(msg); DEBUG.debug msg end
|
252
|
+
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|