hot_tub 0.5.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
- require 'benchmark'
2
+ require 'benchmark/ips'
3
3
  require 'hot_tub'
4
4
 
5
5
  class MocClient
@@ -18,26 +18,37 @@ end
18
18
 
19
19
  puts `ruby -v`
20
20
 
21
- Benchmark.bmbm do |b|
21
+ s1 = HotTub::Pool.new(:size => 1, :max_size => 1, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
22
+ s5 = HotTub::Pool.new(:size => 5, :max_size => 5, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
23
+ s10 = HotTub::Pool.new(:size => 5, :max_size => 10, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
24
+ s0 = HotTub::Pool.new(:size => 5, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
22
25
 
23
- b.report("single thread") do
24
- hot_tub = HotTub.new(:size => 1, :max_size => 1, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
25
- 1000.times.each do
26
- hot_tub.run do |conn|
27
- conn.get
26
+ url = 'http://foo.com'
27
+ HotTub.add(url, {:size => 5, :clean => lambda {|clnt| clnt.clean}}) { MocClient.new }
28
+
29
+ Benchmark.ips do |b|
30
+
31
+ b.report("blocking") do
32
+ threads = []
33
+ 20.times do
34
+ threads << Thread.new do
35
+ s1.run do |conn|
36
+ conn.get
37
+ end
28
38
  end
29
39
  end
40
+ threads.each do |t|
41
+ t.join
42
+ end
30
43
  end
31
44
 
32
- b.report("threaded size 5") do
33
- hot_tub = HotTub.new(:size => 5, :max_size => 5, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
45
+ b.report("max 5") do
34
46
  threads = []
35
- 50.times.each do
47
+ 20.times do
36
48
  threads << Thread.new do
37
- 20.times do
38
- hot_tub.run do |conn|
39
- conn.get
40
- end
49
+
50
+ s5.run do |conn|
51
+ conn.get
41
52
  end
42
53
  end
43
54
  end
@@ -46,15 +57,12 @@ Benchmark.bmbm do |b|
46
57
  end
47
58
  end
48
59
 
49
- b.report("threaded size 5, max 10") do
50
- hot_tub = HotTub.new(:size => 5, :max_size => 10, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
60
+ b.report("max 10") do
51
61
  threads = []
52
- 50.times.each do
62
+ 20.times do
53
63
  threads << Thread.new do
54
- 20.times do
55
- hot_tub.run do |conn|
56
- conn.get
57
- end
64
+ s10.run do |conn|
65
+ conn.get
58
66
  end
59
67
  end
60
68
  end
@@ -63,15 +71,12 @@ Benchmark.bmbm do |b|
63
71
  end
64
72
  end
65
73
 
66
- b.report("threaded, size 5, no max") do
67
- hot_tub = HotTub.new(:size => 5, :clean => lambda {|clnt| clnt.clean}) { MocClient.new }
74
+ b.report("no max") do
68
75
  threads = []
69
- 50.times.each do
76
+ 20.times do
70
77
  threads << Thread.new do
71
- 20.times do
72
- hot_tub.run do |conn|
73
- conn.get
74
- end
78
+ s0.run do |conn|
79
+ conn.get
75
80
  end
76
81
  end
77
82
  end
@@ -80,21 +85,12 @@ Benchmark.bmbm do |b|
80
85
  end
81
86
  end
82
87
 
83
-
84
- b.report("threaded, HotTub.run ") do
85
- urls = ['http://foo.com','http://bar.com','http://zap.com']
86
- urls.each do |url|
87
- HotTub.add(url, {:clean => lambda {|clnt| clnt.clean}}) { MocClient.new }
88
- end
88
+ b.report("HotTub.run") do
89
89
  threads = []
90
- 50.times.each do
90
+ 20.times do
91
91
  threads << Thread.new do
92
- 20.times do
93
- urls.each do |url|
94
- HotTub.run(url) do |conn|
95
- conn.get
96
- end
97
- end
92
+ HotTub.run(url) do |conn|
93
+ conn.get
98
94
  end
99
95
  end
100
96
  end
@@ -103,54 +99,70 @@ Benchmark.bmbm do |b|
103
99
  end
104
100
  end
105
101
 
102
+ b.compare!
106
103
  end
107
104
 
108
105
  # ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin14]
109
- # Rehearsal ------------------------------------------------------------
110
- # single thread 0.090000 0.050000 0.140000 ( 11.056805)
111
- # threaded size 5 0.110000 0.130000 0.240000 ( 2.323854)
112
- # threaded size 5, max 10 0.080000 0.100000 0.180000 ( 1.162047)
113
- # threaded, size 5, no max 0.030000 0.040000 0.070000 ( 0.229230)
114
- # threaded, HotTub.run 0.130000 0.120000 0.250000 ( 0.673286)
115
- # --------------------------------------------------- total: 0.880000sec
116
-
117
- # user system total real
118
- # single thread 0.080000 0.040000 0.120000 ( 10.908496)
119
- # threaded size 5 0.110000 0.130000 0.240000 ( 2.314875)
120
- # threaded size 5, max 10 0.080000 0.100000 0.180000 ( 1.202064)
121
- # threaded, size 5, no max 0.040000 0.040000 0.080000 ( 0.224133)
122
- # threaded, HotTub.run 0.120000 0.120000 0.240000 ( 0.670526)
106
+ # Calculating -------------------------------------
107
+ # blocking 1.000 i/100ms
108
+ # max 5 2.000 i/100ms
109
+ # max 10 4.000 i/100ms
110
+ # no max 8.000 i/100ms
111
+ # HotTub.run 8.000 i/100ms
112
+ # -------------------------------------------------
113
+ # blocking 4.605 (± 0.0%) i/s - 24.000
114
+ # max 5 22.610 (± 0.0%) i/s - 114.000
115
+ # max 10 43.922 (± 0.0%) i/s - 220.000
116
+ # no max 83.448 (± 1.2%) i/s - 424.000
117
+ # HotTub.run 82.757 (± 2.4%) i/s - 416.000
118
+
119
+ # Comparison:
120
+ # no max: 83.4 i/s
121
+ # HotTub.run: 82.8 i/s - 1.01x slower
122
+ # max 10: 43.9 i/s - 1.90x slower
123
+ # max 5: 22.6 i/s - 3.69x slower
124
+ # blocking: 4.6 i/s - 18.12x slower
123
125
 
124
126
 
125
127
  # rubinius 2.5.8 (2.1.0 bef51ae3 2015-07-14 3.5.1 JI) [x86_64-darwin14.4.0]
126
- # Rehearsal ------------------------------------------------------------
127
- # single thread 0.427639 0.053404 0.481043 ( 11.049242)
128
- # threaded size 5 0.214155 0.201618 0.415773 ( 2.255010)
129
- # threaded size 5, max 10 0.170088 0.158815 0.328903 ( 1.159579)
130
- # threaded, size 5, no max 0.132440 0.046240 0.178680 ( 0.240322)
131
- # threaded, HotTub.run 0.169417 0.123085 0.292502 ( 0.676492)
132
- # --------------------------------------------------- total: 1.696901sec
133
-
134
- # user system total real
135
- # single thread 0.155789 0.054654 0.210443 ( 11.052989)
136
- # threaded size 5 0.253045 0.209961 0.463006 ( 2.276732)
137
- # threaded size 5, max 10 0.149159 0.138857 0.288016 ( 1.171204)
138
- # threaded, size 5, no max 0.216853 0.033597 0.250450 ( 0.224729)
139
- # threaded, HotTub.run 0.626503 0.080910 0.707413 ( 0.664893)
128
+ # Calculating -------------------------------------
129
+ # blocking 1.000 i/100ms
130
+ # max 5 2.000 i/100ms
131
+ # max 10 4.000 i/100ms
132
+ # no max 8.000 i/100ms
133
+ # HotTub.run 8.000 i/100ms
134
+ # -------------------------------------------------
135
+ # blocking 4.619 (± 0.0%) i/s - 24.000
136
+ # max 5 22.758 (± 0.0%) i/s - 114.000
137
+ # max 10 44.336 (± 2.3%) i/s - 224.000
138
+ # no max 85.267 (± 3.5%) i/s - 432.000
139
+ # HotTub.run 85.012 (± 3.5%) i/s - 424.000
140
+
141
+ # Comparison:
142
+ # no max: 85.3 i/s
143
+ # HotTub.run: 85.0 i/s - 1.00x slower
144
+ # max 10: 44.3 i/s - 1.92x slower
145
+ # max 5: 22.8 i/s - 3.75x slower
146
+ # blocking: 4.6 i/s - 18.46x slower
140
147
 
141
148
 
142
149
  # jruby 9.0.3.0 (2.2.2) 2015-10-21 633c9aa Java HotSpot(TM) 64-Bit Server VM 23.5-b02 on 1.7.0_09-b05 +jit [darwin-x86_64]
143
- # Rehearsal ------------------------------------------------------------
144
- # single thread 0.890000 0.070000 0.960000 ( 11.343993)
145
- # threaded size 5 0.850000 0.130000 0.980000 ( 2.324202)
146
- # threaded size 5, max 10 0.710000 0.110000 0.820000 ( 1.178593)
147
- # threaded, size 5, no max 0.320000 0.050000 0.370000 ( 0.233405)
148
- # threaded, HotTub.run 1.190000 0.100000 1.290000 ( 0.683554)
149
- # --------------------------------------------------- total: 4.420000sec
150
-
151
- # user system total real
152
- # single thread 0.350000 0.060000 0.410000 ( 11.156489)
153
- # threaded size 5 0.540000 0.130000 0.670000 ( 2.251505)
154
- # threaded size 5, max 10 0.300000 0.100000 0.400000 ( 1.153055)
155
- # threaded, size 5, no max 0.270000 0.050000 0.320000 ( 0.230043)
156
- # threaded, HotTub.run 0.360000 0.110000 0.470000 ( 0.674920)
150
+ # Calculating -------------------------------------
151
+ # blocking 1.000 i/100ms
152
+ # max 5 2.000 i/100ms
153
+ # max 10 4.000 i/100ms
154
+ # no max 6.000 i/100ms
155
+ # HotTub.run 7.000 i/100ms
156
+ # -------------------------------------------------
157
+ # blocking 4.580 (± 0.0%) i/s - 23.000
158
+ # max 5 22.514 (± 4.4%) i/s - 114.000
159
+ # max 10 43.620 (± 2.3%) i/s - 220.000
160
+ # no max 72.260 (± 6.9%) i/s - 360.000
161
+ # HotTub.run 66.509 (±10.5%) i/s - 329.000
162
+
163
+ # Comparison:
164
+ # no max: 72.3 i/s
165
+ # HotTub.run: 66.5 i/s - 1.09x slower
166
+ # max 10: 43.6 i/s - 1.66x slower
167
+ # max 5: 22.5 i/s - 3.21x slower
168
+ # blocking: 4.6 i/s - 15.78x slower
data/lib/hot_tub.rb CHANGED
@@ -64,16 +64,22 @@ module HotTub
64
64
  GLOBAL_SESSIONS.get_or_set(url, opts, &client_block)
65
65
  end
66
66
 
67
+ # Sets a options for new pool for lazy loading
68
+ def self.stage(url,opts={}, &client_block)
69
+ GLOBAL_SESSIONS.stage(url, opts, &client_block)
70
+ end
71
+
67
72
  def self.add(url,opts={}, &client_block)
68
73
  self.get_or_set(url,opts, &client_block)
69
74
  end
70
75
 
71
76
  def self.run(url ,&run_block)
72
77
  pool = GLOBAL_SESSIONS.fetch(url)
73
- pool.run &run_block
78
+ pool.run(&run_block)
74
79
  end
75
80
 
76
81
  def self.new(opts={}, &client_block)
82
+ warn "[DEPRECATION] `HotTub.new` is deprecated. Please use `HotTub::Pool.new` instead."
77
83
  Pool.new(opts,&client_block)
78
84
  end
79
85
  end
data/lib/hot_tub/pool.rb CHANGED
@@ -11,7 +11,6 @@ module HotTub
11
11
  # pool = HotTub::Pool.new(:size => 10) {
12
12
  # uri = URI.parse("http://somewebservice.com")
13
13
  # http = Net::HTTP.new(uri.host, uri.port)
14
- # http.use_ssl = false
15
14
  # http.start
16
15
  # http
17
16
  # }
@@ -44,7 +43,7 @@ module HotTub
44
43
  # begin
45
44
  # pool.run { |clnt| s clnt.head('/').code }
46
45
  # rescue HotTub::Pool::Timeout => e
47
- # puts "Waited too long for a client: {e}"
46
+ # puts "Waited too long for a client: #{e}"
48
47
  # end
49
48
  #
50
49
  #
@@ -110,12 +109,14 @@ module HotTub
110
109
  @cond = ConditionVariable.new
111
110
 
112
111
  @shutdown = false
113
- @blocking_reap = (opts[:reaper] == false && !opts[:sessions])
114
- @reaper = ((opts[:sessions] || (opts[:reaper] == false)) ? false : spawn_reaper)
112
+
113
+ @sessions_key = opts[:sessions_key]
114
+ @blocking_reap = (opts[:reaper] == false && !@sessions_key)
115
+ @reaper = ((@sessions_key || (opts[:reaper] == false)) ? false : spawn_reaper)
115
116
 
116
117
  @never_block = (@max_size == 0)
117
118
 
118
- at_exit {shutdown!} unless opts[:sessions]
119
+ at_exit {shutdown!} unless @sessions_key
119
120
  end
120
121
 
121
122
  # Preform an operations with a client/connection.
@@ -297,9 +298,13 @@ module HotTub
297
298
  # Returns a new client if its allowed.
298
299
  # _add is volatile; and may cause threading issues
299
300
  # if called outside @mutex.synchronize {}
300
- def _fetch_new
301
+ def _fetch_new(&client_block)
301
302
  if (@never_block || (_total_current_size < @max_size))
302
- nc = yield
303
+ if client_block.arity == 0
304
+ nc = yield
305
+ else
306
+ nc = yield @sessions_key
307
+ end
303
308
  HotTub.logger.info "[HotTub] Adding client: #{nc.class.name} to #{@name}." if HotTub.log_trace?
304
309
  nc
305
310
  end
@@ -1,5 +1,10 @@
1
1
  module HotTub
2
2
  class Reaper
3
+ attr_reader :thread
4
+
5
+ def self.spawn(obj)
6
+ self.new(obj)
7
+ end
3
8
 
4
9
  # Creates a new Reaper thread for work.
5
10
  # Expects an object that responses to: :reap!
@@ -7,8 +12,8 @@ module HotTub
7
12
  # Threads swallow exceptions until they are joined,
8
13
  # so we rescue, log, and kill the reaper when an exception occurs
9
14
  # https://bugs.ruby-lang.org/issues/6647
10
- def self.spawn(obj)
11
- th = Thread.new {
15
+ def initialize(obj)
16
+ @thread = Thread.new {
12
17
  loop do
13
18
  begin
14
19
  break if obj.shutdown
@@ -21,21 +26,38 @@ module HotTub
21
26
  end
22
27
  end
23
28
  }
24
- th[:name] = "HotTub::Reaper"
25
- th.abort_on_exception = true
26
- th
29
+ @thread[:name] = "HotTub::Reaper"
30
+ @thread.abort_on_exception = true
31
+ @thread
32
+ end
33
+
34
+ def status
35
+ @thread.status
36
+ end
37
+
38
+ def wakeup
39
+ @thread.wakeup
40
+ end
41
+
42
+ def shutdown
43
+ @thread.kill
44
+ @thread.join
45
+ end
46
+
47
+ def alive?
48
+ @thread.alive?
27
49
  end
28
50
 
29
51
  # Mixin to dry up Reaper usage
30
52
  module Mixin
31
53
  attr_reader :reap_timeout, :shutdown, :reaper
32
54
 
33
- # Setting reaper kills the current reaper.
55
+ # Setting reaper kills the current reaper.
34
56
  # If the values is truthy a new HotTub::Reaper
35
57
  # is created.
36
58
  def reaper=reaper
37
59
  kill_reaper
38
- if reaper
60
+ if reaper
39
61
  @reaper = HotTub::Reaper.new(self)
40
62
  else
41
63
  @reaper = false
@@ -48,8 +70,7 @@ module HotTub
48
70
 
49
71
  def kill_reaper
50
72
  if @reaper
51
- @reaper.kill
52
- @reaper.join
73
+ @reaper.shutdown
53
74
  @reaper = nil if @shutdown
54
75
  end
55
76
  end
@@ -3,72 +3,141 @@ module HotTub
3
3
  class Sessions
4
4
  include HotTub::KnownClients
5
5
  include HotTub::Reaper::Mixin
6
- attr_accessor :name
6
+ attr_accessor :name,
7
+ :default_client
7
8
 
8
9
  # HotTub::Sessions simplifies managing multiple Pools in a single object
9
10
  # and using a single Reaper.
10
11
  #
11
12
  # == Example:
12
13
  #
13
- # url = "http://somewebservice.com"
14
- # url2 = "http://somewebservice2.com"
15
- #
16
- # sessions = HotTub::Sessions
17
- # sessions.add(url,{:size => 12}) {
14
+ # sessions = HotTub::Sessions(:size => 10) do |url|
18
15
  # uri = URI.parse(url)
19
16
  # http = Net::HTTP.new(uri.host, uri.port)
20
- # http.use_ssl = false
21
17
  # http.start
22
18
  # http
23
- # }
24
- # sessions.add(url2,{:size => 5}) {
25
- # Excon.new(url2)
19
+ # end
20
+ #
21
+ # # Every time we pass a url that lacks a entry in our
22
+ # # sessions, a new HotTub::Pool is added for that url
23
+ # # using the &default_client.
24
+ # #
25
+ # sessions.run("https://www.google.com"") do |conn|
26
+ # p conn.get('/').code
27
+ # end
28
+ #
29
+ # sessions.run("https://www.yahoo.com"") do |conn|
30
+ # p conn.get('/').code
31
+ # end
32
+ #
33
+ # # Lazy load a non-default connection
34
+ #
35
+ # excon_url = "http://somewebservice2.com"
36
+ #
37
+ # sessions.stage(excon_url,{:size => 5}) {
38
+ # Excon.new(excon_url, :thread_safe_socket => false)
26
39
  # }
27
40
  #
28
- # sessions.run(url) do |conn|
29
- # p conn.head('/').code
41
+ # # Excon connection is created on the first call to `.run`
42
+ # sessions.run(excon_url) do |conn|
43
+ # p conn.head.code
30
44
  # end
31
45
  #
32
- # sessions.run(url2) do |conn|
33
- # p conn.head('/').code
46
+ #
47
+ # # Add a connection, which returns a HotTub::Pool instance
48
+ #
49
+ # excon_url2 = "http://somewebservice2.com"
50
+ #
51
+ # MY_CON = sessions.add(excon_url2,{:size => 5}) {
52
+ # Excon.new(excon_url2, :thread_safe_socket => false)
53
+ # }
54
+ #
55
+ # # Uses Excon
56
+ # MY_CON.run(excon_url) do |conn|
57
+ # p conn.head.code
34
58
  # end
35
59
  #
36
60
  # === OPTIONS
61
+ #
62
+ # &default_client
63
+ # An optional block for a default client for your pools. If your block accepts a
64
+ # parameters, they session key is passed to the block. Your default client
65
+ # block will be overridden if you pass a client block to get_or_set
66
+ #
67
+ # [:pool_options]
68
+ # Default options for your HotTub::Pools. If you pass options to #get_or_set those options
69
+ # override :pool_options.
70
+ #
37
71
  # [:name]
38
72
  # A string representing the name of your sessions used for logging.
39
73
  #
40
74
  # [:reaper]
41
- # If set to false prevents a HotTub::Reaper from initializing.
75
+ # If set to false prevents a HotTub::Reaper from initializing for these sessions.
42
76
  #
43
77
  # [:reap_timeout]
44
78
  # Default is 600 seconds. An integer that represents the timeout for reaping the pool in seconds.
45
79
  #
46
- def initialize(opts={})
47
- @name = (opts[:name] || self.class.name)
48
- @reaper = opts[:reaper]
49
- @reap_timeout = (opts[:reap_timeout] || 600)
80
+ def initialize(opts={}, &default_client)
81
+ @name = (opts[:name] || self.class.name)
82
+ @reaper = opts[:reaper]
83
+ @reap_timeout = (opts[:reap_timeout] || 600)
84
+ @default_client = default_client
85
+ @pool_options = (opts[:pool_options] || {})
86
+
87
+ @_staged = {}
88
+ @_staged.taint
89
+
90
+ @_sessions = {}
91
+ @_sessions.taint
50
92
 
51
- @_sessions = {}
52
- @mutex = Mutex.new
53
- @shutdown = false
93
+ @mutex = Mutex.new
94
+ @shutdown = false
54
95
 
55
96
  at_exit {shutdown!}
56
97
  end
57
98
 
58
- # Adds a new HotTub::Pool for the given key unless
59
- # one already exists.
60
- def get_or_set(key, pool_options={}, &client_block)
61
- raise ArgumentError, 'a block that initializes a new client is required.' unless block_given?
62
- pool = nil
63
- return pool if pool = @_sessions[key]
64
- pool_options[:sessions] = true
65
- pool_options[:name] = "#{@name} - #{key}"
99
+ # Sets arguments / settings for a session that will be
100
+ # lazy loaded, returns nil because pool is not created
101
+ def stage(key, pool_options={}, &client_block)
66
102
  @mutex.synchronize do
67
- @reaper ||= spawn_reaper if @reaper.nil?
68
- pool = @_sessions[key] ||= HotTub::Pool.new(pool_options, &client_block) unless @shutdown
103
+ @_staged[key] = [pool_options,client_block]
104
+ end
105
+ nil
106
+ end
107
+
108
+ # Returns a HotTub::Pool for the given key. If a session
109
+ # is not found and the is a default_client set, a session will
110
+ # be created for the key using the default_client.
111
+ def get(key)
112
+ pool = @_sessions[key]
113
+ unless pool
114
+ @mutex.synchronize do
115
+ unless @shutdown
116
+ @reaper = spawn_reaper if @reaper.nil?
117
+ unless pool = @_sessions[key]
118
+ settings = @_staged[key]
119
+ clnt_blk = (settings[1] || @default_client)
120
+ op = @pool_options.merge(settings[0])
121
+ op[:sessions_key] = key
122
+ op[:name] = "#{@name} - #{key}"
123
+ pool = @_sessions[key] = HotTub::Pool.new(op, &clnt_blk)
124
+ end
125
+ end
126
+ end
69
127
  end
70
128
  pool
71
129
  end
130
+
131
+ # Adds session unless it already exists and returns
132
+ # the session
133
+ def get_or_set(key, pool_options={}, &client_block)
134
+ unless @_staged[key]
135
+ @mutex.synchronize do
136
+ @_staged[key] ||= [pool_options,client_block]
137
+ end
138
+ end
139
+ get(key)
140
+ end
72
141
  alias :add :get_or_set
73
142
 
74
143
  # Deletes and shutdowns the pool if its found.
@@ -79,7 +148,7 @@ module HotTub
79
148
  pool = @_sessions.delete(key)
80
149
  end
81
150
  if pool
82
- pool.reset!
151
+ pool.shutdown!
83
152
  deleted = true
84
153
  HotTub.logger.info "[HotTub] #{key} was deleted from #{@name}." if HotTub.logger
85
154
  end
@@ -87,8 +156,9 @@ module HotTub
87
156
  end
88
157
 
89
158
  def fetch(key)
90
- pool = @_sessions[key]
91
- raise MissingSession, "A session could not be found for #{key.inspect} #{@name}" unless pool
159
+ unless pool = get(key, &@default_client)
160
+ raise MissingSession, "A session could not be found for #{key.inspect} #{@name}"
161
+ end
92
162
  pool
93
163
  end
94
164
 
@@ -96,7 +166,7 @@ module HotTub
96
166
 
97
167
  def run(key, &run_block)
98
168
  pool = fetch(key)
99
- pool.run &run_block
169
+ pool.run(&run_block)
100
170
  end
101
171
 
102
172
  def clean!