stubbornly 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1205ff4f8568bb88aca04e7f902a002865684100594a40dd3b1b2ad34c3e2590
4
- data.tar.gz: 48b4637aa7db561154399bb69c0f0b1b289700c606d3550a11f139daa4bedffb
3
+ metadata.gz: 61541dc62beb9db0f9aeefe99bf5dd84d1773efe2af2994a445f1b5f33a102b0
4
+ data.tar.gz: e69c526fd99e7ac41e1e5b813511d24d0bf67948ca0ffd5cc7c5ce0d1adf202f
5
5
  SHA512:
6
- metadata.gz: 8c0522171461dccbaf95ad5286d78296201bc3a9bfbf8a06b9a22499a1c74eea32a81c424cca81feba69dd96f269099b36971f9f3d61e1b24bf8e45595d9a06c
7
- data.tar.gz: 0ceedfba3af82388158b7d848650b226384d8337d553361a0e9759703c781e8d56601410dd436d993a3cd675a2b55be6fa072bcc5b9a3bfa5592cb3063c0382f
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 13:37:09 +0200 using RuboCop version 0.57.2.
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: 22
11
+ Max: 27
12
12
 
13
- # Offense count: 1
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: 24
20
+ Max: 27
21
21
 
22
22
  # Offense count: 1
23
23
  Security/Eval:
24
24
  Exclude:
25
- - 'spec/unit/examples_spec.rb'
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.1.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
  [![Build Status](https://travis-ci.org/suhlig/stubbornly.svg?branch=master)](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
- ## Example
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
- @logger = Logger.new(STDOUT)
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
- if Checker.new('http://localhost:8765').up?
29
- puts "Website is up"
30
- else
31
- warn "We are DOWN!"
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
@@ -8,4 +8,8 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  require 'rubocop/rake_task'
9
9
  RuboCop::RakeTask.new
10
10
 
11
+ require 'yard'
12
+ require 'yard/rake/yardoc_task'
13
+ YARD::Rake::YardocTask.new
14
+
11
15
  task default: %i[rubocop spec]
@@ -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
- def initialize(logger: NullLogger.new)
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
- def retry(timeout: Float::INFINITY, attempts: Float::MAX)
12
- @logger.debug("Starting first of not more than #{attempts} attempts") if attempts < Float::MAX
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 = 0
41
+ attempt = 1
16
42
  start = Time.now
17
43
 
18
44
  begin
19
- @logger.info "Attempt ##{attempt}"
20
- yield.tap do
21
- @logger.info "Success after #{elapsed_since(start)}s and #{attempt} attempts"
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
- attempt += 1
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 (#{attempt}) reached"
30
- return false
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 after #{elapsed_since(start)}s"
35
- return false
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Stubbornly
4
- VERSION = '0.1.0'.freeze
4
+ VERSION = '0.2.0'
5
5
  end
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.1.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