wait 0.2.2 → 0.3.2
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.
- data/lib/wait.rb +68 -150
- metadata +2 -2
data/lib/wait.rb
CHANGED
@@ -1,62 +1,59 @@
|
|
1
|
-
require "
|
2
|
-
require "logger"
|
1
|
+
require File.expand_path("../initialize", __FILE__)
|
3
2
|
|
4
3
|
class Wait
|
4
|
+
DEFAULT = {
|
5
|
+
:timeout => 15,
|
6
|
+
:logger => BaseLogger,
|
7
|
+
:attempts => 5,
|
8
|
+
:counter => BaseCounter,
|
9
|
+
:delay => 1,
|
10
|
+
:delayer => RegularDelayer,
|
11
|
+
:tester => TruthyTester,
|
12
|
+
:rescuer => PassiveRescuer
|
13
|
+
}
|
14
|
+
|
5
15
|
# Creates a new Wait instance.
|
6
16
|
#
|
7
17
|
# == Options
|
8
18
|
#
|
9
19
|
# [:attempts]
|
10
|
-
# Number of times to attempt the block. Default is
|
20
|
+
# Number of times to attempt the block (passed to +counter+). Default is
|
21
|
+
# +5+.
|
22
|
+
# [:counter]
|
23
|
+
# Strategy used to count attempts. Default is Wait::BaseCounter.
|
11
24
|
# [:timeout]
|
12
25
|
# Seconds until the block times out. Default is +15+.
|
26
|
+
# [:delay]
|
27
|
+
# Seconds to delay in between attempts (passed to +delayer+). Default is
|
28
|
+
# +1+.
|
13
29
|
# [:delayer]
|
14
|
-
#
|
15
|
-
#
|
30
|
+
# Strategy used to delay in between attempts. Default is
|
31
|
+
# Wait::RegularDelayer.
|
16
32
|
# [:rescue]
|
17
|
-
# One or an array of exceptions to rescue
|
33
|
+
# One or an array of exceptions to rescue (passed to +rescuer+). Default
|
34
|
+
# is +nil+.
|
35
|
+
# [:rescuer]
|
36
|
+
# Strategy used to handle exceptions. Default is Wait::PassiveRescuer.
|
18
37
|
# [:tester]
|
19
|
-
# Strategy
|
38
|
+
# Strategy used to test the result. Default is Wait::TruthyTester.
|
20
39
|
# [:logger]
|
21
|
-
# Ruby logger
|
40
|
+
# Ruby logger used. Default is Wait::BaseLogger.
|
22
41
|
#
|
23
42
|
def initialize(options = {})
|
24
|
-
@
|
25
|
-
|
26
|
-
@
|
27
|
-
|
28
|
-
@
|
29
|
-
|
30
|
-
|
31
|
-
@
|
32
|
-
|
33
|
-
|
43
|
+
@timeout = options[:timeout] || DEFAULT[:timeout]
|
44
|
+
debug = options[:debug]
|
45
|
+
@logger = (options[:logger] || (debug ? DebugLogger : DEFAULT[:logger])).new
|
46
|
+
attempts = options[:attempts] || DEFAULT[:attempts]
|
47
|
+
@counter = (options[:counter] || DEFAULT[:counter]).new(@logger, attempts)
|
48
|
+
delay = options[:delay] || DEFAULT[:delay]
|
49
|
+
@delayer = (options[:delayer] || DEFAULT[:delayer]).new(@logger, delay)
|
50
|
+
@tester = (options[:tester] || DEFAULT[:tester]).new(@logger)
|
51
|
+
exceptions = Array(options[:rescue])
|
52
|
+
@rescuer = (options[:rescuer] || DEFAULT[:rescuer]).new(@logger, @tester.exceptions, exceptions)
|
34
53
|
end
|
35
54
|
|
36
|
-
#
|
37
|
-
|
38
|
-
unless @delayer.respond_to?(:sleep)
|
39
|
-
raise(ArgumentError, "delay strategy does not respond to sleep message: #{@delayer.inspect}")
|
40
|
-
end
|
41
|
-
|
42
|
-
unless @tester.respond_to?(:new)
|
43
|
-
raise(ArgumentError, "tester strategy does not respond to new message: #{@tester.inspect}")
|
44
|
-
end
|
45
|
-
|
46
|
-
unless @tester.new.respond_to?(:valid?)
|
47
|
-
raise(ArgumentError, "tester strategy does not respond to valid? message: #{@tester.inspect}")
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Returns a new (or existing) logger instance.
|
52
|
-
def logger
|
53
|
-
if @logger.nil?
|
54
|
-
@logger = Logger.new(STDOUT)
|
55
|
-
@logger.level = Logger::WARN
|
56
|
-
end
|
57
|
-
|
58
|
-
@logger
|
59
|
-
end
|
55
|
+
# Raised when a block times out.
|
56
|
+
class TimeoutError < Timeout::Error; end
|
60
57
|
|
61
58
|
# == Description
|
62
59
|
#
|
@@ -64,7 +61,7 @@ class Wait
|
|
64
61
|
# result. Useful for blocking script execution until:
|
65
62
|
# * an HTTP request was successful
|
66
63
|
# * a port has opened
|
67
|
-
# *
|
64
|
+
# * a process has started
|
68
65
|
# * etc.
|
69
66
|
#
|
70
67
|
# == Examples
|
@@ -72,8 +69,11 @@ class Wait
|
|
72
69
|
# wait = Wait.new
|
73
70
|
# # => #<Wait>
|
74
71
|
# wait.until { Time.now.sec.even? }
|
75
|
-
# #
|
76
|
-
# #
|
72
|
+
# # [Tester] result: false
|
73
|
+
# # [Rescuer] rescued: Wait::TruthyTester::ResultNotTruthy: false
|
74
|
+
# # [Counter] attempt 1/5 failed
|
75
|
+
# # [Delayer] delaying for 1s
|
76
|
+
# # [Tester] result: true
|
77
77
|
# # => true
|
78
78
|
#
|
79
79
|
# If you wish to handle an exception by attempting the block again, pass one
|
@@ -88,10 +88,14 @@ class Wait
|
|
88
88
|
# when 3 then "foo"
|
89
89
|
# end
|
90
90
|
# end
|
91
|
-
# #
|
92
|
-
# #
|
93
|
-
# #
|
94
|
-
# #
|
91
|
+
# # [Tester] result: nil
|
92
|
+
# # [Rescuer] rescued: Wait::TruthyTester::ResultNotTruthy: nil
|
93
|
+
# # [Counter] attempt 1/5 failed
|
94
|
+
# # [Delayer] delaying for 1s
|
95
|
+
# # [Rescuer] rescued: RuntimeError: RuntimeError
|
96
|
+
# # [Counter] attempt 2/5 failed
|
97
|
+
# # [Delayer] delaying for 1s
|
98
|
+
# # [Tester] result: "foo"
|
95
99
|
# # => "foo"
|
96
100
|
#
|
97
101
|
# == Returns
|
@@ -105,111 +109,25 @@ class Wait
|
|
105
109
|
def until(&block)
|
106
110
|
# Reset the attempt counter.
|
107
111
|
@counter.reset
|
108
|
-
|
109
112
|
begin
|
113
|
+
# Increment the attempt counter.
|
110
114
|
@counter.increment
|
111
|
-
|
112
|
-
result = Timeout.timeout(@timeout,
|
113
|
-
# Execute the block and pass the attempt count to it.
|
115
|
+
# Wrap the given block in a timeout.
|
116
|
+
result = Timeout.timeout(@timeout, TimeoutError) do
|
117
|
+
# Execute the block and pass the attempt count (an +Integer+) to it.
|
114
118
|
yield(@counter.attempt)
|
115
119
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
else
|
128
|
-
logger.debug "Attempt #{@counter} failed, delaying for #{@delayer}"
|
129
|
-
@delayer.sleep
|
130
|
-
retry
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Raised when a block doesn't return a result (+nil+ or +false+).
|
136
|
-
class NoResultError < StandardError; end
|
137
|
-
|
138
|
-
# Raised when a block times out.
|
139
|
-
class TimeoutError < Timeout::Error; end
|
140
|
-
|
141
|
-
class RegularDelayer
|
142
|
-
def initialize(initial_delay = 1)
|
143
|
-
@delay = initial_delay
|
144
|
-
end
|
145
|
-
|
146
|
-
def sleep
|
147
|
-
Kernel.sleep(@delay)
|
148
|
-
end
|
149
|
-
|
150
|
-
def to_s
|
151
|
-
"#{@delay}s"
|
152
|
-
end
|
153
|
-
end # RegularDelayer
|
154
|
-
|
155
|
-
class ExponentialDelayer < RegularDelayer
|
156
|
-
def sleep
|
157
|
-
super
|
158
|
-
increment
|
159
|
-
end
|
160
|
-
|
161
|
-
def increment
|
162
|
-
@delay *= 2
|
163
|
-
end
|
164
|
-
end # ExponentialDelayer
|
165
|
-
|
166
|
-
class AttemptCounter
|
167
|
-
attr_reader :attempt
|
168
|
-
|
169
|
-
def initialize(total)
|
170
|
-
# Prevent accidentally causing an infinite loop.
|
171
|
-
unless total.is_a?(Fixnum) and total > 0
|
172
|
-
raise(ArgumentError, "invalid number of attempts: #{total.inspect}")
|
173
|
-
end
|
174
|
-
|
175
|
-
@total = total
|
176
|
-
reset
|
177
|
-
end
|
178
|
-
|
179
|
-
def reset
|
180
|
-
@attempt = 0
|
181
|
-
end
|
182
|
-
|
183
|
-
def increment
|
184
|
-
@attempt += 1
|
185
|
-
end
|
186
|
-
|
187
|
-
def last_attempt?
|
188
|
-
@attempt == @total
|
189
|
-
end
|
190
|
-
|
191
|
-
def to_s
|
192
|
-
[@attempt, @total].join("/")
|
193
|
-
end
|
194
|
-
end # AttemptCounter
|
195
|
-
|
196
|
-
class TruthyTester
|
197
|
-
class ResultNotTruthy < RuntimeError; end
|
198
|
-
|
199
|
-
def self.exceptions
|
200
|
-
[ResultNotTruthy]
|
201
|
-
end
|
202
|
-
|
203
|
-
def initialize(result = nil)
|
204
|
-
@result = result
|
205
|
-
end
|
206
|
-
|
207
|
-
def raise_unless_valid
|
208
|
-
valid? ? @result : raise(ResultNotTruthy, @result.inspect)
|
209
|
-
end
|
210
|
-
|
211
|
-
def valid?
|
212
|
-
not (@result.nil? or @result == false)
|
120
|
+
# Raise an exception unless the result is valid.
|
121
|
+
@tester.raise_unless_valid(result)
|
122
|
+
rescue *@rescuer.exceptions => exception
|
123
|
+
# Raise the exception unless it can be ignored.
|
124
|
+
@rescuer.raise_unless_ignore(exception)
|
125
|
+
# Raise the exception if this was the last attempt.
|
126
|
+
@counter.raise_if_last_attempt(exception)
|
127
|
+
# Sleep before the next attempt.
|
128
|
+
@delayer.sleep
|
129
|
+
# Try the block again.
|
130
|
+
retry
|
213
131
|
end
|
214
132
|
end
|
215
133
|
end #Wait
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wait
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-01-06 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: todd@paperlesspost.com
|