stubbornly 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +9 -5
- data/Gemfile.lock +3 -1
- data/Guardfile +2 -0
- data/README.markdown +36 -9
- data/Rakefile +4 -0
- data/examples/http_status +41 -0
- data/lib/stubbornly.rb +52 -15
- data/lib/stubbornly/version.rb +1 -1
- data/stubbornly.gemspec +2 -1
- metadata +16 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61541dc62beb9db0f9aeefe99bf5dd84d1773efe2af2994a445f1b5f33a102b0
|
4
|
+
data.tar.gz: e69c526fd99e7ac41e1e5b813511d24d0bf67948ca0ffd5cc7c5ce0d1adf202f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ffeaf2ef474a9f22d0fff5d17dc9d03d1c08dc94862f9cf48b74181501bdace3f65d798ef03aec92d9e3732204b20fce3f72570c5bf9e5394169e7ad046568c3
|
7
|
+
data.tar.gz: e99e74cbe1ba5081f6db301e267ca7fc8b900fc3445782fbf7c0384c224684e06589a918b69e265f17bee9f21641f148ae4a0fb818a00e159b03c90d3ddf2338
|
data/.rubocop_todo.yml
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on 2018-07-01
|
3
|
+
# on 2018-07-01 19:37:52 +0200 using RuboCop version 0.57.2.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
@@ -8,18 +8,22 @@
|
|
8
8
|
|
9
9
|
# Offense count: 1
|
10
10
|
Metrics/AbcSize:
|
11
|
-
Max:
|
11
|
+
Max: 27
|
12
12
|
|
13
|
-
# Offense count:
|
13
|
+
# Offense count: 2
|
14
14
|
Metrics/CyclomaticComplexity:
|
15
15
|
Max: 7
|
16
16
|
|
17
17
|
# Offense count: 2
|
18
18
|
# Configuration parameters: CountComments.
|
19
19
|
Metrics/MethodLength:
|
20
|
-
Max:
|
20
|
+
Max: 27
|
21
21
|
|
22
22
|
# Offense count: 1
|
23
23
|
Security/Eval:
|
24
24
|
Exclude:
|
25
|
-
- 'spec/
|
25
|
+
- 'spec/system/readme_spec.rb'
|
26
|
+
|
27
|
+
Style/MutableConstant:
|
28
|
+
Exclude:
|
29
|
+
- 'lib/stubbornly/version.rb'
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stubbornly (0.
|
4
|
+
stubbornly (0.2.0)
|
5
5
|
null-logger
|
6
6
|
|
7
7
|
GEM
|
@@ -101,6 +101,7 @@ GEM
|
|
101
101
|
unf_ext
|
102
102
|
unf_ext (0.0.7.5)
|
103
103
|
unicode-display_width (1.4.0)
|
104
|
+
yard (0.9.14)
|
104
105
|
|
105
106
|
PLATFORMS
|
106
107
|
ruby
|
@@ -117,6 +118,7 @@ DEPENDENCIES
|
|
117
118
|
rspec
|
118
119
|
rubocop
|
119
120
|
stubbornly!
|
121
|
+
yard
|
120
122
|
|
121
123
|
BUNDLED WITH
|
122
124
|
1.16.2
|
data/Guardfile
CHANGED
@@ -17,5 +17,7 @@ guard :rspec, cmd: 'bundle exec rspec' do
|
|
17
17
|
watch(rspec.spec_helper) { rspec.spec_dir }
|
18
18
|
watch(rspec.spec_files)
|
19
19
|
watch(%r{^lib/.*/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
|
20
|
+
watch('README.markdown') { 'spec/system/examples_spec.rb' }
|
21
|
+
watch(%r{^examples/(.+)}) { |m| "spec/system/examples/#{m[1]}_spec.rb" }
|
20
22
|
dsl.watch_spec_files_for(dsl.ruby.lib_files)
|
21
23
|
end
|
data/README.markdown
CHANGED
@@ -2,20 +2,20 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/suhlig/stubbornly)
|
4
4
|
|
5
|
-
Stubbornly retries a given block until the maximum number of attempts or timeout has been reached.
|
5
|
+
Stubbornly retries a given block until the maximum number of attempts or a timeout has been reached.
|
6
6
|
|
7
|
-
##
|
7
|
+
## Examples
|
8
|
+
|
9
|
+
### Timeout
|
8
10
|
|
9
11
|
```ruby
|
10
12
|
require 'stubbornly'
|
11
13
|
require 'http'
|
12
|
-
require 'logger'
|
13
14
|
|
14
15
|
class Checker
|
15
16
|
def initialize(url)
|
16
17
|
@url = url
|
17
|
-
@
|
18
|
-
@stubbornly = Stubbornly.new(logger: @logger)
|
18
|
+
@stubbornly = Stubbornly.new
|
19
19
|
end
|
20
20
|
|
21
21
|
def up?
|
@@ -25,10 +25,37 @@ class Checker
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
warn "
|
28
|
+
begin
|
29
|
+
Checker.new('http://localhost:8765').up?
|
30
|
+
rescue => e
|
31
|
+
warn "Error: #{e.message}"
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
### Maximum number of attempts
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'stubbornly'
|
39
|
+
require 'http'
|
40
|
+
|
41
|
+
class Checker
|
42
|
+
def initialize(url)
|
43
|
+
@url = url
|
44
|
+
@stubbornly = Stubbornly.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def up?
|
48
|
+
@stubbornly.retry(attempts: 3) do
|
49
|
+
response = HTTP.get(@url)
|
50
|
+
puts "The site at #{@url} is up 👍"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
Checker.new('http://localhost:8765').up?
|
57
|
+
rescue => e
|
58
|
+
warn "Error: #{e.message}"
|
32
59
|
end
|
33
60
|
```
|
34
61
|
|
data/Rakefile
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'stubbornly'
|
5
|
+
require 'http'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
#
|
9
|
+
# Checks a URL
|
10
|
+
#
|
11
|
+
class Checker
|
12
|
+
def initialize(url)
|
13
|
+
@url = url
|
14
|
+
@logger = Logger.new(STDERR, progname: self.class)
|
15
|
+
@stubbornly = Stubbornly.new(logger: Logger.new(STDERR, progname: Stubbornly))
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Return the status code of the given URL.
|
20
|
+
# Retry if failed, but
|
21
|
+
# - not more often than specified
|
22
|
+
# - only within the given timeout.
|
23
|
+
#
|
24
|
+
def status
|
25
|
+
@logger.info "Checking status of #{@url}"
|
26
|
+
@stubbornly.retry(timeout: 10, attempts: 4) do
|
27
|
+
HTTP.get(@url).code.tap do |result|
|
28
|
+
@logger.info "Result is #{result}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
url = ARGV.shift || 'http://example.com'
|
35
|
+
|
36
|
+
begin
|
37
|
+
puts Checker.new(url).status
|
38
|
+
rescue StandardError => e
|
39
|
+
warn "Error: #{e.message}"
|
40
|
+
exit 1
|
41
|
+
end
|
data/lib/stubbornly.rb
CHANGED
@@ -4,37 +4,70 @@ require 'null_logger'
|
|
4
4
|
require 'English'
|
5
5
|
|
6
6
|
class Stubbornly
|
7
|
-
|
7
|
+
#
|
8
|
+
# Constructs a new `Stubbornly` instance.
|
9
|
+
#
|
10
|
+
# If a block is passed, it will be called to calculate the sleep time (in
|
11
|
+
# seconds) before the next attempt is made. The attempt counter is passed
|
12
|
+
# as a block argument.
|
13
|
+
#
|
14
|
+
# This is useful to determine the backoff characteristics. The result of the
|
15
|
+
# block will be the sleep time in seconds. For instance, the following code
|
16
|
+
# would implement an exponential backoff with retries after 1, 3, 7, 15,
|
17
|
+
# 31, ... seconds:
|
18
|
+
#
|
19
|
+
# Stubbornly.new {|attempt| 2**attempt - 1 }
|
20
|
+
#
|
21
|
+
# The default backoff is 1 (constant), i.e. the attempts are equidistant at 1s.
|
22
|
+
#
|
23
|
+
def initialize(logger: NullLogger.new, &block)
|
8
24
|
@logger = logger
|
25
|
+
@backoff = block || proc { 1 }
|
9
26
|
end
|
10
27
|
|
11
|
-
|
12
|
-
|
28
|
+
#
|
29
|
+
# Calls the given block, rescuing its errors, and retries until either the
|
30
|
+
# given timeout or the maximum number of attempts was exceeded.
|
31
|
+
#
|
32
|
+
# When an attempt raises an error, this method will sleep before it retries.
|
33
|
+
# The number of seconds to sleep is determined by the block passed to {#initialize}.
|
34
|
+
#
|
35
|
+
# If the block succeeds, its value is returned.
|
36
|
+
#
|
37
|
+
def retry(timeout: Float::INFINITY, attempts: Float::INFINITY, &block)
|
38
|
+
@logger.debug("Attempting not more than #{attempts} times") unless attempts.infinite?
|
13
39
|
@logger.debug("Will time out after #{timeout}s") unless timeout.infinite?
|
14
40
|
|
15
|
-
attempt =
|
41
|
+
attempt = 1
|
16
42
|
start = Time.now
|
17
43
|
|
18
44
|
begin
|
19
|
-
@logger.info "Attempt ##{attempt}"
|
20
|
-
|
21
|
-
|
45
|
+
@logger.info "Attempt ##{attempt} #{after_elapsed_since(start)}"
|
46
|
+
|
47
|
+
if block
|
48
|
+
yield(attempt, elapsed_since(start)).tap do
|
49
|
+
@logger.info "Success #{after_elapsed_since(start)} and #{attempt} attempts"
|
50
|
+
end
|
22
51
|
end
|
23
|
-
rescue StandardError
|
24
|
-
|
25
|
-
retry_after = 2**attempt - 1
|
26
|
-
@logger.warn "#{$ERROR_INFO.message}. Trying again in #{retry_after}s."
|
52
|
+
rescue StandardError => error
|
53
|
+
@logger.warn error.message
|
27
54
|
|
28
55
|
if attempt >= attempts
|
29
|
-
@logger.error "Maximum number of attempts (#{
|
30
|
-
|
56
|
+
@logger.error "Maximum number of attempts (#{attempts}) reached (#{after_elapsed_since(start)})"
|
57
|
+
raise
|
31
58
|
end
|
32
59
|
|
60
|
+
# TODO: Give up NOW if retry_after would exceed timeout
|
61
|
+
|
33
62
|
if elapsed_since(start) >= timeout
|
34
|
-
@logger.info "Timed out
|
35
|
-
|
63
|
+
@logger.info "Timed out #{after_elapsed_since(start)} (#{attempt} attempts)"
|
64
|
+
raise
|
36
65
|
end
|
37
66
|
|
67
|
+
attempt += 1
|
68
|
+
retry_after = @backoff.call(attempt)
|
69
|
+
|
70
|
+
@logger.warn "Trying again in #{retry_after}s."
|
38
71
|
sleep(retry_after)
|
39
72
|
retry
|
40
73
|
end
|
@@ -45,4 +78,8 @@ class Stubbornly
|
|
45
78
|
def elapsed_since(start)
|
46
79
|
Time.now - start
|
47
80
|
end
|
81
|
+
|
82
|
+
def after_elapsed_since(start)
|
83
|
+
"after #{elapsed_since(start).round(2)}s"
|
84
|
+
end
|
48
85
|
end
|
data/lib/stubbornly/version.rb
CHANGED
data/stubbornly.gemspec
CHANGED
@@ -28,10 +28,11 @@ Gem::Specification.new do |spec|
|
|
28
28
|
spec.add_development_dependency 'guard'
|
29
29
|
spec.add_development_dependency 'guard-bundler'
|
30
30
|
spec.add_development_dependency 'guard-rspec'
|
31
|
-
spec.add_development_dependency 'http'
|
31
|
+
spec.add_development_dependency 'http' # examples
|
32
32
|
spec.add_development_dependency 'pry'
|
33
33
|
spec.add_development_dependency 'pry-byebug'
|
34
34
|
spec.add_development_dependency 'rake'
|
35
35
|
spec.add_development_dependency 'rspec'
|
36
36
|
spec.add_development_dependency 'rubocop'
|
37
|
+
spec.add_development_dependency 'yard'
|
37
38
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stubbornly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Steffen Uhlig
|
@@ -164,6 +164,20 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: yard
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
167
181
|
description: Stubbornly retries a given block until the maximum number of attempts
|
168
182
|
or timeout has been reached.
|
169
183
|
email:
|
@@ -183,6 +197,7 @@ files:
|
|
183
197
|
- LICENSE.txt
|
184
198
|
- README.markdown
|
185
199
|
- Rakefile
|
200
|
+
- examples/http_status
|
186
201
|
- lib/stubbornly.rb
|
187
202
|
- lib/stubbornly/version.rb
|
188
203
|
- stubbornly.gemspec
|