Class: Rufus::Scheduler

Inherits:
Object
  • Object
show all
Defined in:
lib/rufus/scheduler.rb,
lib/rufus/scheduler/jobs.rb,
lib/rufus/scheduler/util.rb,
lib/rufus/scheduler/locks.rb,
lib/rufus/scheduler/zones.rb,
lib/rufus/scheduler/zotime.rb,
lib/rufus/scheduler/cronline.rb,
lib/rufus/scheduler/job_array.rb

Defined Under Namespace

Classes: AtJob, CronJob, CronLine, D, Error, EvInJob, EveryJob, FileLock, InJob, IntervalJob, Job, JobArray, NotRunningError, NullLock, OneTimeJob, RepeatJob, TimeoutError, ZoTime

Constant Summary collapse

VERSION =
'3.1.9'
MAX_WORK_THREADS =

MIN_WORK_THREADS = 3

28
DURATIONS2M =
[
  [ 'y', 365 * 24 * 3600 ],
  [ 'M', 30 * 24 * 3600 ],
  [ 'w', 7 * 24 * 3600 ],
  [ 'd', 24 * 3600 ],
  [ 'h', 3600 ],
  [ 'm', 60 ],
  [ 's', 1 ]
]
DURATIONS2 =
DURATIONS2M.dup
DURATIONS =
DURATIONS2M.inject({}) { |r, (k, v)| r[k] = v; r }
DURATION_LETTERS =
DURATIONS.keys.join
DU_KEYS =
DURATIONS2M.collect { |k, v| k.to_sym }
TIMEZONES =
%w[
GB NZ UCT EET CET PRC ROC WET GMT EST ROK UTC MST HST MET Zulu Cuba Iran W-SU
Eire GMT0 Libya Japan Egypt GMT+0 GMT-0 Israel Poland Navajo Turkey GB-Eire
Iceland PST8PDT Etc/UCT CST6CDT NZ-CHAT MST7MDT Jamaica EST5EDT Etc/GMT Etc/UTC
US/Samoa Etc/GMT0 Portugal Hongkong Etc/Zulu Singapore Asia/Baku Etc/GMT-9
Etc/GMT+1 Etc/GMT+0 Asia/Aden Etc/GMT+2 Etc/GMT+3 Etc/GMT+4 Etc/GMT+5 Etc/GMT+6
Etc/GMT+7 Etc/GMT+8 Etc/GMT+9 Etc/GMT-0 Etc/GMT-1 Universal Asia/Dili Greenwich
Asia/Gaza Etc/GMT-8 Etc/GMT-7 US/Alaska Asia/Oral Etc/GMT-6 Etc/GMT-5 Etc/GMT-4
Asia/Hovd Etc/GMT-3 US/Hawaii Etc/GMT-2 Kwajalein Asia/Omsk Asia/Macao
Etc/GMT-14 Asia/Kabul US/Central Etc/GMT-13 US/Arizona Asia/Macau Asia/Qatar
Asia/Seoul Asia/Tokyo Asia/Dubai US/Pacific Etc/GMT-12 Etc/GMT-11 Etc/GMT-10
Asia/Dhaka Asia/Dacca Asia/Chita Etc/GMT+12 Etc/GMT+10 Asia/Amman Asia/Aqtau
Etc/GMT+11 US/Eastern Asia/Thimbu Asia/Brunei Asia/Tehran Asia/Beirut
Europe/Rome Europe/Riga Brazil/Acre Brazil/East Europe/Oslo Brazil/West
Africa/Lome Asia/Taipei Asia/Saigon Asia/Riyadh Asia/Aqtobe Asia/Anadyr
Europe/Kiev Asia/Almaty Africa/Juba Pacific/Yap US/Aleutian Asia/Muscat
US/Mountain Asia/Harbin Asia/Hebron Asia/Manila Asia/Kuwait Asia/Urumqi
US/Michigan Indian/Mahe SystemV/EST5 Asia/Kashgar Indian/Cocos Asia/Jakarta
Asia/Kolkata Asia/Kuching America/Atka Asia/Irkutsk Pacific/Apia Asia/Magadan
Africa/Dakar America/Lima Pacific/Fiji Pacific/Guam Europe/Vaduz Pacific/Niue
Asia/Nicosia Africa/Ceuta Pacific/Truk America/Adak Pacific/Wake Africa/Tunis
Africa/Cairo Asia/Colombo SystemV/AST4 SystemV/CST6 Asia/Karachi Asia/Rangoon
SystemV/MST7 Asia/Baghdad Europe/Malta Africa/Lagos Europe/Minsk SystemV/PST8
Canada/Yukon Asia/Tbilisi America/Nome Asia/Bahrain Africa/Accra Europe/Paris
Asia/Bangkok Asia/Bishkek Asia/Thimphu SystemV/YST9 Asia/Yerevan Asia/Yakutsk
Europe/Sofia Asia/Ust-Nera Australia/ACT Australia/LHI Europe/Tirane
Asia/Tel_Aviv Australia/NSW Africa/Luanda Asia/Tashkent Africa/Lusaka
Asia/Shanghai Africa/Malabo Asia/Sakhalin Africa/Maputo Africa/Maseru
SystemV/HST10 Africa/Kigali Africa/Niamey Pacific/Samoa America/Sitka
Pacific/Palau Pacific/Nauru Pacific/Efate Asia/Makassar Pacific/Chuuk
Africa/Harare Africa/Douala America/Aruba America/Thule America/Bahia
America/Jujuy America/Belem Asia/Katmandu America/Boise Indian/Comoro
Indian/Chagos Asia/Jayapura Europe/Zurich Asia/Istanbul Europe/Zagreb
Etc/Greenwich Europe/Warsaw Europe/Vienna Etc/Universal Asia/Dushanbe
Europe/Athens Europe/Berlin Africa/Bissau Asia/Damascus Africa/Banjul
Europe/Dublin Africa/Bangui Africa/Bamako Europe/Jersey Africa/Asmera
Europe/Lisbon Africa/Asmara Europe/London Asia/Ashgabat Asia/Calcutta
Europe/Madrid Europe/Monaco Europe/Moscow Europe/Prague Europe/Samara
Europe/Skopje Asia/Khandyga Canada/Pacific Africa/Abidjan America/Manaus
Asia/Chongqing Asia/Chungking Africa/Algiers America/Maceio US/Pacific-New
Africa/Conakry America/La_Paz America/Juneau America/Nassau America/Inuvik
Europe/Andorra Africa/Kampala Asia/Ashkhabad Asia/Hong_Kong America/Havana
Canada/Eastern Europe/Belfast Canada/Central Australia/West Asia/Jerusalem
Africa/Mbabane Asia/Kamchatka America/Virgin America/Guyana Asia/Kathmandu
Mexico/General America/Panama Europe/Nicosia America/Denver Europe/Tallinn
Africa/Nairobi America/Dawson Europe/Vatican Europe/Vilnius America/Cuiaba
Africa/Tripoli Pacific/Wallis Atlantic/Faroe Pacific/Tarawa Pacific/Tahiti
Pacific/Saipan Pacific/Ponape America/Cayman America/Cancun Asia/Pontianak
Asia/Pyongyang Asia/Vientiane Asia/Qyzylorda Pacific/Noumea America/Bogota
Pacific/Midway Pacific/Majuro Asia/Samarkand Indian/Mayotte Pacific/Kosrae
Asia/Singapore Indian/Reunion America/Belize America/Regina America/Recife
Pacific/Easter Mexico/BajaSur America/Merida Pacific/Chatham Pacific/Fakaofo
Pacific/Gambier America/Rosario Asia/Ulan_Bator Indian/Maldives Pacific/Norfolk
America/Antigua Asia/Phnom_Penh America/Phoenix America/Caracas America/Cayenne
Atlantic/Azores Pacific/Pohnpei Atlantic/Canary America/Chicago Atlantic/Faeroe
Africa/Windhoek America/Cordoba America/Creston Africa/Timbuktu America/Curacao
Africa/Sao_Tome Africa/Ndjamena SystemV/AST4ADT Europe/Uzhgorod Europe/Tiraspol
SystemV/CST6CDT Africa/Monrovia America/Detroit Europe/Sarajevo Australia/Eucla
America/Tijuana America/Toronto America/Godthab America/Grenada Europe/Istanbul
America/Ojinaga America/Tortola Australia/Perth Europe/Helsinki Australia/South
Europe/Guernsey SystemV/EST5EDT Europe/Chisinau SystemV/MST7MDT Europe/Busingen
Europe/Budapest Europe/Brussels America/Halifax America/Mendoza America/Noronha
America/Nipigon Canada/Atlantic America/Yakutat SystemV/PST8PDT SystemV/YST9YDT
Canada/Mountain Africa/Kinshasa Africa/Khartoum Africa/Gaborone Africa/Freetown
America/Iqaluit America/Jamaica US/East-Indiana Africa/El_Aaiun America/Knox_IN
Africa/Djibouti Africa/Blantyre America/Moncton America/Managua Asia/Choibalsan
America/Marigot Australia/North Europe/Belgrade America/Resolute
America/Mazatlan Pacific/Funafuti Pacific/Auckland Pacific/Honolulu
Pacific/Johnston America/Miquelon America/Santarem Mexico/BajaNorte
America/Santiago Antarctica/Troll America/Asuncion America/Atikokan
America/Montreal America/Barbados Africa/Bujumbura Pacific/Pitcairn
Asia/Ulaanbaatar Indian/Mauritius America/New_York Antarctica/Syowa
America/Shiprock Indian/Kerguelen Asia/Novosibirsk America/Anguilla
Indian/Christmas Asia/Vladivostok Asia/Ho_Chi_Minh Antarctica/Davis
Atlantic/Bermuda Europe/Amsterdam Antarctica/Casey America/St_Johns
Atlantic/Madeira America/Winnipeg America/St_Kitts Europe/Volgograd
Brazil/DeNoronha Europe/Bucharest Africa/Mogadishu America/St_Lucia
Atlantic/Stanley Europe/Stockholm Australia/Currie Europe/Gibraltar
Australia/Sydney Asia/Krasnoyarsk Australia/Darwin America/Dominica
America/Edmonton America/Eirunepe Europe/Podgorica America/Ensenada
Europe/Ljubljana Australia/Hobart Europe/Mariehamn Africa/Lubumbashi
America/Goose_Bay Europe/Luxembourg America/Menominee America/Glace_Bay
America/Fortaleza Africa/Nouakchott America/Matamoros Pacific/Galapagos
America/Guatemala Pacific/Kwajalein Pacific/Marquesas America/Guayaquil
Asia/Kuala_Lumpur Europe/San_Marino America/Monterrey Europe/Simferopol
America/Araguaina Antarctica/Vostok Europe/Copenhagen America/Catamarca
Pacific/Pago_Pago America/Sao_Paulo America/Boa_Vista America/St_Thomas
Chile/Continental America/Vancouver Africa/Casablanca Europe/Bratislava
Pacific/Enderbury Pacific/Rarotonga Europe/Zaporozhye US/Indiana-Starke
Antarctica/Palmer Asia/Novokuznetsk Africa/Libreville America/Chihuahua
America/Anchorage Pacific/Tongatapu Antarctica/Mawson Africa/Porto-Novo
Asia/Yekaterinburg America/Paramaribo America/Hermosillo Atlantic/Jan_Mayen
Antarctica/McMurdo America/Costa_Rica Antarctica/Rothera America/Grand_Turk
Atlantic/Reykjavik Atlantic/St_Helena Australia/Victoria Chile/EasterIsland
Asia/Ujung_Pandang Australia/Adelaide America/Montserrat America/Porto_Acre
Africa/Brazzaville Australia/Brisbane America/Kralendijk America/Montevideo
America/St_Vincent America/Louisville Australia/Canberra Australia/Tasmania
Europe/Isle_of_Man Europe/Kaliningrad Africa/Ouagadougou America/Rio_Branco
Pacific/Kiritimati Africa/Addis_Ababa America/Metlakatla America/Martinique
Asia/Srednekolymsk America/Guadeloupe America/Fort_Wayne Australia/Lindeman
America/Whitehorse Arctic/Longyearbyen America/Pangnirtung America/Mexico_City
America/Los_Angeles America/Rainy_River Atlantic/Cape_Verde Pacific/Guadalcanal
Indian/Antananarivo America/El_Salvador Australia/Lord_Howe Africa/Johannesburg
America/Tegucigalpa Canada/Saskatchewan America/Thunder_Bay Canada/Newfoundland
America/Puerto_Rico America/Yellowknife Australia/Melbourne America/Porto_Velho
Australia/Queensland Australia/Yancowinna America/Santa_Isabel
America/Blanc-Sablon America/Scoresbysund America/Danmarkshavn
Pacific/Port_Moresby Antarctica/Macquarie America/Buenos_Aires
Africa/Dar_es_Salaam America/Campo_Grande America/Dawson_Creek
America/Indianapolis Pacific/Bougainville America/Rankin_Inlet
America/Indiana/Knox America/Lower_Princes America/Coral_Harbour
America/St_Barthelemy Australia/Broken_Hill America/Cambridge_Bay
America/Indiana/Vevay America/Swift_Current America/Port_of_Spain
Antarctica/South_Pole America/Santo_Domingo Atlantic/South_Georgia
America/Port-au-Prince America/Bahia_Banderas America/Indiana/Winamac
America/Indiana/Marengo America/Argentina/Jujuy America/Argentina/Salta
Canada/East-Saskatchewan America/Indiana/Vincennes America/Argentina/Tucuman
America/Argentina/Ushuaia Antarctica/DumontDUrville America/Indiana/Tell_City
America/Argentina/Mendoza America/Argentina/Cordoba America/Indiana/Petersburg
America/Argentina/San_Luis America/Argentina/San_Juan America/Argentina/La_Rioja
America/North_Dakota/Center America/Kentucky/Monticello
America/North_Dakota/Beulah America/Kentucky/Louisville
America/Argentina/Catamarca America/Indiana/Indianapolis
America/North_Dakota/New_Salem America/Argentina/Rio_Gallegos
America/Argentina/Buenos_Aires America/Argentina/ComodRivadavia
]
TIMEZONEs =
TIMEZONES.collect(&:downcase)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Scheduler

