advanced_connection 0.5.1
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/.document +5 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.travis.yml +63 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +218 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +165 -0
- data/advanced_connection.gemspec +49 -0
- data/gemfiles/jruby/rails4_1.gemfile +7 -0
- data/gemfiles/jruby/rails4_2.gemfile +7 -0
- data/gemfiles/ruby/rails4_1.gemfile +7 -0
- data/gemfiles/ruby/rails4_2.gemfile +7 -0
- data/lib/advanced_connection/active_record_ext/abstract_adapter/statement_pooling.rb +121 -0
- data/lib/advanced_connection/active_record_ext/abstract_adapter.rb +48 -0
- data/lib/advanced_connection/active_record_ext/connection_pool/idle_manager.rb +271 -0
- data/lib/advanced_connection/active_record_ext/connection_pool/queues.rb +60 -0
- data/lib/advanced_connection/active_record_ext/connection_pool/statement_pooling.rb +42 -0
- data/lib/advanced_connection/active_record_ext/connection_pool/without_connection.rb +93 -0
- data/lib/advanced_connection/active_record_ext/connection_pool.rb +38 -0
- data/lib/advanced_connection/active_record_ext/without_connection.rb +30 -0
- data/lib/advanced_connection/active_record_ext.rb +59 -0
- data/lib/advanced_connection/config.rb +247 -0
- data/lib/advanced_connection/error.rb +27 -0
- data/lib/advanced_connection/railtie.rb +40 -0
- data/lib/advanced_connection/version.rb +29 -0
- data/lib/advanced_connection.rb +65 -0
- data/lib/generators/advanced_connection/install/USAGE +5 -0
- data/lib/generators/advanced_connection/install/install_generator.rb +30 -0
- data/lib/generators/advanced_connection/install/templates/advanced_connection.rb +142 -0
- data/lib/tasks/advanced_connection_tasks.rake +25 -0
- data/spec/config/database.yml +18 -0
- data/spec/config/database.yml.erb +54 -0
- data/spec/dummy/.gitignore +1 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/images/.keep +0 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/controllers/concerns/.keep +0 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.keep +0 -0
- data/spec/dummy/app/models/.keep +0 -0
- data/spec/dummy/app/models/book.rb +2 -0
- data/spec/dummy/app/models/concerns/.keep +0 -0
- data/spec/dummy/app/views/layouts/application.html.erb +13 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +29 -0
- data/spec/dummy/config/application.rb +32 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +17 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +48 -0
- data/spec/dummy/config/environments/production.rb +79 -0
- data/spec/dummy/config/environments/test.rb +42 -0
- data/spec/dummy/config/initializers/advanced_connection.rb +142 -0
- data/spec/dummy/config/initializers/assets.rb +11 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +56 -0
- data/spec/dummy/config/secrets.yml +22 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20160222045238_create_books.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/lib/assets/.keep +0 -0
- data/spec/dummy/log/.keep +0 -0
- data/spec/dummy/log/test.log +327 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/test/fixtures/books.yml +9 -0
- data/spec/dummy/test/models/book_test.rb +7 -0
- data/spec/idle_manager_spec.rb +16 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/db_config.rb +61 -0
- metadata +399 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "advanced_connection/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "advanced_connection"
|
9
|
+
spec.version = AdvancedConnection::VERSION
|
10
|
+
spec.authors = ["Carl P. Corliss"]
|
11
|
+
spec.email = ["carl.corliss@finalsite.com"]
|
12
|
+
spec.homepage = "https://github.com/finalsite/advanced_connection"
|
13
|
+
spec.summary = "Provides advanced connection options for rails connection pools"
|
14
|
+
spec.description = "Adds idle connection management, statement pooling, and other advanced connection features"
|
15
|
+
spec.license = "LGPL"
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# eventually support this:
|
22
|
+
# spec.required_engine_version = {
|
23
|
+
# :ruby => '~> 2.0',
|
24
|
+
# :jruby => '~> 1.7',
|
25
|
+
# }
|
26
|
+
|
27
|
+
spec.add_runtime_dependency "rails", ">= 4.1.0", "< 5.0"
|
28
|
+
spec.add_runtime_dependency "activerecord", ">= 4.1.0", "< 5.0"
|
29
|
+
spec.add_runtime_dependency "activesupport", ">= 4.1.0", "< 5.0"
|
30
|
+
|
31
|
+
spec.add_development_dependency "rake", '~> 10.5'
|
32
|
+
spec.add_development_dependency "rack", '~> 1.6'
|
33
|
+
spec.add_development_dependency "rspec", '~> 3.4'
|
34
|
+
spec.add_development_dependency "rspec-its", '~> 1.2'
|
35
|
+
spec.add_development_dependency "rspec-collection_matchers", '~> 1.1'
|
36
|
+
|
37
|
+
# optional dependencies
|
38
|
+
if RUBY_ENGINE == 'jruby'
|
39
|
+
spec.add_development_dependency "activerecord-jdbcpostgresql-adapter", "~> 1.3"
|
40
|
+
else
|
41
|
+
spec.add_development_dependency "pg", "~> 0.18"
|
42
|
+
spec.add_development_dependency "pry", '~> 0.10'
|
43
|
+
spec.add_development_dependency "pry-nav", '~> 0.2'
|
44
|
+
end
|
45
|
+
|
46
|
+
spec.add_development_dependency "guard-rspec", '~> 4.6'
|
47
|
+
spec.add_development_dependency "coveralls", '~> 0.8'
|
48
|
+
spec.add_development_dependency 'copyright-header', '= 1.0.15'
|
49
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2016 Finalsite, LLC
|
3
|
+
# Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
#
|
22
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
23
|
+
|
24
|
+
module AdvancedConnection::ActiveRecordExt
|
25
|
+
module AbstractAdapter
|
26
|
+
module StatementPooling
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
module ExecuteWrapper
|
30
|
+
def __wrap_adapter_exec_methods(*methods)
|
31
|
+
Array(methods).flatten.collect(&:to_sym).each { |exec_method|
|
32
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
33
|
+
def #{exec_method}_with_callback(sql, *args, &block)
|
34
|
+
if Thread.current[:without_callbacks] || sql =~ /^BEGIN/i || transaction_open? || pool.nil?
|
35
|
+
#{exec_method}_without_callback(sql, *args, &block)
|
36
|
+
else
|
37
|
+
run_callbacks(:statement_pooling_connection_checkin) do
|
38
|
+
$stderr.puts "#{Thread.current.object_id} executing sql -> \#{sql.inspect}"
|
39
|
+
#{exec_method}_without_callback(sql, *args, &block).tap {
|
40
|
+
$stderr.puts "#{Thread.current.object_id} Releasing connection..."
|
41
|
+
reset!
|
42
|
+
pool.release_connection
|
43
|
+
$stderr.puts "#{Thread.current.object_id} Connection Released..."
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
EOS
|
49
|
+
alias_method_chain exec_method, :callback
|
50
|
+
}
|
51
|
+
end
|
52
|
+
alias_method :__wrap_adapter_exec_method, :__wrap_adapter_exec_methods
|
53
|
+
|
54
|
+
def __wrap_without_callbacks(*methods)
|
55
|
+
Array(methods).flatten.collect(&:to_sym).each { |m|
|
56
|
+
target, punctuation = m.to_s.sub(/([?!=])$/, ''), $1
|
57
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
58
|
+
def #{target}_with_no_callbacks#{punctuation}(*args, &block)
|
59
|
+
# $stderr.puts "setting without_callbacks to true"
|
60
|
+
Thread.current[:without_callbacks] = true
|
61
|
+
#{target}_without_no_callbacks#{punctuation}(*args, &block)
|
62
|
+
ensure
|
63
|
+
Thread.current[:without_callbacks] = nil
|
64
|
+
end
|
65
|
+
EOS
|
66
|
+
alias_method_chain m, :no_callbacks
|
67
|
+
}
|
68
|
+
end
|
69
|
+
alias_method :__wrap_without_callback, :__wrap_without_callbacks
|
70
|
+
end
|
71
|
+
|
72
|
+
included do
|
73
|
+
include ::ActiveSupport::Callbacks
|
74
|
+
extend ExecuteWrapper
|
75
|
+
|
76
|
+
define_callbacks :statement_pooling_connection_checkin
|
77
|
+
set_callback :statement_pooling_connection_checkin, :around, :around_connection_checkin
|
78
|
+
set_callback :statement_pooling_connection_checkin, :before, :before_connection_checkin
|
79
|
+
set_callback :statement_pooling_connection_checkin, :after, :after_connection_checkin
|
80
|
+
|
81
|
+
DEFAULT_EXEC_METHODS = [ :execute, :exec_no_cache, :query ]
|
82
|
+
JDBC_EXEC_METHODS = %w[
|
83
|
+
execute
|
84
|
+
exec_query
|
85
|
+
exec_query_raw
|
86
|
+
exec_insert
|
87
|
+
exec_update
|
88
|
+
exec_delete
|
89
|
+
transaction
|
90
|
+
].collect(&:to_sym)
|
91
|
+
|
92
|
+
if RUBY_ENGINE == 'jruby'
|
93
|
+
JDBC_EXEC_METHODS.each { |m| __wrap_adapter_exec_methods m }
|
94
|
+
else
|
95
|
+
DEFAULT_EXEC_METHODS.each { |m| __wrap_adapter_exec_methods m }
|
96
|
+
end
|
97
|
+
|
98
|
+
[ :active?, :reset!, :disconnect!, :reconnect! ].each { |m|
|
99
|
+
__wrap_without_callbacks m
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def around_connection_checkin(&block)
|
104
|
+
callbacks = AdvancedConnection.callbacks.statement_pooling
|
105
|
+
if callbacks.around.respond_to? :call
|
106
|
+
callbacks.around.call() { block.call }
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def before_connection_checkin
|
111
|
+
callbacks = AdvancedConnection.callbacks.statement_pooling
|
112
|
+
callbacks.before.call if callbacks.before.respond_to? :call
|
113
|
+
end
|
114
|
+
|
115
|
+
def after_connection_checkin
|
116
|
+
callbacks = AdvancedConnection.callbacks.statement_pooling
|
117
|
+
callbacks.after.call if callbacks.after.respond_to? :call
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2016 Finalsite, LLC
|
3
|
+
# Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
#
|
22
|
+
require 'active_record/connection_adapters/abstract_adapter'
|
23
|
+
|
24
|
+
module AdvancedConnection::ActiveRecordExt
|
25
|
+
module AbstractAdapter
|
26
|
+
extend ActiveSupport::Autoload
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
eager_autoload do
|
30
|
+
autoload :StatementPooling
|
31
|
+
end
|
32
|
+
|
33
|
+
included do
|
34
|
+
attr_accessor :last_checked_in, :instantiated_at
|
35
|
+
alias_method_chain :initialize, :advanced_connection
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize_with_advanced_connection(*args, &block)
|
39
|
+
@last_checked_in = Time.now - 1.year
|
40
|
+
@instantiated_at = Time.now
|
41
|
+
initialize_without_advanced_connection(*args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def instance_age
|
45
|
+
(Time.now - @instantiated_at).to_f
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2016 Finalsite, LLC
|
3
|
+
# Copyright (C) 2016 Carl P. Corliss <carl.corliss@finalsite.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
# this software and associated documentation files (the "Software"), to deal in
|
7
|
+
# the Software without restriction, including without limitation the rights to
|
8
|
+
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
# the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
# subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
13
|
+
# copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
#
|
22
|
+
module AdvancedConnection::ActiveRecordExt
|
23
|
+
module ConnectionPool
|
24
|
+
module IdleManager
|
25
|
+
extend ActiveSupport::Concern
|
26
|
+
|
27
|
+
included do
|
28
|
+
alias_method_chain :initialize, :advanced_connection
|
29
|
+
alias_method_chain :checkin, :last_checked_in
|
30
|
+
|
31
|
+
class IdleManager
|
32
|
+
attr_reader :pool, :interval, :thread
|
33
|
+
|
34
|
+
def initialize(pool, interval)
|
35
|
+
@pool = pool
|
36
|
+
@interval = interval.to_i
|
37
|
+
@thread = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
return unless interval > 0
|
42
|
+
|
43
|
+
@thread ||= Thread.new(pool, interval) { |_pool, _interval|
|
44
|
+
_pool.send(:idle_info, "starting idle manager; running every #{_interval} seconds")
|
45
|
+
|
46
|
+
loop do
|
47
|
+
sleep _interval
|
48
|
+
|
49
|
+
begin
|
50
|
+
start = Time.now
|
51
|
+
_pool.send(:idle_info, "beginning idle connection cleanup")
|
52
|
+
_pool.remove_idle_connections
|
53
|
+
_pool.send(:idle_info, "beginning idle connection warmup")
|
54
|
+
_pool.create_idle_connections
|
55
|
+
finish = (Time.now - start).round(6)
|
56
|
+
_pool.send(:idle_info, "finished idle connection tasks in #{finish} seconds.; next run in #{pool.max_idle_time} seconds")
|
57
|
+
rescue => e
|
58
|
+
Rails.logger.error "#{e.class.name}: #{e.message}"
|
59
|
+
e.backtrace.each { |line| Rails.logger.error line }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize_with_advanced_connection(spec)
|
68
|
+
initialize_without_advanced_connection(spec)
|
69
|
+
|
70
|
+
@available = case queue_type
|
71
|
+
when :prefer_older then Queues::OldAgeBiased.new
|
72
|
+
when :prefer_younger then Queues::YoungAgeBiased.new
|
73
|
+
when :lifo, :stack then Queues::Stack.new
|
74
|
+
else
|
75
|
+
Rails.logger.warn "Unknown queue_type #{queue_type.inspect} - using standard FIFO instead"
|
76
|
+
Queues::FIFO.new
|
77
|
+
end
|
78
|
+
|
79
|
+
@idle_manager = IdleManager.new(self, idle_check_interval).tap { |m| m.run }
|
80
|
+
end
|
81
|
+
|
82
|
+
def queue_type
|
83
|
+
@queue_type ||= spec.config.fetch(:queue_type,
|
84
|
+
AdvancedConnection.connection_pool_queue_type).to_s.downcase.to_sym
|
85
|
+
end
|
86
|
+
|
87
|
+
def warmup_connection_count
|
88
|
+
@warmup_connection_count ||= begin
|
89
|
+
conns = spec.config[:warmup_connections] || AdvancedConnection.warmup_connections
|
90
|
+
conns.to_i > connection_limit ? connection_limit : conns.to_i
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def max_idle_time
|
95
|
+
@max_idle_time ||= (spec.config[:max_idle_time] || \
|
96
|
+
AdvancedConnection.max_idle_time).to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
def idle_check_interval
|
100
|
+
@idle_check_interval ||= (spec.config[:idle_check_interval] || \
|
101
|
+
AdvancedConnection.idle_check_interval || \
|
102
|
+
max_idle_time).to_i
|
103
|
+
end
|
104
|
+
|
105
|
+
def max_idle_connections
|
106
|
+
@max_idle_connections ||= begin
|
107
|
+
begin
|
108
|
+
(spec.config[:max_idle_connections] || \
|
109
|
+
AdvancedConnection.max_idle_connections).to_i
|
110
|
+
rescue FloatDomainError => e
|
111
|
+
raise unless e.message =~ /infinity/i
|
112
|
+
::Float::INFINITY
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def min_idle_connections
|
118
|
+
@min_idle_connections ||= begin
|
119
|
+
min_idle = (spec.config[:min_idle_connections] || AdvancedConnection.min_idle_connections).to_i
|
120
|
+
min_idle = (min_idle > 0 ? min_idle : 0)
|
121
|
+
min_idle <= max_idle_connections ? min_idle : max_idle_connections
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def connection_limit
|
126
|
+
@size
|
127
|
+
end
|
128
|
+
|
129
|
+
def checkin_with_last_checked_in(conn)
|
130
|
+
conn.last_checked_in = Time.now
|
131
|
+
idle_debug "checking in connection #{conn.object_id} at #{conn.last_checked_in}"
|
132
|
+
checkin_without_last_checked_in(conn)
|
133
|
+
end
|
134
|
+
|
135
|
+
def active_connections
|
136
|
+
@connections.select(&:in_use?)
|
137
|
+
end
|
138
|
+
|
139
|
+
def available_connections
|
140
|
+
@connections.reject(&:in_use?)
|
141
|
+
end
|
142
|
+
|
143
|
+
def idle_connections
|
144
|
+
available_connections.select do |conn|
|
145
|
+
(Time.now - conn.last_checked_in).to_f > max_idle_time
|
146
|
+
end.sort { |a,b|
|
147
|
+
case queue_type
|
148
|
+
when :prefer_younger then
|
149
|
+
# when prefering younger, we sort oldest->youngest
|
150
|
+
# this ensures that older connections will be culled
|
151
|
+
# during #remove_idle_connections()
|
152
|
+
-(a.instance_age <=> b.instance_age)
|
153
|
+
when :prefer_older then
|
154
|
+
# when prefering older, we sort youngest->oldest
|
155
|
+
# this ensures that younger connections will be culled
|
156
|
+
# during #remove_idle_connections()
|
157
|
+
(a.instance_age <=> b.instance_age)
|
158
|
+
else
|
159
|
+
# with fifo / lifo queues, we only care about the
|
160
|
+
# last time a given connection was used (inferred
|
161
|
+
# by when it was last checked into the pool).
|
162
|
+
# This ensures that the longer idling connections
|
163
|
+
# will be culled.
|
164
|
+
-(a.last_checked_in <=> b.last_checked_in)
|
165
|
+
end
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def pool_statistics
|
170
|
+
idle = active = available = 0
|
171
|
+
synchronize do
|
172
|
+
idle = idle_connections.size
|
173
|
+
active = active_connections.size
|
174
|
+
available = available_connections.size
|
175
|
+
end
|
176
|
+
total = active + available
|
177
|
+
|
178
|
+
ActiveSupport::OrderedOptions.new.merge({
|
179
|
+
total: total, idle: idle, active: active, available: available,
|
180
|
+
})
|
181
|
+
end
|
182
|
+
|
183
|
+
def warmup_connections(count = nil)
|
184
|
+
count ||= warmup_connection_count
|
185
|
+
slots = connection_limit - @connections.size
|
186
|
+
count = slots if slots < count
|
187
|
+
|
188
|
+
if slots >= count
|
189
|
+
idle_info "Warming up #{count} connection#{count > 1 ? 's' : ''}"
|
190
|
+
synchronize do
|
191
|
+
count.times {
|
192
|
+
conn = checkout_new_connection
|
193
|
+
@available.add conn
|
194
|
+
}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def create_idle_connections
|
200
|
+
idle_count = idle_connections.size
|
201
|
+
open_slots = connection_limit - @connections.size
|
202
|
+
|
203
|
+
# if we already have enough idle connections, do nothing
|
204
|
+
return unless idle_count < min_idle_connections
|
205
|
+
|
206
|
+
# if we don't have enough available slots (i.e., current pool size
|
207
|
+
# is greater than max pool size) then do nothing
|
208
|
+
return unless open_slots > 0
|
209
|
+
|
210
|
+
# otherwise, spin up connections up to our min_idle_connections setting
|
211
|
+
create_count = min_idle_connections - idle_count
|
212
|
+
create_count = open_slots if create_count > open_slots
|
213
|
+
|
214
|
+
warmup_connections(create_count)
|
215
|
+
end
|
216
|
+
|
217
|
+
def remove_idle_connections
|
218
|
+
# don't attempt to remove idle connections if we have threads waiting
|
219
|
+
return if @available.num_waiting > 0
|
220
|
+
|
221
|
+
idle_conns = idle_connections
|
222
|
+
idle_count = idle_conns.size
|
223
|
+
|
224
|
+
if idle_count > max_idle_connections
|
225
|
+
cull_count = (idle_count - max_idle_connections)
|
226
|
+
|
227
|
+
culled = 0
|
228
|
+
idle_conns.each_with_index do |conn, idx|
|
229
|
+
last_ci = (Time.now - conn.last_checked_in).to_f
|
230
|
+
if idx < cull_count
|
231
|
+
culled += remove_connection(conn) ? 1 : 0
|
232
|
+
idle_info "culled connection ##{idx} id##{conn.object_id} - age:#{conn.instance_age} last_checkin:#{last_ci}"
|
233
|
+
else
|
234
|
+
idle_info "kept connection ##{idx} id##{conn.object_id} - age:#{conn.instance_age} last_checkin:#{last_ci}"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
idle_info "culled %d connections" % culled
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
def remove_connection(conn)
|
245
|
+
synchronize do
|
246
|
+
return false if conn.in_use?
|
247
|
+
remove conn
|
248
|
+
conn.disconnect!
|
249
|
+
end
|
250
|
+
true
|
251
|
+
end
|
252
|
+
|
253
|
+
def idle_message(format, *args)
|
254
|
+
stats = pool_statistics
|
255
|
+
format(
|
256
|
+
"IdleManager (Actv:%d,Avail:%d,Idle:%d,Total:%d): #{format}",
|
257
|
+
stats.active, stats.available, stats.idle, stats.total,
|
258
|
+
*args
|
259
|
+
)
|
260
|
+
end
|
261
|
+
|
262
|
+
def idle_debug(format, *args)
|
263
|
+
Rails.logger.debug idle_message(format, *args)
|
264
|
+
end
|
265
|
+
|
266
|
+
def idle_info(format, *args)
|
267
|
+
Rails.logger.info idle_message(format, *args)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|