hot_tub 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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