Class: Philiprehberger::TimeoutKit::Deadline

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/timeout_kit/deadline.rb

Overview

A cooperative deadline that tracks remaining time and supports nesting.

Deadlines do not use Thread.raise. Instead, callers must explicitly call #check! at safe cancellation points.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(seconds, name: nil, grace: nil, on_expire: nil) ⇒ Deadline

Create a new deadline.

Parameters:

  • seconds (Numeric)

    the number of seconds until the deadline expires

  • name (String, nil) (defaults to: nil)

    optional human-readable name for the deadline

  • grace (Numeric, nil) (defaults to: nil)

    optional grace period in seconds after the primary deadline

  • on_expire (Proc, nil) (defaults to: nil)

    optional callback that fires once when expiry is detected



27
28
29
30
31
32
33
34
35
36
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 27

def initialize(seconds, name: nil, grace: nil, on_expire: nil)
  @started_at = now
  @duration = seconds.to_f
  @expires_at = @started_at + seconds
  @name = name
  @grace_seconds = grace
  @grace_expires_at = @grace_seconds ? @expires_at + @grace_seconds : nil
  @on_expire = on_expire
  @expire_callback_fired = false
end

Instance Attribute Details

#durationFloat (readonly)

The original budget passed to new as seconds.

Returns:

  • (Float)

    the original deadline duration in seconds



19
20
21
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 19

def duration
  @duration
end

#expires_atFloat (readonly)

Returns the absolute monotonic time when the deadline expires.

Returns:

  • (Float)

    the absolute monotonic time when the deadline expires



11
12
13
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 11

def expires_at
  @expires_at
end

#nameString? (readonly)

Returns the human-readable name for this deadline.

Returns:

  • (String, nil)

    the human-readable name for this deadline



14
15
16
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 14

def name
  @name
end

Instance Method Details

#check!void

This method returns an undefined value.

Check whether the deadline has expired.

Raises:

  • (DeadlineExceeded)

    if the deadline has passed (and grace period, if any, has also passed)



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 50

def check!
  return unless expired?

  fire_expire_callback

  # If we have a grace period and are still within it, don't raise
  return if in_grace?

  message = @name ? "Deadline '#{@name}' exceeded" : 'Deadline exceeded'
  raise DeadlineExceeded, message
end

#elapsedFloat

Return the number of seconds elapsed since the deadline was created. This is a pure wall-clock reading from the monotonic clock and continues to increase past the original budget after expiration. Independent of #expired? and #in_grace?.

Returns:

  • (Float)

    seconds elapsed since creation



81
82
83
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 81

def elapsed
  now - @started_at
end

#expired?Boolean

Whether the primary deadline has expired.

Returns:

  • (Boolean)


109
110
111
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 109

def expired?
  now >= @expires_at
end

#grace_remainingFloat

Return the remaining time in the grace period.

Returns:

  • (Float)

    seconds remaining in grace period (0.0 if no grace period or grace expired)



99
100
101
102
103
104
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 99

def grace_remaining
  return 0.0 unless @grace_expires_at

  r = @grace_expires_at - now
  r.negative? ? 0.0 : r
end

#in_grace?Boolean

Whether the deadline is currently in the grace period. True only when the primary deadline has expired but the grace period has not.

Returns:

  • (Boolean)


117
118
119
120
121
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 117

def in_grace?
  return false unless @grace_expires_at

  expired? && now < @grace_expires_at
end

#on_expire { ... } ⇒ void

This method returns an undefined value.

Register a callback that fires once when expiry is detected.

Yields:

  • the block to call on expiry



42
43
44
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 42

def on_expire(&block)
  @on_expire = block
end

#progressFloat

Fraction of the original budget that has elapsed (0.0..1.0+). Exceeds 1.0 once the primary deadline has expired (and during grace period). Returns 1.0 if the original budget is zero.

Returns:

  • (Float)

    elapsed / duration



90
91
92
93
94
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 90

def progress
  return 1.0 if @duration.zero?

  elapsed / @duration
end

#remainingFloat

Return the remaining time in seconds until the primary deadline. Can be negative during the grace period.

Returns:

  • (Float)

    seconds remaining (negative if past primary deadline)



66
67
68
69
70
71
72
73
# File 'lib/philiprehberger/timeout_kit/deadline.rb', line 66

def remaining
  r = @expires_at - now
  if @grace_seconds
    r
  else
    r.negative? ? 0.0 : r
  end
end