Class: Wurk::Limiter::Base

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

Overview

Shared base for every limiter type. Holds the public introspection contract documented in §1.5 — name / type / options / size / status / reset / delete plus the within_limit(...) { ... } block. Subclasses override the acquire path and the per-type metric/size methods.

status is uniform across types (#16): { used:, limit:, reset_at:, available? }. Subclasses supply the three values via build_status; Concurrent additionally merges its metric counters.

Direct Known Subclasses

Bucket, Concurrent, Leaky, Points, Window

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, register: true, **options) ⇒ Base

register: defaults true so constructing a limiter publishes its metadata + lmtr-list membership. The dashboard reconstructs limiters purely to read status on a GET, so it passes register: false to keep introspection side-effect-free.

Raises:

  • (ArgumentError)


24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/wurk/limiter/base.rb', line 24

def initialize(name, register: true, **options)
  unless name.is_a?(String) && NAME_PATTERN.match?(name)
    raise ArgumentError, "limiter name must match #{NAME_PATTERN.inspect} (got #{name.inspect})"
  end

  ttl = options[:ttl] || DEFAULT_TTL
  # Spec §1.2: ttl floor of 24h. Anything tighter risks losing the
  # metadata hash mid-job and orphaning slots that read it.
  raise ArgumentError, 'ttl must be >= 86_400' if ttl < 86_400

  @name = name.dup.freeze
  @options = options
  register! if register
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



18
19
20
# File 'lib/wurk/limiter/base.rb', line 18

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



18
19
20
# File 'lib/wurk/limiter/base.rb', line 18

def options
  @options
end

Instance Method Details

#build_status(used:, limit:, reset_at:) ⇒ Object (protected)

Assemble the uniform status hash. available? is derived: an unlimited (nil) limit is always available; otherwise headroom remains while used < limit. reset_at is an epoch-seconds Float (or nil when the type has no clock-driven reset).



84
85
86
87
# File 'lib/wurk/limiter/base.rb', line 84

def build_status(used:, limit:, reset_at:)
  { used: used, limit: limit, reset_at: reset_at,
    available?: limit.nil? || used < limit }
end

#deleteObject



63
64
65
66
67
68
# File 'lib/wurk/limiter/base.rb', line 63

def delete
  Wurk::Limiter.redis do |c|
    (state_keys + [meta_key]).each { |k| c.call('DEL', k) }
    c.call('SREM', LIST_KEY, @name)
  end
end

#fingerprintObject

Stable fingerprint for the limiter — Sidekiq 8.0+ switched from SHA1 → SHA256 (~10% larger encoding). Web UI groups limiters by this so that interpolated names (stripe-#{user_id}) get a single row per shape.



74
75
76
# File 'lib/wurk/limiter/base.rb', line 74

def fingerprint
  @fingerprint ||= Digest::SHA256.hexdigest("#{type}|#{@name}|#{JSON.dump(serializable_options)}")
end

#lua(name, keys:, argv:) ⇒ Object (protected)



131
132
133
134
135
# File 'lib/wurk/limiter/base.rb', line 131

def lua(name, keys:, argv:)
  Wurk::Limiter.redis do |c|
    Wurk::Lua::Loader.eval_cached(c, name, keys: keys, argv: argv)
  end
end

#meta_keyObject (protected)



89
90
91
# File 'lib/wurk/limiter/base.rb', line 89

def meta_key
  "lmtr:#{@name}"
end

#random_idObject (protected)

Stable random slot id (Concurrent) / nonce (Window). 16 hex chars

8 bytes — wide enough to avoid collision under any realistic

burst and short enough to keep ZSET memory bounded.



127
128
129
# File 'lib/wurk/limiter/base.rb', line 127

def random_id
  SecureRandom.hex(8)
end

#register!Object (protected)



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/wurk/limiter/base.rb', line 107

def register!
  Wurk::Limiter.redis do |c|
    c.call('SADD', LIST_KEY, @name)
    c.call(
      'HSET', meta_key,
      'type', type.to_s,
      'options', JSON.dump(serializable_options),
      'fingerprint', fingerprint
    )
    c.call('EXPIRE', meta_key, @options[:ttl])
  end
end

#resetObject



57
58
59
60
61
# File 'lib/wurk/limiter/base.rb', line 57

def reset
  Wurk::Limiter.redis do |c|
    state_keys.each { |k| c.call('DEL', k) }
  end
end

#serializable_optionsObject (protected)



97
98
99
100
101
102
103
104
105
# File 'lib/wurk/limiter/base.rb', line 97

def serializable_options
  @options.transform_values do |v|
    case v
    when Proc then '<proc>'
    when Symbol, Numeric, String, true, false, nil then v
    else v.to_s
    end
  end
end

#sizeObject



47
48
49
# File 'lib/wurk/limiter/base.rb', line 47

def size
  0
end

#state_keysObject (protected)



93
94
95
# File 'lib/wurk/limiter/base.rb', line 93

def state_keys
  []
end

#statusObject

Uniform across types (#16). Subclasses override to fill in real numbers; the default reports an idle, unlimited shape.



53
54
55
# File 'lib/wurk/limiter/base.rb', line 53

def status
  build_status(used: 0, limit: nil, reset_at: nil)
end

#ttlObject (protected)



120
121
122
# File 'lib/wurk/limiter/base.rb', line 120

def ttl
  @options[:ttl]
end

#typeObject

Raises:

  • (NotImplementedError)


39
40
41
# File 'lib/wurk/limiter/base.rb', line 39

def type
  raise NotImplementedError
end

#within_limitObject

Raises:

  • (NotImplementedError)


43
44
45
# File 'lib/wurk/limiter/base.rb', line 43

def within_limit(**, &)
  raise NotImplementedError
end