Returns a new instance of Scheduler.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/rufus/scheduler.rb', line 76

def initialize(opts={})

  @opts = opts

  @started_at = nil
  @paused = false

  @jobs = JobArray.new

  @frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
  @mutexes = {}

  @work_queue = Queue.new

  #@min_work_threads = opts[:min_work_threads] || MIN_WORK_THREADS
  @max_work_threads = opts[:max_work_threads] || MAX_WORK_THREADS

  @stderr = $stderr

  @thread_key = "rufus_scheduler_#{self.object_id}"

  @scheduler_lock =
    if lockfile = opts[:lockfile]
      Rufus::Scheduler::FileLock.new(lockfile)
    else
      opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
    end

  @trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new

  # If we can't grab the @scheduler_lock, don't run.
  @scheduler_lock.lock || return

  start
end

Instance Attribute Details

#frequencyObject

Returns the value of attribute frequency.



63
64
65
# File 'lib/rufus/scheduler.rb', line 63

def frequency
  @frequency
end

#max_work_threadsObject

attr_accessor :min_work_threads



70
71
72
# File 'lib/rufus/scheduler.rb', line 70

def max_work_threads
  @max_work_threads
end

#mutexesObject (readonly)

Returns the value of attribute mutexes.



67
68
69
# File 'lib/rufus/scheduler.rb', line 67

