hot_tub 0.3.0 → 0.4.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.
@@ -4,12 +4,12 @@ module HotTub
4
4
  include HotTub::KnownClients
5
5
  include HotTub::Reaper::Mixin
6
6
 
7
- # HotTub::Session is a ThreadSafe::Cache where URLs are mapped to clients or pools.
8
- #
7
+ # HotTub::Session is a ThreadSafe::Cache where URLs are mapped HotTub::Pools.
9
8
  #
10
- # == Example with Pool:
9
+ #
10
+ # == Example:
11
11
  # You can initialize a HotTub::Pool with each client by passing :with_pool as true and any pool options
12
- # sessions = HotTub::Sessions.new(:with_pool => true, :size => 12) {
12
+ # sessions = HotTub::Sessions.new(:size => 12) {
13
13
  # uri = URI.parse("http://somewebservice.com")
14
14
  # http = Net::HTTP.new(uri.host, uri.port)
15
15
  # http.use_ssl = false
@@ -25,26 +25,7 @@ module HotTub
25
25
  # p conn.head('/').code
26
26
  # end
27
27
  #
28
- # == Excon clients are initialized to a specific domain. Its sometimes useful
29
- # to have the options of initializing Excon connections after startup, in
30
- # a thread safe manner for multiple urls with a single object.
31
- # Example:
32
- #
33
- # sessions = HotTub::Sessions.new { |url| Excon.new(url) }
34
- #
35
- # sessions.run("http://wwww.yahoo.com") do |conn|
36
- # p conn.head.status
37
- # end
38
- #
39
- # sessions.run("https://wwww.google.com") do |conn|
40
- # p conn.head.status
41
- # end
42
- #
43
- #
44
28
  # === OPTIONS
45
- # [:with_pool]
46
- # Default is false. A boolean that if true, wraps the new_client block in HotTub::Pool.new. All options
47
- # are passed to the new HotTub::Pool object. See HotTub::Pool for options
48
29
  # [:close]
49
30
  # Default is nil. Can be a symbol representing an method to call on a client to close the client or a lambda
50
31
  # that accepts the client as a parameter that will close a client. The close option is performed on clients
@@ -63,7 +44,6 @@ module HotTub
63
44
  #
64
45
  def initialize(opts={},&new_client)
65
46
  raise ArgumentError, "HotTub::Sessions require a block on initialization that accepts a single argument" unless block_given?
66
- @with_pool = opts[:with_pool] # Set to true to use HotTub::Pool with supplied new_client block
67
47
  @close_client = opts[:close] # => lambda {|clnt| clnt.close}
68
48
  @clean_client = opts[:clean] # => lambda {|clnt| clnt.clean}
69
49
  @reap_client = opts[:reap] # => lambda {|clnt| clnt.reap?} # should return boolean
@@ -72,72 +52,65 @@ module HotTub
72
52
  @shutdown = false
73
53
  @reap_timeout = (opts[:reap_timeout] || 600) # the interval to reap connections in seconds
74
54
  @reaper = Reaper.spawn(self) unless opts[:no_reaper]
75
- @pool_options = {:no_reaper => true}.merge(opts) if @with_pool
55
+ @pool_options = {:no_reaper => true}.merge(opts)
76
56
  at_exit {drain!}
77
57
  end
78
58
 
79
- # Safely initializes of sessions
59
+ # Safely initializes sessions
80
60
  # expects a url string or URI
81
61
  def session(url)
82
62
  key = to_key(url)
83
63
  return @sessions.get(key) if @sessions.get(key)
84
- if @with_pool
85
- @sessions.compute_if_absent(key) {
86
- HotTub::Pool.new(@pool_options) { @new_client.call(url) }
87
- }
88
- else
89
- @sessions.compute_if_absent(key) {@new_client.call(url)}
90
- end
64
+ @sessions.compute_if_absent(key) {
65
+ HotTub::Pool.new(@pool_options) { @new_client.call(url) }
66
+ }
91
67
  @sessions.get(key)
92
68
  end
93
69
  alias :sessions :session
94
70
 
95
71
  def run(url,&block)
96
72
  session = sessions(url)
97
- return session.run(&block) if session.is_a?(HotTub::Pool)
98
- block.call(sessions(url))
73
+ session.run(&block) if session
99
74
  end
100
75
 
101
76
  def clean!
102
- @sessions.each_pair do |key,clnt|
103
- if clnt.is_a?(HotTub::Pool)
104
- clnt.clean!
105
- else
106
- clean_client(clnt)
107
- end
77
+ @sessions.each_pair do |key,pool|
78
+ pool.clean!
108
79
  end
