Module: Wurk::Cron
- Defined in:
- lib/wurk/cron.rb
Overview
Sidekiq Enterprise periodic jobs. Pure leader-driven cron — only the elected leader enqueues per tick; followers run nothing. No backfill on restart. DST-aware via per-loop timezone. In-tree crontab parser (no fugit dependency) supporting 5-field expressions plus the standard ‘@hourly` / `@daily` / `@weekly` / `@monthly` / `@yearly` aliases.
Spec: docs/target/sidekiq-ent.md §2.
Layout:
* `Cron::Parser` — crontab → wall-clock match + `next_fire_at`. Walks
forward minute-by-minute in the loop's TZ; DST gaps are skipped
naturally because the wall-clock components advance past them.
* `Cron::Loop` — one registered job. Identity = SHA1(schedule+klass+opts)
so a re-registration of the same loop is idempotent.
* `Cron::Manager` — registration DSL. `mgr.register(cron, klass, **opts)`
with `tz=` mass-setter. Writes to Redis (`periodic` SET + `loops:{lid}`
HASH).
* `Cron::LoopSet` — Enumerable view (`each`/`size`/`fetch(lid)`).
* `Cron::ConfigTester` — boot-time validator. Verifies cron syntax and
that every worker class constant resolves.
* `Cron::Poller` — once-per-minute tick loop. Only the cluster leader
(`Component#leader?` / `dear-leader`) enqueues; non-leaders return
early without iterating loops.
Wire-compat: ‘periodic`, `loops:Cron.lid`, `loop-history:Cron.lid` per docs/target/sidekiq-ent.md §2.7. Periodic enqueue is gated by the single cluster leader (§6, `Component#leader?`) rather than a separate `cron-leader` lock — see the §2.7 divergence note.
Defined Under Namespace
Classes: ConfigTester, Loop, LoopSet, Manager, Parser, Poller
Constant Summary collapse
- PERIODIC_KEY =
'periodic'- LOOP_PREFIX =
'loops:'- HISTORY_PREFIX =
'loop-history:'- HISTORY_CAP =
25- DEFAULT_TICK_SECONDS =
60- MISSED_TICK_THRESHOLD =
90
Class Method Summary collapse
-
.fire!(lid) ⇒ Object
Test/ops helper: fire one registered loop immediately, ignoring the leader gate and the schedule due-check.
- .jobs ⇒ Object
-
.lid(schedule, klass, options) ⇒ Object
Stable 16-hex lid from (schedule, klass, options).
- .persist(loop_obj) ⇒ Object
-
.register(name, cron, worker_class, args = [], **opts) ⇒ Object
Task-stated convenience signature.
-
.reset! ⇒ Object
Test helper: wipe every Cron Redis key.
-
.unregister(lid) ⇒ Object
Drop a loop entirely.
Class Method Details
.fire!(lid) ⇒ Object
Test/ops helper: fire one registered loop immediately, ignoring the leader gate and the schedule due-check. Records history + advances the fire marks just like a leader tick, so specs can assert on the enqueue and history deterministically without waiting on wall-clock or stubbing leadership. Returns the enqueued jid, or nil for an unknown lid. Aliased as ‘Sidekiq::Periodic.fire!`.
626 627 628 629 630 631 |
# File 'lib/wurk/cron.rb', line 626 def fire!(lid) loop_obj = LoopSet.new.fetch(lid) return nil if loop_obj.nil? Poller.new(Wurk.configuration).fire(loop_obj) end |
.lid(schedule, klass, options) ⇒ Object
Stable 16-hex lid from (schedule, klass, options). Re-registering the same triple no-ops because the Redis writes overwrite under the same key.
571 572 573 574 |
# File 'lib/wurk/cron.rb', line 571 def lid(schedule, klass, ) opts = .is_a?(Hash) ? : {} ::Digest::SHA1.hexdigest("#{schedule}|#{klass}|#{JSON.dump(opts.sort.to_h)}")[0, 16] end |
.persist(loop_obj) ⇒ Object
586 587 588 589 590 591 592 |
# File 'lib/wurk/cron.rb', line 586 def persist(loop_obj) Wurk.redis do |c| c.call('SADD', PERIODIC_KEY, loop_obj.lid) c.call('HSET', "#{LOOP_PREFIX}#{loop_obj.lid}", *loop_obj.to_redis_hash.flatten) end loop_obj end |
.register(name, cron, worker_class, args = [], **opts) ⇒ Object
Task-stated convenience signature. ‘name` is treated as a label; the lid is still derived from (schedule, klass, opts) so the call is idempotent. Callers that want the Sidekiq DSL should use `Manager#register` via `config.periodic { |mgr| … }`.
580 581 582 583 584 |
# File 'lib/wurk/cron.rb', line 580 def register(name, cron, worker_class, args = [], **opts) merged = opts.merge(args: args) merged[:label] = name if name Loop.new(schedule: cron, klass: worker_class.to_s, options: merged).tap { |lp| persist(lp) } end |
.reset! ⇒ Object
Test helper: wipe every Cron Redis key. Production code must not call this — it removes every registered loop in the cluster.
606 607 608 609 610 611 612 613 614 |
# File 'lib/wurk/cron.rb', line 606 def reset! Wurk.redis do |c| lids = c.call('SMEMBERS', PERIODIC_KEY) lids.each do |lid| c.call('DEL', "#{LOOP_PREFIX}#{lid}", "#{HISTORY_PREFIX}#{lid}") end c.call('DEL', PERIODIC_KEY) end end |
.unregister(lid) ⇒ Object
Drop a loop entirely. Used by the Web UI delete action and by the config-reload path so a removed ‘register(…)` line vanishes from Redis on next boot.
597 598 599 600 601 602 |
# File 'lib/wurk/cron.rb', line 597 def unregister(lid) Wurk.redis do |c| c.call('SREM', PERIODIC_KEY, lid) c.call('DEL', "#{LOOP_PREFIX}#{lid}", "#{HISTORY_PREFIX}#{lid}") end end |