def mutexes
  @mutexes
end

#started_atObject (readonly)

Returns the value of attribute started_at.



64
65
66
# File 'lib/rufus/scheduler.rb', line 64

def started_at
  @started_at
end

#stderrObject

Returns the value of attribute stderr.



72
73
74
# File 'lib/rufus/scheduler.rb', line 72

def stderr
  @stderr
end

#threadObject (readonly)

Returns the value of attribute thread.



65
66
67
# File 'lib/rufus/scheduler.rb', line 65

def thread
  @thread
end

#thread_keyObject (readonly)

Returns the value of attribute thread_key.



66
67
68
# File 'lib/rufus/scheduler.rb', line 66

def thread_key
  @thread_key
end

#work_queueObject (readonly)

Returns the value of attribute work_queue.



74
75
76
# File 'lib/rufus/scheduler.rb', line 74

def work_queue
  @work_queue
end

Class Method Details

.h_to_s(t = Time.now) ⇒ Object

Produces a hour/min/sec/milli string representation of Time instance



293
294
295
296
# File 'lib/rufus/scheduler/util.rb', line 293

def self.h_to_s(t=Time.now)

  "#{t.strftime('%H:%M:%S')}.#{sprintf('%06d', t.usec)}"
end

.parse(o, opts = {}) ⇒ Object