80
+ @sessions
109
81
  end
110
82
 
111
83
  def drain!
112
- @sessions.each_pair do |key,clnt|
113
- if clnt.is_a?(HotTub::Pool)
114
- clnt.drain!
115
- else
116
- close_client(clnt)
117
- end
84
+ @sessions.each_pair do |key,pool|
85
+ pool.drain!
86
+ end
87
+ @sessions
88
+ end
89
+
90
+ def reset!
91
+ @sessions.each_pair do |key,pool|
92
+ pool.reset!
118
93
  end
119
94
  @sessions.clear
95
+ @sessions = ThreadSafe::Cache.new
96
+ @sessions
120
97
  end
121
98
 
122
99
  def shutdown!
123
- @sessions.each_pair do |key,clnt|
124
- if clnt.is_a?(HotTub::Pool)
125
- clnt.shutdown!
126
- else
127
- close_client(clnt)
128
- end
100
+ @shutdown = true
101
+ begin
102
+ kill_reaper
103
+ ensure
104
+ drain!
105
+ @sessions = nil
129
106
  end
130
- @sessions.clear
107
+ nil
131
108
  end
132
109
 
133
110
  # Remove and close extra clients
134
111
  def reap!
135
- @sessions.each_pair do |key,clnt|
136
- if clnt.is_a?(HotTub::Pool)
137
- clnt.reap!
138
- else
139
- close_client(clnt) if reap_client?(clnt)
140
- end
112
+ @sessions.each_pair do |key,pool|
113
+ pool.reap!
141
114
  end
142
115
  end
143
116
 
@@ -1,3 +1,3 @@
1
1
  module HotTub
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/hot_tub.rb CHANGED
@@ -8,7 +8,8 @@ require "hot_tub/pool"
8
8
  require "hot_tub/sessions"
9
9
 
10
10
  module HotTub
11
- @@logger = Logger.new(STDOUT)
11
+ @@logger = nil
12
+
12
13
  def self.logger
13
14
  @@logger
14
15
  end
@@ -22,7 +23,7 @@ module HotTub
22
23
  end
23
24
 
24
25
  def self.rbx?
25
- defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx'
26
+ (defined?(RUBY_ENGINE) and RUBY_ENGINE == 'rbx')
26
27
  end
27
28
 
28
29
  def self.new(opts={},&client_block)
@@ -18,6 +18,7 @@ end
18
18
  class MocReaperPool < MocPool
19
19
  def initialize
20
20
  super
21
+ @reap_timeout = 1
21
22
  @reaper = HotTub::Reaper.spawn(self)
22
23
  end
23
24
  end
@@ -15,24 +15,26 @@ module HotTub
15
15
  set :server, 'puma'
16
16
  set :port, 9595
17
17
 
18
- get '/data/:amount' do |amount|
19
- (('x' * amount.to_i ) << Random.new.rand(0..999999).to_s)
18
+ get '/fast' do
19
+ sleep(0.01)
20
+ "foo"
20
21
  end
21
22
 
22
- def self.teardown
23
- @server.stop(true) if @server
23
+ get '/slow' do
24
+ sleep(1)
25
+ "foooooooooooo"
24
26
  end
25
27
 
26
- def self.size
27
- 10_000
28
+ def self.teardown
29
+ @server.stop(true) if @server
28
30
  end
29
31
 
30
- def self.path
31
- '/data/' << size.to_s
32
+ def self.url
33
+ 'http://127.0.0.1:9595/fast'
32
34
  end
33
35
 
34
- def self.url
35
- 'http://127.0.0.1:9595' << path
36
+ def self.slow_url
37
+ 'http://127.0.0.1:9595/slow'
36
38
  end
37
39
  end
38
40
 
@@ -50,9 +52,9 @@ module HotTub
50
52
  set :server, 'puma'
51
53
  set :port, 9393
52
54
 
53
-
54
- get '/quick' do
55
- (Random.new.rand(0..999999).to_s)
55
+ get '/fast' do
56
+ sleep(0.01)
57
+ "foo"
56
58
  end
57
59
 
58
60
  def self.teardown
@@ -60,7 +62,7 @@ module HotTub
60
62
  end
61
63
 
62
64
  def self.url
63
- 'http://127.0.0.1:9393/quick'
65
+ 'http://127.0.0.1:9393/fast'
64
66
  end
65
67
  end
66
68
  end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+
