frugal_timeout 0.0.8 → 0.0.9
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/frugal_timeout.rb +77 -45
- metadata +2 -18
    
        data/lib/frugal_timeout.rb
    CHANGED
    
    | @@ -2,7 +2,6 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'hitimes'
         | 
| 4 4 | 
             
            require 'monitor'
         | 
| 5 | 
            -
            require 'null_object'
         | 
| 6 5 | 
             
            require 'thread'
         | 
| 7 6 | 
             
            require 'timeout'
         | 
| 8 7 |  | 
| @@ -55,11 +54,11 @@ module FrugalTimeout | |
| 55 54 | 
             
                include Comparable
         | 
| 56 55 | 
             
                @@mutex = Mutex.new
         | 
| 57 56 |  | 
| 58 | 
            -
                attr_reader :at, : | 
| 57 | 
            +
                attr_reader :at, :klass, :thread
         | 
| 59 58 |  | 
| 60 59 | 
             
                def initialize thread, at, klass
         | 
| 61 60 | 
             
                  @thread, @at, @klass = thread, at, klass
         | 
| 62 | 
            -
                  @defused | 
| 61 | 
            +
                  @defused = false
         | 
| 63 62 | 
             
                end
         | 
| 64 63 |  | 
| 65 64 | 
             
                def <=>(other)
         | 
| @@ -75,12 +74,9 @@ module FrugalTimeout | |
| 75 74 | 
             
                  @@mutex.synchronize { @defused }
         | 
| 76 75 | 
             
                end
         | 
| 77 76 |  | 
| 78 | 
            -
                def enforceTimeout | 
| 77 | 
            +
                def enforceTimeout
         | 
| 79 78 | 
             
                  @@mutex.synchronize {
         | 
| 80 | 
            -
            	 | 
| 81 | 
            -
             | 
| 82 | 
            -
            	filter[@thread] = true
         | 
| 83 | 
            -
            	@thread.raise @exception, 'execution expired'
         | 
| 79 | 
            +
            	@thread.raise @klass, 'execution expired' unless @defused
         | 
| 84 80 | 
             
                  }
         | 
| 85 81 | 
             
                end
         | 
| 86 82 | 
             
              end
         | 
| @@ -95,6 +91,11 @@ module FrugalTimeout | |
| 95 91 | 
             
                  @onNewNearestRequest, @requests = proc {}, SortedQueue.new
         | 
| 96 92 | 
             
                end
         | 
| 97 93 |  | 
| 94 | 
            +
                def defuse_thread! thread
         | 
| 95 | 
            +
                  @requests.each { |r| r.defuse! if r.thread == thread }
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
                private :defuse_thread!
         | 
| 98 | 
            +
             | 
| 98 99 | 
             
                def onNewNearestRequest &b
         | 
| 99 100 | 
             
                  @onNewNearestRequest = b
         | 
| 100 101 | 
             
                end
         | 
| @@ -102,13 +103,19 @@ module FrugalTimeout | |
| 102 103 | 
             
                # Purge and enforce expired timeouts. Only enforce once for each thread,
         | 
| 103 104 | 
             
                # even if multiple timeouts for that thread expire at once.
         | 
| 104 105 | 
             
                def purgeExpired
         | 
| 105 | 
            -
                  filter, now = {}, MonotonicTime.now
         | 
| 106 | 
            -
                  @requests.reject_and_get! { |r| r.at <= now }.each { |r|
         | 
| 107 | 
            -
            	r.enforceTimeout filter
         | 
| 108 | 
            -
                  }
         | 
| 109 | 
            -
             | 
| 106 | 
            +
                  expiredRequests, filter, now = nil, {}, MonotonicTime.now
         | 
| 110 107 | 
             
                  @requests.synchronize {
         | 
| 111 | 
            -
            	@ | 
| 108 | 
            +
            	@requests.reject_and_get! { |r| r.at <= now }.each { |r|
         | 
| 109 | 
            +
            	  next if filter[r.thread]
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            	  r.enforceTimeout
         | 
| 112 | 
            +
            	  defuse_thread! r.thread
         | 
| 113 | 
            +
            	  filter[r.thread] = true
         | 
| 114 | 
            +
            	}
         | 
| 115 | 
            +
             | 
| 116 | 
            +
            	# It's necessary to call onNewNearestRequest inside synchronize as other
         | 
| 117 | 
            +
            	# threads may #queue requests.
         | 
| 118 | 
            +
            	@onNewNearestRequest.call @requests.first unless @requests.empty?
         | 
| 112 119 | 
             
                  }
         | 
| 113 120 | 
             
                end
         | 
| 114 121 |  | 
| @@ -123,56 +130,73 @@ module FrugalTimeout | |
| 123 130 | 
             
              end
         | 