– time and string methods ++



34
35
36
37
38
39
40
41
42
# File 'lib/rufus/scheduler/util.rb', line 34

def self.parse(o, opts={})

  opts[:no_error] = true

  parse_cron(o, opts) ||
  parse_in(o, opts) || # covers 'every' schedule strings
  parse_at(o, opts) ||
  raise(ArgumentError.new("couldn't parse \"#{o}\""))
end

.parse_at(o, opts = {}) ⇒ Object



49
50
51
52
53
54
55
56
57
# File 'lib/rufus/scheduler/util.rb', line 49

def self.parse_at(o, opts={})

  Rufus::Scheduler::ZoTime.parse(o, opts).time

rescue StandardError => se

  return nil if opts[:no_error]
  raise se
end

.parse_cron(o, opts) ⇒ Object



59
60
61
62
63
64
65
66
67
# File 'lib/rufus/scheduler/util.rb', line 59

def self.parse_cron(o, opts)

  CronLine.new(o)

rescue ArgumentError => ae

  return nil if opts[:no_error]
  raise ae
end

.parse_duration(string, opts = {}) ⇒ Object Also known as: parse_duration_string, parse_time_string

Turns a string like ‘1m10s’ into a float like ‘70.0’, more formally, turns a time duration expressed as a string into a Float instance (millisecond count).

w -> week d -> day h -> hour m -> minute s -> second M -> month y -> year ‘nada’ -> millisecond

Some examples:

Rufus::Scheduler.parse_duration "0.5"    # => 0.5
Rufus::Scheduler.parse_duration "500"    # => 0.5
Rufus::Scheduler.parse_duration "1000"   # => 1.0
Rufus::Scheduler.parse_duration "1h"     # => 3600.0
Rufus::Scheduler.parse_duration "1h10s"  # => 3610.0
Rufus::Scheduler.parse_duration "1w2d"   # => 777600.0

Negative time strings are OK (Thanks Danny Fullerton):

Rufus::Scheduler.parse_duration "-0.5"   # => -0.5
Rufus::Scheduler.parse_duration "-1h"    # => -3600.0

Raises:

  • (ArgumentError)


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rufus/scheduler/util.rb', line 126

