[DRE-commits] [ruby-timers] 01/05: Imported Upstream version 4.0.1

Praveen Arimbrathodiyil praveen at moszumanska.debian.org
Tue May 12 06:46:34 UTC 2015


This is an automated email from the git hooks/post-receive script.

praveen pushed a commit to branch master
in repository ruby-timers.

commit 8f2caf4e2c140d9bf993242b2631a0dd6811f3d1
Author: Praveen Arimbrathodiyil <praveen at debian.org>
Date:   Tue May 12 12:03:23 2015 +0530

    Imported Upstream version 4.0.1
---
 .coveralls.yml           |   1 +
 .gitignore               |  17 ++++
 .rspec                   |   3 +-
 .travis.yml              |  17 ++++
 AUTHORS.md               |  15 +++
 CHANGES.md               |  30 ++++++
 Gemfile                  |   6 ++
 LICENSE                  |   5 +-
 README.md                |  49 ++++++---
 lib/timers.rb            | 123 +----------------------
 lib/timers/events.rb     | 115 +++++++++++++++++++++
 lib/timers/group.rb      | 118 ++++++++++++++++++++++
 lib/timers/timer.rb      | 128 ++++++++++++++++++++++++
 lib/timers/version.rb    |   4 +-
 lib/timers/wait.rb       |  48 +++++++++
 spec/cancel_spec.rb      |  46 +++++++++
 spec/events_spec.rb      |  57 +++++++++++
 spec/every_spec.rb       |  19 ++++
 spec/group_spec.rb       | 254 +++++++++++++++++++++++++++++++++++++++++++++++
 spec/performance_spec.rb |  83 ++++++++++++++++
 spec/spec_helper.rb      |  19 +++-
 spec/strict_spec.rb      |  37 +++++++
 spec/timeout_spec.rb     |  29 ++++++
 spec/timers_spec.rb      |  77 --------------
 timers.gemspec           |   7 +-
 25 files changed, 1089 insertions(+), 218 deletions(-)

diff --git a/.coveralls.yml b/.coveralls.yml
new file mode 100644
index 0000000..e1da6a3
--- /dev/null
+++ b/.coveralls.yml
@@ -0,0 +1 @@
+service-name: travis-pro
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d87d4be
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*.gem
+*.rbc
+.bundle
+.config
+.yardoc
+Gemfile.lock
+InstalledFiles
+_yardoc
+coverage
+doc/
+lib/bundler/man
+pkg
+rdoc
+spec/reports
+test/tmp
+test/version_tmp
+tmp
diff --git a/.rspec b/.rspec
index 675313d..57badd3 100644
--- a/.rspec
+++ b/.rspec
@@ -1,4 +1,5 @@
 --color
 --format documentation
 --backtrace
---default_path spec
+--order random
+--warnings
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4626e21
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,17 @@
+rvm:
+  - 1.9.3
+  - 2.0.0
+  - 2.1.2
+  - ruby-head
+  - jruby
+  - jruby-head
+  - rbx-2
+
+matrix:
+  allow_failures:
+    - rvm: ruby-head
+    - rvm: jruby-head
+    - rvm: rbx-2
+
+notifications:
+  irc: "irc.freenode.org#celluloid"
diff --git a/AUTHORS.md b/AUTHORS.md
new file mode 100644
index 0000000..08eb922
--- /dev/null
+++ b/AUTHORS.md
@@ -0,0 +1,15 @@
+# The Celluloid timers gem is beamed directly to you from the minds of...
+
+- Tony Arcieri <bascule at gmail.com>
+- Jeremy Hinegardner
+- Sean Gregory
+- Chuck Remes
+- Utenmiki
+- Ron Evans
+- Larry Lv
+- Bruno Enten
+- Jesse Cooke
+- Nicholas Evans
+- Dimitrij Denissenko
+- Ryan LeCompte
+- Samuel G. D. Williams
\ No newline at end of file
diff --git a/CHANGES.md b/CHANGES.md
index 2b4ed3c..97611b0 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,33 @@
+4.0.1 (2014-09-10)
+------------------
+* Memory leak fixes
+* mathn fix for those crazy enough to use it
+
+4.0.0 (2014-07-27)
+------------------
+* Replace Timers::Timeout with Timers::Wait
+* Timers::Group#wait_interval now returns nil when no timers, a postive or
+  negative interval which if positive is the amount of time required to wait
+  and if negative, how far in the past the latest timer should have fired
+* Performance improvements
+
+3.0.1 (2014-06-27)
+------------------
+* Require 'set' automatically
+
+3.0.0 (2014-06-14)
+------------------
+* Refactor `Timers` class into `Timers::Group`
+* Add `Timers::Timeout` class for implementing timeouts
+* Fix timer fudging
+* Update to RSpec 3
+
+2.0.0 (2013-12-30)
+------------------
+* Switch to Hitimes for high precision monotonic counters
+* Removed Timers#time. Replaced with Timers#current_offset which provides a
+  monotonic time counter.
+
 1.1.0
 -----
 * Timers#after_milliseconds and #after_ms for waiting in milliseconds
diff --git a/Gemfile b/Gemfile
index 380558a..f47ca08 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,10 @@
 source 'https://rubygems.org'
 
+gem 'coveralls', :require => false
+
 # Specify your gem's dependencies in timers.gemspec
 gemspec
+
+unless RUBY_PLATFORM =~ /java/ 
+  gem "ruby-prof", :group => :development
+end
diff --git a/LICENSE b/LICENSE
index f5ef3a0..1189def 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
-Copyright (c) 2012 Tony Arcieri
+Copyright (c) 2012-14 The Celluloid Timers Developers: given in the file
+AUTHORS.md at https://github.com/celluloid/timers/blob/master/AUTHORS.md
 
 MIT License
 
