Class: Wurk::Cron::Loop
- Inherits:
-
Object
- Object
- Wurk::Cron::Loop
- Defined in:
- lib/wurk/cron.rb
Overview
One registered loop. Carries identity, schedule, options, and the
cached parser. Immutable after register! — re-registering the same
(schedule, klass, options) triple no-ops.
Instance Attribute Summary collapse
-
#fire_marks ⇒ Object
The raw
lf/nfstrings of the hash this loop was read from, so a tick decides on the read it already made; nil for a loop built in code. -
#klass ⇒ Object
readonly
Returns the value of attribute klass.
-
#lid ⇒ Object
readonly
Returns the value of attribute lid.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#schedule ⇒ Object
readonly
Returns the value of attribute schedule.
-
#tz ⇒ Object
readonly
Returns the value of attribute tz.
Class Method Summary collapse
Instance Method Summary collapse
-
#args ⇒ Object
args:is the static argument list splatted into perform. - #history ⇒ Object
-
#hourly? ⇒ Boolean
True when the schedule fires every hour (hour field is
*/ all 24). -
#initialize(schedule:, klass:, options: {}, tz: nil, lid: nil, paused: nil) ⇒ Loop
constructor
paused:is the loop HASH'spausedfield when read back from Redis — the runtime owns it (Web UI pause/unpause, ent §2.4). -
#job_item ⇒ Object
The payload one tick pushes.
-
#last_fired_at ⇒ Object
Epoch of the most recent fire, or nil if this loop has never run.
- #local_components(epoch) ⇒ Object
-
#next_fire_after(fired_slot, advance_from = fired_slot) ⇒ Object
Next scheduled fire after firing at
fired_slot. - #next_fire_at(from_epoch = ::Time.now.to_i) ⇒ Object
- #parser ⇒ Object
- #paused? ⇒ Boolean
-
#queue ⇒ Object
The queue a tick lands in: the loop's own
queue:, else the worker'ssidekiq_optionsqueue, asperform_asyncwould pick (ent §2.2). -
#retry_value ⇒ Object
The loop's
retry:, else the worker's, else Sidekiq's defaulttrue. - #to_redis_hash ⇒ Object
- #tz_name ⇒ Object
-
#worker_class ⇒ Object
The worker constant, or nil when it is not loadable in this process.
Constructor Details
#initialize(schedule:, klass:, options: {}, tz: nil, lid: nil, paused: nil) ⇒ Loop
paused: is the loop HASH's paused field when read back from Redis —
the runtime owns it (Web UI pause/unpause, ent §2.4). A code-declared
paused: option only seeds that field at first registration, so it
must not keep the loop paused once the field says otherwise.
301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/wurk/cron.rb', line 301 def initialize(schedule:, klass:, options: {}, tz: nil, lid: nil, paused: nil) raise ArgumentError, 'klass must be a String' unless klass.is_a?(String) && !klass.empty? @schedule = schedule @klass = klass @options = () @tz = tz @parser = Parser.cached(schedule) @lid = lid || Cron.lid(schedule, klass, @options) @paused = paused.nil? ? [true, '1', 1].include?(@options['paused']) : paused @fire_marks = nil end |
Instance Attribute Details
#fire_marks ⇒ Object
The raw lf / nf strings of the hash this loop was read from, so a
tick decides on the read it already made; nil for a loop built in code.
316 317 318 |
# File 'lib/wurk/cron.rb', line 316 def fire_marks @fire_marks end |
#klass ⇒ Object (readonly)
Returns the value of attribute klass.
295 296 297 |
# File 'lib/wurk/cron.rb', line 295 def klass @klass end |
#lid ⇒ Object (readonly)
Returns the value of attribute lid.
295 296 297 |
# File 'lib/wurk/cron.rb', line 295 def lid @lid end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
295 296 297 |
# File 'lib/wurk/cron.rb', line 295 def @options end |
#schedule ⇒ Object (readonly)
Returns the value of attribute schedule.
295 296 297 |
# File 'lib/wurk/cron.rb', line 295 def schedule @schedule end |
#tz ⇒ Object (readonly)
Returns the value of attribute tz.
295 296 297 |
# File 'lib/wurk/cron.rb', line 295 def tz @tz end |
Class Method Details
.from_redis(lid, hash) ⇒ Object
444 445 446 447 448 449 450 |
# File 'lib/wurk/cron.rb', line 444 def self.from_redis(lid, hash) h = hash.is_a?(Array) ? hash.each_slice(2).to_h : hash opts = h['options'] ? JSON.parse(h['options']) : {} tz = h['tz'].to_s.empty? ? nil : h['tz'] new(lid: lid, schedule: h['schedule'], klass: h['klass'], options: opts, tz: tz, paused: h['paused'] == '1') end |
Instance Method Details
#args ⇒ Object
args: is the static argument list splatted into perform. A bare value
(a Hash most often) is one argument — Array({ 'a' => 1 }) would turn
it into [['a', 1]].
335 336 337 338 339 340 |
# File 'lib/wurk/cron.rb', line 335 def args value = @options['args'] return [] if value.nil? value.is_a?(::Array) ? value : [value] end |
#history ⇒ Object
370 371 372 373 374 375 |
# File 'lib/wurk/cron.rb', line 370 def history Wurk.redis do |c| entries = c.call('LRANGE', "#{HISTORY_PREFIX}#{@lid}", 0, -1) entries.map { |e| JSON.parse(e) } end end |
#hourly? ⇒ Boolean
True when the schedule fires every hour (hour field is * / all 24).
Only such a schedule legitimately runs in both repeated fall-back hours;
a fixed-hour slot (even a multi-hour one like 0 1,2 * * *) must dedup.
432 433 434 |
# File 'lib/wurk/cron.rb', line 432 def hourly? parser.fields[1].size == 24 end |
#job_item ⇒ Object
The payload one tick pushes. Ent §2.2: a tick is klass.perform_async(*args)
with the loop's retry / queue laid over the worker's own options, so
the resolved class goes to the client — JobUtil#normalize_item merges its
get_sidekiq_options (unique_for, encrypt, expires_in, …) only for a
Class. Only the overrides the loop actually declares are set.
355 356 357 358 359 360 361 |
# File 'lib/wurk/cron.rb', line 355 def job_item worker = worker_class item = { 'class' => worker.respond_to?(:get_sidekiq_options) ? worker : @klass, 'args' => args } item['queue'] = @options['queue'] if @options.key?('queue') item['retry'] = @options['retry'] if @options.key?('retry') item end |
#last_fired_at ⇒ Object
Epoch of the most recent fire, or nil if this loop has never run. The
poller LPUSHes [fired_at, jid] tuples so index 0 is newest; we read
only that one entry instead of the whole history list.
380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/wurk/cron.rb', line 380 def last_fired_at raw = Wurk.redis { |c| c.call('LINDEX', "#{HISTORY_PREFIX}#{@lid}", 0) } return nil if raw.nil? entry = JSON.parse(raw) return nil unless entry.is_a?(Array) fired_at = entry[0] fired_at.is_a?(Numeric) ? fired_at.to_i : nil rescue JSON::ParserError nil end |
#local_components(epoch) ⇒ Object
425 426 427 |
# File 'lib/wurk/cron.rb', line 425 def local_components(epoch) parser.local_components(epoch, @tz) end |
#next_fire_after(fired_slot, advance_from = fired_slot) ⇒ Object
Next scheduled fire after firing at fired_slot. advance_from (the
poller passes now) is the search origin so missed ticks are not
backfilled. The DST guard: on a fall-back the clock rolls back, so the
same wall-clock minute recurs at a later UTC instant. That equality
alone isn't enough to skip — it would also drop a legitimate hourly run
whose repeated hour is real. So we only suppress the duplicate for a
FIXED daily slot; an hourly schedule (wildcard hour) keeps both fold
hours. A sub-hourly schedule (e.g. */30) lands on a different minute,
so the equality never even triggers. Spec: no DST double-fire without
skipping legitimate hourly runs (sidekiq-ent.md §2.6).
417 418 419 420 421 422 423 |
# File 'lib/wurk/cron.rb', line 417 def next_fire_after(fired_slot, advance_from = fired_slot) nxt = next_fire_at(advance_from) return nxt if nxt.nil? || local_components(nxt) != local_components(fired_slot) return nxt if hourly? # repeated fall-back hour is a genuine second run next_fire_at(nxt) # fixed daily slot: drop the fold duplicate end |
#next_fire_at(from_epoch = ::Time.now.to_i) ⇒ Object
403 404 405 |
# File 'lib/wurk/cron.rb', line 403 def next_fire_at(from_epoch = ::Time.now.to_i) parser.next_fire_at(from_epoch, @tz) end |
#parser ⇒ Object
318 319 320 |
# File 'lib/wurk/cron.rb', line 318 def parser @parser ||= Parser.cached(@schedule) end |
#paused? ⇒ Boolean
322 323 324 |
# File 'lib/wurk/cron.rb', line 322 def paused? @paused end |
#queue ⇒ Object
The queue a tick lands in: the loop's own queue:, else the worker's
sidekiq_options queue, as perform_async would pick (ent §2.2).
328 329 330 |
# File 'lib/wurk/cron.rb', line 328 def queue @options['queue'] || worker_option('queue') || 'default' end |
#retry_value ⇒ Object
The loop's retry:, else the worker's, else Sidekiq's default true.
343 344 345 346 347 348 |
# File 'lib/wurk/cron.rb', line 343 def retry_value return @options['retry'] if @options.key?('retry') worker_retry = worker_option('retry') worker_retry.nil? || worker_retry end |
#to_redis_hash ⇒ Object
393 394 395 396 397 398 399 400 401 |
# File 'lib/wurk/cron.rb', line 393 def to_redis_hash { 'schedule' => @schedule, 'klass' => @klass, 'options' => Wurk.dump_json(@options), 'tz' => tz_name.to_s, 'paused' => paused? ? '1' : '0' } end |
#tz_name ⇒ Object
436 437 438 439 440 441 442 |
# File 'lib/wurk/cron.rb', line 436 def tz_name return nil if @tz.nil? return @tz.name if @tz.respond_to?(:name) return @tz.identifier if @tz.respond_to?(:identifier) @tz.to_s end |
#worker_class ⇒ Object
The worker constant, or nil when it is not loadable in this process.
364 365 366 367 368 |
# File 'lib/wurk/cron.rb', line 364 def worker_class @klass.split('::').inject(::Object) { |mod, name| mod.const_get(name) } rescue ::NameError nil end |