Class: Wurk::Cron::Loop

Inherits:
Object
  • Object
show all
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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(schedule:, klass:, options: {}, tz: nil, lid: nil) ⇒ Loop

Returns a new instance of Loop.

Raises:

  • (ArgumentError)


261
262
263
264
265
266
267
268
269
270
# File 'lib/wurk/cron.rb', line 261

def initialize(schedule:, klass:, options: {}, tz: nil, lid: nil)
  raise ArgumentError, 'klass must be a String' unless klass.is_a?(String) && !klass.empty?

  @schedule = schedule
  @klass = klass
  @options = stringify_options(options)
  @tz = tz
  @parser = Parser.new(schedule)
  @lid = lid || Cron.lid(schedule, klass, @options)
end

Instance Attribute Details

#klassObject (readonly)

Returns the value of attribute klass.



259
260
261
# File 'lib/wurk/cron.rb', line 259

def klass
  @klass
end

#lidObject (readonly)

Returns the value of attribute lid.



259
260
261
# File 'lib/wurk/cron.rb', line 259

def lid
  @lid
end

#optionsObject (readonly)

Returns the value of attribute options.



259
260
261
# File 'lib/wurk/cron.rb', line 259

def options
  @options
end

#scheduleObject (readonly)

Returns the value of attribute schedule.



259
260
261
# File 'lib/wurk/cron.rb', line 259

def schedule
  @schedule
end

#tzObject (readonly)

Returns the value of attribute tz.



259
260
261
# File 'lib/wurk/cron.rb', line 259

def tz
  @tz
end

Class Method Details

.from_redis(lid, hash) ⇒ Object



366
367
368
369
370
371
372
# File 'lib/wurk/cron.rb', line 366

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']) : {}
  opts['paused'] = '1' if h['paused'] == '1'
  tz = h['tz'].to_s.empty? ? nil : h['tz']
  new(lid: lid, schedule: h['schedule'], klass: h['klass'], options: opts, tz: tz)
end

Instance Method Details

#argsObject



284
285
286
# File 'lib/wurk/cron.rb', line 284

def args
  Array(@options['args'])
end

#historyObject



292
293
294
295
296
297
# File 'lib/wurk/cron.rb', line 292

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.

Returns:

  • (Boolean)


354
355
356
# File 'lib/wurk/cron.rb', line 354

def hourly?
  parser.fields[1].size == 24
end

#last_fired_atObject

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.



302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/wurk/cron.rb', line 302

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



347
348
349
# File 'lib/wurk/cron.rb', line 347

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).



339
340
341
342
343
344
345
# File 'lib/wurk/cron.rb', line 339

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



325
326
327
# File 'lib/wurk/cron.rb', line 325

def next_fire_at(from_epoch = ::Time.now.to_i)
  parser.next_fire_at(from_epoch, @tz)
end

#parserObject



272
273
274
# File 'lib/wurk/cron.rb', line 272

def parser
  @parser ||= Parser.new(@schedule)
end

#paused?Boolean

Returns:

  • (Boolean)


276
277
278
# File 'lib/wurk/cron.rb', line 276

def paused?
  @options['paused'].to_s == '1' || @options['paused'] == true
end

#queueObject



280
281
282
# File 'lib/wurk/cron.rb', line 280

def queue
  @options['queue'] || 'default'
end

#retry_valueObject



288
289
290
# File 'lib/wurk/cron.rb', line 288

def retry_value
  @options.fetch('retry', true)
end

#to_redis_hashObject



315
316
317
318
319
320
321
322
323
# File 'lib/wurk/cron.rb', line 315

def to_redis_hash
  {
    'schedule' => @schedule,
    'klass' => @klass,
    'options' => Wurk.dump_json(@options),
    'tz' => tz_name.to_s,
    'paused' => paused? ? '1' : '0'
  }
end

#tz_nameObject



358
359
360
361
362
363
364
# File 'lib/wurk/cron.rb', line 358

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