rhod 0.0.3 → 0.1.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/Changes.md +8 -0
- data/README.md +64 -168
- data/lib/rhod.rb +6 -16
- data/lib/rhod/backoffs.rb +15 -51
- data/lib/rhod/backoffs/backoff.rb +16 -0
- data/lib/rhod/backoffs/constant.rb +5 -0
- data/lib/rhod/backoffs/exponential.rb +5 -0
- data/lib/rhod/backoffs/logarithmic.rb +5 -0
- data/lib/rhod/backoffs/random.rb +6 -0
- data/lib/rhod/command.rb +8 -6
- data/lib/rhod/profile.rb +37 -0
- data/lib/rhod/version.rb +1 -1
- data/test/test_backoffs.rb +39 -15
- data/test/test_command.rb +5 -5
- data/test/test_profile.rb +30 -0
- metadata +11 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f1d610e43d6cd0ea21bc4bf99bd68e4728f26cbd
         | 
| 4 | 
            +
              data.tar.gz: ea6aba49b9e5df02cedf9988f6ba8915df2e1f99
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0b30aebc6f171b35d4dc581824d7e0a1466d308e4cc1f4927c5d791d3382ef3b75880cb7e1fa7a122daafd4df45b96c5d978d3ea2e6f377cd933c8cbd6142dc5
         | 