def self.parse_duration(string, opts={})

  string = string.to_s

  return 0.0 if string == ''

  m = string.match(/^(-?)([\d\.#{DURATION_LETTERS}]+)$/)

  return nil if m.nil? && opts[:no_error]
  raise ArgumentError.new("cannot parse '#{string}'") if m.nil?

  mod = m[1] == '-' ? -1.0 : 1.0
  val = 0.0

  s = m[2]

  while s.length > 0
    m = nil
    if m = s.match(/^(\d+|\d+\.\d*|\d*\.\d+)([#{DURATION_LETTERS}])(.*)$/)
      val += m[1].to_f * DURATIONS[m[2]]
    elsif s.match(/^\d+$/)
      val += s.to_i
    elsif s.match(/^\d*\.\d*$/)
      val += s.to_f
    elsif opts[:no_error]
      return nil
    else
      raise ArgumentError.new(
        "cannot parse '#{string}' (especially '#{s}')"
      )
    end
    break unless m && m[3]
    s = m[3]
  end

  mod * val
end

.parse_in(o, opts = {}) ⇒ Object



44
45
46
47
# File 'lib/rufus/scheduler/util.rb', line 44

def self.parse_in(o, opts={})

  o.is_a?(String) ? parse_duration(o, opts) : o
end

.parse_to_time(o) ⇒ Object

Raises:

  • (ArgumentError)


69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/rufus/scheduler/util.rb', line 69

def self.parse_to_time(o)

  t = o
  t = parse(t) if t.is_a?(String)
  t = Time.now + t if t.is_a?(Numeric)

  raise ArgumentError.new(
    "cannot turn #{o.inspect} to a point in time, doesn't make sense"
  ) unless t.is_a?(Time)

  t
end

.s(opts = {}) ⇒ Object

Alias for Rufus::Scheduler.singleton



121
# File 'lib/rufus/scheduler.rb', line 121

def self.s(opts={}); singleton(opts); end

.singleton(opts = {}) ⇒ Object

Returns a singleton Rufus::Scheduler instance



114
115
116
117
# File 'lib/rufus/scheduler.rb', line 114

def self.singleton(opts={})

  @singleton ||= Rufus::Scheduler.new(opts)
end

.start_newObject

Releasing the gem would probably require redirecting .start_new to .new and emit a simple deprecation message.

For now, let’s assume the people pointing at rufus-scheduler/master on GitHub know what they do…



129
130
131
132
# File 'lib/rufus/scheduler.rb', line 129

def self.start_new

  fail "this is rufus-scheduler 3.0, use .new instead of .start_new"
end

.to_duration(seconds, options = {}) ⇒ Object Also known as: to_duration_string, to_time_string

Turns a number of seconds into a a time string

Rufus.to_duration 0                    # => '0s'
Rufus.to_duration 60                   # => '1m'
Rufus.to_duration 3661                 # => '1h1m1s'
Rufus.to_duration 7 * 24 * 3600        # => '1w'
Rufus.to_duration 30 * 24 * 3600 + 1   # => "4w2d1s"

It goes from seconds to the year. Months are not counted (as they are of variable length). Weeks are counted.

For 30 days months to be counted, the second parameter of this method can be set to true.

Rufus.to_duration 30 * 24 * 3600 + 1, true   # => "1M1s"

If a Float value is passed, milliseconds will be displayed without ‘marker’

Rufus.to_duration 0.051                       # => "51"
Rufus.to_duration 7.051                       # => "7s51"
Rufus.to_duration 0.120 + 30 * 24 * 3600 + 1  # => "4w2d1s120"

(this behaviour mirrors the one found for parse_time_string()).

Options are :

  • :months, if set to true, months (M) of 30 days will be taken into account when building up the result

  • :drop_seconds, if set to true, seconds and milliseconds will be trimmed from the result



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/rufus/scheduler/util.rb', line 205

def self.to_duration(seconds, options={})

  h = to_duration_hash(seconds, options)

  return (options[:drop_seconds] ? '0m' : '0s') if h.empty?

  s =
    DU_KEYS.inject('') { |r, key|
      count = h[key]
      count = nil if count == 0
      r << "#{count}#{key}" if count
      r
    }

  ms = h[:ms]
  s << ms.to_s if ms

  s
end

.to_duration_hash(seconds, options = {}) ⇒ Object

Turns a number of seconds (integer or Float) into a hash like in :

Rufus.to_duration_hash 0.051
  # => { :ms => "51" }
Rufus.to_duration_hash 7.051
  # => { :s => 7, :ms => "51" }
Rufus.to_duration_hash 0.120 + 30 * 24 * 3600 + 1
  # => { :w => 4, :d => 2, :s => 1, :ms => "120" }

This method is used by to_duration behind the scenes.

Options are :

  • :months, if set to true, months (M) of 30 days will be taken into account when building up the result

  • :drop_seconds, if set to true, seconds and milliseconds will be trimmed from the result



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/rufus/scheduler/util.rb', line 251

def self.to_duration_hash(seconds, options={})

  h = {}

  if seconds.is_a?(Float)
    h[:ms] = (seconds % 1 * 1000).to_i
    seconds = seconds.to_i
  end

  if options[:drop_seconds]
    h.delete(:ms)
    seconds = (seconds - seconds % 60)
  end

  durations = options[:months] ? DURATIONS2M : DURATIONS2

  durations.each do |key, duration|

    count = seconds / duration
    seconds = seconds % duration

    h[key.to_sym] = count if count > 0
  end

  h
end

.utc_to_s(t = Time.now) ⇒ Object

Produces the UTC string representation of a Time instance

like “2009/11/23 11:11:50.947109 UTC”



286
287
288
289
# File 'lib/rufus/scheduler/util.rb', line 286

def self.utc_to_s(t=Time.now)

  "#{t.utc.strftime('%Y-%m-%d %H:%M:%S')}.#{sprintf('%06d', t.usec)} UTC"
end

Instance Method Details

#at(time, callable = nil, opts = {}, &block) ⇒ Object

– scheduling methods ++



203
204
205
206
# File 'lib/rufus/scheduler.rb', line 203

def at(time, callable=nil, opts={}, &block)

  do_schedule(:once, time, callable, opts, opts[:job], block)
end

#at_jobs(opts = {}) ⇒ Object



308
309
310
311
# File 'lib/rufus/scheduler.rb', line 308

def at_jobs(opts={})

  jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
end

#confirm_lockObject

Callback called when a job is triggered. If the lock cannot be acquired, the job won’t run (though it’ll still be scheduled to run again if necessary).



376
377
378
379
# File 'lib/rufus/scheduler.rb', line 376

def confirm_lock

  @trigger_lock.lock
end

#cron(cronline, callable = nil, opts = {}, &block) ⇒ Object



243
244
245
246
# File 'lib/rufus/scheduler.rb', line 243

def cron(cronline, callable=nil, opts={}, &block)

  do_schedule(:cron, cronline, callable, opts, opts[:job], block)
end

#cron_jobs(opts = {}) ⇒ Object



328
329
330
331
# File 'lib/rufus/scheduler.rb', line 328

def cron_jobs(opts={})

  jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
end

#down?Boolean

Returns:

  • (Boolean)


174
175
176
177
# File 'lib/rufus/scheduler.rb', line 174

def down?

  ! @started_at
end

#every(duration, callable = nil, opts = {}, &block) ⇒ Object



223
224
225
226
# File 'lib/rufus/scheduler.rb', line 223

def every(duration, callable=nil, opts={}, &block)

  do_schedule(:every, duration, callable, opts, opts[:job], block)
end

#every_jobs(opts = {}) ⇒ Object



318
319
320
321
# File 'lib/rufus/scheduler.rb', line 318

def every_jobs(opts={})

  jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
end

#in(duration, callable = nil, opts = {}, &block) ⇒ Object



213
214
215
216
# File 'lib/rufus/scheduler.rb', line 213

def in(duration, callable=nil, opts={}, &block)

  do_schedule(:once, duration, callable, opts, opts[:job], block)
end

#in_jobs(opts = {}) ⇒ Object



313
314
315
316
# File 'lib/rufus/scheduler.rb', line 313

def in_jobs(opts={})

  jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
end

#interval(duration, callable = nil, opts = {}, &block) ⇒ Object



233
234
235
236
# File 'lib/rufus/scheduler.rb', line 233

def interval(duration, callable=nil, opts={}, &block)

  do_schedule(:interval, duration, callable, opts, opts[:job], block)
end

#interval_jobs(opts = {}) ⇒ Object



323
324
325
326
# File 'lib/rufus/scheduler.rb', line 323

def interval_jobs(opts={})

  jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
end

#job(job_id) ⇒ Object



333
334
335
336
# File 'lib/rufus/scheduler.rb', line 333

def job(job_id)

  @jobs[job_id]
end

#jobs(opts = {}) ⇒ Object

Returns all the scheduled jobs (even those right before re-schedule).



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/rufus/scheduler.rb', line 290

def jobs(opts={})

  opts = { opts => true } if opts.is_a?(Symbol)

  jobs = @jobs.to_a

  if opts[:running]
    jobs = jobs.select { |j| j.running? }
  elsif ! opts[:all]
    jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
  end

  tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
  jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }

  jobs
end

#joinObject



165
166
167
168
169
170
171
172
# File 'lib/rufus/scheduler.rb', line 165

def join

  fail NotRunningError.new(
    'cannot join scheduler that is not running'
  ) unless @thread

  @thread.join
end

#lockObject

Returns true if the scheduler has acquired the [exclusive] lock and thus may run.

Most of the time, a scheduler is run alone and this method should return true. It is useful in cases where among a group of applications only one of them should run the scheduler. For schedulers that should not run, the method should return false.

Out of the box, rufus-scheduler proposes the :lockfile => ‘path/to/lock/file’ scheduler start option. It makes it easy for schedulers on the same machine to determine which should run (the first to write the lockfile and lock it). It uses “man 2 flock” so it probably won’t work reliably on distributed file systems.

If one needs to use a special/different locking mechanism, the scheduler accepts :scheduler_lock => lock_object. lock_object only needs to respond to #lock and #unlock, and both of these methods should be idempotent.

Look at rufus/scheduler/locks.rb for an example.



359
360
361
362
# File 'lib/rufus/scheduler.rb', line 359

def lock

  @scheduler_lock.lock
end

#occurrences(time0, time1, format = :per_job) ⇒ Object



432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
# File 'lib/rufus/scheduler.rb', line 432

def occurrences(time0, time1, format=:per_job)

  h = {}

  jobs.each do |j|
    os = j.occurrences(time0, time1)
    h[j] = os if os.any?
  end

  if format == :timeline
    a = []
    h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
    a.sort_by { |(t, j)| t }
  else
    h
  end
end

#on_error(job, err) ⇒ Object



455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
# File 'lib/rufus/scheduler.rb', line 455

def on_error(job, err)

  pre = err.object_id.to_s

  ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }

  stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
  stderr.puts("  #{pre}   job:")
  stderr.puts("  #{pre}     #{job.class} #{job.original.inspect} #{job.opts.inspect}")
  # TODO: eventually use a Job#detail or something like that
  stderr.puts("  #{pre}   error:")
  stderr.puts("  #{pre}     #{err.object_id}")
  stderr.puts("  #{pre}     #{err.class}")
  stderr.puts("  #{pre}     #{err}")
  err.backtrace.each do |l|
    stderr.puts("  #{pre}       #{l}")
  end
  stderr.puts("  #{pre}   tz:")
  stderr.puts("  #{pre}     ENV['TZ']: #{ENV['TZ']}")
  stderr.puts("  #{pre}     Time.now: #{Time.now}")
  stderr.puts("  #{pre}   scheduler:")
  stderr.puts("  #{pre}     object_id: #{object_id}")
  stderr.puts("  #{pre}     opts:")
  stderr.puts("  #{pre}       #{@opts.inspect}")
  stderr.puts("  #{pre}       frequency: #{self.frequency}")
  stderr.puts("  #{pre}       scheduler_lock: #{@scheduler_lock.inspect}")
  stderr.puts("  #{pre}       trigger_lock: #{@trigger_lock.inspect}")
  stderr.puts("  #{pre}     uptime: #{uptime} (#{uptime_s})")
  stderr.puts("  #{pre}     down?: #{down?}")
  stderr.puts("  #{pre}     threads: #{self.threads.size}")
  stderr.puts("  #{pre}       thread: #{self.thread}")
  stderr.puts("  #{pre}       thread_key: #{self.thread_key}")
  stderr.puts("  #{pre}       work_threads: #{work_threads.size}")
  stderr.puts("  #{pre}         active: #{work_threads(:active).size}")
  stderr.puts("  #{pre}         vacant: #{work_threads(:vacant).size}")
  stderr.puts("  #{pre}         max_work_threads: #{max_work_threads}")
  stderr.puts("  #{pre}       mutexes: #{ms.inspect}")
  stderr.puts("  #{pre}     jobs: #{jobs.size}")
  stderr.puts("  #{pre}       at_jobs: #{at_jobs.size}")
  stderr.puts("  #{pre}       in_jobs: #{in_jobs.size}")
  stderr.puts("  #{pre}       every_jobs: #{every_jobs.size}")
  stderr.puts("  #{pre}       interval_jobs: #{interval_jobs.size}")
  stderr.puts("  #{pre}       cron_jobs: #{cron_jobs.size}")
  stderr.puts("  #{pre}     running_jobs: #{running_jobs.size}")
  stderr.puts("  #{pre}     work_queue: #{work_queue.size}")
  stderr.puts("} #{pre} .")

rescue => e

  stderr.puts("failure in #on_error itself:")
  stderr.puts(e.inspect)
  stderr.puts(e.backtrace)

ensure

  stderr.flush
end

#pauseObject



189
190
191
192
# File 'lib/rufus/scheduler.rb', line 189

def pause

  @paused = true
end

#paused?Boolean

Returns:

  • (Boolean)


184
185
186
187
# File 'lib/rufus/scheduler.rb', line 184

def paused?

  @paused
end

#repeat(arg, callable = nil, opts = {}, &block) ⇒ Object



264
265
266
267
268
269
270
271
272
# File 'lib/rufus/scheduler.rb', line 264

def repeat(arg, callable=nil, opts={}, &block)

  opts[:_t] = Scheduler.parse(arg, opts)

  case opts[:_t]
    when CronLine then schedule_cron(arg, callable, opts, &block)
    else schedule_every(arg, callable, opts, &block)
  end
end

#resumeObject



194
195
196
197
# File 'lib/rufus/scheduler.rb', line 194

def resume

  @paused = false
end

#running_jobs(opts = {}) ⇒ Object



427
428
429
430
# File 'lib/rufus/scheduler.rb', line 427

def running_jobs(opts={})

  jobs(opts.merge(:running => true))
end

#schedule(arg, callable = nil, opts = {}, &block) ⇒ Object



253
254
255
256
257
258
259
260
261
262
# File 'lib/rufus/scheduler.rb', line 253

def schedule(arg, callable=nil, opts={}, &block)

  opts[:_t] = Scheduler.parse(arg, opts)

  case opts[:_t]
    when CronLine then schedule_cron(arg, callable, opts, &block)
    when Time then schedule_at(arg, callable, opts, &block)
    else schedule_in(arg, callable, opts, &block)
  end
end

#schedule_at(time, callable = nil, opts = {}, &block) ⇒ Object



208
209
210
211
# File 'lib/rufus/scheduler.rb', line 208

def schedule_at(time, callable=nil, opts={}, &block)

  do_schedule(:once, time, callable, opts, true, block)
end

#schedule_cron(cronline, callable = nil, opts = {}, &block) ⇒ Object



248
249
250
251
# File 'lib/rufus/scheduler.rb', line 248

def schedule_cron(cronline, callable=nil, opts={}, &block)

  do_schedule(:cron, cronline, callable, opts, true, block)
end

#schedule_every(duration, callable = nil, opts = {}, &block) ⇒ Object



228
229
230
231
# File 'lib/rufus/scheduler.rb', line 228

def schedule_every(duration, callable=nil, opts={}, &block)

  do_schedule(:every, duration, callable, opts, true, block)
end

#schedule_in(duration, callable = nil, opts = {}, &block) ⇒ Object



218
219
220
221
# File 'lib/rufus/scheduler.rb', line 218

def schedule_in(duration, callable=nil, opts={}, &block)

  do_schedule(:once, duration, callable, opts, true, block)
end

#schedule_interval(duration, callable = nil, opts = {}, &block) ⇒ Object



238
239
240
241
# File 'lib/rufus/scheduler.rb', line 238

def schedule_interval(duration, callable=nil, opts={}, &block)

  do_schedule(:interval, duration, callable, opts, true, block)
end

#scheduled?(job_or_job_id) ⇒ Boolean

Returns true if this job is currently scheduled.

Takes extra care to answer true if the job is a repeat job currently firing.

Returns:

  • (Boolean)


386
387
388
389
390
391
# File 'lib/rufus/scheduler.rb', line 386

def scheduled?(job_or_job_id)

  job, job_id = fetch(job_or_job_id)

  !! (job && job.unscheduled_at.nil? && job.next_time != nil)
end

#shutdown(opt = nil) ⇒ Object Also known as: stop



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/rufus/scheduler.rb', line 134

def shutdown(opt=nil)

  @started_at = nil

  #jobs.each { |j| j.unschedule }
    # provokes https://github.com/jmettraux/rufus-scheduler/issue/98
  @jobs.array.each { |j| j.unschedule }

  @work_queue.clear

  if opt == :wait
    join_all_work_threads
  elsif opt == :kill
    kill_all_work_threads
  end

  unlock
end

#threadsObject

Lists all the threads associated with this scheduler.



395
396
397
398
# File 'lib/rufus/scheduler.rb', line 395

def threads

  Thread.list.select { |t| t[thread_key] }
end

#timeline(time0, time1) ⇒ Object



450
451
452
453
# File 'lib/rufus/scheduler.rb', line 450

def timeline(time0, time1)

  occurrences(time0, time1, :timeline)
end

#unlockObject

Sister method to #lock, is called when the scheduler shuts down.



366
367
368
369
370
# File 'lib/rufus/scheduler.rb', line 366

def unlock

  @trigger_lock.unlock
  @scheduler_lock.unlock
end

#unschedule(job_or_job_id) ⇒ Object



274
275
276
277
278
279
280
281
# File 'lib/rufus/scheduler.rb', line 274

def unschedule(job_or_job_id)

  job, job_id = fetch(job_or_job_id)

  fail ArgumentError.new("no job found with id '#{job_id}'") unless job

  job.unschedule if job
end

#up?Boolean

Returns:

  • (Boolean)


179
180
181
182
# File 'lib/rufus/scheduler.rb', line 179

def up?

  !! @started_at
end

#uptimeObject



155
156
157
158
# File 'lib/rufus/scheduler.rb', line 155

def uptime

  @started_at ? Time.now - @started_at : nil
end

#uptime_sObject



160
161
162
163
# File 'lib/rufus/scheduler.rb', line 160

def uptime_s

  self.class.to_duration(uptime)
end

#work_threads(query = :all) ⇒ Object

Lists all the work threads (the ones actually running the scheduled block code)

Accepts a query option, which can be set to:

  • :all (default), returns all the threads that are work threads or are currently running a job

  • :active, returns all threads that are currenly running a job

  • :vacant, returns the threads that are not running a job

If, thanks to :blocking => true, a job is scheduled to monopolize the main scheduler thread, that thread will get returned when :active or :all.



413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/rufus/scheduler.rb', line 413

def work_threads(query=:all)

  ts =
    threads.select { |t|
      t[:rufus_scheduler_job] || t[:rufus_scheduler_work_thread]
    }

  case query
    when :active then ts.select { |t| t[:rufus_scheduler_job] }
    when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
    else ts
  end
end