3
+ describe HotTub do
4
+
5
+ context "blocking (size equals max_size)" do
6
+ let(:pool) do
7
+ HotTub.new(:size => 4, :max_size => 4) {
8
+ Excon.new(HotTub::Server.url, :thread_safe_sockets => false)
9
+ }
10
+ end
11
+
12
+ let(:threads) { [] }
13
+
14
+ before(:each) do
15
+ 5.times do
16
+ excon_thread_work(pool, 30, threads)
17
+ end
18
+ end
19
+
20
+ it { expect(pool.current_size).to eql(4) }# make sure the pool grew beyond size
21
+
22
+ it "should work" do
23
+ results = threads.collect{ |t| t[:status]}
24
+ expect(results.length).to eql(150) # make sure all threads are present
25
+ expect(results.uniq).to eql([200]) # make sure all returned status 200
26
+ end
27
+
28
+ it "should shutdown" do
29
+ pool.shutdown!
30
+ expect(pool.current_size).to eql(0)
31
+ end
32
+ end
33
+
34
+ context "with larger max" do
35
+ let(:pool) do
36
+ HotTub.new(:size => 4, :max_size => 8) {
37
+ Excon.new(HotTub::Server.url, :thread_safe_sockets => false)
38
+ }
39
+ end
40
+
41
+ let(:threads) { [] }
42
+
43
+ before(:each) do
44
+ 5.times do
45
+ excon_thread_work(pool, 30, threads)
46
+ end
47
+ end
48
+
49
+ it { expect(pool.current_size).to be >= 4 }
50
+
51
+ it { expect(pool.current_size).to be <= 8 }
52
+
53
+ it "should reap" do
54
+ pool.reap!
55
+ expect(pool.current_size).to eql(4)
56
+ end
57
+
58
+ it "should work" do
59
+ results = threads.collect{ |t| t[:status]}
60
+ expect(results.length).to eql(150) # make sure all threads are present
61
+ expect(results.uniq).to eql([200]) # make sure all returned status 200
62
+ end
63
+ end
64
+
65
+ context "sized without max" do
66
+ let(:pool) do
67
+ HotTub.new(:size => 4) {
68
+ Excon.new(HotTub::Server.url, :thread_safe_sockets => false)
69
+ }
70
+ end
71
+
72
+ let(:threads) { [] }
73
+
74
+ before(:each) do
75
+ 5.times do
76
+ excon_thread_work(pool, 30, threads)
77
+ end
78
+ end
79
+
80
+ it { expect(pool.current_size).to be > 4 }# make sure the pool grew beyond size
81
+
82
+ it "should reap" do
83
+ pool.reap!
84
+ expect(pool.current_size).to eql(4)
85
+ end
86
+
87
+ it "should work" do
88
+ results = threads.collect{ |t| t[:status]}
89
+ expect(results.length).to eql(150) # make sure all threads are present
90
+ expect(results.uniq).to eql([200]) # make sure all returned status 200
91
+ end
92
+ end
93
+
94
+ context "shutdown with slow client" do
95
+ let(:pool) do
96
+ HotTub.new(:size => 1) {
97
+ Excon.new(HotTub::Server.slow_url, :thread_safe_sockets => false, :read_timeout => 2)
98
+ }
99
+ end
100
+
101
+ it "should work" do
102
+ conn = nil
103
+
104
+ begin
105
+ th = Thread.new do
106
+ uri = URI.parse(HotTub::Server.slow_url)
107
+ pool.run do |connection|
108
+ conn = connection
109
+ connection.get(:path => uri.path).status
110
+ end
111
+ end
112
+ sleep(0.01)
113
+ pool.shutdown!
114
+ th.join
115
+ rescue => e
116
+ puts e.message
117
+ end
118
+
119
+ expect(pool.shutdown).to eql(true)
120
+ expect(pool.current_size).to eql(0)
121
+ expect(conn.send(:sockets)).to be_empty
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+
128
+
129
+ def excon_thread_work(pool,thread_count=0, threads=[])
130
+ thread_count.times.each do
131
+ threads << Thread.new do
132
+ uri = URI.parse(HotTub::Server.url)
133
+ pool.run{|connection| Thread.current[:status] = connection.get(:path => uri.path).status }
134
+ end
135
+ end
136
+ threads.each do |t|
137
+ t.join
138
+ end
139
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe HotTub do
4
+ context "blocking (size equals max_size)" do
5
+ let(:pool) do
6
+ HotTub.new(:size => 4, :max_size => 4) {
7
+ uri = URI.parse(HotTub::Server.url)
8
+ http = Net::HTTP.new(uri.host, uri.port)
9
+ http.use_ssl = false
10
+ http.start
11
+ http
12
+ }
13
+ end
14
+
15
+ let(:threads) { [] }
16
+
17
+ before(:each) do
18
+ 5.times do
19
+ net_http_thread_work(pool, 30, threads)
20
+ end
21
+ end
22
+
23
+ it { expect(pool.current_size).to eql(4) } # make sure the pool grew beyond size
24
+
25
+ it "should work" do
26
+ results = threads.collect{ |t| t[:status]}
27
+ expect(results.length).to eql(150) # make sure all threads are present
28
+ expect(results.uniq).to eql(['200']) # make sure all returned status 200
29
+ end
30
+
31
+ it "should shutdown" do
32
+ pool.shutdown!
33
+ expect(pool.current_size).to eql(0)
34
+ end
35
+ end
36
+
37
+ context "with larger max" do
38
+ let(:pool) do
39
+ HotTub.new(:size => 4, :max_size => 8) {
40
+ uri = URI.parse(HotTub::Server.url)
41
+ http = Net::HTTP.new(uri.host, uri.port)
42
+ http.use_ssl = false
43
+ http.start
44
+ http
45
+ }
46
+ end
47
+
48
+ let(:threads) { [] }
49
+
50
+ before(:each) do
51
+ 5.times do
52
+ net_http_thread_work(pool, 30, threads)
53
+ end
54
+ end
55
+
56
+ it { expect(pool.current_size).to be >= 4 }
57
+ it { expect(pool.current_size).to be <= 8 }
58
+ it "should work" do
59
+ results = threads.collect{ |t| t[:status]}
60
+ expect(results.length).to eql(150) # make sure all threads are present
61
+ expect(results.uniq).to eql(['200']) # make sure all returned status 200
62
+ end
63
+ end
64
+
65
+ context "sized without max" do
66
+ let(:pool) do
67
+ HotTub.new(:size => 4) {
68
+ uri = URI.parse(HotTub::Server.url)
69
+ http = Net::HTTP.new(uri.host, uri.port)
70
+ http.use_ssl = false
71
+ http.start
72
+ http
73
+ }
74
+ end
75
+
76
+ let(:threads) { [] }
77
+
78
+ before(:each) do
79
+ 5.times do
80
+ net_http_thread_work(pool, 30, threads)
81
+ end
82
+ end
83
+
84
+ it { expect(pool.current_size).to be > 4 } # make sure the pool grew beyond size
85
+
86
+ it "should work" do
87
+ results = threads.collect{ |t| t[:status]}
88
+ expect(results.length).to eql(150) # make sure all threads are present
89
+ expect(results.uniq).to eql(['200']) # make sure all returned status 200
90
+ end
91
+ end
92
+ end
93
+
94
+ def net_http_thread_work(pool,thread_count=0, threads=[])
95
+ thread_count.times.each do
96
+ threads << Thread.new do
97
+ uri = URI.parse(HotTub::Server.url)
98
+ pool.run{|connection| Thread.current[:status] = connection.get(uri.path).code }
99
+ end
100
+ end
101
+ threads.each do |t|
102
+ t.join
103
+ end
104
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe HotTub, 'with sessions' do
4
+
5
+ let(:url) { HotTub::Server.url }
6
+ let(:url2) { HotTub::Server2.url }
7
+ let(:sessions) do
8
+ HotTub.new(:sessions => true) { |url|
9
+ uri = URI.parse(url)
10
+ http = Net::HTTP.new(uri.host, uri.port)
11
+ http.use_ssl = false
12
+ http.start
13
+ http
14
+ }
15
+ end
16
+
17
+ let(:threads) { [] }
18
+
19
+ before(:each) do
20
+ 10.times.each do
21
+ threads << Thread.new do
22
+ sessions.run(url) { |clnt| Thread.current[:result] = clnt.get(URI.parse(url).path).code }
23
+ sessions.run(url2) { |clnt| Thread.current[:result] = clnt.get(URI.parse(url).path).code }
24
+ end
25
+ end
26
+ threads.each do |t|
27
+ t.join
28
+ end
29
+ end
30
+
31
+ it "should work" do
32
+ results = threads.collect{ |t| t[:result]}
33
+ expect(results.length).to eql(10)
34
+ expect(results.uniq).to eql([results.first])
35
+ end
36
+
37
+ it "should work" do
38
+ expect(sessions.instance_variable_get(:@sessions).keys.length).to eql(2)
39
+ end
40
+ end