Class: Wurk::Limiter::Bucket

Inherits:
Base
  • Object
show all
Defined in:
lib/wurk/limiter/bucket.rb

Overview

Cardinal-boundary counter. Reset at the top of the unit (00:00 of minute/hour/day). On exhaustion sleep until next boundary, retry, OverLimit if that exceeds wait_timeout.

Instance Method Summary collapse

Constructor Details

#initialize(name, **options) ⇒ Bucket

Returns a new instance of Bucket.



13
14
15
16
17
18
# File 'lib/wurk/limiter/bucket.rb', line 13

def initialize(name, **options)
  # Eager interval validation so a typo in `:fortnight` blows up at boot,
  # not at first call. Lazy validation defers failures to runtime.
  Limiter.interval_seconds(options[:interval], allow_integer: false)
  super
end

Instance Method Details

#sizeObject



20
21
22
# File 'lib/wurk/limiter/bucket.rb', line 20

def size
  Wurk::Limiter.redis { |c| (c.call('GET', epoch_key) || '0').to_i }
end

#state_keysObject (protected)



47
48
49
50
51
52
53
# File 'lib/wurk/limiter/bucket.rb', line 47

def state_keys
  # Bucket keys are per-epoch and self-expiring; reset wipes the
  # current epoch, the only one a caller could care about. Older
  # epochs already EXPIRE'd.
  epoch = ::Time.now.to_i / interval_seconds
  ["lmtr-b:#{@name}:#{epoch}"]
end

#statusObject

used = this period's count; limit = count; reset_at = the next cardinal boundary, when the counter rolls back to zero (#16).



26
27
28
# File 'lib/wurk/limiter/bucket.rb', line 26

def status
  build_status(used: size, limit: @options[:count], reset_at: next_boundary)
end

#typeObject



11
# File 'lib/wurk/limiter/bucket.rb', line 11

def type = :bucket

#within_limit(used: 1, &block) ⇒ Object

Raises:

  • (ArgumentError)


30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/wurk/limiter/bucket.rb', line 30

def within_limit(used: 1, &block)
  raise ArgumentError, 'block required' unless block

  deadline = ::Time.now.to_f + @options[:wait_timeout]
  loop do
    ok, _current, secs_to_next = acquire(used)
    return block.call if ok.to_i == 1

    remaining = deadline - ::Time.now.to_f
    raise OverLimit, self if remaining <= 0

    sleep [remaining, secs_to_next.to_f, 0.05].compact.min.clamp(0.0, remaining)
  end
end