| 124 131 |  | 
| 125 132 | 
             
              # {{{1 SleeperNotifier
         | 
| 133 | 
            +
              # Executes callback when a request expires.
         | 
| 134 | 
            +
              # 1. Set callback to execute with #onExpiry=.
         | 
| 135 | 
            +
              # 2. Set expiry time with #expireAt.
         | 
| 136 | 
            +
              # 3. After the expiry time comes, execute the callback.
         | 
| 137 | 
            +
              #
         | 
| 138 | 
            +
              # It's possible to set a new expiry time before the time set previously
         | 
| 139 | 
            +
              # expires. In this case, processing of the old request stops and the new
         | 
| 140 | 
            +
              # request processing starts.
         | 
| 126 141 | 
             
              class SleeperNotifier # :nodoc:
         | 
| 127 142 | 
             
                include MonitorMixin
         | 
| 128 143 |  | 
| 144 | 
            +
                DO_NOTHING = proc {}
         | 
| 145 | 
            +
             | 
| 129 146 | 
             
                def initialize
         | 
| 130 147 | 
             
                  super()
         | 
| 131 | 
            -
                  @condVar, @ | 
| 148 | 
            +
                  @condVar, @expireAt, @onExpiry = new_cond, nil, DO_NOTHING
         | 
| 132 149 |  | 
| 133 150 | 
             
                  @thread = Thread.new {
         | 
| 134 151 | 
             
            	loop {
         | 
| 135 | 
            -
            	  @onExpiry.call if synchronize {
         | 
| 136 | 
            -
            	     | 
| 137 | 
            -
            	     | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 140 | 
            -
            	      @request = nil
         | 
| 141 | 
            -
            	      true
         | 
| 152 | 
            +
            	  synchronize { @onExpiry }.call if synchronize {
         | 
| 153 | 
            +
            	    # Sleep forever until a request comes in.
         | 
| 154 | 
            +
            	    unless @expireAt
         | 
| 155 | 
            +
            	      wait
         | 
| 156 | 
            +
            	      next
         | 
| 142 157 | 
             
            	    end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
            	    timeLeft = calcTimeLeft
         | 
| 160 | 
            +
            	    disposeOfRequest
         | 
| 161 | 
            +
            	    elapsedTime = MonotonicTime.measure { wait timeLeft }
         | 
| 162 | 
            +
             | 
| 163 | 
            +
            	    elapsedTime >= timeLeft
         | 
| 143 164 | 
             
            	  }
         | 
| 144 165 | 
             
            	}
         | 
| 145 166 | 
             
                  }
         | 
| 146 167 | 
             
                  ObjectSpace.define_finalizer self, proc { @thread.kill }
         | 
| 147 168 | 
             
                end
         | 
| 148 169 |  | 
| 149 | 
            -
                def  | 
| 170 | 
            +
                def onExpiry &b
         | 
| 171 | 
            +
                  synchronize { @onExpiry = b || DO_NOTHING }
         | 
| 172 | 
            +
                end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                def expireAt time
         | 
| 150 175 | 
             
                  synchronize {
         | 
| 151 | 
            -
            	 | 
| 176 | 
            +
            	@expireAt = time
         | 
| 177 | 
            +
            	signalThread
         | 
| 178 | 
            +
                  }
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                private
         | 
| 152 182 |  | 
| 153 | 
            -
             | 
| 183 | 
            +
                def calcTimeLeft
         | 
| 184 | 
            +
                  synchronize {
         | 
| 185 | 
            +
            	delay = @expireAt - MonotonicTime.now
         | 
| 154 186 | 
             
            	delay < 0 ? 0 : delay
         | 
| 155 187 | 
             
                  }
         | 
| 156 188 | 
             
                end
         | 
| 157 | 
            -
                private :latestDelay
         | 
| 158 189 |  | 
| 159 | 
            -
                def  | 
| 160 | 
            -
                  @ | 
| 190 | 
            +
                def disposeOfRequest
         | 
| 191 | 
            +
                  @expireAt = nil
         | 
| 161 192 | 
             
                end
         | 
| 162 | 
            -
                private :notify
         | 
| 163 193 |  | 
| 164 | 
            -
                def  | 
| 165 | 
            -
                  @ | 
| 194 | 
            +
                def signalThread
         | 
| 195 | 
            +
                  @condVar.signal
         | 
| 166 196 | 
             
                end
         | 
| 167 197 |  | 
| 168 | 
            -
                 | 
| 169 | 
            -
             | 
| 170 | 
            -
                # 3. The latest passed request expires and @onExpiry is called. Goto 1.
         | 
| 171 | 
            -
                def sleepUntilExpires request
         | 
| 172 | 
            -
                  synchronize {
         | 
| 173 | 
            -
            	@request = request
         | 
| 174 | 
            -
            	notify
         | 
| 175 | 
            -
                  }
         | 
| 198 | 
            +
                def wait sec=nil
         | 
| 199 | 
            +
                  @condVar.wait sec
         | 
| 176 200 | 
             
                end
         | 
| 177 201 | 
             
              end
         | 
| 178 202 |  | 
| @@ -185,6 +209,10 @@ module FrugalTimeout | |
| 185 209 | 
             
                  @array, @unsorted = storage, false
         | 
| 186 210 | 
             
                end
         | 
| 187 211 |  | 
| 212 | 
            +
                def each &b
         | 
| 213 | 
            +
                  synchronize { @array.each &b }
         | 
| 214 | 
            +
                end
         | 
| 215 | 
            +
             | 
| 188 216 | 
             
                def empty?
         | 
| 189 217 | 
             
                  synchronize { @array.empty? }
         | 
| 190 218 | 
             
                end
         | 
| @@ -249,7 +277,7 @@ module FrugalTimeout | |
| 249 277 | 
             
              @requestQueue = RequestQueue.new
         | 
| 250 278 | 
             
              sleeper = SleeperNotifier.new
         | 
| 251 279 | 
             
              @requestQueue.onNewNearestRequest { |request|
         | 
| 252 | 
            -
                sleeper. | 
| 280 | 
            +
                sleeper.expireAt request.at
         | 
| 253 281 | 
             
              }
         | 
| 254 282 | 
             
              sleeper.onExpiry { @requestQueue.purgeExpired }
         | 
| 255 283 |  | 
| @@ -263,18 +291,22 @@ module FrugalTimeout | |
| 263 291 | 
             
                   end'
         | 
| 264 292 | 
             
              end
         | 
| 265 293 |  | 
| 294 | 
            +
              def self.on_ensure &b # :nodoc:
         | 
| 295 | 
            +
                @onEnsure = b
         | 
| 296 | 
            +
              end
         | 
| 297 | 
            +
             | 
| 266 298 | 
             
              # Same as Timeout.timeout()
         | 
| 267 299 | 
             
              def self.timeout sec, klass=Error
         | 
| 268 300 | 
             
                return yield sec if sec.nil? || sec <= 0
         | 
| 269 301 |  | 
| 270 | 
            -
                 | 
| 302 | 
            +
                innerException = Class.new Timeout::ExitException
         | 
| 303 | 
            +
                request = @requestQueue.queue(sec, innerException)
         | 
| 271 304 | 
             
                begin
         | 
| 272 305 | 
             
                  yield sec
         | 
| 273 | 
            -
                rescue  | 
| 274 | 
            -
                  raise  | 
| 275 | 
            -
             | 
| 276 | 
            -
                  raise request.klass, e.message, e.backtrace
         | 
| 306 | 
            +
                rescue innerException => e
         | 
| 307 | 
            +
                  raise klass, e.message, e.backtrace
         | 
| 277 308 | 
             
                ensure
         | 
| 309 | 
            +
                  @onEnsure.call if @onEnsure
         | 
| 278 310 | 
             
                  request.defuse!
         | 
| 279 311 | 
             
                end
         | 
| 280 312 | 
             
              end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: frugal_timeout
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.9
         | 
| 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: 2014-01-01 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: rspec
         | 
| @@ -43,22 +43,6 @@ dependencies: | |
| 43 43 | 
             
                - - ~>
         | 
| 44 44 | 
             
                  - !ruby/object:Gem::Version
         | 
| 45 45 | 
             
                    version: '1.2'
         | 
| 46 | 
            -
            - !ruby/object:Gem::Dependency
         | 
| 47 | 
            -
              name: null_object
         | 
| 48 | 
            -
              requirement: !ruby/object:Gem::Requirement
         | 
| 49 | 
            -
                none: false
         | 
| 50 | 
            -
                requirements:
         | 
| 51 | 
            -
                - - ~>
         | 
| 52 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 53 | 
            -
                    version: '0.0'
         | 
| 54 | 
            -
              type: :runtime
         | 
| 55 | 
            -
              prerelease: false
         | 
| 56 | 
            -
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 57 | 
            -
                none: false
         | 
| 58 | 
            -
                requirements:
         | 
| 59 | 
            -
                - - ~>
         | 
| 60 | 
            -
                  - !ruby/object:Gem::Version
         | 
| 61 | 
            -
                    version: '0.0'
         | 
| 62 46 | 
             
            description: Timeout.timeout replacement that uses only 1 thread
         | 
| 63 47 | 
             
            email: ledestin@gmail.com
         | 
| 64 48 | 
             
            executables: []
         |