hot_tub 0.5.3 → 1.0.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.
@@ -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!