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)


219
220
221
222
223
224
225
226
227
228
# File 'lib/wurk/cron.rb', line 219

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.



217
218
219
# File 'lib/wurk/cron.rb', line 217

def klass
  @klass
end

#lidObject (readonly)

Returns the value of attribute lid.



217
218
219
# File 'lib/wurk/cron.rb', line 217

def lid
  @lid
end

#optionsObject (readonly)

Returns the value of attribute options.



217
218
219
# File 'lib/wurk/cron.rb', line 217

def options
  @options
end

#scheduleObject (readonly)

Returns the value of attribute schedule.



217
218
219
# File 'lib/wurk/cron.rb', line 217

def schedule
  @schedule
end

#tzObject (readonly)

Returns the value of attribute tz.



217
218
219
# File 'lib/wurk/cron.rb', line 217

def tz
  @tz
end

Class Method Details

.from_redis(lid, hash) ⇒ Object



324
325
326
327
328
329
330
# File 'lib/wurk/cron.rb', line 324

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



242
243
244
# File 'lib/wurk/cron.rb', line 242

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

#historyObject



250
251
252
253
254
255
# File 'lib/wurk/cron.rb', line 250

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)


312
313
314
# File 'lib/wurk/cron.rb', line 312

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.



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

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



305
306
307
# File 'lib/wurk/cron.rb', line 305

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



297
298
299
300
301
302
303
# File 'lib/wurk/cron.rb', line 297

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



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

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

#parserObject



230
231
232
# File 'lib/wurk/cron.rb', line 230

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

#paused?Boolean

Returns:

  • (Boolean)


234
235
236
# File 'lib/wurk/cron.rb', line 234

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

#queueObject



238
239
240
# File 'lib/wurk/cron.rb', line 238

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

#retry_valueObject



246
247
248
# File 'lib/wurk/cron.rb', line 246

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

#to_redis_hashObject



273
274
275
276
277
278
279
280
281
# File 'lib/wurk/cron.rb', line 273

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



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

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