Class: Polyphony::Timer

Inherits:
Object show all
Defined in:
lib/polyphony/core/timer.rb

Overview

Implements a common timer for running multiple timeouts. This class may be used to reduce the timer granularity in case a large number of timeouts is used concurrently. This class basically provides the same methods as global methods concerned with timeouts, such as #cancel_after, #every etc.

Instance Method Summary collapse

Constructor Details

#initialize(tag = nil, resolution:) ⇒ Timer

Initializes a new timer with the given resolution.

Parameters:

  • tag (any) (defaults to: nil)

    tag to use for the timer's fiber

  • resolution (Number)

    timer granularity in seconds or fractions thereof



13
14
15
16
# File 'lib/polyphony/core/timer.rb', line 13

def initialize(tag = nil, resolution:)
  @fiber = spin_loop(tag, interval: resolution) { update }
  @timeouts = {}
end

Instance Method Details

#after(interval, &block) ⇒ Fiber

Spins up a fiber that will run the given block after sleeping for the given delay.

Parameters:

  • interval (Number)

    delay in seconds before running the given block

Returns:



45
46
47
48
49
50
# File 'lib/polyphony/core/timer.rb', line 45

def after(interval, &block)
  spin do
    self.sleep interval
    block.()
  end
end

#cancel_after(interval) {|Fiber| ... } ⇒ any #cancel_after(interval, with_exception: exception) {|Fiber| ... } ⇒ any #cancel_after(interval, with_exception: [klass, message]) {|Fiber| ... } ⇒ any

Runs the given block after setting up a cancellation timer for cancellation. If the cancellation timer elapses, the execution will be interrupted with an exception defaulting to Polyphony::Cancel.

This method should be used when a timeout should cause an exception to be propagated down the call stack or up the fiber tree.

Example of normal use:

def read_from_io_with_timeout(io) timer.cancel_after(10) { io.read } rescue Polyphony::Cancel nil end

The timeout period can be reset using Timer#reset, as shown in the following example:

timer.cancel_after(10) do loop do msg = socket.gets timer.reset handle_msg(msg) end end

Overloads:

  • #cancel_after(interval) {|Fiber| ... } ⇒ any

    Returns block's return value.

    Parameters:

    • interval (Number)

      timout in seconds

    Yields:

    • (Fiber)

      timeout fiber

    Returns:

    • (any)

      block's return value

  • #cancel_after(interval, with_exception: exception) {|Fiber| ... } ⇒ any

    Returns block's return value.

    Parameters:

    • interval (Number)

      timout in seconds

    • with_exception (Class, Exception) (defaults to: exception)

      exception or exception class

    Yields:

    • (Fiber)

      timeout fiber

    Returns:

    • (any)

      block's return value

  • #cancel_after(interval, with_exception: [klass, message]) {|Fiber| ... } ⇒ any

    Returns block's return value.

    Parameters:

    • interval (Number)

      timout in seconds

    • with_exception (Array) (defaults to: [klass, message])

      array containing class and message to use as exception

    Yields:

    • (Fiber)

      timeout fiber

    Returns:

    • (any)

      block's return value



111
112
113
114
115
116
117
118
119
120
121
# File 'lib/polyphony/core/timer.rb', line 111

def cancel_after(interval, with_exception: Polyphony::Cancel)
  fiber = Fiber.current
  @timeouts[fiber] = {
    interval:,
    target_stamp: now + interval,
    exception:    with_exception
  }
  yield
ensure
  @timeouts.delete(fiber)
end

#every(interval) ⇒ Object

Runs the given block in an infinite loop with a regular interval between consecutive iterations.

Parameters:

  • interval (Number)

    interval between consecutive iterations in seconds



56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/polyphony/core/timer.rb', line 56

def every(interval)
  fiber = Fiber.current
  @timeouts[fiber] = {
    interval:,
    target_stamp: now + interval,
    recurring:    true
  }
  while true
    Polyphony.backend_wait_event(true)
    yield
  end
ensure
  @timeouts.delete(fiber)
end

#move_on_after(interval) {|Fiber| ... } ⇒ any #move_on_after(interval, with_value: value) {|Fiber| ... } ⇒ any

Runs the given block after setting up a cancellation timer for cancellation. If the cancellation timer elapses, the execution will be interrupted with a Polyphony::MoveOn exception, which will be rescued, and with cause the operation to return the given value.

This method should be used when a timeout is to be handled locally, without generating an exception that is to propagated down the call stack or up the fiber tree.

Example of normal use:

timer.move_on_after(10) { sleep 60 42 } #=> nil

timer.move_on_after(10, with_value: :oops) { sleep 60 42 } #=> :oops

The timeout period can be reset using Timer#reset, as shown in the following example:

timer.move_on_after(10) do loop do msg = socket.gets timer.reset handle_msg(msg) end end

Overloads:

  • #move_on_after(interval) {|Fiber| ... } ⇒ any

    Returns block's return value.

    Parameters:

    • interval (Number)

      timout in seconds

    Yields:

    • (Fiber)

      timeout fiber

    Returns:

    • (any)

      block's return value

  • #move_on_after(interval, with_value: value) {|Fiber| ... } ⇒ any

    Returns block's return value.

    Parameters:

    • interval (Number)

      timout in seconds

    • with_value (any) (defaults to: value)

      return value in case of timeout

    Yields:

    • (Fiber)

      timeout fiber

    Returns:

    • (any)

      block's return value



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/polyphony/core/timer.rb', line 164

def move_on_after(interval, with_value: nil)
  fiber = Fiber.current
  @timeouts[fiber] = {
    interval:,
    target_stamp: now + interval,
    exception:    [Polyphony::MoveOn, with_value]
  }
  yield
rescue Polyphony::MoveOn => e
  e.value
ensure
  @timeouts.delete(fiber)
end

#resetObject

Resets the timeout for the current fiber.



179
180
181
182
183
184
# File 'lib/polyphony/core/timer.rb', line 179

def reset
  record = @timeouts[Fiber.current]
  return unless record

  record[:target_stamp] = now + record[:interval]
end

#sleep(duration) ⇒ Object

Sleeps for the given duration.

Parameters:

  • duration (Number)

    sleep duration in seconds



29
30
31
32
33
34
35
36
37
38
# File 'lib/polyphony/core/timer.rb', line 29

def sleep(duration)
  fiber = Fiber.current
  @timeouts[fiber] = {
    interval:     duration,
    target_stamp: now + duration
  }
  Polyphony.backend_wait_event(true)
ensure
  @timeouts.delete(fiber)
end

#stopPolyphony::Timer

Stops the timer's associated fiber.

Returns:



21
22
23
24
# File 'lib/polyphony/core/timer.rb', line 21

def stop
  @fiber.stop
  self
end