| 7 | 
            +
              data.tar.gz: 2997412a10c2d10e3a3a2f2eca8df58387459f95fbe6a5da15f6473cb07383bdc5bc6c21ad62d83e0572154144cac0d324f0e0322843ac11ec09b0fc7f3573aa
         | 
    
        data/Changes.md
    ADDED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -12,214 +12,107 @@ A Lightweight High Avalibility framework for Ruby, inspired by [Hystrix](https:/ | |
| 12 12 |  | 
| 13 13 | 
             
            Rhod helps you handle failures gracefully, even during a firefight. When your code has to interact with other services, it also means writing code to keep it running in the event of failure. Failures can include exceptions, timeouts, downed hosts, and any number of issues that are caused by events outside of your application.
         | 
| 14 14 |  | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
              - Fail Fast
         | 
| 18 | 
            -
              - Retry N times before Fail
         | 
| 19 | 
            -
              - Retry N times with progressive backoffs before Fail
         | 
| 20 | 
            -
              - Fail Silent
         | 
| 21 | 
            -
              - Fail w/ Fallback
         | 
| 22 | 
            -
              - Primary / Secondary ("hot spare") switch over
         | 
| 23 | 
            -
             | 
| 24 | 
            -
            ## Installation
         | 
| 25 | 
            -
             | 
| 26 | 
            -
            Rhod requires Ruby 1.9.2 or greater.
         | 
| 27 | 
            -
             | 
| 28 | 
            -
            Add this line to your application's Gemfile:
         | 
| 29 | 
            -
             | 
| 30 | 
            -
            ```ruby
         | 
| 31 | 
            -
            gem 'rhod'
         | 
| 32 | 
            -
            ```
         | 
| 15 | 
            +
            # Is it any good?
         | 
| 33 16 |  | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
                $ bundle
         | 
| 37 | 
            -
             | 
| 38 | 
            -
            Or install it yourself as:
         | 
| 39 | 
            -
             | 
| 40 | 
            -
                $ gem install rhod
         | 
| 41 | 
            -
             | 
| 42 | 
            -
            ## Configuration
         | 
| 43 | 
            -
             | 
| 44 | 
            -
            To configure Rhod's defaults, change any of the keys in `Rhod.defaults`
         | 
| 45 | 
            -
             | 
| 46 | 
            -
            ```ruby
         | 
| 47 | 
            -
            Rhod.defaults
         | 
| 48 | 
            -
            => {:retries=>0,
         | 
| 49 | 
            -
             :backoffs=>#<Enumerator: ...>,
         | 
| 50 | 
            -
             :fallback=>nil}
         | 
| 51 | 
            -
            ```
         | 
| 17 | 
            +
            [Yes](https://news.ycombinator.com/item?id=3067434)
         | 
| 52 18 |  | 
| 53 19 | 
             
            ## Usage
         | 
| 54 20 |  | 
| 55 21 | 
             
            Rhod has a very simple API. Design your application as you would normally, then enclose network accessing portions of your code with:
         | 
| 56 22 |  | 
| 57 23 | 
             
            ```ruby
         | 
| 58 | 
            -
            Rhod. | 
| 24 | 
            +
            Rhod.with_default do
         | 
| 59 25 | 
             
              ...
         | 
| 60 26 | 
             
            end
         | 
| 61 27 | 
             
            ```
         | 
| 62 28 |  | 
| 63 | 
            -
            This implements the  | 
| 64 | 
            -
             | 
| 65 | 
            -
            Example, open a remote reasource, fail immediately if it fails:
         | 
| 66 | 
            -
             | 
| 67 | 
            -
            ```ruby
         | 
| 68 | 
            -
            require 'open-uri'
         | 
| 69 | 
            -
            require 'rhod'
         | 
| 70 | 
            -
             | 
| 71 | 
            -
            Rhod.execute { open("http://google.com").read }
         | 
| 72 | 
            -
            ```
         | 
| 73 | 
            -
             | 
| 74 | 
            -
            ## An Important note about arguments:
         | 
| 75 | 
            -
             | 
| 76 | 
            -
            Do not reach into the outer scope when using Rhod, instead you can pass arguments into your block like so:
         | 
| 77 | 
            -
             | 
| 78 | 
            -
            ```ruby
         | 
| 79 | 
            -
            address = "http://google.com"
         | 
| 80 | 
            -
             | 
| 81 | 
            -
            Rhod.execute(address) do |url|
         | 
| 82 | 
            -
               open(url).read
         | 
| 83 | 
            -
            end
         | 
| 84 | 
            -
            ```
         | 
| 85 | 
            -
             | 
| 86 | 
            -
            If you need to pass options to Rhod, pass them as the last argument:
         | 
| 87 | 
            -
             | 
| 88 | 
            -
            ```ruby
         | 
| 89 | 
            -
            # Works the same as the above, with but with retires.
         | 
| 90 | 
            -
            address = "http://google.com"
         | 
| 91 | 
            -
             | 
| 92 | 
            -
            Rhod.execute(address, :retries => 5) do |url|
         | 
| 93 | 
            -
               open(url).read
         | 
| 94 | 
            -
            end
         | 
| 95 | 
            -
            ```
         | 
| 96 | 
            -
             | 
| 97 | 
            -
            ### Retries with and without backoffs
         | 
| 29 | 
            +
            This implements the [Fail Fast](https://github.com/dinedal/rhod/wiki/Fail-Fast) scenario by default.
         | 
| 98 30 |  | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
            Code within a `Rhod::Command` block with reties in use must be _idempotent_, i.e., safe to run multiple times.
         | 
| 31 | 
            +
            Rhod allows you to fully customize how your application reacts when it can't reach a service it needs. but by default it is configured for a 'fail fast' scenario. With some configuration, Rhod can support the following failure scenarios and variations on them:
         | 
| 102 32 |  | 
| 103 | 
            -
             | 
| 33 | 
            +
              - [Fail Fast](https://github.com/dinedal/rhod/wiki/Fail-Fast)
         | 
| 34 | 
            +
              - [Retry N times before Fail](https://github.com/dinedal/rhod/wiki/Retry-N-times-before-Fail)
         | 
| 35 | 
            +
              - [Retry N times with progressive backoffs before Fail](Retry-N-times-with-progressive-backoffs-before-Fail)
         | 
| 36 | 
            +
              - [Fail Silent](https://github.com/dinedal/rhod/wiki/Fail-Silent)
         | 
| 37 | 
            +
              - [Fail w/ Fallback](https://github.com/dinedal/rhod/wiki/Fail-with-Fallback)
         | 
| 38 | 
            +
              - [Primary / Secondary ("hot spare") switch over](https://github.com/dinedal/rhod/wiki/Primary-Secondary-Switchover)
         | 
| 104 39 |  | 
| 105 | 
            -
             | 
| 106 | 
            -
            Rhod::Backoffs.default.take(5)
         | 
| 107 | 
            -
            # [0.7570232465074598, 2.403267722339301, 3.444932048942182, 4.208673319629471, 4.811984719351674]
         | 
| 108 | 
            -
            ```
         | 
| 40 | 
            +
            Check the [wiki](https://github.com/dinedal/rhod/wiki/) for more documentation.
         | 
| 109 41 |  | 
| 110 | 
            -
             | 
| 42 | 
            +
            ## Upgrading from v0.0.x to v0.1.x
         | 
| 111 43 |  | 
| 112 | 
            -
             | 
| 44 | 
            +
            The only breaking API change is that backoffs have changed in their creation, dropping `Enumerator` in favor of a simple threadsafe class. Please switch any custom backoff code subclass `Rhod::Backoffs::Backoff`.
         | 
| 113 45 |  | 
| 114 | 
            -
             | 
| 115 | 
            -
            require 'open-uri'
         | 
| 116 | 
            -
            require 'rhod'
         | 
| 46 | 
            +
            ## Installation
         | 
| 117 47 |  | 
| 118 | 
            -
            Rhod | 
| 119 | 
            -
            ```
         | 
| 48 | 
            +
            Rhod requires Ruby 1.9.2 or greater.
         | 
| 120 49 |  | 
| 121 | 
            -
             | 
| 50 | 
            +
            Add this line to your application's Gemfile:
         | 
| 122 51 |  | 
| 123 52 | 
             
            ```ruby
         | 
| 124 | 
            -
             | 
| 125 | 
            -
            require 'rhod'
         | 
| 126 | 
            -
             | 
| 127 | 
            -
                Rhod.execute(:retries => 10, :backoffs => 0.2) do
         | 
| 128 | 
            -
                  open("http://google.com").read
         | 
| 129 | 
            -
                end
         | 
| 53 | 
            +
            gem 'rhod'
         | 
| 130 54 | 
             
            ```
         | 
| 131 55 |  | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
            ```ruby
         | 
| 135 | 
            -
            require 'open-uri'
         | 
| 136 | 
            -
            require 'rhod'
         | 
| 56 | 
            +
            And then execute:
         | 
| 137 57 |  | 
| 138 | 
            -
             | 
| 139 | 
            -
              open("http://google.com").read
         | 
| 140 | 
            -
            end
         | 
| 141 | 
            -
            ```
         | 
| 58 | 
            +
                $ bundle
         | 
| 142 59 |  | 
| 143 | 
            -
             | 
| 60 | 
            +
            Or install it yourself as:
         | 
| 144 61 |  | 
| 145 | 
            -
             | 
| 146 | 
            -
            require 'open-uri'
         | 
| 147 | 
            -
            require 'rhod'
         | 
| 62 | 
            +
                $ gem install rhod
         | 
| 148 63 |  | 
| 149 | 
            -
             | 
| 150 | 
            -
              open("http://google.com").read
         | 
| 151 | 
            -
            end
         | 
| 152 | 
            -
            ```
         | 
| 64 | 
            +
            ## Configuration
         | 
| 153 65 |  | 
| 154 | 
            -
             | 
| 66 | 
            +
            To configure Rhod's defaults, just overwrite the default profile with any changes you'd like to make.
         | 
| 155 67 |  | 
| 156 68 | 
             
            ```ruby
         | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
               | 
| 162 | 
            -
             | 
| 69 | 
            +
            Rhod.create_profile(:default, retries: 10)
         | 
| 70 | 
            +
            # => {:retries=>10,
         | 
| 71 | 
            +
            #  :backoffs=>#<Rhod::Backoffs::Logarithmic:0x007f89afaeb4c0 @state=1.3>,
         | 
| 72 | 
            +
            #  :fallback=>nil,
         | 
| 73 | 
            +
            #  :pool=>
         | 
| 74 | 
            +
            #   #<ConnectionPool:0x007f89afaeb470
         | 
| 75 | 
            +
            #    @available=
         | 
| 76 | 
            +
            #     #<ConnectionPool::TimedStack:0x007f89afaeb3d0
         | 
| 77 | 
            +
            #      @mutex=#<Mutex:0x007f89afaeb358>,
         | 
| 78 | 
            +
            #      @que=[nil],
         | 
| 79 | 
            +
            #      @resource=
         | 
| 80 | 
            +
            #       #<ConditionVariable:0x007f89afaeb330
         | 
| 81 | 
            +
            #        @waiters={},
         | 
| 82 | 
            +
            #        @waiters_mutex=#<Mutex:0x007f89afaeb2e0>>>,
         | 
| 83 | 
            +
            #    @key=:"current-70114667354600",
         | 
| 84 | 
            +
            #    @size=1,
         | 
| 85 | 
            +
            #    @timeout=0>,
         | 
| 86 | 
            +
            #  :exceptions=>[Exception, StandardError]}
         | 
| 163 87 | 
             
            ```
         | 
| 164 88 |  | 
| 165 | 
            -
             | 
| 166 | 
            -
            ### Fail Silent
         | 
| 167 | 
            -
             | 
| 168 | 
            -
            In the event of a failure, Rhod falls back to a `fallback`. The most basic case is to fall back to a constant value.
         | 
| 169 | 
            -
             | 
| 170 | 
            -
            Example, open a remote reasource, if it fails return them empty string.
         | 
| 89 | 
            +
            Creating a new profile will copy from the default profile any unspecified options:
         | 
| 171 90 |  | 
| 172 91 | 
             
            ```ruby
         | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 92 | 
            +
            Rhod.create_profile(:redis,
         | 
| 93 | 
            +
              retries: 10,
         | 
| 94 | 
            +
              backoffs: :^,
         | 
| 95 | 
            +
              pool: ConnectionPool.new(size: 3, timeout: 10) { Redis.new },
         | 
| 96 | 
            +
              exceptions: [Redis::BaseError])
         | 
| 175 97 |  | 
| 176 | 
            -
            Rhod. | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 98 | 
            +
            Rhod.with_redis("1") {|r, a| r.set('test',a)}
         | 
| 99 | 
            +
            # => "OK"
         | 
| 100 | 
            +
            Rhod.with_redis {|r| r.get('test')}
         | 
| 101 | 
            +
            # => "1"
         | 
| 179 102 | 
             
            ```
         | 
| 180 103 |  | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
            If there is another network call that can be used to fetch the reasource, it's possible to use another `Rhod::Command` once a failure has occurred.
         | 
| 184 | 
            -
             | 
| 185 | 
            -
            ```ruby
         | 
| 186 | 
            -
            require 'open-uri'
         | 
| 187 | 
            -
            require 'rhod'
         | 
| 104 | 
            +
            ## Idempotence Caution
         | 
| 188 105 |  | 
| 189 | 
            -
             | 
| 190 | 
            -
              :fallback => -> {""} # couldn't get anything
         | 
| 191 | 
            -
            ) do
         | 
| 192 | 
            -
              open("https://yahoo.com").read
         | 
| 193 | 
            -
            end
         | 
| 106 | 
            +
            Code within a `Rhod::Command` block with reties in use must be _idempotent_, i.e., safe to run multiple times.
         | 
| 194 107 |  | 
| 195 | 
            -
             | 
| 196 | 
            -
              open("http://google.com").read
         | 
| 197 | 
            -
            end
         | 
| 198 | 
            -
            ```
         | 
| 108 | 
            +
            ## Passing arguments
         | 
| 199 109 |  | 
| 200 | 
            -
             | 
| 110 | 
            +
            Code within a `Rhod::Command` should avoid leaking memory and/or scope by having arguments passed to it:
         | 
| 201 111 |  | 
| 202 | 
            -
             | 
| 112 | 
            +
            ### Good use of argument passing:
         | 
| 203 113 |  | 
| 204 114 | 
             
            ```ruby
         | 
| 205 | 
            -
             | 
| 206 | 
            -
            require 'rhod'
         | 
| 207 | 
            -
             | 
| 208 | 
            -
            class SearchEngineHTML
         | 
| 209 | 
            -
              attr_accessor :secondary
         | 
| 210 | 
            -
             | 
| 211 | 
            -
              def fetch
         | 
| 212 | 
            -
                url = !@secondary ? "http://google.com" : "https://yahoo.com"
         | 
| 213 | 
            -
             | 
| 214 | 
            -
                Rhod.execute(url, :fallback => Proc.new { @secondary = !@secondary; fetch }) do |url|
         | 
| 215 | 
            -
                  open(url).read
         | 
| 216 | 
            -
                end
         | 
| 217 | 
            -
              end
         | 
| 218 | 
            -
            end
         | 
| 219 | 
            -
             | 
| 220 | 
            -
            search_engine_html = SearchEngineHTML.new
         | 
| 221 | 
            -
             | 
| 222 | 
            -
            search_engine_html.fetch
         | 
| 115 | 
            +
            Rhod.with_default("http://google.com") {|url| open(url).read}
         | 
| 223 116 | 
             
            ```
         | 
| 224 117 |  | 
| 225 118 | 
             
            ## Connection Pools
         | 
| @@ -231,9 +124,12 @@ require 'rhod' | |
| 231 124 | 
             
            require 'redis'
         | 
| 232 125 | 
             
            require 'connection_pool'
         | 
| 233 126 |  | 
| 234 | 
            -
            Rhod. | 
| 127 | 
            +
            Rhod.create_profile(:redis,
         | 
| 128 | 
            +
              pool: ConnectionPool.new(size: 3, timeout: 5) { Redis.new }
         | 
| 129 | 
            +
              )
         | 
| 235 130 |  | 
| 236 | 
            -
            Rhod. | 
| 131 | 
            +
            Rhod.with_redis {|redis| redis.set("foo", "bar") }
         | 
| 132 | 
            +
            # => "OK"
         | 
| 237 133 | 
             
            ```
         | 
| 238 134 |  | 
| 239 135 | 
             
            The connection is always the first argument passed into the block, the other arguments are passed in their original order after.
         | 
| @@ -241,7 +137,7 @@ The connection is always the first argument passed into the block, the other arg | |
| 241 137 | 
             
            ```ruby
         | 
| 242 138 | 
             
            key   = "foo"
         | 
| 243 139 | 
             
            value = "bar"
         | 
| 244 | 
            -
            Rhod. | 
| 140 | 
            +
            Rhod.with_redis(key, value) {|redis, k, v| redis.set(k, v) }
         | 
| 245 141 | 
             
            ```
         | 
| 246 142 |  | 
| 247 143 | 
             
            ## Contributing
         | 
    
        data/lib/rhod.rb
    CHANGED
    
    | @@ -1,27 +1,17 @@ | |
| 1 | 
            +
            require 'connection_pool'
         | 
| 1 2 | 
             
            require_relative "rhod/version"
         | 
| 2 3 | 
             
            require_relative "rhod/backoffs"
         | 
| 3 4 | 
             
            require_relative "rhod/command"
         | 
| 4 | 
            -
             | 
| 5 | 
            +
            require_relative "rhod/profile"
         | 
| 5 6 |  | 
| 6 7 | 
             
            module Rhod
         | 
| 8 | 
            +
             | 
| 7 9 | 
             
              def self.execute(*args, &block)
         | 
| 8 | 
            -
                Rhod | 
| 10 | 
            +
                Rhod.with_default(*args, &block)
         | 
| 9 11 | 
             
              end
         | 
| 10 12 |  | 
| 11 | 
            -
               | 
| 12 | 
            -
                 | 
| 13 | 
            -
             | 
| 14 | 
            -
                attr_accessor :connection_pools
         | 
| 13 | 
            +
              def self.create_profile(name, options={})
         | 
| 14 | 
            +
                Rhod::Profile.new(name, options)
         | 
| 15 15 | 
             
              end
         | 
| 16 16 |  | 
| 17 | 
            -
              self.defaults = {
         | 
| 18 | 
            -
                retries: 0,
         | 
| 19 | 
            -
                backoffs: Rhod::Backoffs.default,
         | 
| 20 | 
            -
                fallback: nil,
         | 
| 21 | 
            -
              }
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              self.connection_pools = {
         | 
| 24 | 
            -
                default: ConnectionPool.new(size: 1, timeout: 0) { nil }
         | 
| 25 | 
            -
              }
         | 
| 26 | 
            -
             | 
| 27 17 | 
             
            end
         | 
    
        data/lib/rhod/backoffs.rb
    CHANGED
    
    | @@ -3,76 +3,40 @@ module Rhod::Backoffs | |
| 3 3 | 
             
              extend self
         | 
| 4 4 |  | 
| 5 5 | 
             
              def backoff_sugar_to_enumerator(backoff)
         | 
| 6 | 
            -
                if backoff.is_a?( | 
| 6 | 
            +
                if backoff.is_a?(Rhod::Backoffs::Backoff)
         | 
| 7 7 | 
             
                  backoff
         | 
| 8 8 | 
             
                elsif backoff.is_a?(Numeric)
         | 
| 9 | 
            -
                   | 
| 9 | 
            +
                  Rhod::Backoffs::Constant.new(backoff)
         | 
| 10 10 | 
             
                elsif backoff.is_a?(Range)
         | 
| 11 | 
            -
                   | 
| 11 | 
            +
                  Rhod::Backoffs::Random.new(backoff)
         | 
| 12 12 | 
             
                elsif backoff.is_a?(String)
         | 
| 13 13 | 
             
                  n = (backoff[1..-1].to_f)
         | 
| 14 14 | 
             
                  case backoff[0]
         | 
| 15 15 | 
             
                  when "^"
         | 
| 16 | 
            -
                     | 
| 16 | 
            +
                    Rhod::Backoffs::Exponential.new(n)
         | 
| 17 17 | 
             
                  when "l"
         | 
| 18 | 
            -
                     | 
| 18 | 
            +
                    Rhod::Backoffs::Logarithmic.new(n)
         | 
| 19 19 | 
             
                  when "r"
         | 
| 20 20 | 
             
                    min = backoff[1..-1].split("..")[0].to_f
         | 
| 21 21 | 
             
                    max = backoff[1..-1].split("..")[1].to_f
         | 
| 22 | 
            -
                     | 
| 22 | 
            +
                    Rhod::Backoffs::Random.new((min..max))
         | 
| 23 23 | 
             
                  end
         | 
| 24 24 | 
             
                elsif backoff.is_a?(Symbol)
         | 
| 25 25 | 
             
                  case backoff
         | 
| 26 26 | 
             
                  when :^
         | 
| 27 | 
            -
                     | 
| 27 | 
            +
                    Rhod::Backoffs::Exponential.new(0)
         | 
| 28 28 | 
             
                  when :l
         | 
| 29 | 
            -
                     | 
| 29 | 
            +
                    Rhod::Backoffs::Logarithmic.new(1.3)
         | 
| 30 30 | 
             
                  when :r
         | 
| 31 | 
            -
                     | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
                end
         | 
| 34 | 
            -
              end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
              # Returns a generator of a expoentially increasing series starting at n
         | 
| 37 | 
            -
              def expoential_backoffs(n=1)
         | 
| 38 | 
            -
                Enumerator.new do |yielder|
         | 
| 39 | 
            -
                  x = (n - 1)
         | 
| 40 | 
            -
                  loop do
         | 
| 41 | 
            -
                    x += 1
         | 
| 42 | 
            -
                    yielder << 2.0**x
         | 
| 43 | 
            -
                  end
         | 
| 44 | 
            -
                end
         | 
| 45 | 
            -
              end
         | 
| 46 | 
            -
             | 
| 47 | 
            -
              # Returns a generator of a logarithmicly increasing series starting at n
         | 
| 48 | 
            -
              def logarithmic_backoffs(n=0.3)
         | 
| 49 | 
            -
                Enumerator.new do |yielder|
         | 
| 50 | 
            -
                  x = n
         | 
| 51 | 
            -
                  loop do
         | 
| 52 | 
            -
                    x += 1
         | 
| 53 | 
            -
                    yielder << Math.log2(x**2)
         | 
| 54 | 
            -
                  end
         | 
| 55 | 
            -
                end
         | 
| 56 | 
            -
              end
         | 
| 57 | 
            -
              alias default logarithmic_backoffs
         | 
| 58 | 
            -
             | 
| 59 | 
            -
              # Always the same backoff
         | 
| 60 | 
            -
              def constant_backoff(n)
         | 
| 61 | 
            -
                Enumerator.new do |yielder|
         | 
| 62 | 
            -
                  loop do
         | 
| 63 | 
            -
                    yielder << n
         | 
| 64 | 
            -
                  end
         | 
| 65 | 
            -
                end
         | 
| 66 | 
            -
              end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
              # Returns a generator of random numbers falling inside of a range
         | 
| 69 | 
            -
              def random_backoffs(range=(0..10))
         | 
| 70 | 
            -
                float_range = (range.min.to_f..range.max.to_f)
         | 
| 71 | 
            -
                Enumerator.new do |yielder|
         | 
| 72 | 
            -
                  loop do
         | 
| 73 | 
            -
                    yielder << rand(float_range)
         | 
| 31 | 
            +
                    Rhod::Backoffs::Random.new(0..10)
         | 
| 74 32 | 
             
                  end
         | 
| 75 33 | 
             
                end
         | 
| 76 34 | 
             
              end
         | 
| 77 35 |  | 
| 78 36 | 
             
            end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            require_relative 'backoffs/backoff.rb'
         | 
| 39 | 
            +
            require_relative 'backoffs/constant.rb'
         | 
| 40 | 
            +
            require_relative 'backoffs/exponential.rb'
         | 
| 41 | 
            +
            require_relative 'backoffs/logarithmic.rb'
         | 
| 42 | 
            +
            require_relative 'backoffs/random.rb'
         | 
    
        data/lib/rhod/command.rb
    CHANGED
    
    | @@ -10,17 +10,19 @@ class Rhod::Command | |
| 10 10 | 
             
                @request = block
         | 
| 11 11 |  | 
| 12 12 | 
             
                @retries = opts[:retries]
         | 
| 13 | 
            -
                @retries ||=  | 
| 13 | 
            +
                @retries ||= 0
         | 
| 14 14 | 
             
                @attempts = 0
         | 
| 15 15 |  | 
| 16 16 | 
             
                @backoffs = Rhod::Backoffs.backoff_sugar_to_enumerator(opts[:backoffs])
         | 
| 17 | 
            -
                @backoffs ||= Rhod. | 
| 17 | 
            +
                @backoffs ||= Rhod::Backoffs::Logarithmic.new(1.3)
         | 
| 18 18 |  | 
| 19 19 | 
             
                @fallback = opts[:fallback]
         | 
| 20 | 
            -
                @fallback ||= Rhod.defaults[:fallback]
         | 
| 21 20 |  | 
| 22 | 
            -
                @pool =  | 
| 23 | 
            -
                @pool ||=  | 
| 21 | 
            +
                @pool = opts[:pool]
         | 
| 22 | 
            +
                @pool ||= ConnectionPool.new(size: 1, timeout: 0) { nil }
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                @exceptions = opts[:exceptions]
         | 
| 25 | 
            +
                @exceptions ||= EXCEPTIONS
         | 
| 24 26 | 
             
              end
         | 
| 25 27 |  | 
| 26 28 | 
             
              ### Class methods
         | 
| @@ -40,7 +42,7 @@ class Rhod::Command | |
| 40 42 |  | 
| 41 43 | 
             
                    @request.call(*@args)
         | 
| 42 44 | 
             
                  end
         | 
| 43 | 
            -
                rescue  | 
| 45 | 
            +
                rescue *@exceptions
         | 
| 44 46 | 
             
                  @attempts += 1
         | 
| 45 47 | 
             
                  if @attempts <= @retries
         | 
| 46 48 | 
             
                    sleep(@backoffs.next)
         | 
    
        data/lib/rhod/profile.rb
    ADDED
    
    | @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            class Rhod::Profile < Hash
         | 
| 2 | 
            +
              @@profiles = {}
         | 
| 3 | 
            +
             | 
| 4 | 
            +
              def initialize(name, options={})
         | 
| 5 | 
            +
                # When creating new profiles, copy from the global default, incase it was customized.
         | 
| 6 | 
            +
                if @@profiles[:default]
         | 
| 7 | 
            +
                  default = @@profiles[:default].dup
         | 
| 8 | 
            +
                else
         | 
| 9 | 
            +
                  default = {}
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                default.each {|k,v| self[k] = v }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                options.each {|k,v| self[k] = v }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                # Syntax sugar: named .with_#{profile} methods on this class and the module
         | 
| 17 | 
            +
                @@profiles[name] = self
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                self.class.__send__(:define_method, :"with_#{name}") do |*args, &block|
         | 
| 20 | 
            +
                  Rhod::Command.execute(*args, @@profiles[name], &block)
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                Rhod.class.__send__(:define_method, :"with_#{name}") do |*args, &block|
         | 
| 24 | 
            +
                  Rhod::Command.execute(*args, @@profiles[name], &block)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                self
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
            end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Rhod::Profile.new(:default,
         | 
| 32 | 
            +
              retries: 0,
         | 
| 33 | 
            +
              backoffs: Rhod::Backoffs::Logarithmic.new(1.3),
         | 
| 34 | 
            +
              fallback: nil,
         | 
| 35 | 
            +
              pool: ConnectionPool.new(size: 1, timeout: 0) { nil },
         | 
| 36 | 
            +
              exceptions: [Exception, StandardError],
         | 
| 37 | 
            +
            )
         | 
    
        data/lib/rhod/version.rb
    CHANGED
    
    
    
        data/test/test_backoffs.rb
    CHANGED
    
    | @@ -4,7 +4,7 @@ require File.expand_path(File.dirname(__FILE__) + '/helper') | |
| 4 4 | 
             
            describe Rhod::Backoffs do
         | 
| 5 5 | 
             
              describe "backoff_sugar_to_enumerator" do
         | 
| 6 6 | 
             
                it "returns enumerators as is" do
         | 
| 7 | 
            -
                  e = Rhod::Backoffs. | 
| 7 | 
            +
                  e = Rhod::Backoffs::Constant.new(0)
         | 
| 8 8 | 
             
                  Rhod::Backoffs.backoff_sugar_to_enumerator(e).must_equal e
         | 
| 9 9 | 
             
                end
         | 
| 10 10 |  | 
| @@ -14,35 +14,59 @@ describe Rhod::Backoffs do | |
| 14 14 | 
             
                end
         | 
| 15 15 |  | 
| 16 16 | 
             
                it "generates expoential backoffs with '^' syntax" do
         | 
| 17 | 
            -
                   | 
| 17 | 
            +
                  results = []
         | 
| 18 | 
            +
                  eb = Rhod::Backoffs.backoff_sugar_to_enumerator("^2.0")
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  3.times { results << eb.next }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  results.must_equal [4.0, 8.0, 16.0]
         | 
| 18 23 | 
             
                end
         | 
| 19 24 |  | 
| 20 25 | 
             
                it "generates logarithmic backoffs with 'l' syntax" do
         | 
| 21 | 
            -
                   | 
| 22 | 
            -
             | 
| 26 | 
            +
                  results = []
         | 
| 27 | 
            +
                  lb = Rhod::Backoffs.backoff_sugar_to_enumerator("l2.0")
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  3.times { results << lb.next }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  results.must_equal [2.0, 3.169925001442312, 4.0]
         | 
| 23 32 | 
             
                end
         | 
| 24 33 |  | 
| 25 34 | 
             
                it "generates random backoffs with 'r' syntax" do
         | 
| 26 | 
            -
                   | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
                   | 
| 35 | 
            +
                  results = []
         | 
| 36 | 
            +
                  rb = Rhod::Backoffs.backoff_sugar_to_enumerator("r1..2")
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  10.times { results << rb.next }
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  results.min.floor.must_equal 1
         | 
| 41 | 
            +
                  results.max.ceil.must_equal 2
         | 
| 30 42 | 
             
                end
         | 
| 31 43 |  | 
| 32 44 | 
             
                it "generates expoential backoffs with :^ syntax" do
         | 
| 33 | 
            -
                   | 
| 45 | 
            +
                  results = []
         | 
| 46 | 
            +
                  eb = Rhod::Backoffs.backoff_sugar_to_enumerator(:^)
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  3.times { results << eb.next }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  results.must_equal [1.0, 2.0, 4.0]
         | 
| 34 51 | 
             
                end
         | 
| 35 52 |  | 
| 36 53 | 
             
                it "generates logarithmic backoffs with :l syntax" do
         | 
| 37 | 
            -
                   | 
| 38 | 
            -
             | 
| 54 | 
            +
                  results = []
         | 
| 55 | 
            +
                  lb = Rhod::Backoffs.backoff_sugar_to_enumerator(:l)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  3.times { results << lb.next }
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  results.must_equal [0.7570232465074598, 2.403267722339301, 3.444932048942182]
         | 
| 39 60 | 
             
                end
         | 
| 40 61 |  | 
| 41 62 | 
             
                it "generates random backoffs with :r syntax" do
         | 
| 42 | 
            -
                   | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
                   | 
| 63 | 
            +
                  results = []
         | 
| 64 | 
            +
                  rb = Rhod::Backoffs.backoff_sugar_to_enumerator(:r)
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  100.times { results << rb.next }
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  results.min.floor.must_equal 0
         | 
| 69 | 
            +
                  results.max.ceil.must_equal 10
         | 
| 46 70 | 
             
                end
         | 
| 47 71 | 
             
              end
         | 
| 48 72 | 
             
            end
         | 
    
        data/test/test_command.rb
    CHANGED
    
    | @@ -34,7 +34,7 @@ describe Rhod::Command do | |
| 34 34 | 
             
                      backoff = MiniTest::Mock.new
         | 
| 35 35 | 
             
                      backoff.expect(:next, 0)
         | 
| 36 36 |  | 
| 37 | 
            -
                      Rhod::Backoffs.stub(: | 
| 37 | 
            +
                      Rhod::Backoffs::Constant.stub(:new, backoff) do
         | 
| 38 38 | 
             
                        begin
         | 
| 39 39 | 
             
                          Rhod::Command.new(:retries => 1, :backoffs => 0) do
         | 
| 40 40 | 
             
                            val += 1
         | 
| @@ -75,14 +75,14 @@ describe Rhod::Command do | |
| 75 75 |  | 
| 76 76 | 
             
                describe "with connection pools" do
         | 
| 77 77 | 
             
                  it "uses the provided pool" do
         | 
| 78 | 
            -
                     | 
| 78 | 
            +
                    pool = ConnectionPool.new(size: 1, timeout: 0) { :conn }
         | 
| 79 79 | 
             
                    Rhod::Command.new {|a| a}.execute.must_equal nil
         | 
| 80 | 
            -
                    Rhod::Command.new(pool:  | 
| 80 | 
            +
                    Rhod::Command.new(pool: pool) {|a| a}.execute.must_equal :conn
         | 
| 81 81 | 
             
                  end
         | 
| 82 82 |  | 
| 83 83 | 
             
                  it "correctly handles arguements" do
         | 
| 84 | 
            -
                     | 
| 85 | 
            -
                    Rhod::Command.new(1, pool:  | 
| 84 | 
            +
                    pool = ConnectionPool.new(size: 1, timeout: 0) { :conn }
         | 
| 85 | 
            +
                    Rhod::Command.new(1, pool: pool) {|a, b| [a,b]}.execute.must_equal [:conn, 1]
         | 
| 86 86 | 
             
                  end
         | 
| 87 87 | 
             
                end
         | 
| 88 88 |  | 
| @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            require 'minitest/autorun'
         | 
| 2 | 
            +
            require File.expand_path(File.dirname(__FILE__) + '/helper')
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Rhod::Profile do
         | 
| 5 | 
            +
              describe "defaults" do
         | 
| 6 | 
            +
                it "always has them" do
         | 
| 7 | 
            +
                  assert_instance_of Rhod::Profile, Rhod::Profile.class_variable_get(:@@profiles)[:default]
         | 
| 8 | 
            +
                  defined?(Rhod.with_default).must_equal "method"
         | 
| 9 | 
            +
                  defined?(Rhod::Profile.with_default).must_equal "method"
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              describe "self.new" do
         | 
| 14 | 
            +
                it "copies missing attributes from the defaults" do
         | 
| 15 | 
            +
                  Rhod::Profile.new(:test1, retries: 55 )
         | 
| 16 | 
            +
                  Rhod::Profile.class_variable_get(:@@profiles)[:test1][:retries].must_equal 55
         | 
| 17 | 
            +
                  Rhod::Profile.class_variable_get(:@@profiles)[:test1][:exceptions].must_equal [Exception, StandardError]
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                it "creates a new method on itself and the module for new profiles" do
         | 
| 21 | 
            +
                  defined?(Rhod.with_test2).must_equal nil
         | 
| 22 | 
            +
                  defined?(Rhod::Profile.with_test2).must_equal nil
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  Rhod::Profile.new(:test2)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  defined?(Rhod.with_test2).must_equal "method"
         | 
| 27 | 
            +
                  defined?(Rhod::Profile.with_test2).must_equal "method"
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: rhod
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 0.1.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Paul Bergeron
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2013- | 
| 11 | 
            +
            date: 2013-05-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -103,18 +103,26 @@ extra_rdoc_files: [] | |
| 103 103 | 
             
            files:
         | 
| 104 104 | 
             
            - .gitignore
         | 
| 105 105 | 
             
            - .pryrc
         | 
| 106 | 
            +
            - Changes.md
         | 
| 106 107 | 
             
            - Gemfile
         | 
| 107 108 | 
             
            - LICENSE.txt
         | 
| 108 109 | 
             
            - README.md
         | 
| 109 110 | 
             
            - Rakefile
         | 
| 110 111 | 
             
            - lib/rhod.rb
         | 
| 111 112 | 
             
            - lib/rhod/backoffs.rb
         | 
| 113 | 
            +
            - lib/rhod/backoffs/backoff.rb
         | 
| 114 | 
            +
            - lib/rhod/backoffs/constant.rb
         | 
| 115 | 
            +
            - lib/rhod/backoffs/exponential.rb
         | 
| 116 | 
            +
            - lib/rhod/backoffs/logarithmic.rb
         | 
| 117 | 
            +
            - lib/rhod/backoffs/random.rb
         | 
| 112 118 | 
             
            - lib/rhod/command.rb
         | 
| 119 | 
            +
            - lib/rhod/profile.rb
         | 
| 113 120 | 
             
            - lib/rhod/version.rb
         | 
| 114 121 | 
             
            - rhod.gemspec
         | 
| 115 122 | 
             
            - test/helper.rb
         | 
| 116 123 | 
             
            - test/test_backoffs.rb
         | 
| 117 124 | 
             
            - test/test_command.rb
         | 
| 125 | 
            +
            - test/test_profile.rb
         | 
| 118 126 | 
             
            homepage: https://github.com/dinedal/rhod
         | 
| 119 127 | 
             
            licenses:
         | 
| 120 128 | 
             
            - MIT
         | 
| @@ -143,3 +151,4 @@ test_files: | |
| 143 151 | 
             
            - test/helper.rb
         | 
| 144 152 | 
             
            - test/test_backoffs.rb
         | 
| 145 153 | 
             
            - test/test_command.rb
         | 
| 154 | 
            +
            - test/test_profile.rb
         |