@@ -19,4 +20,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index e29ac83..221d9c4 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,30 @@
 Timers
 ======
-[![Build Status](https://secure.travis-ci.org/tarcieri/timers.png?branch=master)](http://travis-ci.org/tarcieri/timers)
+[![Gem Version](https://badge.fury.io/rb/timers.png)](http://rubygems.org/gems/timers)
+[![Build Status](https://secure.travis-ci.org/celluloid/timers.png?branch=master)](http://travis-ci.org/celluloid/timers)
+[![Code Climate](https://codeclimate.com/github/celluloid/timers.png)](https://codeclimate.com/github/celluloid/timers)
+[![Coverage Status](https://coveralls.io/repos/celluloid/timers/badge.png?branch=master)](https://coveralls.io/r/celluloid/timers)
 
-Pure Ruby timer collections. Schedule several procs to fire after configurable
-delays or at periodic intervals.
+Ruby timer collections. Schedule several procs to fire after configurable delays
+or at periodic intervals.
 
 This gem is especially useful when you are faced with an API that accepts a
 single timeout but you want to run multiple timers on top of it. An example of
-such a library is [nio4r](https://github.com/tarcieri/nio4r), a cross-platform
+such a library is [nio4r](https://github.com/celluloid/nio4r), a cross-platform
 Ruby library for using system calls like epoll and kqueue.
 
 Usage
 -----
 
-Create a new timer group with `Timers.new`:
+Create a new timer group with `Timers::Group.new`:
 
 ```ruby
 require 'timers'
 
-timers = Timers.new
+timers = Timers::Group.new
 ```
 
-Schedule a proc to run after 5 seconds with `Timers#after`:
+Schedule a proc to run after 5 seconds with `Timers::Group#after`:
 
 ```ruby
 five_second_timer = timers.after(5) { puts "Take five" }
@@ -30,7 +33,7 @@ five_second_timer = timers.after(5) { puts "Take five" }
 The `five_second_timer` variable is now bound to a Timers::Timer object. To
 cancel a timer, use `Timers::Timer#cancel`
 
-Once you've scheduled a timer, you can wait until the next timer fires with `Timers#wait`:
+Once you've scheduled a timer, you can wait until the next timer fires with `Timers::Group#wait`:
 
 ```ruby
 # Waits 5 seconds
@@ -39,7 +42,7 @@ timers.wait
 # The script will now print "Take five"
 ```
 
-You can schedule a block to run periodically with `Timers#every`:
+You can schedule a block to run periodically with `Timers::Group#every`:
 
 ```ruby
 every_five_seconds = timers.every(5) { puts "Another 5 seconds" }
@@ -48,8 +51,8 @@ loop { timers.wait }
 ```
 
 If you'd like another method to do the waiting for you, e.g. `Kernel.select`,
-you can use `Timers#wait_interval` to obtain the amount of time to wait. When
-a timeout is encountered, you can fire all pending timers with `Timers#fire`:
+you can use `Timers::Group#wait_interval` to obtain the amount of time to wait. When
+a timeout is encountered, you can fire all pending timers with `Timers::Group#fire`:
 
 ```ruby
 loop do
@@ -66,8 +69,28 @@ loop do
 end
 ```
 
+You can also pause and continue individual timers, or all timers:
+
+```ruby
+paused_timer = timers.every(5) { puts "I was paused" }
+
+paused_timer.pause
+10.times { timers.wait } # will not fire paused timer
+
+paused_timer.resume
+10.times { timers.wait } # will fire timer
+
+timers.pause
+10.times { timers.wait } # will not fire any timers
+
+timers.resume
+10.times { timers.wait } # will fire all timers
+```
+
 License
 -------
 
-Copyright (c) 2012 Tony Arcieri. Distributed under the MIT License. See
-LICENSE for further details.
+Copyright (c) 2014 Celluloid timers project developers (given in the file
+AUTHORS.md).
+
+Distributed under the MIT License. See LICENSE file for further details.
diff --git a/lib/timers.rb b/lib/timers.rb
index 14bc40d..8f80d4b 100644
--- a/lib/timers.rb
+++ b/lib/timers.rb
@@ -1,122 +1,5 @@
-require 'set'
-require 'forwardable'
-require 'timers/version'
-
-# Low precision timers implemented in pure Ruby
-class Timers
-  include Enumerable
-  extend  Forwardable
-  def_delegators :@timers, :delete, :each, :empty?
-
-  def initialize
-    @timers = SortedSet.new
-  end
-
-  # Call the given block after the given interval
-  def after(interval, &block)
-    Timer.new(self, interval, false, &block)
-  end
-  
-  # Call the given block after the given interval has expired. +interval+
-  # is measured in milliseconds.
-  #
-  #  Timer.new.after_milliseconds(25) { puts "fired!" }
-  #
-  def after_milliseconds(interval, &block)
-    after(interval / 1000.0, &block)
-  end
-  alias_method :after_ms, :after_milliseconds
-
-  # Call the given block periodically at the given interval
-  def every(interval, &block)
-    Timer.new(self, interval, true, &block)
-  end
-
-  # Wait for the next timer and fire it
-  def wait
-    i = wait_interval
-    sleep i if i
-    fire
-  end
-
-  # Interval to wait until when the next timer will fire
-  def wait_interval(now = Time.now)
-    timer = @timers.first
-    return unless timer
-    interval = timer.time - now
-    interval > 0 ? interval : 0
-  end
-
-  # Fire all timers that are ready
-  def fire(now = Time.now)
-    time = now + 0.001 # Fudge 1ms in case of clock imprecision
-    while (timer = @timers.first) && (time >= timer.time)
-      @timers.delete timer
-      timer.fire(now)
-    end
-  end
-
-  def add(timer)
-    raise TypeError, "not a Timers::Timer" unless timer.is_a? Timers::Timer
-    @timers.add(timer)
-  end
-
-  alias_method :cancel, :delete
 
-  # An individual timer set to fire a given proc at a given time
-  class Timer
-    include Comparable
-    attr_reader :interval, :time, :recurring
-
-    def initialize(timers, interval, recurring = false, &block)
-      @timers, @interval, @recurring = timers, interval, recurring
-      @block = block
-      @time  = nil
-
-      reset
-    end
-
-    def <=>(other)
-      @time <=> other.time
-    end
-
-    # Cancel this timer
-    def cancel
-      @timers.cancel self
-    end
-
-    # Reset this timer
-    def reset(now = Time.now)
-      @timers.cancel self if @time
-      @time = now + @interval
-      @timers.add self
-    end
-
-    # Fire the block
-    def fire(now = Time.now)
-      reset(now) if recurring
-      @block.call
-    end
-    alias_method :call, :fire
-
-    # Inspect a timer
-    def inspect
-      str = "#<Timers::Timer:#{object_id.to_s(16)} "
-      now = Time.now
-
-      if @time
-        if @time >= now
-          str << "fires in #{@time - now} seconds"
-        else
-          str << "fired #{now - @time} seconds ago"
-        end
-
-        str << ", recurs every #{interval}" if recurring
-      else
-        str << "dead"
-      end
+require 'timers/version'
 
-      str << ">"
-    end
-  end
-end
+require 'timers/group'
+require 'timers/wait'
diff --git a/lib/timers/events.rb b/lib/timers/events.rb
new file mode 100644
index 0000000..8097bc6
--- /dev/null
+++ b/lib/timers/events.rb
@@ -0,0 +1,115 @@
+
+require 'forwardable'
+require 'hitimes'
+
+require 'timers/timer'
+
+module Timers
+  # Maintains an ordered list of events, which can be cancelled.
+  class Events
+    # Represents a cancellable handle for a specific timer event.
+    class Handle
+      def initialize(time, callback)
+        @time = time
+        @callback = callback
+      end
+      
+      # The absolute time that the handle should be fired at.
+      attr :time
+      
+      # Cancel this timer, O(1).
+      def cancel!
+        # The simplest way to keep track of cancelled status is to nullify the
+        # callback. This should also be optimal for garbage collection.
+        @callback = nil
+      end
+      
+      # Has this timer been cancelled? Cancelled timer's don't fire.
+      def cancelled?
+        @callback.nil?
+      end
+      
+      def > other
+        @time > other.to_f
+      end
+      
+      def to_f
+        @time
+      end
+      
+      # Fire the callback if not cancelled with the given time parameter.
+      def fire(time)
+        if @callback
+          @callback.call(time)
+        end
+      end
+    end
+    
+    def initialize
+      # A sequence of handles, maintained in sorted order, future to present.
+      # @sequence.last is the next event to be fired.
+      @sequence = []
+    end
+    
+    # Add an event at the given time.
+    def schedule(time, callback)
+      handle = Handle.new(time.to_f, callback)
+      
+      index = bisect_left(@sequence, handle)
+      
+      # Maintain sorted order, O(logN) insertion time.
+      @sequence.insert(index, handle)
+      
+      return handle
+    end
+    
+    # Returns the first non-cancelled handle.
+    def first
+      while handle = @sequence.last
+        if handle.cancelled?
+          @sequence.pop
+        else
+          return handle
+        end
+      end
+    end
+    
+    # Returns the number of pending (possibly cancelled) events.
+    def size
+      @sequence.size
+    end
+    
+    # Fire all handles for which Handle#time is less than the given time.
+    def fire(time)
+      pop(time).reverse_each do |handle|
+        handle.fire(time)
+      end
+    end
+
+    private
+
+    # Efficiently take k handles for which Handle#time is less than the given 
+    # time.
+    def pop(time)
+      index = bisect_left(@sequence, time)
+      
+      return @sequence.pop(@sequence.size - index)
+    end
+    
+    # Return the left-most index where to insert item e, in a list a, assuming 
+    # a is sorted in descending order.
+    def bisect_left(a, e, l = 0, u = a.length)
+      while l < u
+        m = l + (u-l).div(2)
+        
+        if a[m] > e
+          l = m+1
+        else
+          u = m
+        end
+      end
+      
+      return l
+    end
+  end
+end
diff --git a/lib/timers/group.rb b/lib/timers/group.rb
new file mode 100644
index 0000000..d649bbd
--- /dev/null
+++ b/lib/timers/group.rb
@@ -0,0 +1,118 @@
+
+require 'set'
+require 'forwardable'
+require 'hitimes'
+
+require 'timers/timer'
+require 'timers/events'
+
+module Timers
+  class Group
+    include Enumerable
+
+    extend Forwardable
+    def_delegators :@timers, :each, :empty?
+
+    def initialize
+      @events = Events.new
+      
+      @timers = Set.new
+      @paused_timers = Set.new
+      
+      @interval = Hitimes::Interval.new
+      @interval.start
+    end
+
+    # Scheduled events:
+    attr :events
+    
+    # Active timers:
+    attr :timers
+    
+    # Paused timers:
+    attr :paused_timers
+
+    # Call the given block after the given interval. The first argument will be
+    # the time at which the group was asked to fire timers for.
+    def after(interval, &block)
+      Timer.new(self, interval, false, &block)
+    end
+
+    # Call the given block periodically at the given interval. The first 
+    # argument will be the time at which the group was asked to fire timers for.
+    def every(interval, recur = true, &block)
+      Timer.new(self, interval, recur, &block)
+    end
+
+    # Wait for the next timer and fire it. Can take a block, which should behave
+    # like sleep(n), except that n may be nil (sleep forever) or a negative
+    # number (fire immediately after return).
+    def wait(&block)
+      if block_given?
+        yield wait_interval
+        
+        while interval = wait_interval and interval > 0
+          yield interval
+        end
+      else
+        while interval = wait_interval and interval > 0
+          # We cannot assume that sleep will wait for the specified time, it might be +/- a bit.
+          sleep interval
+        end
+      end
+
+      fire
+    end
+
+    # Interval to wait until when the next timer will fire.
+    # - nil: no timers
+    # - -ve: timers expired already
+    # -   0: timers ready to fire
+    # - +ve: timers waiting to fire
+    def wait_interval(offset = self.current_offset)
+      if handle = @events.first
+        return handle.time - Float(offset)
+      end
+    end
+
+    # Fire all timers that are ready.
+    def fire(offset = self.current_offset)
+      @events.fire(offset)
+    end
+
+    # Pause all timers.
+    def pause
+      @timers.dup.each do |timer|
+        timer.pause
+      end
+    end
+
+    # Resume all timers.
+    def resume
+      @paused_timers.dup.each do |timer|
+        timer.resume
+      end
+    end
+
+    alias_method :continue, :resume
+
+    # Delay all timers.
+    def delay(seconds)
+      @timers.each do |timer|
+        timer.delay(seconds)
+      end
+    end
+
+    # Cancel all timers.
+    def cancel
+      @timers.dup.each do |timer|
+        timer.cancel
+      end
+    end
+
+    # The group's current time.
+    def current_offset
+      @interval.to_f
+    end
+  end
+end
diff --git a/lib/timers/timer.rb b/lib/timers/timer.rb
new file mode 100644
index 0000000..8dc1ea6
--- /dev/null
+++ b/lib/timers/timer.rb
@@ -0,0 +1,128 @@
+
+module Timers
+  # An individual timer set to fire a given proc at a given time. A timer is 
+  # always connected to a Timer::Group but it would ONLY be in @group.timers
+  # if it also has a @handle specified. Otherwise it is either PAUSED or has
+  # been FIRED and is not recurring. You can manually enter this state by
+  # calling #cancel and resume normal operation by calling #reset.
+  class Timer
+    include Comparable
+    attr_reader :interval, :offset, :recurring
+
+    def initialize(group, interval, recurring = false, offset = nil, &block)
+      @group = group
+      
+      @interval = interval
+      @recurring = recurring
+      @block = block
+      @offset = offset
+      
+      @handle = nil
+      
+      # If a start offset was supplied, use that, otherwise use the current timers offset.
+      reset(@offset || @group.current_offset)
+    end
+
+    def paused?
+      @group.paused_timers.include? self
+    end
+
+    def pause
+      return if paused?
+      
+      @group.timers.delete self
+      @group.paused_timers.add self
+      
+      @handle.cancel! if @handle
+      @handle = nil
+    end
+
+    def resume
+      return unless paused?
+      
+      @group.paused_timers.delete self
+      
+      # This will add us back to the group:
+      reset
+    end
+
+    alias_method :continue, :resume
+
+    # Extend this timer
+    def delay(seconds)
+      @handle.cancel! if @handle
+      
+      @offset += seconds
+      
+      @handle = @group.events.schedule(@offset, self)
+    end
+    
+    # Cancel this timer. Do not call while paused.
+    def cancel
+      return unless @handle
+      
+      @handle.cancel! if @handle
+      @handle = nil
+      
+      # This timer is no longer valid:
+      @group.timers.delete self if @group
+    end
+
+    # Reset this timer. Do not call while paused.
+    def reset(offset = @group.current_offset)
+      # This logic allows us to minimise the interaction with @group.timers.
+      # A timer with a handle is always registered with the group.
+      if @handle
+        @handle.cancel!
+      else
+        @group.timers << self
+      end
+      
+      @offset = Float(offset) + @interval
+      
+      @handle = @group.events.schedule(@offset, self)
+    end
+
+    # Fire the block.
+    def fire(offset = @group.current_offset)
+      if recurring == :strict
+        # ... make the next interval strictly the last offset + the interval:
+        reset(@offset)
+      elsif recurring
+        reset(offset)
+      else
+        @offset = offset
+      end
+
+      @block.call(offset)
+      
+      cancel unless recurring
+    end
+
+    alias_method :call, :fire
+
+    # Number of seconds until next fire / since last fire
+    def fires_in
+      @offset - @group.current_offset if @offset
+    end
+
+    # Inspect a timer
+    def inspect
+      str = "#<Timers::Timer:#{object_id.to_s(16)} "
+
+      if @offset
+        if fires_in >= 0
+          str << "fires in #{fires_in} seconds"
+        else
+          str << "fired #{fires_in.abs} seconds ago"
+        end
+
+        str << ", recurs every #{interval}" if recurring
+      else
+        str << "dead"
+      end
+
+      str << ">"
+    end
+  end
+end
diff --git a/lib/timers/version.rb b/lib/timers/version.rb
index e6fa596..db430ad 100644
--- a/lib/timers/version.rb
+++ b/lib/timers/version.rb
@@ -1,3 +1,3 @@
-class Timers
-  VERSION = "1.1.0"
+module Timers
+  VERSION = "4.0.1"
 end
diff --git a/lib/timers/wait.rb b/lib/timers/wait.rb
new file mode 100644
index 0000000..ce2b8c9
--- /dev/null
+++ b/lib/timers/wait.rb
@@ -0,0 +1,48 @@
+
+require 'hitimes'
+
+module Timers
+  # An exclusive, monotonic timeout class.
+  class Wait
+    def self.for(duration, &block)
+      if duration
+        timeout = self.new(duration)
+        
+        timeout.while_time_remaining(&block)
+      else
+        while true
+          yield(nil)
+        end
+      end
+    end
+    
+    def initialize(duration)
+      @duration = duration
+      @remaining = true
+    end
+    
+    attr :duration
+    attr :remaining
+    
+    # Yields while time remains for work to be done:
+    def while_time_remaining(&block)
+      @interval = Hitimes::Interval.new
+      @interval.start
+      
+      while time_remaining?
+        yield @remaining
+      end
+    ensure
+      @interval.stop
+      @interval = nil
+    end
+    
+    private
+    
+    def time_remaining?
+      @remaining = (@duration - @interval.duration)
+    
+      return @remaining > 0
+    end
+  end
+end
diff --git a/spec/cancel_spec.rb b/spec/cancel_spec.rb
new file mode 100644
index 0000000..12f4e7a
--- /dev/null
+++ b/spec/cancel_spec.rb
@@ -0,0 +1,46 @@
+
+require 'spec_helper'
+
+RSpec.describe Timers::Group do
+  it "should be able to cancel twice" do
+    fired = false
+
+    timer = subject.after(0.1) { fired = true }
+    
+    2.times do
+      timer.cancel
+      subject.wait
+    end
+
+    expect(fired).to be false
+  end
+  
+  it "should be possble to reset after cancel" do
+    fired = false
+    
+    timer = subject.after(0.1) { fired = true }
+    timer.cancel
+    
+    subject.wait
+    
+    timer.reset
+    
+    subject.wait
+    
+    expect(fired).to be true
+  end
+  
+  it "should cancel and remove one shot timers after they fire" do
+    x = 0
+
+    Timers::Wait.for(2) do |remaining|
+      timer = subject.every(0.2) { x += 1 }
+      subject.after(0.1) { timer.cancel }
+      
+      subject.wait
+    end
+    
+    expect(subject.timers).to be_empty
+    expect(x).to be == 0
+  end
+end
diff --git a/spec/events_spec.rb b/spec/events_spec.rb
new file mode 100644
index 0000000..685f6ea
--- /dev/null
+++ b/spec/events_spec.rb
@@ -0,0 +1,57 @@
+
+require 'spec_helper'
+
+RSpec.describe Timers::Events do
+  it "should register an event" do
+    fired = false
+    
+    callback = proc do |time|
+      fired = true
+    end
+    
+    subject.schedule(0.1, callback)
+    
+    expect(subject.size).to be == 1
+    
+    subject.fire(0.15)
+    
+    expect(subject.size).to be == 0
+    
+    expect(fired).to be true
+  end
+  
+  it "should register events in order" do
+    fired = []
+    
+    times = [0.95, 0.1, 0.3, 0.5, 0.4, 0.2, 0.01, 0.9]
+    
+    times.each do |requested_time|
+      callback = proc do |time|
+        fired << requested_time
+      end
+      
+      subject.schedule(requested_time, callback)
+    end
+    
+    subject.fire(0.5)
+    expect(fired).to be == times.sort.first(6)
+    
+    subject.fire(1.0)
+    expect(fired).to be == times.sort
+  end
+  
+  it "should fire events with the time they were fired at" do
+    fired_at = :not_fired
+    
+    callback = proc do |time|
+      # The time we actually were fired at:
+      fired_at = time
+    end
+    
+    subject.schedule(0.5, callback)
+    
+    subject.fire(1.0)
+    
+    expect(fired_at).to be == 1.0
+  end
+end
diff --git a/spec/every_spec.rb b/spec/every_spec.rb
new file mode 100644
index 0000000..309559e
--- /dev/null
+++ b/spec/every_spec.rb
@@ -0,0 +1,19 @@
+
+require 'spec_helper'
+
+RSpec.describe Timers::Group do
+  it "should fire several times" do
+    result = []
+
+    subject.every(0.7) { result << :a }
+    subject.every(2.3) { result << :b }
+    subject.every(1.3) { result << :c }
+    subject.every(2.4) { result << :d }
+
+    Timers::Wait.for(2.5) do |remaining|
+      subject.wait if subject.wait_interval < remaining
+    end
+
+    expect(result).to be == [:a, :c, :a, :a, :b, :d]
+  end
+end
diff --git a/spec/group_spec.rb b/spec/group_spec.rb
new file mode 100644
index 0000000..c090670
--- /dev/null
+++ b/spec/group_spec.rb
@@ -0,0 +1,254 @@
+
+require 'spec_helper'
+
+RSpec.describe Timers::Group do
+  describe "#wait" do
+    it "calls the wait block with nil" do
+      called = false
+      
+      subject.wait do |interval|
+        expect(interval).to be == nil
+        called = true
+      end
+      
+      expect(called).to be true
+    end
+  
+    it "calls the wait block with an interval" do
+      called = false
+      fired = false
+
+      subject.after(0.1) { fired = true }
+
+      subject.wait do |interval|
+        expect(interval).to be_within(TIMER_QUANTUM).of(0.1)
+        called = true
+        sleep 0.2
+      end
+
+      expect(called).to be true
+      expect(fired).to be true
+    end
+  end
+  
+  it "sleeps until the next timer" do
+    interval   = TIMER_QUANTUM * 2
+    started_at = Time.now
+
+    fired = false
+    subject.after(interval) { fired = true }
+    subject.wait
+
+    expect(fired).to be true
+    expect(Time.now - started_at).to be_within(TIMER_QUANTUM).of interval
+  end
+
+  it "fires instantly when next timer is in the past" do
+    fired = false
+    subject.after(TIMER_QUANTUM) { fired = true }
+    sleep(TIMER_QUANTUM * 2)
+    subject.wait
+
+    expect(fired).to be true
+  end
+
+  it "calculates the interval until the next timer should fire" do
+    interval = 0.1
+
+    subject.after(interval)
+    expect(subject.wait_interval).to be_within(TIMER_QUANTUM).of interval
+
+    sleep(interval)
+    expect(subject.wait_interval).to be <= 0
+  end
+
+  it "fires timers in the correct order" do
+    result = []
+
+    subject.after(TIMER_QUANTUM * 2) { result << :two }
+    subject.after(TIMER_QUANTUM * 3) { result << :three }
+    subject.after(TIMER_QUANTUM * 1) { result << :one }
+
+    sleep TIMER_QUANTUM * 4
+    subject.fire
+
+    expect(result).to eq [:one, :two, :three]
+  end
+
+  it "raises TypeError if given an invalid time" do
+    expect do
+      subject.after(nil) { nil }
+    end.to raise_exception(TypeError)
+  end
+
+  describe "recurring timers" do
+    it "continues to fire the timers at each interval" do
+      result = []
+
+      subject.every(TIMER_QUANTUM * 2) { result << :foo }
+
+      sleep TIMER_QUANTUM * 3
+      subject.fire
+      expect(result).to eq [:foo]
+
+      sleep TIMER_QUANTUM * 5
+      subject.fire
+      expect(result).to eq [:foo, :foo]
+    end
+  end
+
+  it "calculates the proper interval to wait until firing" do
+    interval_ms = 25
+
+    subject.after(interval_ms / 1000.0)
+
+    expect(subject.wait_interval).to be_within(TIMER_QUANTUM).of(interval_ms / 1000.0)
+  end
+
+  describe "pause and continue timers" do
+    before(:each) do
+      @interval   = TIMER_QUANTUM * 2
+
+      @fired = false
+      @timer = subject.after(@interval) { @fired = true }
+      @fired2 = false
+      @timer2 = subject.after(@interval) { @fired2 = true }
+    end
+
+    it "does not fire when paused" do
+      @timer.pause
+      subject.wait
+      expect(@fired).to be false
+    end
+
+    it "fires when continued after pause" do
+      @timer.pause
+      subject.wait
+      @timer.resume
+      
+      sleep @timer.interval
+      subject.wait
+      
+      expect(@fired).to be true
+    end
+
+    it "can pause all timers at once" do
+      subject.pause
+      subject.wait
+      expect(@fired).to be false
+      expect(@fired2).to be false
+    end
+
+    it "can continue all timers at once" do
+      subject.pause
+      subject.wait
+      subject.resume
+      
+      # We need to wait until we are sure both timers will fire, otherwise highly accurate clocks (e.g. JVM) may only fire the first timer, but not the second, because they are actually schedueled at different times.
+      sleep TIMER_QUANTUM * 2
+      subject.wait
+      
+      expect(@fired).to be true
+      expect(@fired2).to be true
+    end
+
+    it "can fire the timer directly" do
+      fired = false
+      timer = subject.after( TIMER_QUANTUM * 1 ) { fired = true }
+      timer.pause
+      subject.wait
+      expect(fired).not_to be true
+      timer.resume
+      expect(fired).not_to be true
+      timer.fire
+      expect(fired).to be true
+    end
+
+  end
+
+  describe "delay timer" do
+    it "adds appropriate amount of time to timer" do
+      timer = subject.after(10)
+      timer.delay(5)
+      expect(timer.offset - subject.current_offset).to be_within(TIMER_QUANTUM).of(15)
+    end
+  end
+
+  describe "delay timer collection" do
+    it "delay on set adds appropriate amount of time to all timers" do
+      timer = subject.after(10)
+      timer2 = subject.after(20)
+      subject.delay(5)
+      expect(timer.offset - subject.current_offset).to be_within(TIMER_QUANTUM).of(15)
+      expect(timer2.offset - subject.current_offset).to be_within(TIMER_QUANTUM).of(25)
+    end
+  end
+
+  describe "on delaying a timer" do
+    it "fires timers in the correct order" do
+      result = []
+
+      subject.after(TIMER_QUANTUM * 2) { result << :two }
+      subject.after(TIMER_QUANTUM * 3) { result << :three }
+      first = subject.after(TIMER_QUANTUM * 1) { result << :one }
+      first.delay(TIMER_QUANTUM * 3)
+
+      sleep TIMER_QUANTUM * 5
+      subject.fire
+
+      expect(result).to eq [:two, :three, :one]
+    end
+  end
+
+  describe "Timer inspection" do
+    it "before firing" do
+      fired = false
+      timer = subject.after(TIMER_QUANTUM * 5) { fired = true }
+      timer.pause
+      expect(fired).not_to be true
+      expect(timer.inspect).to match(/\A#<Timers::Timer:[\da-f]+ fires in [-\.\de]+ seconds>\Z/)
+    end
+
+    it "after firing" do
+      fired = false
+      timer = subject.after(TIMER_QUANTUM) { fired = true }
+
+      subject.wait
+
+      expect(fired).to be true
+      expect(timer.inspect).to match(/\A#<Timers::Timer:[\da-f]+ fired [-\.\de]+ seconds ago>\Z/)
+    end
+
+    it "recurring firing" do
+      result = []
+      timer = subject.every(TIMER_QUANTUM) { result << :foo }
+
+      subject.wait
+      expect(result).not_to be_empty
+      expect(timer.inspect).to match(/\A#<Timers::Timer:[\da-f]+ fires in [-\.\de]+ seconds, recurs every #{sprintf("%0.2f", TIMER_QUANTUM)}>\Z/)
+    end
+  end
+
+  describe "fires_in" do
+    let(:interval) { TIMER_QUANTUM * 2 }
+
+    it "calculates the interval until the next fire if it's recurring" do
+      timer = subject.every(interval) { true }
+      expect(timer.fires_in).to be_within(TIMER_QUANTUM).of(interval)
+    end
+
+    context "when timer is not recurring" do
+      let!(:timer) { subject.after(interval) { true } }
+
+      it "calculates the interval until the next fire if it hasn't already fired" do
+        expect(timer.fires_in).to be_within(TIMER_QUANTUM).of(interval)
+      end
+
+      it "calculates the interval since last fire if already fired" do
+        subject.wait
+        sleep(interval)
+        expect(timer.fires_in).to be_within(TIMER_QUANTUM).of(0 - interval)
+      end
+    end
+  end
+end
diff --git a/spec/performance_spec.rb b/spec/performance_spec.rb
new file mode 100644
index 0000000..a540c38
--- /dev/null
+++ b/spec/performance_spec.rb
@@ -0,0 +1,83 @@
+
+require 'spec_helper'
+require 'ruby-prof' unless RUBY_PLATFORM =~ /java/
+
+# Event based timers:
+
+# Serviced 31812 events in 2.39075272 seconds, 13306.320832794887 e/s.
+# Thread ID: 7336700
+# Fiber ID: 30106340
+# Total: 2.384043
+# Sort by: self_time
+
+# %self      total      self      wait     child     calls  name
+# 13.48      0.510     0.321     0.000     0.189    369133  Timers::Events::Handle#<=>
+#  8.12      0.194     0.194     0.000     0.000    427278  Timers::Events::Handle#to_f
+#  4.55      0.109     0.109     0.000     0.000    427278  Float#<=>
+#  4.40      1.857     0.105     0.000     1.752    466376 *Timers::Events#bsearch
+#  4.30      0.103     0.103     0.000     0.000    402945  Float#to_f
+#  2.65      0.063     0.063     0.000     0.000     33812  Array#insert
+#  2.64      1.850     0.063     0.000     1.787     33812  Timers::Events#schedule
+#  2.40      1.930     0.057     0.000     1.873     33812  Timers::Timer#reset
+#  1.89      1.894     0.045     0.000     1.849     31812  Timers::Timer#fire
+#  1.69      1.966     0.040     0.000     1.926     31812  Timers::Events::Handle#fire
+#  1.35      0.040     0.032     0.000     0.008     33812  Timers::Events::Handle#initialize
+#  1.29      0.044     0.031     0.000     0.013     44451  Timers::Group#current_offset
+
+# SortedSet based timers:
+
+# Serviced 32516 events in 66.753277275 seconds, 487.1072288781219 e/s.
+# Thread ID: 15995640
+# Fiber ID: 38731780
+# Total: 66.716394
+# Sort by: self_time
+
+# %self      total      self      wait     child     calls  name
+# 54.73     49.718    36.513     0.000    13.205  57084873  Timers::Timer#<=>
+# 23.74     65.559    15.841     0.000    49.718     32534  Array#sort!
+# 19.79     13.205    13.205     0.000     0.000  57084873  Float#<=>
+
+# Max out events performance (on my computer):
+# Serviced 1142649 events in 11.194903921 seconds, 102068.70405115146 e/s.
+
+RSpec.describe Timers::Group do
+  if defined? RubyProf
+    before(:each) do
+      # Running RubyProf makes the code slightly slower.
+      RubyProf.start
+      puts "*** Running with RubyProf reduces performance ***"
+    end
+
+    after(:each) do |arg|
+      if RubyProf.running?
+        # file = arg.metadata[:description].gsub(/\s+/, '-')
+      
+        result = RubyProf.stop
+      
+        printer = RubyProf::FlatPrinter.new(result)
+        printer.print($stderr, min_percent: 1.0)
+      end
+    end
+  end
+  
+  it "run efficiently" do
+    result = []
+    range = (1..500)
+    duration = 2.0
+
+    total = 0
+    range.each do |index|
+      offset = index.to_f / range.max
+      total += (duration / offset).floor
+      
+      subject.every(index.to_f / range.max, :strict) { result << index }
+    end
+    
+    subject.wait while result.size < total
+    
+    rate = result.size.to_f / subject.current_offset
+    puts "Serviced #{result.size} events in #{subject.current_offset} seconds, #{rate} e/s."
+    
+    expect(subject.current_offset).to be_within(TIMER_QUANTUM).of(duration)
+  end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f98dd86..09a9449 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,2 +1,19 @@
+require 'coveralls'
+Coveralls.wear!
+
 require 'bundler/setup'
-require 'timers'
\ No newline at end of file
+require 'timers'
+
+# Level of accuracy enforced by tests (50ms)
+TIMER_QUANTUM = 0.05
+
+RSpec.configure do |config|
+  # Setting this config option `false` removes rspec-core's monkey patching of the
+  # top level methods like `describe`, `shared_examples_for` and `shared_context`
+  # on `main` and `Module`. The methods are always available through the `RSpec`
+  # module like `RSpec.describe` regardless of this setting.
+  # For backwards compatibility this defaults to `true`.
+  #
+  # https://relishapp.com/rspec/rspec-core/v/3-0/docs/configuration/global-namespace-dsl
+  config.expose_dsl_globally = false
+end
diff --git a/spec/strict_spec.rb b/spec/strict_spec.rb
new file mode 100644
index 0000000..cc2b15d
--- /dev/null
+++ b/spec/strict_spec.rb
@@ -0,0 +1,37 @@
+
+require 'spec_helper'
+
+RSpec.describe Timers::Group do
+	it "should not diverge too much" do
+		fired = :not_fired_yet
+		count = 0
+		quantum = 0.01
+		
+		start_offset = subject.current_offset
+		Timers::Timer.new(subject, quantum, :strict, start_offset) do |offset|
+			fired = offset
+			count += 1
+		end
+		
+		iterations = 1000
+		subject.wait while count < iterations
+		
+		# In my testing on the JVM, without the :strict recurring, I noticed 60ms of error here.
+		expect(fired - start_offset).to be_within(quantum).of(iterations * quantum)
+	end
+	
+	it "should only fire once" do
+		fired = :not_fired_yet
+		count = 0
+
+		start_offset = subject.current_offset
+		Timers::Timer.new(subject, 0, :strict, start_offset) do |offset|
+			fired = offset
+			count += 1
+		end
+		
+		subject.wait
+		
+		expect(count).to be == 1
+	end
+end
diff --git a/spec/timeout_spec.rb b/spec/timeout_spec.rb
new file mode 100644
index 0000000..6664b51
--- /dev/null
+++ b/spec/timeout_spec.rb
@@ -0,0 +1,29 @@
+
+require 'spec_helper'
+require 'timers/wait'
+
+RSpec.describe Timers::Wait do
+  it "repeats until timeout expired" do
+    timeout = Timers::Wait.new(5)
+    count = 0
+    
+    timeout.while_time_remaining do |remaining|
+      expect(remaining).to be_within(TIMER_QUANTUM).of (timeout.duration - count)
+      
+      count += 1
+      sleep 1
+    end
+    
+    expect(count).to eq(5)
+  end
+  
+  it "yields results as soon as possible" do
+    timeout = Timers::Wait.new(5)
+    
+    result = timeout.while_time_remaining do |remaining|
+      break :done
+    end
+    
+    expect(result).to eq(:done)
+  end
+end
diff --git a/spec/timers_spec.rb b/spec/timers_spec.rb
deleted file mode 100644
index 375e653..0000000
--- a/spec/timers_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-require 'spec_helper'
-
-describe Timers do
-  # Level of accuracy enforced by most tests (50ms)
-  Q = 0.05
-
-  it "sleeps until the next timer" do
-    interval   = Q * 2
-    started_at = Time.now
-
-    fired = false
-    subject.after(interval) { fired = true }
-    subject.wait
-
-    fired.should be_true
-    (Time.now - started_at).should be_within(Q).of interval
-  end
-
-  it "fires instantly when next timer is in the past" do
-    fired = false
-    subject.after(Q) { fired = true }
-    sleep(Q * 2)
-    subject.wait
-
-    fired.should be_true
-  end
-
-  it "calculates the interval until the next timer should fire" do
-    interval = 0.1
-
-    subject.after(interval)
-    subject.wait_interval.should be_within(Q).of interval
-
-    sleep(interval)
-    subject.wait_interval.should be(0)
-  end
-
-  it "fires timers in the correct order" do
-    result = []
-
-    subject.after(Q * 2) { result << :two }
-    subject.after(Q * 3) { result << :three }
-    subject.after(Q * 1) { result << :one }
-
-    sleep Q * 4
-    subject.fire
-
-    result.should == [:one, :two, :three]
-  end
-
-  describe "recurring timers" do
-    it "continues to fire the timers at each interval" do
-      result = []
-
-      subject.every(Q * 2) { result << :foo }
-
-      sleep Q * 3
-      subject.fire
-      result.should == [:foo]
-
-      sleep Q * 5
-      subject.fire
-      result.should == [:foo, :foo]
-    end
-  end
-  
-  describe "millisecond timers" do
-    it "calculates the proper interval to wait until firing" do
-      interval_ms = 25
-
-      subject.after_milliseconds(interval_ms)
-      expected_elapse = subject.wait_interval
-
-      subject.wait_interval.should be_within(Q).of (interval_ms / 1000.0)
-    end
-  end
-end
diff --git a/timers.gemspec b/timers.gemspec
index 98da21f..9806a69 100644
--- a/timers.gemspec
+++ b/timers.gemspec
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
   gem.email         = ["tony.arcieri at gmail.com"]
   gem.description   = "Pure Ruby one-shot and periodic timers"
   gem.summary       = "Schedule procs to run after a certain time, or at periodic intervals, using any API that accepts a timeout"
-  gem.homepage      = "https://github.com/tarcieri/timers"
+  gem.homepage      = "https://github.com/celluloid/timers"
 
   gem.files         = `git ls-files`.split($\)
   gem.executables   = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
@@ -14,7 +14,10 @@ Gem::Specification.new do |gem|
   gem.name          = "timers"
   gem.require_paths = ["lib"]
   gem.version       = Timers::VERSION
+  gem.licenses      = ['MIT']
+
+  gem.add_runtime_dependency 'hitimes'
 
   gem.add_development_dependency 'rake'
-  gem.add_development_dependency 'rspec'
+  gem.add_development_dependency 'rspec', '~> 3.0.0'
 end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-timers.git



More information about the Pkg-ruby-extras-commits mailing list