Class: Wurk::Limiter::Base
- Inherits:
-
Object
- Object
- Wurk::Limiter::Base
- Defined in:
- lib/wurk/limiter/base.rb
Overview
Shared base for every limiter type: name / options / reset / delete /
fingerprint and the metadata registration. Each subclass supplies the
rest of the §1.5 introspection contract — type, size, status,
within_limit(...) { ... } — plus the state_keys reset/delete clear.
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
Constant Summary collapse
- MIN_TTL =
86_400
Instance Attribute Summary collapse
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
-
#build_status(used:, limit:, reset_at:) ⇒ Object
protected
Assemble the uniform status hash.
- #delete ⇒ Object
-
#fingerprint ⇒ Object
Stable fingerprint for the limiter — Sidekiq 8.0+ switched from SHA1 → SHA256 (~10% larger encoding).
-
#initialize(name, register: true, **options) ⇒ Base
constructor
register:defaults true so constructing a limiter publishes its metadata +lmtr-listmembership. - #lua(name, keys:, argv:) ⇒ Object protected
- #meta_key ⇒ Object protected
-
#random_id ⇒ Object
protected
Stable random slot id (Concurrent) / nonce (Window).
-
#register! ⇒ Object
protected
Metadata HSET + EXPIRE +
lmtr-listSADD in a single round trip. -
#register_or_defer ⇒ Object
protected
Limiters are commonly built at class load, and a Redis that is LOADING, out of memory or briefly gone must not turn
requireinto a process exit. - #reset ⇒ Object
- #serializable_options ⇒ Object protected
- #ttl ⇒ Object protected
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.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/wurk/limiter/base.rb', line 26 def initialize(name, register: true, **) unless name.is_a?(String) && NAME_PATTERN.match?(name) raise ArgumentError, "limiter name must match #{NAME_PATTERN.inspect} (got #{name.inspect})" end # Spec §1.2: ttl floor of 24h. Anything tighter risks losing the # metadata hash mid-job and orphaning slots that read it. Raised to # the floor rather than rejected: Sidekiq Ent accepts any ttl, so an # app passing a short one must keep booting after the gem swap. ttl = [[:ttl] || DEFAULT_TTL, MIN_TTL].max @name = name.dup.freeze @options = .merge(ttl: ttl) @register_pending = false register_or_defer if register end |
Instance Attribute Details
#name ⇒ Object (readonly)
Returns the value of attribute name.
20 21 22 |
# File 'lib/wurk/limiter/base.rb', line 20 def name @name end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
20 21 22 |
# File 'lib/wurk/limiter/base.rb', line 20 def @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).
70 71 72 73 |
# File 'lib/wurk/limiter/base.rb', line 70 def build_status(used:, limit:, reset_at:) { used: used, limit: limit, reset_at: reset_at, available?: limit.nil? || used < limit } end |
#delete ⇒ Object
49 50 51 52 53 54 |
# File 'lib/wurk/limiter/base.rb', line 49 def delete Wurk::Limiter.redis do |c| (state_keys + []).each { |k| c.call('DEL', k) } c.call('SREM', LIST_KEY, @name) end end |
#fingerprint ⇒ Object
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.
60 61 62 |
# File 'lib/wurk/limiter/base.rb', line 60 def fingerprint @fingerprint ||= Digest::SHA256.hexdigest("#{type}|#{@name}|#{JSON.dump()}") end |
#lua(name, keys:, argv:) ⇒ Object (protected)
122 123 124 125 126 127 128 129 130 |
# File 'lib/wurk/limiter/base.rb', line 122 def lua(name, keys:, argv:) if @register_pending @register_pending = false register_or_defer end Wurk::Limiter.redis do |c| Wurk::Lua::Loader.eval_cached(c, name, keys: keys, argv: argv) end end |
#meta_key ⇒ Object (protected)
75 76 77 |
# File 'lib/wurk/limiter/base.rb', line 75 def "lmtr:#{@name}" end |
#random_id ⇒ Object (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.
118 119 120 |
# File 'lib/wurk/limiter/base.rb', line 118 def random_id SecureRandom.hex(8) end |
#register! ⇒ Object (protected)
Metadata HSET + EXPIRE + lmtr-list SADD in a single round trip. The
script also gates the SADD on the metadata being newly written, so a
per-job construction (stripe-#{user_id}) stops rewriting a set that
grows one member per name — see limiter_register.lua for why that gate
is only safe while it is atomic with the HSET.
94 95 96 97 98 |
# File 'lib/wurk/limiter/base.rb', line 94 def register! lua(:limiter_register, keys: [, LIST_KEY], argv: [@name, type.to_s, JSON.dump(), fingerprint, ttl]) end |
#register_or_defer ⇒ Object (protected)
Limiters are commonly built at class load, and a Redis that is LOADING, out
of memory or briefly gone must not turn require into a process exit. The
metadata is only what the dashboard lists; the limit itself lives in the
limiter's own keys, so the next operation can finish the job.
104 105 106 107 108 109 |
# File 'lib/wurk/limiter/base.rb', line 104 def register_or_defer register! rescue ::RedisClient::Error, ::ConnectionPool::TimeoutError => e @register_pending = true Wurk.logger.warn { "Wurk limiter #{@name.inspect} not registered yet (#{e.class}: #{e.}); will retry" } end |
#reset ⇒ Object
43 44 45 46 47 |
# File 'lib/wurk/limiter/base.rb', line 43 def reset Wurk::Limiter.redis do |c| state_keys.each { |k| c.call('DEL', k) } end end |
#serializable_options ⇒ Object (protected)
79 80 81 82 83 84 85 86 87 |
# File 'lib/wurk/limiter/base.rb', line 79 def @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 |
#ttl ⇒ Object (protected)
111 112 113 |
# File 'lib/wurk/limiter/base.rb', line 111 def ttl @options[:ttl] end |