rufus-scheduler 2.0.23 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +12 -0
- data/CREDITS.txt +4 -0
- data/README.md +1064 -0
- data/Rakefile +1 -4
- data/TODO.txt +145 -55
- data/lib/rufus/{sc → scheduler}/cronline.rb +46 -17
- data/lib/rufus/{sc/version.rb → scheduler/job_array.rb} +56 -4
- data/lib/rufus/scheduler/jobs.rb +548 -0
- data/lib/rufus/scheduler/util.rb +318 -0
- data/lib/rufus/scheduler.rb +502 -26
- data/rufus-scheduler.gemspec +30 -4
- data/spec/cronline_spec.rb +29 -8
- data/spec/error_spec.rb +116 -0
- data/spec/job_array_spec.rb +39 -0
- data/spec/job_at_spec.rb +58 -0
- data/spec/job_cron_spec.rb +67 -0
- data/spec/job_every_spec.rb +71 -0
- data/spec/job_in_spec.rb +20 -0
- data/spec/job_interval_spec.rb +68 -0
- data/spec/job_repeat_spec.rb +308 -0
- data/spec/job_spec.rb +387 -115
- data/spec/lockfile_spec.rb +61 -0
- data/spec/parse_spec.rb +203 -0
- data/spec/schedule_at_spec.rb +129 -0
- data/spec/schedule_cron_spec.rb +66 -0
- data/spec/schedule_every_spec.rb +109 -0
- data/spec/schedule_in_spec.rb +80 -0
- data/spec/schedule_interval_spec.rb +128 -0
- data/spec/scheduler_spec.rb +831 -124
- data/spec/spec_helper.rb +65 -0
- data/spec/threads_spec.rb +75 -0
- metadata +64 -65
- data/README.rdoc +0 -661
- data/lib/rufus/otime.rb +0 -3
- data/lib/rufus/sc/jobqueues.rb +0 -160
- data/lib/rufus/sc/jobs.rb +0 -471
- data/lib/rufus/sc/rtime.rb +0 -363
- data/lib/rufus/sc/scheduler.rb +0 -636
- data/spec/at_in_spec.rb +0 -47
- data/spec/at_spec.rb +0 -125
- data/spec/blocking_spec.rb +0 -64
- data/spec/cron_spec.rb +0 -134
- data/spec/every_spec.rb +0 -304
- data/spec/exception_spec.rb +0 -113
- data/spec/in_spec.rb +0 -150
- data/spec/mutex_spec.rb +0 -159
- data/spec/rtime_spec.rb +0 -137
- data/spec/schedulable_spec.rb +0 -97
- data/spec/spec_base.rb +0 -87
- data/spec/stress_schedule_unschedule_spec.rb +0 -159
- data/spec/timeout_spec.rb +0 -148
- data/test/kjw.rb +0 -113
- data/test/t.rb +0 -20
data/lib/rufus/scheduler.rb
CHANGED
|
@@ -22,41 +22,517 @@
|
|
|
22
22
|
# Made in Japan.
|
|
23
23
|
#++
|
|
24
24
|
|
|
25
|
+
require 'date' if RUBY_VERSION < '1.9.0'
|
|
26
|
+
require 'time'
|
|
27
|
+
require 'thread'
|
|
28
|
+
require 'tzinfo'
|
|
29
|
+
require 'fileutils'
|
|
25
30
|
|
|
26
|
-
require 'rufus/sc/scheduler'
|
|
27
31
|
|
|
32
|
+
module Rufus
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
class Scheduler
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
36
|
+
require 'rufus/scheduler/util'
|
|
37
|
+
require 'rufus/scheduler/jobs'
|
|
38
|
+
require 'rufus/scheduler/cronline'
|
|
39
|
+
require 'rufus/scheduler/job_array'
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
VERSION = '3.0.0'
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# This error is thrown when the :timeout attribute triggers
|
|
45
|
+
#
|
|
46
|
+
class TimeoutError < StandardError; end
|
|
47
|
+
|
|
48
|
+
#MIN_WORK_THREADS = 7
|
|
49
|
+
MAX_WORK_THREADS = 35
|
|
50
|
+
|
|
51
|
+
attr_accessor :frequency
|
|
52
|
+
attr_reader :started_at
|
|
53
|
+
attr_reader :thread
|
|
54
|
+
attr_reader :thread_key
|
|
55
|
+
attr_reader :mutexes
|
|
56
|
+
|
|
57
|
+
#attr_accessor :min_work_threads
|
|
58
|
+
attr_accessor :max_work_threads
|
|
59
|
+
|
|
60
|
+
attr_accessor :stderr
|
|
61
|
+
|
|
62
|
+
attr_reader :work_queue
|
|
63
|
+
|
|
64
|
+
def initialize(opts={})
|
|
65
|
+
|
|
66
|
+
@opts = opts
|
|
67
|
+
|
|
68
|
+
@started_at = nil
|
|
69
|
+
@paused = false
|
|
70
|
+
|
|
71
|
+
@jobs = JobArray.new
|
|
72
|
+
|
|
73
|
+
@frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
|
|
74
|
+
@mutexes = {}
|
|
75
|
+
|
|
76
|
+
@work_queue = Queue.new
|
|
37
77
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
#@min_work_threads = opts[:min_work_threads] || MIN_WORK_THREADS
|
|
79
|
+
@max_work_threads = opts[:max_work_threads] || MAX_WORK_THREADS
|
|
80
|
+
|
|
81
|
+
@stderr = $stderr
|
|
82
|
+
|
|
83
|
+
@thread_key = "rufus_scheduler_#{self.object_id}"
|
|
84
|
+
|
|
85
|
+
consider_lockfile || return
|
|
86
|
+
|
|
87
|
+
start
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns a singleton Rufus::Scheduler instance
|
|
91
|
+
#
|
|
92
|
+
def self.singleton(opts={})
|
|
93
|
+
|
|
94
|
+
@singleton ||= Rufus::Scheduler.new(opts)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Alias for Rufus::Scheduler.singleton
|
|
98
|
+
#
|
|
99
|
+
def self.s(opts={}); singleton(opts); end
|
|
100
|
+
|
|
101
|
+
# Releasing the gem would probably require redirecting .start_new to
|
|
102
|
+
# .new and emit a simple deprecation message.
|
|
103
|
+
#
|
|
104
|
+
# For now, let's assume the people pointing at rufus-scheduler/master
|
|
105
|
+
# on GitHub know what they do...
|
|
106
|
+
#
|
|
107
|
+
def self.start_new
|
|
108
|
+
|
|
109
|
+
fail "this is rufus-scheduler 3.0, use .new instead of .start_new"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def shutdown(opt=nil)
|
|
113
|
+
|
|
114
|
+
@started_at = nil
|
|
115
|
+
|
|
116
|
+
jobs.each { |j| j.unschedule }
|
|
117
|
+
|
|
118
|
+
@work_queue.clear
|
|
119
|
+
|
|
120
|
+
if opt == :wait
|
|
121
|
+
join_all_work_threads
|
|
122
|
+
elsif opt == :kill
|
|
123
|
+
kill_all_work_threads
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
@lockfile.flock(File::LOCK_UN) if @lockfile
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
alias stop shutdown
|
|
130
|
+
|
|
131
|
+
def uptime
|
|
132
|
+
|
|
133
|
+
@started_at ? Time.now - @started_at : nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def uptime_s
|
|
137
|
+
|
|
138
|
+
self.class.to_duration(uptime)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def join
|
|
142
|
+
|
|
143
|
+
@thread.join
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def paused?
|
|
147
|
+
|
|
148
|
+
@paused
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def pause
|
|
152
|
+
|
|
153
|
+
@paused = true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def resume
|
|
157
|
+
|
|
158
|
+
@paused = false
|
|
52
159
|
end
|
|
53
|
-
end
|
|
54
160
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
161
|
+
#--
|
|
162
|
+
# scheduling methods
|
|
163
|
+
#++
|
|
164
|
+
|
|
165
|
+
def at(time, callable=nil, opts={}, &block)
|
|
166
|
+
|
|
167
|
+
do_schedule(:once, time, callable, opts, opts[:job], block)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def schedule_at(time, callable=nil, opts={}, &block)
|
|
171
|
+
|
|
172
|
+
do_schedule(:once, time, callable, opts, true, block)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def in(duration, callable=nil, opts={}, &block)
|
|
176
|
+
|
|
177
|
+
do_schedule(:once, duration, callable, opts, opts[:job], block)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def schedule_in(duration, callable=nil, opts={}, &block)
|
|
181
|
+
|
|
182
|
+
do_schedule(:once, duration, callable, opts, true, block)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def every(duration, callable=nil, opts={}, &block)
|
|
186
|
+
|
|
187
|
+
do_schedule(:every, duration, callable, opts, opts[:job], block)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def schedule_every(duration, callable=nil, opts={}, &block)
|
|
191
|
+
|
|
192
|
+
do_schedule(:every, duration, callable, opts, true, block)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def interval(duration, callable=nil, opts={}, &block)
|
|
196
|
+
|
|
197
|
+
do_schedule(:interval, duration, callable, opts, opts[:job], block)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def schedule_interval(duration, callable=nil, opts={}, &block)
|
|
201
|
+
|
|
202
|
+
do_schedule(:interval, duration, callable, opts, true, block)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def cron(cronline, callable=nil, opts={}, &block)
|
|
206
|
+
|
|
207
|
+
do_schedule(:cron, cronline, callable, opts, opts[:job], block)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def schedule_cron(cronline, callable=nil, opts={}, &block)
|
|
211
|
+
|
|
212
|
+
do_schedule(:cron, cronline, callable, opts, true, block)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def schedule(arg, callable=nil, opts={}, &block)
|
|
216
|
+
|
|
217
|
+
# TODO: eventually, spare one parse call
|
|
218
|
+
|
|
219
|
+
case Scheduler.parse(arg)
|
|
220
|
+
when CronLine then schedule_cron(arg, callable, opts, &block)
|
|
221
|
+
when Time then schedule_at(arg, callable, opts, &block)
|
|
222
|
+
else schedule_in(arg, callable, opts, &block)
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def repeat(arg, callable=nil, opts={}, &block)
|
|
227
|
+
|
|
228
|
+
# TODO: eventually, spare one parse call
|
|
229
|
+
|
|
230
|
+
case Scheduler.parse(arg)
|
|
231
|
+
when CronLine then schedule_cron(arg, callable, opts, &block)
|
|
232
|
+
else schedule_every(arg, callable, opts, &block)
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def unschedule(job_or_job_id)
|
|
237
|
+
|
|
238
|
+
job, job_id = fetch(job_or_job_id)
|
|
239
|
+
|
|
240
|
+
fail ArgumentError.new("no job found with id '#{job_id}'") unless job
|
|
241
|
+
|
|
242
|
+
job.unschedule if job
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
#--
|
|
246
|
+
# jobs methods
|
|
247
|
+
#++
|
|
248
|
+
|
|
249
|
+
# Returns all the scheduled jobs
|
|
250
|
+
# (even those right before re-schedule).
|
|
251
|
+
#
|
|
252
|
+
def jobs(opts={})
|
|
253
|
+
|
|
254
|
+
opts = { opts => true } if opts.is_a?(Symbol)
|
|
255
|
+
|
|
256
|
+
jobs = @jobs.to_a
|
|
257
|
+
|
|
258
|
+
if opts[:running]
|
|
259
|
+
jobs = jobs.select { |j| j.running? }
|
|
260
|
+
elsif ! opts[:all]
|
|
261
|
+
jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
|
265
|
+
jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
|
|
266
|
+
|
|
267
|
+
jobs
|
|
268
|
+
end
|
|
58
269
|
|
|
59
|
-
|
|
270
|
+
def at_jobs(opts={})
|
|
271
|
+
|
|
272
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def in_jobs(opts={})
|
|
276
|
+
|
|
277
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def every_jobs(opts={})
|
|
281
|
+
|
|
282
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def interval_jobs(opts={})
|
|
286
|
+
|
|
287
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def cron_jobs(opts={})
|
|
291
|
+
|
|
292
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def job(job_id)
|
|
296
|
+
|
|
297
|
+
@jobs[job_id]
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Returns true if this job is currently scheduled.
|
|
301
|
+
#
|
|
302
|
+
# Takes extra care to answer true if the job is a repeat job
|
|
303
|
+
# currently firing.
|
|
304
|
+
#
|
|
305
|
+
def scheduled?(job_or_job_id)
|
|
306
|
+
|
|
307
|
+
job, job_id = fetch(job_or_job_id)
|
|
308
|
+
|
|
309
|
+
!! (job && job.next_time != nil)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Lists all the threads associated with this scheduler.
|
|
313
|
+
#
|
|
314
|
+
def threads
|
|
315
|
+
|
|
316
|
+
Thread.list.select { |t| t[thread_key] }
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Lists all the work threads (the ones actually running the scheduled
|
|
320
|
+
# block code)
|
|
321
|
+
#
|
|
322
|
+
# Accepts a query option, which can be set to:
|
|
323
|
+
# * :all (default), returns all the threads that are work threads
|
|
324
|
+
# or are currently running a job
|
|
325
|
+
# * :active, returns all threads that are currenly running a job
|
|
326
|
+
# * :vacant, returns the threads that are not running a job
|
|
327
|
+
#
|
|
328
|
+
# If, thanks to :blocking => true, a job is scheduled to monopolize the
|
|
329
|
+
# main scheduler thread, that thread will get returned when :active or
|
|
330
|
+
# :all.
|
|
331
|
+
#
|
|
332
|
+
def work_threads(query=:all)
|
|
333
|
+
|
|
334
|
+
ts =
|
|
335
|
+
threads.select { |t|
|
|
336
|
+
t[:rufus_scheduler_job] || t[:rufus_scheduler_work_thread]
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
case query
|
|
340
|
+
when :active then ts.select { |t| t[:rufus_scheduler_job] }
|
|
341
|
+
when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
|
|
342
|
+
else ts
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def running_jobs(opts={})
|
|
347
|
+
|
|
348
|
+
jobs(opts.merge(:running => true))
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def on_error(job, err)
|
|
352
|
+
|
|
353
|
+
pre = err.object_id.to_s
|
|
354
|
+
|
|
355
|
+
stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
|
|
356
|
+
stderr.puts(" #{pre} job:")
|
|
357
|
+
stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
|
|
358
|
+
stderr.puts(" #{pre} error:")
|
|
359
|
+
stderr.puts(" #{pre} #{err.object_id}")
|
|
360
|
+
stderr.puts(" #{pre} #{err.class}")
|
|
361
|
+
stderr.puts(" #{pre} #{err}")
|
|
362
|
+
err.backtrace.each do |l|
|
|
363
|
+
stderr.puts(" #{pre} #{l}")
|
|
364
|
+
end
|
|
365
|
+
stderr.puts("} #{pre} .")
|
|
366
|
+
|
|
367
|
+
rescue => e
|
|
368
|
+
|
|
369
|
+
stderr.puts("failure in #on_error itself:")
|
|
370
|
+
stderr.puts(e.inspect)
|
|
371
|
+
stderr.puts(e.backtrace)
|
|
372
|
+
|
|
373
|
+
ensure
|
|
374
|
+
|
|
375
|
+
stderr.flush
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
protected
|
|
379
|
+
|
|
380
|
+
# Returns [ job, job_id ]
|
|
381
|
+
#
|
|
382
|
+
def fetch(job_or_job_id)
|
|
383
|
+
|
|
384
|
+
if job_or_job_id.respond_to?(:job_id)
|
|
385
|
+
[ job_or_job_id, job_or_job_id.job_id ]
|
|
386
|
+
else
|
|
387
|
+
[ job(job_or_job_id), job_or_job_id ]
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def consider_lockfile
|
|
392
|
+
|
|
393
|
+
@lockfile = nil
|
|
394
|
+
|
|
395
|
+
return true unless f = @opts[:lockfile]
|
|
396
|
+
|
|
397
|
+
raise ArgumentError.new(
|
|
398
|
+
":lockfile argument must be a string, not a #{f.class}"
|
|
399
|
+
) unless f.is_a?(String)
|
|
400
|
+
|
|
401
|
+
FileUtils.mkdir_p(File.dirname(f))
|
|
402
|
+
|
|
403
|
+
f = File.new(f, File::RDWR | File::CREAT)
|
|
404
|
+
locked = f.flock(File::LOCK_NB | File::LOCK_EX)
|
|
405
|
+
|
|
406
|
+
return false unless locked
|
|
407
|
+
|
|
408
|
+
now = Time.now
|
|
409
|
+
|
|
410
|
+
f.print("pid: #{$$}, ")
|
|
411
|
+
f.print("scheduler.object_id: #{self.object_id}, ")
|
|
412
|
+
f.print("time: #{now}, ")
|
|
413
|
+
f.print("timestamp: #{now.to_f}")
|
|
414
|
+
f.flush
|
|
415
|
+
|
|
416
|
+
@lockfile = f
|
|
417
|
+
|
|
418
|
+
true
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def terminate_all_jobs
|
|
422
|
+
|
|
423
|
+
jobs.each { |j| j.unschedule }
|
|
424
|
+
|
|
425
|
+
sleep 0.01 while running_jobs.size > 0
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def join_all_work_threads
|
|
429
|
+
|
|
430
|
+
work_threads.size.times { @work_queue << :sayonara }
|
|
431
|
+
|
|
432
|
+
work_threads.each { |t| t.join }
|
|
433
|
+
|
|
434
|
+
@work_queue.clear
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def kill_all_work_threads
|
|
438
|
+
|
|
439
|
+
work_threads.each { |t| t.kill }
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
#def free_all_work_threads
|
|
443
|
+
#
|
|
444
|
+
# work_threads.each { |t| t.raise(KillSignal) }
|
|
445
|
+
#end
|
|
446
|
+
|
|
447
|
+
def start
|
|
448
|
+
|
|
449
|
+
@started_at = Time.now
|
|
450
|
+
|
|
451
|
+
@thread =
|
|
452
|
+
Thread.new do
|
|
453
|
+
|
|
454
|
+
while @started_at do
|
|
455
|
+
|
|
456
|
+
unschedule_jobs
|
|
457
|
+
trigger_jobs unless @paused
|
|
458
|
+
timeout_jobs
|
|
459
|
+
|
|
460
|
+
sleep(@frequency)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
@thread[@thread_key] = true
|
|
465
|
+
@thread[:rufus_scheduler] = self
|
|
466
|
+
@thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def unschedule_jobs
|
|
470
|
+
|
|
471
|
+
@jobs.delete_unscheduled
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def trigger_jobs
|
|
475
|
+
|
|
476
|
+
now = Time.now
|
|
477
|
+
|
|
478
|
+
@jobs.each(now) do |job|
|
|
479
|
+
|
|
480
|
+
job.trigger(now)
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def timeout_jobs
|
|
485
|
+
|
|
486
|
+
work_threads(:active).each do |t|
|
|
487
|
+
|
|
488
|
+
job = t[:rufus_scheduler_job]
|
|
489
|
+
to = t[:rufus_scheduler_timeout]
|
|
490
|
+
|
|
491
|
+
next unless job && to
|
|
492
|
+
# thread might just have become inactive (job -> nil)
|
|
493
|
+
|
|
494
|
+
ts = t[:rufus_scheduler_time]
|
|
495
|
+
to = to.is_a?(Time) ? to : ts + to
|
|
496
|
+
|
|
497
|
+
next if to > Time.now
|
|
498
|
+
|
|
499
|
+
t.raise(Rufus::Scheduler::TimeoutError)
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def do_schedule(job_type, t, callable, opts, return_job_instance, block)
|
|
504
|
+
|
|
505
|
+
raise RuntimeError.new(
|
|
506
|
+
'cannot schedule, scheduler is down or shutting down'
|
|
507
|
+
) if @started_at == nil
|
|
508
|
+
|
|
509
|
+
callable, opts = nil, callable if callable.is_a?(Hash)
|
|
510
|
+
return_job_instance ||= opts[:job]
|
|
511
|
+
|
|
512
|
+
job_class =
|
|
513
|
+
case job_type
|
|
514
|
+
when :once
|
|
515
|
+
tt = Rufus::Scheduler.parse(t)
|
|
516
|
+
tt.is_a?(Time) ? AtJob : InJob
|
|
517
|
+
when :every
|
|
518
|
+
EveryJob
|
|
519
|
+
when :interval
|
|
520
|
+
IntervalJob
|
|
521
|
+
when :cron
|
|
522
|
+
CronJob
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
job = job_class.new(self, t, opts, block || callable)
|
|
526
|
+
|
|
527
|
+
raise ArgumentError.new(
|
|
528
|
+
"job frequency (#{job.frequency}) is higher than " +
|
|
529
|
+
"scheduler frequency (#{@frequency})"
|
|
530
|
+
) if job.respond_to?(:frequency) && job.frequency < @frequency
|
|
531
|
+
|
|
532
|
+
@jobs.push(job)
|
|
533
|
+
|
|
534
|
+
return_job_instance ? job : job.job_id
|
|
535
|
+
end
|
|
60
536
|
end
|
|
61
537
|
end
|
|
62
538
|
|
data/rufus-scheduler.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |s|
|
|
|
4
4
|
s.name = 'rufus-scheduler'
|
|
5
5
|
|
|
6
6
|
s.version = File.read(
|
|
7
|
-
File.expand_path('../lib/rufus/
|
|
7
|
+
File.expand_path('../lib/rufus/scheduler.rb', __FILE__)
|
|
8
8
|
).match(/ VERSION *= *['"]([^'"]+)/)[1]
|
|
9
9
|
|
|
10
10
|
s.platform = Gem::Platform::RUBY
|
|
@@ -12,8 +12,8 @@ Gem::Specification.new do |s|
|
|
|
12
12
|
s.email = [ 'jmettraux@gmail.com' ]
|
|
13
13
|
s.homepage = 'http://github.com/jmettraux/rufus-scheduler'
|
|
14
14
|
s.rubyforge_project = 'rufus'
|
|
15
|
-
s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
|
|
16
15
|
s.license = 'MIT'
|
|
16
|
+
s.summary = 'job scheduler for Ruby (at, cron, in and every jobs)'
|
|
17
17
|
|
|
18
18
|
s.description = %{
|
|
19
19
|
job scheduler for Ruby (at, cron, in and every jobs).
|
|
@@ -26,11 +26,37 @@ job scheduler for Ruby (at, cron, in and every jobs).
|
|
|
26
26
|
'*.gemspec', '*.txt', '*.rdoc', '*.md'
|
|
27
27
|
]
|
|
28
28
|
|
|
29
|
-
s.add_runtime_dependency 'tzinfo'
|
|
29
|
+
s.add_runtime_dependency 'tzinfo'
|
|
30
30
|
|
|
31
31
|
s.add_development_dependency 'rake'
|
|
32
|
-
s.add_development_dependency 'rspec', '>= 2.
|
|
32
|
+
s.add_development_dependency 'rspec', '>= 2.13.0'
|
|
33
33
|
|
|
34
34
|
s.require_path = 'lib'
|
|
35
|
+
|
|
36
|
+
s.post_install_message =
|
|
37
|
+
%{
|
|
38
|
+
***
|
|
39
|
+
|
|
40
|
+
Thanks for installing rufus-scheduler #{s.version}
|
|
41
|
+
|
|
42
|
+
It might not be 100% compatible with rufus-scheduler 2.x.
|
|
43
|
+
|
|
44
|
+
If you encounter issues with this new rufus-scheduler, especially
|
|
45
|
+
if your app worked fine with previous versions of it, you can
|
|
46
|
+
|
|
47
|
+
A) Forget it and peg your Gemfile to rufus-scheduler 2.0.24
|
|
48
|
+
|
|
49
|
+
and / or
|
|
50
|
+
|
|
51
|
+
B) Take some time to carefully report the issue at
|
|
52
|
+
https://github.com/jmettraux/rufus-scheduler/issue
|
|
53
|
+
|
|
54
|
+
For general help about rufus-scheduler, ask via:
|
|
55
|
+
http://stackoverflow.com/questions/ask?tags=rufus-scheduler+ruby
|
|
56
|
+
|
|
57
|
+
Cheers.
|
|
58
|
+
|
|
59
|
+
***
|
|
60
|
+
}
|
|
35
61
|
end
|
|
36
62
|
|
data/spec/cronline_spec.rb
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
# Sat Mar 21 12:55:27 JST 2009
|
|
6
6
|
#
|
|
7
7
|
|
|
8
|
-
require '
|
|
8
|
+
require 'tzinfo'
|
|
9
|
+
require 'spec_helper'
|
|
9
10
|
|
|
10
11
|
|
|
11
|
-
describe Rufus::CronLine do
|
|
12
|
+
describe Rufus::Scheduler::CronLine do
|
|
12
13
|
|
|
13
14
|
def cl(cronline_string)
|
|
14
|
-
Rufus::CronLine.new(cronline_string)
|
|
15
|
+
Rufus::Scheduler::CronLine.new(cronline_string)
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def match(line, time)
|
|
@@ -111,7 +112,7 @@ describe Rufus::CronLine do
|
|
|
111
112
|
it 'does not support ranges for monthdays (sun#1-sun#2)' do
|
|
112
113
|
|
|
113
114
|
lambda {
|
|
114
|
-
Rufus::CronLine.new('* * * * sun#1-sun#2')
|
|
115
|
+
Rufus::Scheduler::CronLine.new('* * * * sun#1-sun#2')
|
|
115
116
|
}.should raise_error(ArgumentError)
|
|
116
117
|
end
|
|
117
118
|
|
|
@@ -165,7 +166,7 @@ describe Rufus::CronLine do
|
|
|
165
166
|
describe '#next_time' do
|
|
166
167
|
|
|
167
168
|
def nt(cronline, now)
|
|
168
|
-
Rufus::CronLine.new(cronline).next_time(now)
|
|
169
|
+
Rufus::Scheduler::CronLine.new(cronline).next_time(now)
|
|
169
170
|
end
|
|
170
171
|
|
|
171
172
|
it 'computes the next occurence correctly' do
|
|
@@ -185,6 +186,7 @@ describe Rufus::CronLine do
|
|
|
185
186
|
# that slowness is gone)
|
|
186
187
|
|
|
187
188
|
nt('0 0 * * thu', now).should == now + 604800
|
|
189
|
+
nt('00 0 * * thu', now).should == now + 604800
|
|
188
190
|
|
|
189
191
|
nt('0 0 * * *', now).should == now + 24 * 3600
|
|
190
192
|
nt('0 24 * * *', now).should == now + 24 * 3600
|
|
@@ -294,12 +296,18 @@ describe Rufus::CronLine do
|
|
|
294
296
|
nt('* * L * *', lo(1972, 2, 1)).should == lo(1972, 2, 29)
|
|
295
297
|
nt('* * L * *', lo(1970, 4, 1)).should == lo(1970, 4, 30)
|
|
296
298
|
end
|
|
299
|
+
|
|
300
|
+
it 'returns a time with subseconds chopped off' do
|
|
301
|
+
|
|
302
|
+
nt('* * * * *', Time.now).usec.should == 0
|
|
303
|
+
nt('* * * * *', Time.now).iso8601(10).match(/\.0+[^\d]/).should_not == nil
|
|
304
|
+
end
|
|
297
305
|
end
|
|
298
306
|
|
|
299
307
|
describe '#previous_time' do
|
|
300
308
|
|
|
301
309
|
def pt(cronline, now)
|
|
302
|
-
Rufus::CronLine.new(cronline).previous_time(now)
|
|
310
|
+
Rufus::Scheduler::CronLine.new(cronline).previous_time(now)
|
|
303
311
|
end
|
|
304
312
|
|
|
305
313
|
it 'returns the previous time the cron should have triggered' do
|
|
@@ -386,11 +394,11 @@ describe Rufus::CronLine do
|
|
|
386
394
|
|
|
387
395
|
it 'returns the appropriate "sun#2"-like string' do
|
|
388
396
|
|
|
389
|
-
class Rufus::CronLine
|
|
397
|
+
class Rufus::Scheduler::CronLine
|
|
390
398
|
public :monthdays
|
|
391
399
|
end
|
|
392
400
|
|
|
393
|
-
cl = Rufus::CronLine.new('* * * * *')
|
|
401
|
+
cl = Rufus::Scheduler::CronLine.new('* * * * *')
|
|
394
402
|
|
|
395
403
|
cl.monthdays(local(1970, 1, 1)).should == %w[ thu#1 thu#-5 ]
|
|
396
404
|
cl.monthdays(local(1970, 1, 7)).should == %w[ wed#1 wed#-4 ]
|
|
@@ -399,5 +407,18 @@ describe Rufus::CronLine do
|
|
|
399
407
|
cl.monthdays(local(2011, 3, 11)).should == %w[ fri#2 fri#-3 ]
|
|
400
408
|
end
|
|
401
409
|
end
|
|
410
|
+
|
|
411
|
+
describe '#frequency' do
|
|
412
|
+
|
|
413
|
+
it 'returns the shortest delta between two occurrences' do
|
|
414
|
+
|
|
415
|
+
Rufus::Scheduler::CronLine.new('* * * * *').frequency.should == 60
|
|
416
|
+
Rufus::Scheduler::CronLine.new('* * * * * *').frequency.should == 1
|
|
417
|
+
|
|
418
|
+
Rufus::Scheduler::CronLine.new('5 23 * * *').frequency.should == 24 * 3600
|
|
419
|
+
Rufus::Scheduler::CronLine.new('5 * * * *').frequency.should == 3600
|
|
420
|
+
Rufus::Scheduler::CronLine.new('10,20,30 * * * *').frequency.should == 600
|
|
421
|
+
end
|
|
422
|
+
end
|
|
402
423
|
end
|
|
403
424
|
|