advanced_connection 0.5.14 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -12
- data/lib/advanced_connection/active_record_ext.rb +3 -12
- data/lib/advanced_connection/active_record_ext/abstract_adapter.rb +17 -8
- data/lib/advanced_connection/active_record_ext/connection_pool/idle_manager.rb +113 -168
- data/lib/advanced_connection/active_record_ext/connection_pool/queues.rb +5 -0
- data/lib/advanced_connection/active_record_ext/connection_pool/without_connection.rb +24 -12
- data/lib/advanced_connection/config.rb +26 -15
- data/lib/advanced_connection/error.rb +1 -0
- data/lib/advanced_connection/railtie.rb +0 -6
- data/lib/advanced_connection/version.rb +2 -2
- data/lib/generators/advanced_connection/install/templates/advanced_connection.rb +12 -21
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9dd080219f299367a2de7b23c0e7ce0d83bc20d3
|
4
|
+
data.tar.gz: d3d6b6cca8b0fc5bd717843f00ac5eea6a922589
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df32536d7ff51cea3a0620211290ebec729ecb3c3bc21c6845cea8d2baf8ca300dcb296f6fb3c2704eb14bebc6515a3a305d0df10a1baef19bc48e5ebec8cbfc
|
7
|
+
data.tar.gz: dc871c1ee46031e645cfd0417a6c6261dcf708947cab05b7c400007cbdcf1f298eba9edf8ba97e6a188bc3e6c8fe3f398720d42e41d7f662fc89e19e3da9b788
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -34,8 +34,7 @@ bundle exec rails generate advanced_connection:install
|
|
34
34
|
#### Idle Connection Manager
|
35
35
|
|
36
36
|
Enabling this will enable idle connection management. This allows you to specify settings
|
37
|
-
to enable automatic
|
38
|
-
idle connection culling.
|
37
|
+
to enable automatic idle connection reaping.
|
39
38
|
|
40
39
|
```text
|
41
40
|
enable_idle_connection_manager = true | false
|
@@ -61,16 +60,6 @@ Pool queue type determines both how free connections will be checkout out of the
|
|
61
60
|
connection_pool_queue_type = :fifo | :lifo | :stack | :prefer_older | :prefer_younger
|
62
61
|
```
|
63
62
|
|
64
|
-
How many connections to prestart on initial startup of rails. This can help to reduce the time it takes a restarted production node to start responding again.
|
65
|
-
```text
|
66
|
-
warmup_connections = integer | false
|
67
|
-
```
|
68
|
-
|
69
|
-
Minimum number of connection to keep idle. If, during the idle check, you have fewer than this many connections idle, then a number of new connections will be created up to this this number.
|
70
|
-
```text
|
71
|
-
min_idle_connections = integer
|
72
|
-
```
|
73
|
-
|
74
63
|
Maximum number of connections that can remain idle without being culled. If you have
|
75
64
|
more idle conections than this, only the difference between the total idle and this
|
76
65
|
maximum will be culled.
|
@@ -38,21 +38,12 @@ module AdvancedConnection
|
|
38
38
|
end
|
39
39
|
|
40
40
|
ActiveRecord::ConnectionAdapters::ConnectionPool.instance_exec do
|
41
|
-
if AdvancedConnection.enable_idle_connection_manager
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
if AdvancedConnection.enable_statement_pooling
|
46
|
-
include ConnectionPool::StatementPooling
|
47
|
-
elsif AdvancedConnection.enable_without_connection
|
48
|
-
include ConnectionPool::WithoutConnection
|
49
|
-
end
|
41
|
+
include ConnectionPool::IdleManager if AdvancedConnection.enable_idle_connection_manager
|
42
|
+
include ConnectionPool::WithoutConnection
|
50
43
|
end
|
51
44
|
|
52
45
|
ActiveRecord::Base.instance_exec do
|
53
|
-
|
54
|
-
extend ActiveRecordExt::WithoutConnection unless AdvancedConnection.enable_statement_pooling
|
55
|
-
end
|
46
|
+
extend ActiveRecordExt::WithoutConnection
|
56
47
|
end
|
57
48
|
end
|
58
49
|
end
|
@@ -31,16 +31,25 @@ module AdvancedConnection::ActiveRecordExt
|
|
31
31
|
end
|
32
32
|
|
33
33
|
included do
|
34
|
-
attr_accessor :last_checked_in, :instantiated_at
|
34
|
+
attr_accessor :last_checked_in, :last_checked_out, :instantiated_at
|
35
35
|
alias_method_chain :initialize, :advanced_connection
|
36
|
+
alias_method_chain :lease, :advanced_connection
|
36
37
|
end
|
37
38
|
|
38
39
|
def initialize_with_advanced_connection(*args, &block)
|
39
|
-
@
|
40
|
-
@
|
40
|
+
@instantiated_at = Time.now
|
41
|
+
@last_checked_in = @instantiated_at
|
42
|
+
@last_checked_out = false
|
41
43
|
initialize_without_advanced_connection(*args, &block)
|
42
44
|
end
|
43
45
|
|
46
|
+
def lease_with_advanced_connection
|
47
|
+
synchronize {
|
48
|
+
@last_checked_out = Time.now
|
49
|
+
lease_without_advanced_connection
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
44
53
|
def instance_age
|
45
54
|
(Time.now - @instantiated_at).to_f
|
46
55
|
end
|
@@ -57,20 +66,20 @@ module AdvancedConnection::ActiveRecordExt
|
|
57
66
|
case pool.queue_type
|
58
67
|
when :prefer_younger then
|
59
68
|
# when prefering younger, we sort oldest->youngest
|
60
|
-
#
|
69
|
+
# to ensure that older connections will be reaped
|
61
70
|
# during #remove_idle_connections()
|
62
71
|
-(instance_age <=> other.instance_age)
|
63
72
|
when :prefer_older then
|
64
|
-
# when prefering older, we sort youngest->oldest
|
65
|
-
#
|
73
|
+
# when prefering older, we sort youngest->oldest to
|
74
|
+
# ensure that younger connections will be reaped
|
66
75
|
# during #remove_idle_connections()
|
67
76
|
(instance_age <=> other.instance_age)
|
68
77
|
else
|
69
|
-
#
|
78
|
+
# With fifo / lifo queues, we only care about the
|
70
79
|
# last time a given connection was used (inferred
|
71
80
|
# by when it was last checked into the pool).
|
72
81
|
# This ensures that the longer idling connections
|
73
|
-
# will be
|
82
|
+
# will be reaped.
|
74
83
|
-(last_checked_in <=> other.last_checked_in)
|
75
84
|
end
|
76
85
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
1
|
# Copyright (C) 2016 Finalsite, LLC
|
3
2
|
# Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
|
4
3
|
#
|
@@ -32,95 +31,99 @@ module AdvancedConnection::ActiveRecordExt
|
|
32
31
|
|
33
32
|
class IdleManager
|
34
33
|
attr_accessor :interval
|
35
|
-
attr_reader :
|
36
|
-
private :thread
|
34
|
+
attr_reader :logger
|
37
35
|
|
38
36
|
def initialize(pool, interval)
|
39
37
|
@pool = pool
|
40
38
|
@interval = interval.to_i
|
41
|
-
@thread = nil
|
42
39
|
@logger = ActiveSupport::Logger.new(Rails.root.join('log', 'idle_manager.log'))
|
43
|
-
@logger.level = Rails.logger.level
|
44
|
-
end
|
45
40
|
|
46
|
-
|
47
|
-
@logger.info(format("#{idle_stats} #{format}", *args))
|
41
|
+
@logger.level = AdvancedConnection.idle_manager_log_level || ::Logger::INFO
|
48
42
|
end
|
49
43
|
|
50
|
-
|
51
|
-
|
44
|
+
%w[debug info warn error].each do |level|
|
45
|
+
define_method("log_#{level}") do |fmt, *args|
|
46
|
+
@logger.send(level, format("[#{Time.now}] #{idle_stats} #{level.upcase}: #{fmt}", *args))
|
47
|
+
end
|
52
48
|
end
|
53
49
|
|
54
|
-
def
|
55
|
-
@
|
50
|
+
def reserved_connections
|
51
|
+
reserved = @pool.instance_variable_get(:@reserved_connections).dup
|
52
|
+
Hash[reserved.keys.zip(reserved.values)]
|
56
53
|
end
|
57
54
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
55
|
+
def dump_connections
|
56
|
+
return unless (stats_dump = Rails.root.join('tmp', 'dump-connections.txt')).exist?
|
57
|
+
|
58
|
+
log_info "Dumping connections"
|
59
|
+
|
60
|
+
id_size = object_id.to_s.size
|
61
|
+
@logger.info(format("%3s: %#{id_size}s\t%9s\t%9s\t%4s\t%s", 'IDX', 'OID', 'AGE', 'IDLE', 'ACTV', 'OWNER'))
|
62
|
+
|
63
|
+
@pool.connections.dup.each_with_index do |connection, index|
|
64
|
+
if connection.in_use?
|
65
|
+
thread_id = reserved_connections.index(connection) || 0
|
66
|
+
thread_hexid = "0x" << (thread_id << 1).to_s(16)
|
66
67
|
end
|
67
|
-
|
68
|
+
|
69
|
+
@logger.info(format("%3d: %#{id_size}d\t%9d\t%9d\t%4s\t%s",
|
70
|
+
index, connection.object_id,
|
71
|
+
connection.instance_age, connection.idle_time,
|
72
|
+
connection.in_use?.to_s, thread_hexid))
|
68
73
|
end
|
74
|
+
|
75
|
+
!!(stats_dump.unlink rescue true) # rubocop:disable Style/RescueModifier
|
69
76
|
end
|
70
77
|
|
71
78
|
def idle_stats
|
72
79
|
stats = @pool.pool_statistics
|
73
|
-
format(
|
74
|
-
|
75
|
-
stats.active, stats.available, stats.idle, stats.total,
|
76
|
-
)
|
80
|
+
format("[Act: %d / Avail: %d (%d idle) / Total: %d]",
|
81
|
+
stats.active, stats.available, stats.idle, stats.total)
|
77
82
|
end
|
78
83
|
|
79
|
-
def
|
80
|
-
|
81
|
-
@thread.alive? ? :running : :dead
|
82
|
-
else
|
83
|
-
:stopped
|
84
|
-
end
|
85
|
-
end
|
84
|
+
def safe_timed_run(last_run = nil)
|
85
|
+
return unless block_given?
|
86
86
|
|
87
|
-
|
88
|
-
stop.start
|
89
|
-
end
|
87
|
+
log_debug "last run was #{Time.now - last_run} seconds ago" if last_run
|
90
88
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
89
|
+
begin
|
90
|
+
start = Time.now
|
91
|
+
yield
|
92
|
+
rescue StandardError => e
|
93
|
+
log_error "#{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
94
|
+
ensure
|
95
|
+
finish = ((last_run = Time.now) - start).round(6)
|
96
|
+
log_info("finished idle connection tasks in #{finish} seconds")
|
97
|
+
end
|
98
|
+
|
99
|
+
last_run
|
95
100
|
end
|
96
101
|
|
97
|
-
def
|
102
|
+
def run
|
98
103
|
return unless @interval > 0
|
99
104
|
|
100
|
-
|
105
|
+
Thread.new(@pool, @interval) do |pool, interval|
|
106
|
+
Thread.current.name = self.class.name if Thread.current.respond_to? :name=
|
107
|
+
|
108
|
+
begin
|
109
|
+
pool.release_connection if pool.active_connection?
|
110
|
+
rescue StandardError => e
|
111
|
+
log_error "#{e.class.name}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
112
|
+
end
|
113
|
+
|
101
114
|
log_info("starting idle manager; running every #{interval} seconds")
|
115
|
+
last_run = nil
|
102
116
|
|
103
|
-
loop
|
117
|
+
loop {
|
104
118
|
sleep interval
|
105
119
|
|
106
|
-
|
107
|
-
|
108
|
-
dump_stats
|
109
|
-
|
110
|
-
log_debug("beginning idle connection cleanup")
|
120
|
+
last_run = safe_timed_run(last_run) do
|
121
|
+
dump_connections
|
111
122
|
pool.remove_idle_connections
|
112
|
-
|
113
|
-
log_debug("beginning idle connection warmup")
|
114
|
-
pool.create_idle_connections
|
115
|
-
|
116
|
-
finish = (Time.now - start).round(6)
|
117
|
-
log_info("finished idle connection tasks in #{finish} seconds; next run in #{interval} seconds")
|
118
|
-
rescue => e
|
119
|
-
Rails.logger.error "#{e.class.name}: #{e.message}"
|
120
|
-
e.backtrace.each { |line| Rails.logger.error line }
|
121
123
|
end
|
122
|
-
|
123
|
-
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
124
127
|
self
|
125
128
|
end
|
126
129
|
end
|
@@ -139,41 +142,19 @@ module AdvancedConnection::ActiveRecordExt
|
|
139
142
|
Queues::FIFO.new
|
140
143
|
end
|
141
144
|
|
142
|
-
@idle_manager = IdleManager.new(self, idle_check_interval).tap(&:
|
145
|
+
@idle_manager = IdleManager.new(self, idle_check_interval).tap(&:run)
|
143
146
|
end
|
144
147
|
|
145
|
-
#
|
146
|
-
## SETINGS
|
147
|
-
#
|
148
|
-
|
149
148
|
def queue_type
|
150
|
-
@queue_type ||= spec.config.fetch(
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
def warmup_connection_count
|
155
|
-
@warmup_connection_count ||= begin
|
156
|
-
conns = spec.config[:warmup_connections] || AdvancedConnection.warmup_connections
|
157
|
-
conns.to_i > connection_limit ? connection_limit : conns.to_i
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def max_idle_time
|
162
|
-
@max_idle_time ||= (spec.config[:max_idle_time] || \
|
163
|
-
AdvancedConnection.max_idle_time).to_i
|
164
|
-
end
|
165
|
-
|
166
|
-
def idle_check_interval
|
167
|
-
@idle_check_interval ||= (spec.config[:idle_check_interval] || \
|
168
|
-
AdvancedConnection.idle_check_interval || \
|
169
|
-
max_idle_time).to_i
|
149
|
+
@queue_type ||= spec.config.fetch(
|
150
|
+
:queue_type, AdvancedConnection.connection_pool_queue_type
|
151
|
+
).to_s.downcase.to_sym
|
170
152
|
end
|
171
153
|
|
172
154
|
def max_idle_connections
|
173
155
|
@max_idle_connections ||= begin
|
174
156
|
begin
|
175
|
-
|
176
|
-
AdvancedConnection.max_idle_connections).to_i
|
157
|
+
spec.config.fetch(:max_idle_connections, AdvancedConnection.max_idle_connections).to_i
|
177
158
|
rescue FloatDomainError => e
|
178
159
|
raise unless e.message =~ /infinity/i
|
179
160
|
::Float::INFINITY
|
@@ -181,120 +162,84 @@ module AdvancedConnection::ActiveRecordExt
|
|
181
162
|
end
|
182
163
|
end
|
183
164
|
|
184
|
-
def
|
185
|
-
@
|
186
|
-
|
187
|
-
min_idle = (min_idle > 0 ? min_idle : 0)
|
188
|
-
min_idle <= max_idle_connections ? min_idle : max_idle_connections
|
165
|
+
def max_idle_time
|
166
|
+
@max_idle_time ||= begin
|
167
|
+
spec.config.fetch(:max_idle_time, AdvancedConnection.max_idle_time).to_i
|
189
168
|
end
|
190
169
|
end
|
191
170
|
|
192
|
-
def
|
193
|
-
@
|
171
|
+
def idle_check_interval
|
172
|
+
@idle_check_interval ||= begin
|
173
|
+
spec.config[:idle_check_interval] || \
|
174
|
+
AdvancedConnection.idle_check_interval || \
|
175
|
+
max_idle_time
|
176
|
+
end.to_i
|
194
177
|
end
|
195
178
|
|
196
179
|
def checkin_with_last_checked_in(conn)
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
total = @connections.size
|
209
|
-
idle = @connections.count(&:idle?)
|
210
|
-
active = @connections.count(&:in_use?)
|
211
|
-
available = total - active
|
212
|
-
|
213
|
-
ActiveSupport::OrderedOptions.new.merge(
|
214
|
-
total: total,
|
215
|
-
idle: idle,
|
216
|
-
active: active,
|
217
|
-
available: available
|
218
|
-
)
|
180
|
+
begin
|
181
|
+
if conn.last_checked_out
|
182
|
+
previous_checkin, conn.last_checked_in = conn.last_checked_in, Time.now
|
183
|
+
idle_manager.log_debug "checking in connection %s at %s (checked out for %.3f seconds)",
|
184
|
+
conn.object_id, conn.last_checked_in,
|
185
|
+
(conn.last_checked_in - conn.last_checked_out).to_f.round(6)
|
186
|
+
else
|
187
|
+
idle_manager.log_debug "checking in connection #{conn.object_id}"
|
188
|
+
end
|
189
|
+
ensure
|
190
|
+
checkin_without_last_checked_in(conn)
|
219
191
|
end
|
220
192
|
end
|
221
193
|
|
222
|
-
def
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
# if we don't have enough available slots (i.e., current pool size
|
230
|
-
# is greater than max pool size) then do nothing
|
231
|
-
return unless open_slots > 0
|
232
|
-
|
233
|
-
# otherwise, spin up connections up to our min_idle_connections setting
|
234
|
-
create_count = min_idle_connections - idle_count
|
235
|
-
create_count = open_slots if create_count > open_slots
|
236
|
-
|
237
|
-
warmup_connections(create_count)
|
194
|
+
def pool_statistics
|
195
|
+
ActiveSupport::OrderedOptions.new.merge(
|
196
|
+
total: (total = @connections.size),
|
197
|
+
idle: (idle = @connections.count(&:idle?)),
|
198
|
+
active: (active = @connections.count(&:in_use?)),
|
199
|
+
available: (total - active)
|
200
|
+
)
|
238
201
|
end
|
239
202
|
|
240
|
-
def
|
241
|
-
|
242
|
-
slots = connection_limit - @connections.size
|
243
|
-
count = slots if slots < count
|
244
|
-
|
245
|
-
return unless slots >= count
|
246
|
-
|
247
|
-
idle_manager.log_info "Warming up #{count} connection#{count > 1 ? 's' : ''}"
|
248
|
-
synchronize do
|
249
|
-
count.times {
|
250
|
-
conn = checkout_new_connection
|
251
|
-
@available.add conn
|
252
|
-
}
|
253
|
-
end
|
203
|
+
def idle_connections
|
204
|
+
@connections.select(&:idle?).sort
|
254
205
|
end
|
255
206
|
|
256
207
|
def remove_idle_connections
|
257
208
|
# don't attempt to remove idle connections if we have threads waiting
|
258
209
|
if @available.num_waiting > 0
|
259
|
-
idle_manager.log_warn "
|
210
|
+
idle_manager.log_warn "Skipping reap while #{@available.num_waiting} thread(s) are actively waiting on database connections..."
|
260
211
|
return
|
261
212
|
end
|
262
213
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
unless idle_count > max_idle_connections
|
267
|
-
idle_manager.log_warn "idle count (#{idle_count}) does not exceed max idle connections (#{max_idle_connections}); skipping reap."
|
268
|
-
return
|
269
|
-
end
|
214
|
+
return unless (candidates = idle_connections.size - max_idle_connections) > 0
|
215
|
+
idle_manager.log_info "attempting to reap #{candidates} candidate connections"
|
270
216
|
|
271
|
-
|
217
|
+
reaped = 0
|
272
218
|
|
273
|
-
|
274
|
-
|
275
|
-
if idx < cull_count
|
219
|
+
synchronize do
|
220
|
+
idle_connections[0...candidates].each_with_index { |conn, idx|
|
276
221
|
if remove_connection(conn)
|
277
|
-
|
278
|
-
idle_manager.log_info "
|
222
|
+
reaped += 1
|
223
|
+
idle_manager.log_info "reaped candidate connection #%d id#%d age:%d idle:%d" % [
|
224
|
+
idx, conn.object_id, conn.instance_age.to_i, conn.idle_time.to_i
|
225
|
+
]
|
279
226
|
else
|
280
|
-
idle_manager.log_info "kept connection
|
227
|
+
idle_manager.log_info "kept candidate connection #%d id#%d age:%d idle:%d" % [
|
228
|
+
idx, conn.object_id, conn.instance_age.to_i, conn.idle_time.to_i
|
229
|
+
]
|
281
230
|
end
|
282
|
-
|
283
|
-
idle_manager.log_info "kept connection ##{idx} id##{conn.object_id} - age:#{conn.instance_age.to_i} idle_time:#{conn.idle_time.to_i}"
|
284
|
-
end
|
231
|
+
}
|
285
232
|
end
|
286
233
|
|
287
|
-
idle_manager.log_info "
|
234
|
+
idle_manager.log_info "reaped #{reaped} of #{candidates} candidate connections"
|
288
235
|
end
|
289
236
|
|
290
237
|
private
|
291
238
|
|
292
239
|
def remove_connection(conn)
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
conn.disconnect!
|
297
|
-
end
|
240
|
+
return false if conn.in_use?
|
241
|
+
|
242
|
+
remove(conn.tap { |c| c.disconnect! })
|
298
243
|
true
|
299
244
|
end
|
300
245
|
end
|
@@ -38,6 +38,7 @@ module AdvancedConnection::ActiveRecordExt
|
|
38
38
|
class AgeSorted < Default
|
39
39
|
def poll(timeout = nil)
|
40
40
|
synchronize do
|
41
|
+
# always sort age based queues from youngest to oldest
|
41
42
|
@queue.sort_by!(&:instance_age)
|
42
43
|
no_wait_poll || (timeout && wait_poll(timeout))
|
43
44
|
end
|
@@ -46,12 +47,16 @@ module AdvancedConnection::ActiveRecordExt
|
|
46
47
|
|
47
48
|
class YoungAgeBiased < AgeSorted
|
48
49
|
def remove
|
50
|
+
# Think of this like a stack, sorted youngest to oldest, bottom to top. To to
|
51
|
+
# aquire the youngest entry, we shift it off the bottom (i.e., the first element)
|
49
52
|
@queue.shift
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
53
56
|
class OldAgeBiased < AgeSorted
|
54
57
|
def remove
|
58
|
+
# Think of this like a stack, sorted youngest to oldest, bottom to top. To to
|
59
|
+
# aquire the oldest entry, we pop it off the top (i.e., the last element)
|
55
60
|
@queue.pop
|
56
61
|
end
|
57
62
|
end
|
@@ -24,6 +24,8 @@ module AdvancedConnection::ActiveRecordExt
|
|
24
24
|
module WithoutConnection
|
25
25
|
extend ActiveSupport::Concern
|
26
26
|
|
27
|
+
MAX_REAQUIRE_ATTEMPTS = 3
|
28
|
+
|
27
29
|
included do
|
28
30
|
include ::ActiveSupport::Callbacks
|
29
31
|
alias_method :retrieve_connection, :connection
|
@@ -34,19 +36,25 @@ module AdvancedConnection::ActiveRecordExt
|
|
34
36
|
set_callback :without_connection, :after, :after_without_connection
|
35
37
|
end
|
36
38
|
|
37
|
-
def without_connection
|
39
|
+
def without_connection(&block)
|
38
40
|
return unless block_given?
|
39
41
|
|
42
|
+
# if we're not enabled just execute the block and return
|
43
|
+
return block.call unless AdvancedConnection.enable_without_connection
|
44
|
+
|
45
|
+
# if we have a transaction open, we can't release the database connection
|
46
|
+
# or Bad Things (tm) happen - so we just execute our block and return
|
47
|
+
if transaction_open?
|
48
|
+
Rails.logger.warn "WithoutConnection skipped due to open transaction."
|
49
|
+
return block.call
|
50
|
+
end
|
51
|
+
|
40
52
|
if AdvancedConnection.callbacks.without_connection.present?
|
41
53
|
run_callbacks(:without_connection) do
|
42
|
-
__without_connection
|
43
|
-
yield
|
44
|
-
end
|
54
|
+
__without_connection { block.call }
|
45
55
|
end
|
46
56
|
else
|
47
|
-
__without_connection
|
48
|
-
yield
|
49
|
-
end
|
57
|
+
__without_connection { block.call }
|
50
58
|
end
|
51
59
|
end
|
52
60
|
|
@@ -58,15 +66,19 @@ module AdvancedConnection::ActiveRecordExt
|
|
58
66
|
raise Error::UnableToReleaseConnection if active_connection?
|
59
67
|
yield
|
60
68
|
ensure
|
61
|
-
|
69
|
+
attempt = 0
|
62
70
|
begin
|
63
71
|
# attempt to retrieve another connection
|
64
72
|
retrieve_connection
|
65
73
|
rescue ActiveRecord::ConnectionTimeoutError
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
74
|
+
if attempt >= MAX_REAQUIRE_ATTEMPTS
|
75
|
+
Rails.logger.info "Giving up on trying to reacquire database connection"
|
76
|
+
raise Error::UnableToReaquireConnection
|
77
|
+
else
|
78
|
+
Rails.logger.warn "Failed to reaquire database connection - reattempt #{attempt += 1}/#{MAX_REAQUIRE_ATTEMPTS} ..."
|
79
|
+
end
|
80
|
+
|
81
|
+
retry
|
70
82
|
end
|
71
83
|
end
|
72
84
|
|
@@ -21,10 +21,12 @@
|
|
21
21
|
#
|
22
22
|
require 'singleton'
|
23
23
|
require 'monitor'
|
24
|
+
require 'logger'
|
24
25
|
|
25
26
|
module AdvancedConnection
|
26
27
|
class Config
|
27
28
|
include Singleton
|
29
|
+
include MonitorMixin
|
28
30
|
|
29
31
|
VALID_QUEUE_TYPES = [
|
30
32
|
:fifo, :lifo, :stack, :prefer_younger, :prefer_older
|
@@ -37,12 +39,13 @@ module AdvancedConnection
|
|
37
39
|
).freeze unless defined? CALLBACK_TYPES
|
38
40
|
|
39
41
|
DEFAULT_CONFIG = ActiveSupport::OrderedOptions.new.merge(
|
42
|
+
idle_manager_log_level: ::Logger::INFO,
|
40
43
|
enable_without_connection: false,
|
41
44
|
enable_statement_pooling: false,
|
42
45
|
enable_idle_connection_manager: false,
|
43
46
|
connection_pool_queue_type: :fifo,
|
44
|
-
warmup_connections:
|
45
|
-
min_idle_connections:
|
47
|
+
warmup_connections: nil,
|
48
|
+
min_idle_connections: nil,
|
46
49
|
max_idle_connections: ::Float::INFINITY,
|
47
50
|
max_idle_time: 0,
|
48
51
|
idle_check_interval: nil,
|
@@ -140,6 +143,21 @@ module AdvancedConnection
|
|
140
143
|
@config.callbacks
|
141
144
|
end
|
142
145
|
|
146
|
+
def idle_manager_log_level
|
147
|
+
@config[:log_level]
|
148
|
+
end
|
149
|
+
|
150
|
+
def idle_manager_log_level=(level)
|
151
|
+
@config[:log_level] = case level
|
152
|
+
when :debug, /debug/i, ::Logger::DEBUG then ::Logger::DEBUG
|
153
|
+
when :info, /info/i, ::Logger::INFO then ::Logger::INFO
|
154
|
+
when :warn, /warn/i, ::Logger::WARN then ::Logger::WARN
|
155
|
+
when :error, /error/i, ::Logger::ERROR then ::Logger::ERROR
|
156
|
+
when :fatal, /fatal/i, ::Logger::FATAL then ::Logger::FATAL
|
157
|
+
else ::Logger::INFO
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
143
161
|
def enable_without_connection
|
144
162
|
@config[:enable_without_connection]
|
145
163
|
end
|
@@ -171,28 +189,21 @@ module AdvancedConnection
|
|
171
189
|
end
|
172
190
|
|
173
191
|
def warmup_connections
|
174
|
-
|
192
|
+
# deprecated and not used
|
193
|
+
0
|
175
194
|
end
|
176
195
|
|
177
196
|
def warmup_connections=(value)
|
178
|
-
|
179
|
-
fail Error::ConfigError, 'Expected warmup_connections to be nil, false ' \
|
180
|
-
"or a valid positive integer, but found `#{value.inspect}`"
|
181
|
-
end
|
182
|
-
|
183
|
-
synchronize { @config[:warmup_connections] = value.to_s =~ /^\d+$/ ? value.to_i : false }
|
197
|
+
# deprecated and not used
|
184
198
|
end
|
185
199
|
|
186
200
|
def min_idle_connections
|
187
|
-
|
201
|
+
# deprecated and not used
|
202
|
+
0
|
188
203
|
end
|
189
204
|
|
190
205
|
def min_idle_connections=(value)
|
191
|
-
|
192
|
-
fail Error::ConfigError, 'Expected min_idle_connections to be ' \
|
193
|
-
"a valid integer value, but found `#{value.inspect}`"
|
194
|
-
end
|
195
|
-
synchronize { @config[:min_idle_connections] = value.to_i }
|
206
|
+
# deprecated and not used
|
196
207
|
end
|
197
208
|
|
198
209
|
def max_idle_connections
|
@@ -41,12 +41,6 @@ module AdvancedConnection
|
|
41
41
|
ActiveRecord::Base.send(:include, AdvancedConnection::ActiveRecordExt)
|
42
42
|
}
|
43
43
|
end
|
44
|
-
|
45
|
-
config.after_initialize do
|
46
|
-
if AdvancedConnection.enable_idle_connection_manager && AdvancedConnection.warmup_connections
|
47
|
-
ActiveRecord::Base.connection_handler.connection_pool_list.each(&:warmup_connections)
|
48
|
-
end
|
49
|
-
end
|
50
44
|
end
|
51
45
|
end
|
52
46
|
end
|
@@ -6,42 +6,33 @@ AdvancedConnection.configure do |config|
|
|
6
6
|
## Idle Manager
|
7
7
|
#
|
8
8
|
# Enabling this will enable idle connection management. This allows you to specify settings
|
9
|
-
# to enable automatic
|
10
|
-
#
|
9
|
+
# to enable the automatic reaping of idle database connections.
|
10
|
+
#
|
11
11
|
# config.enable_idle_connection_manager = <%= AdvancedConnection::Config::DEFAULT_CONFIG.enable_idle_connection_manager.inspect %>
|
12
12
|
|
13
13
|
# Pool queue type determines both how free connections will be checkout out
|
14
|
-
# of the pool, as well as how idle connections will be
|
14
|
+
# of the pool, as well as how idle connections will be reaped. The options are:
|
15
15
|
#
|
16
|
-
# :fifo - All connections will have an equal opportunity to be used and
|
16
|
+
# :fifo - All connections will have an equal opportunity to be used and reaped (default)
|
17
17
|
# :lifo/:stack - More frequently used connections will be reused, leaving less frequently used
|
18
|
-
# connections to be
|
18
|
+
# connections to be reaped
|
19
19
|
# :prefer_older - Longer lived connections will tend to stick around longer, with younger
|
20
|
-
# connections being
|
20
|
+
# connections being reaped
|
21
21
|
# :prefer_younger - Younger lived connections will tend to stick around longer, with older
|
22
|
-
# connections being
|
22
|
+
# connections being reaped
|
23
23
|
#
|
24
24
|
# config.connection_pool_queue_type = <%= AdvancedConnection::Config::DEFAULT_CONFIG.connection_pool_queue_type.inspect %>
|
25
25
|
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# responding again.
|
29
|
-
#
|
30
|
-
# config.warmup_connections = <%= AdvancedConnection::Config::DEFAULT_CONFIG.warmup_connections.inspect %>
|
31
|
-
|
32
|
-
# Minimum number of connection to keep idle. If, during the idle check, you have fewer
|
33
|
-
# than this many connections idle, then a number of new connections will be created
|
34
|
-
# up to this this number.
|
35
|
-
#
|
36
|
-
# config.min_idle_connections = <%= AdvancedConnection::Config::DEFAULT_CONFIG.min_idle_connections.inspect %>
|
26
|
+
# What log level to log idle manager stats and details at (see ::Logger#level)
|
27
|
+
# config.idle_manager_log_level = ::Logger::INFO
|
37
28
|
|
38
|
-
# Maximum number of connections that can remain idle without being
|
29
|
+
# Maximum number of connections that can remain idle without being reaped. If you have
|
39
30
|
# more idle conections than this, only the difference between the total idle and this
|
40
|
-
# maximum will be
|
31
|
+
# maximum will be reaped.
|
41
32
|
#
|
42
33
|
# config.max_idle_connections = ::Float::INFINITY
|
43
34
|
|
44
|
-
# How long (in seconds) a connection can remain idle before being
|
35
|
+
# How long (in seconds) a connection can remain idle before being reaped
|
45
36
|
#
|
46
37
|
# config.max_idle_time = 90
|
47
38
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: advanced_connection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carl P. Corliss
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|