Class: Wurk::Limiter::Points

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

Overview

Token-bucket with explicit ‘estimate:` per call. Refills at `refill_per_second` capped at `initial_points`. Failure mode is immediate (spec §1.4 — no sleep loop). The block is invoked with a `Handle` so user code may refund/over-charge via `handle.points_used`.

Defined Under Namespace

Classes: Handle

Instance Attribute Summary

Attributes inherited from Base

#name, #options

Instance Method Summary collapse

Methods inherited from Base

#delete, #fingerprint, #initialize, #reset

Constructor Details

This class inherits a constructor from Wurk::Limiter::Base

Instance Method Details

#sizeObject

Apply refill on read so the size matches what the next acquire would see. Stored balance only updates on acquire/refund; without this, a fully-refilled bucket reports stale low numbers.



31
32
33
34
35
36
37
38
39
40
# File 'lib/wurk/limiter/points.rb', line 31

def size
  cap = @options[:initial].to_f
  data = Wurk::Limiter.redis { |c| c.call('HMGET', state_key, 'points', 'last') }
  stored = data[0]
  return cap if stored.nil?

  last = (data[1] || ::Time.now.to_f).to_f
  elapsed = [::Time.now.to_f - last, 0.0].max
  [cap, stored.to_f + (elapsed * @options[:refill].to_f)].min
end

#statusObject

used = points consumed (cap − available); limit = the cap; reset_at = when the bucket refills to full, or nil when already full (#16).



44
45
46
47
48
49
50
51
# File 'lib/wurk/limiter/points.rb', line 44

def status
  cap = @options[:initial].to_f
  available = size
  used = cap - available
  refill = @options[:refill].to_f
  reset_at = available < cap && refill.positive? ? ::Time.now.to_f + ((cap - available) / refill) : nil
  build_status(used: used, limit: cap, reset_at: reset_at)
end

#typeObject



26
# File 'lib/wurk/limiter/points.rb', line 26

def type = :points

#within_limit(estimate:, &block) ⇒ Object

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
62
# File 'lib/wurk/limiter/points.rb', line 53

def within_limit(estimate:, &block)
  raise ArgumentError, 'block required' unless block
  raise ArgumentError, 'estimate must be positive' if estimate <= 0

  ok, _remaining = acquire(estimate)
  raise OverLimit, self unless ok.to_i == 1

  handle = Handle.new(self, estimate)
  block.call(handle)
end