rufus-scheduler 2.0.24 → 3.1.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 +76 -0
- data/CREDITS.txt +23 -0
- data/LICENSE.txt +1 -1
- data/README.md +1439 -0
- data/Rakefile +1 -5
- data/TODO.txt +149 -55
- data/lib/rufus/{sc → scheduler}/cronline.rb +167 -53
- data/lib/rufus/scheduler/job_array.rb +92 -0
- data/lib/rufus/scheduler/jobs.rb +633 -0
- data/lib/rufus/scheduler/locks.rb +95 -0
- data/lib/rufus/scheduler/util.rb +306 -0
- data/lib/rufus/scheduler/zones.rb +174 -0
- data/lib/rufus/scheduler/zotime.rb +154 -0
- data/lib/rufus/scheduler.rb +608 -27
- data/rufus-scheduler.gemspec +6 -4
- data/spec/basics_spec.rb +54 -0
- data/spec/cronline_spec.rb +479 -152
- data/spec/error_spec.rb +139 -0
- data/spec/job_array_spec.rb +39 -0
- data/spec/job_at_spec.rb +58 -0
- data/spec/job_cron_spec.rb +128 -0
- data/spec/job_every_spec.rb +104 -0
- data/spec/job_in_spec.rb +20 -0
- data/spec/job_interval_spec.rb +68 -0
- data/spec/job_repeat_spec.rb +357 -0
- data/spec/job_spec.rb +498 -109
- data/spec/lock_custom_spec.rb +47 -0
- data/spec/lock_flock_spec.rb +47 -0
- data/spec/lock_lockfile_spec.rb +61 -0
- data/spec/lock_spec.rb +59 -0
- data/spec/parse_spec.rb +263 -0
- data/spec/schedule_at_spec.rb +158 -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 +928 -124
- data/spec/spec_helper.rb +126 -0
- data/spec/threads_spec.rb +96 -0
- data/spec/zotime_spec.rb +396 -0
- metadata +56 -33
- 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/lib/rufus/sc/version.rb +0 -32
- 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
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2006-
|
2
|
+
# Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
5
|
# of this software and associated documentation files (the "Software"), to deal
|
@@ -22,41 +22,622 @@
|
|
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'
|
25
29
|
|
26
|
-
require 'rufus/sc/scheduler'
|
27
30
|
|
31
|
+
module Rufus
|
28
32
|
|
29
|
-
|
33
|
+
class Scheduler
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
require 'rufus/scheduler/util'
|
36
|
+
require 'rufus/scheduler/zotime'
|
37
|
+
require 'rufus/scheduler/jobs'
|
38
|
+
require 'rufus/scheduler/cronline'
|
39
|
+
require 'rufus/scheduler/job_array'
|
40
|
+
require 'rufus/scheduler/locks'
|
34
41
|
|
35
|
-
|
36
|
-
|
42
|
+
VERSION = '3.1.0'
|
43
|
+
|
44
|
+
#
|
45
|
+
# A common error class for rufus-scheduler
|
46
|
+
#
|
47
|
+
class Error < StandardError; end
|
48
|
+
|
49
|
+
#
|
50
|
+
# This error is thrown when the :timeout attribute triggers
|
51
|
+
#
|
52
|
+
class TimeoutError < Error; end
|
53
|
+
|
54
|
+
#
|
55
|
+
# For when the scheduler is not running
|
56
|
+
# (it got shut down or didn't start because of a lock)
|
57
|
+
#
|
58
|
+
class NotRunningError < Error; end
|
59
|
+
|
60
|
+
#MIN_WORK_THREADS = 3
|
61
|
+
MAX_WORK_THREADS = 28
|
62
|
+
|
63
|
+
attr_accessor :frequency
|
64
|
+
attr_reader :started_at
|
65
|
+
attr_reader :thread
|
66
|
+
attr_reader :thread_key
|
67
|
+
attr_reader :mutexes
|
68
|
+
|
69
|
+
#attr_accessor :min_work_threads
|
70
|
+
attr_accessor :max_work_threads
|
71
|
+
|
72
|
+
attr_accessor :stderr
|
73
|
+
|
74
|
+
attr_reader :work_queue
|
75
|
+
|
76
|
+
def initialize(opts={})
|
77
|
+
|
78
|
+
@opts = opts
|
79
|
+
|
80
|
+
@started_at = nil
|
81
|
+
@paused = false
|
82
|
+
|
83
|
+
@jobs = JobArray.new
|
84
|
+
|
85
|
+
@frequency = Rufus::Scheduler.parse(opts[:frequency] || 0.300)
|
86
|
+
@mutexes = {}
|
37
87
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
88
|
+
@work_queue = Queue.new
|
89
|
+
|
90
|
+
#@min_work_threads = opts[:min_work_threads] || MIN_WORK_THREADS
|
91
|
+
@max_work_threads = opts[:max_work_threads] || MAX_WORK_THREADS
|
92
|
+
|
93
|
+
@stderr = $stderr
|
94
|
+
|
95
|
+
@thread_key = "rufus_scheduler_#{self.object_id}"
|
96
|
+
|
97
|
+
@scheduler_lock =
|
98
|
+
if lockfile = opts[:lockfile]
|
99
|
+
Rufus::Scheduler::FileLock.new(lockfile)
|
100
|
+
else
|
101
|
+
opts[:scheduler_lock] || Rufus::Scheduler::NullLock.new
|
102
|
+
end
|
103
|
+
|
104
|
+
@trigger_lock = opts[:trigger_lock] || Rufus::Scheduler::NullLock.new
|
105
|
+
|
106
|
+
# If we can't grab the @scheduler_lock, don't run.
|
107
|
+
@scheduler_lock.lock || return
|
108
|
+
|
109
|
+
start
|
52
110
|
end
|
53
|
-
end
|
54
111
|
|
55
|
-
|
56
|
-
|
57
|
-
|
112
|
+
# Returns a singleton Rufus::Scheduler instance
|
113
|
+
#
|
114
|
+
def self.singleton(opts={})
|
115
|
+
|
116
|
+
@singleton ||= Rufus::Scheduler.new(opts)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Alias for Rufus::Scheduler.singleton
|
120
|
+
#
|
121
|
+
def self.s(opts={}); singleton(opts); end
|
122
|
+
|
123
|
+
# Releasing the gem would probably require redirecting .start_new to
|
124
|
+
# .new and emit a simple deprecation message.
|
125
|
+
#
|
126
|
+
# For now, let's assume the people pointing at rufus-scheduler/master
|
127
|
+
# on GitHub know what they do...
|
128
|
+
#
|
129
|
+
def self.start_new
|
130
|
+
|
131
|
+
fail "this is rufus-scheduler 3.0, use .new instead of .start_new"
|
132
|
+
end
|
133
|
+
|
134
|
+
def shutdown(opt=nil)
|
135
|
+
|
136
|
+
@started_at = nil
|
137
|
+
|
138
|
+
#jobs.each { |j| j.unschedule }
|
139
|
+
# provokes https://github.com/jmettraux/rufus-scheduler/issue/98
|
140
|
+
@jobs.array.each { |j| j.unschedule }
|
141
|
+
|
142
|
+
@work_queue.clear
|
143
|
+
|
144
|
+
if opt == :wait
|
145
|
+
join_all_work_threads
|
146
|
+
elsif opt == :kill
|
147
|
+
kill_all_work_threads
|
148
|
+
end
|
149
|
+
|
150
|
+
unlock
|
151
|
+
end
|
152
|
+
|
153
|
+
alias stop shutdown
|
154
|
+
|
155
|
+
def uptime
|
156
|
+
|
157
|
+
@started_at ? Time.now - @started_at : nil
|
158
|
+
end
|
159
|
+
|
160
|
+
def uptime_s
|
161
|
+
|
162
|
+
self.class.to_duration(uptime)
|
163
|
+
end
|
164
|
+
|
165
|
+
def join
|
166
|
+
|
167
|
+
fail NotRunningError.new(
|
168
|
+
'cannot join scheduler that is not running'
|
169
|
+
) unless @thread
|
170
|
+
|
171
|
+
@thread.join
|
172
|
+
end
|
173
|
+
|
174
|
+
def down?
|
175
|
+
|
176
|
+
! @started_at
|
177
|
+
end
|
178
|
+
|
179
|
+
def up?
|
180
|
+
|
181
|
+
!! @started_at
|
182
|
+
end
|
183
|
+
|
184
|
+
def paused?
|
185
|
+
|
186
|
+
@paused
|
187
|
+
end
|
188
|
+
|
189
|
+
def pause
|
190
|
+
|
191
|
+
@paused = true
|
192
|
+
end
|
193
|
+
|
194
|
+
def resume
|
195
|
+
|
196
|
+
@paused = false
|
197
|
+
end
|
198
|
+
|
199
|
+
#--
|
200
|
+
# scheduling methods
|
201
|
+
#++
|
202
|
+
|
203
|
+
def at(time, callable=nil, opts={}, &block)
|
204
|
+
|
205
|
+
do_schedule(:once, time, callable, opts, opts[:job], block)
|
206
|
+
end
|
207
|
+
|
208
|
+
def schedule_at(time, callable=nil, opts={}, &block)
|
209
|
+
|
210
|
+
do_schedule(:once, time, callable, opts, true, block)
|
211
|
+
end
|
212
|
+
|
213
|
+
def in(duration, callable=nil, opts={}, &block)
|
214
|
+
|
215
|
+
do_schedule(:once, duration, callable, opts, opts[:job], block)
|
216
|
+
end
|
217
|
+
|
218
|
+
def schedule_in(duration, callable=nil, opts={}, &block)
|
219
|
+
|
220
|
+
do_schedule(:once, duration, callable, opts, true, block)
|
221
|
+
end
|
222
|
+
|
223
|
+
def every(duration, callable=nil, opts={}, &block)
|
224
|
+
|
225
|
+
do_schedule(:every, duration, callable, opts, opts[:job], block)
|
226
|
+
end
|
227
|
+
|
228
|
+
def schedule_every(duration, callable=nil, opts={}, &block)
|
229
|
+
|
230
|
+
do_schedule(:every, duration, callable, opts, true, block)
|
231
|
+
end
|
232
|
+
|
233
|
+
def interval(duration, callable=nil, opts={}, &block)
|
234
|
+
|
235
|
+
do_schedule(:interval, duration, callable, opts, opts[:job], block)
|
236
|
+
end
|
237
|
+
|
238
|
+
def schedule_interval(duration, callable=nil, opts={}, &block)
|
239
|
+
|
240
|
+
do_schedule(:interval, duration, callable, opts, true, block)
|
241
|
+
end
|
242
|
+
|
243
|
+
def cron(cronline, callable=nil, opts={}, &block)
|
244
|
+
|
245
|
+
do_schedule(:cron, cronline, callable, opts, opts[:job], block)
|
246
|
+
end
|
247
|
+
|
248
|
+
def schedule_cron(cronline, callable=nil, opts={}, &block)
|
249
|
+
|
250
|
+
do_schedule(:cron, cronline, callable, opts, true, block)
|
251
|
+
end
|
252
|
+
|
253
|
+
def schedule(arg, callable=nil, opts={}, &block)
|
254
|
+
|
255
|
+
opts[:_t] = Scheduler.parse(arg, opts)
|
256
|
+
|
257
|
+
case opts[:_t]
|
258
|
+
when CronLine then schedule_cron(arg, callable, opts, &block)
|
259
|
+
when Time then schedule_at(arg, callable, opts, &block)
|
260
|
+
else schedule_in(arg, callable, opts, &block)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def repeat(arg, callable=nil, opts={}, &block)
|
265
|
+
|
266
|
+
opts[:_t] = Scheduler.parse(arg, opts)
|
267
|
+
|
268
|
+
case opts[:_t]
|
269
|
+
when CronLine then schedule_cron(arg, callable, opts, &block)
|
270
|
+
else schedule_every(arg, callable, opts, &block)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def unschedule(job_or_job_id)
|
275
|
+
|
276
|
+
job, job_id = fetch(job_or_job_id)
|
277
|
+
|
278
|
+
fail ArgumentError.new("no job found with id '#{job_id}'") unless job
|
279
|
+
|
280
|
+
job.unschedule if job
|
281
|
+
end
|
282
|
+
|
283
|
+
#--
|
284
|
+
# jobs methods
|
285
|
+
#++
|
286
|
+
|
287
|
+
# Returns all the scheduled jobs
|
288
|
+
# (even those right before re-schedule).
|
289
|
+
#
|
290
|
+
def jobs(opts={})
|
291
|
+
|
292
|
+
opts = { opts => true } if opts.is_a?(Symbol)
|
293
|
+
|
294
|
+
jobs = @jobs.to_a
|
295
|
+
|
296
|
+
if opts[:running]
|
297
|
+
jobs = jobs.select { |j| j.running? }
|
298
|
+
elsif ! opts[:all]
|
299
|
+
jobs = jobs.reject { |j| j.next_time.nil? || j.unscheduled_at }
|
300
|
+
end
|
58
301
|
|
59
|
-
|
302
|
+
tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
303
|
+
jobs = jobs.reject { |j| tags.find { |t| ! j.tags.include?(t) } }
|
304
|
+
|
305
|
+
jobs
|
306
|
+
end
|
307
|
+
|
308
|
+
def at_jobs(opts={})
|
309
|
+
|
310
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::AtJob) }
|
311
|
+
end
|
312
|
+
|
313
|
+
def in_jobs(opts={})
|
314
|
+
|
315
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::InJob) }
|
316
|
+
end
|
317
|
+
|
318
|
+
def every_jobs(opts={})
|
319
|
+
|
320
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::EveryJob) }
|
321
|
+
end
|
322
|
+
|
323
|
+
def interval_jobs(opts={})
|
324
|
+
|
325
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::IntervalJob) }
|
326
|
+
end
|
327
|
+
|
328
|
+
def cron_jobs(opts={})
|
329
|
+
|
330
|
+
jobs(opts).select { |j| j.is_a?(Rufus::Scheduler::CronJob) }
|
331
|
+
end
|
332
|
+
|
333
|
+
def job(job_id)
|
334
|
+
|
335
|
+
@jobs[job_id]
|
336
|
+
end
|
337
|
+
|
338
|
+
# Returns true if the scheduler has acquired the [exclusive] lock and
|
339
|
+
# thus may run.
|
340
|
+
#
|
341
|
+
# Most of the time, a scheduler is run alone and this method should
|
342
|
+
# return true. It is useful in cases where among a group of applications
|
343
|
+
# only one of them should run the scheduler. For schedulers that should
|
344
|
+
# not run, the method should return false.
|
345
|
+
#
|
346
|
+
# Out of the box, rufus-scheduler proposes the
|
347
|
+
# :lockfile => 'path/to/lock/file' scheduler start option. It makes
|
348
|
+
# it easy for schedulers on the same machine to determine which should
|
349
|
+
# run (the first to write the lockfile and lock it). It uses "man 2 flock"
|
350
|
+
# so it probably won't work reliably on distributed file systems.
|
351
|
+
#
|
352
|
+
# If one needs to use a special/different locking mechanism, the scheduler
|
353
|
+
# accepts :scheduler_lock => lock_object. lock_object only needs to respond
|
354
|
+
# to #lock
|
355
|
+
# and #unlock, and both of these methods should be idempotent.
|
356
|
+
#
|
357
|
+
# Look at rufus/scheduler/locks.rb for an example.
|
358
|
+
#
|
359
|
+
def lock
|
360
|
+
|
361
|
+
@scheduler_lock.lock
|
362
|
+
end
|
363
|
+
|
364
|
+
# Sister method to #lock, is called when the scheduler shuts down.
|
365
|
+
#
|
366
|
+
def unlock
|
367
|
+
|
368
|
+
@trigger_lock.unlock
|
369
|
+
@scheduler_lock.unlock
|
370
|
+
end
|
371
|
+
|
372
|
+
# Callback called when a job is triggered. If the lock cannot be acquired,
|
373
|
+
# the job won't run (though it'll still be scheduled to run again if
|
374
|
+
# necessary).
|
375
|
+
#
|
376
|
+
def confirm_lock
|
377
|
+
|
378
|
+
@trigger_lock.lock
|
379
|
+
end
|
380
|
+
|
381
|
+
# Returns true if this job is currently scheduled.
|
382
|
+
#
|
383
|
+
# Takes extra care to answer true if the job is a repeat job
|
384
|
+
# currently firing.
|
385
|
+
#
|
386
|
+
def scheduled?(job_or_job_id)
|
387
|
+
|
388
|
+
job, job_id = fetch(job_or_job_id)
|
389
|
+
|
390
|
+
!! (job && job.next_time != nil)
|
391
|
+
end
|
392
|
+
|
393
|
+
# Lists all the threads associated with this scheduler.
|
394
|
+
#
|
395
|
+
def threads
|
396
|
+
|
397
|
+
Thread.list.select { |t| t[thread_key] }
|
398
|
+
end
|
399
|
+
|
400
|
+
# Lists all the work threads (the ones actually running the scheduled
|
401
|
+
# block code)
|
402
|
+
#
|
403
|
+
# Accepts a query option, which can be set to:
|
404
|
+
# * :all (default), returns all the threads that are work threads
|
405
|
+
# or are currently running a job
|
406
|
+
# * :active, returns all threads that are currenly running a job
|
407
|
+
# * :vacant, returns the threads that are not running a job
|
408
|
+
#
|
409
|
+
# If, thanks to :blocking => true, a job is scheduled to monopolize the
|
410
|
+
# main scheduler thread, that thread will get returned when :active or
|
411
|
+
# :all.
|
412
|
+
#
|
413
|
+
def work_threads(query=:all)
|
414
|
+
|
415
|
+
ts =
|
416
|
+
threads.select { |t|
|
417
|
+
t[:rufus_scheduler_job] || t[:rufus_scheduler_work_thread]
|
418
|
+
}
|
419
|
+
|
420
|
+
case query
|
421
|
+
when :active then ts.select { |t| t[:rufus_scheduler_job] }
|
422
|
+
when :vacant then ts.reject { |t| t[:rufus_scheduler_job] }
|
423
|
+
else ts
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def running_jobs(opts={})
|
428
|
+
|
429
|
+
jobs(opts.merge(:running => true))
|
430
|
+
end
|
431
|
+
|
432
|
+
def occurrences(time0, time1, format=:per_job)
|
433
|
+
|
434
|
+
h = {}
|
435
|
+
|
436
|
+
jobs.each do |j|
|
437
|
+
os = j.occurrences(time0, time1)
|
438
|
+
h[j] = os if os.any?
|
439
|
+
end
|
440
|
+
|
441
|
+
if format == :timeline
|
442
|
+
a = []
|
443
|
+
h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
|
444
|
+
a.sort_by { |(t, j)| t }
|
445
|
+
else
|
446
|
+
h
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
def timeline(time0, time1)
|
451
|
+
|
452
|
+
occurrences(time0, time1, :timeline)
|
453
|
+
end
|
454
|
+
|
455
|
+
def on_error(job, err)
|
456
|
+
|
457
|
+
pre = err.object_id.to_s
|
458
|
+
|
459
|
+
ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }
|
460
|
+
|
461
|
+
stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
|
462
|
+
stderr.puts(" #{pre} job:")
|
463
|
+
stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
|
464
|
+
# TODO: eventually use a Job#detail or something like that
|
465
|
+
stderr.puts(" #{pre} error:")
|
466
|
+
stderr.puts(" #{pre} #{err.object_id}")
|
467
|
+
stderr.puts(" #{pre} #{err.class}")
|
468
|
+
stderr.puts(" #{pre} #{err}")
|
469
|
+
err.backtrace.each do |l|
|
470
|
+
stderr.puts(" #{pre} #{l}")
|
471
|
+
end
|
472
|
+
stderr.puts(" #{pre} tz:")
|
473
|
+
stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
|
474
|
+
stderr.puts(" #{pre} Time.now: #{Time.now}")
|
475
|
+
stderr.puts(" #{pre} scheduler:")
|
476
|
+
stderr.puts(" #{pre} object_id: #{object_id}")
|
477
|
+
stderr.puts(" #{pre} opts:")
|
478
|
+
stderr.puts(" #{pre} #{@opts.inspect}")
|
479
|
+
stderr.puts(" #{pre} frequency: #{self.frequency}")
|
480
|
+
stderr.puts(" #{pre} scheduler_lock: #{@scheduler_lock.inspect}")
|
481
|
+
stderr.puts(" #{pre} trigger_lock: #{@trigger_lock.inspect}")
|
482
|
+
stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
|
483
|
+
stderr.puts(" #{pre} down?: #{down?}")
|
484
|
+
stderr.puts(" #{pre} threads: #{self.threads.size}")
|
485
|
+
stderr.puts(" #{pre} thread: #{self.thread}")
|
486
|
+
stderr.puts(" #{pre} thread_key: #{self.thread_key}")
|
487
|
+
stderr.puts(" #{pre} work_threads: #{work_threads.size}")
|
488
|
+
stderr.puts(" #{pre} active: #{work_threads(:active).size}")
|
489
|
+
stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
|
490
|
+
stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
|
491
|
+
stderr.puts(" #{pre} mutexes: #{ms.inspect}")
|
492
|
+
stderr.puts(" #{pre} jobs: #{jobs.size}")
|
493
|
+
stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
|
494
|
+
stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
|
495
|
+
stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
|
496
|
+
stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
|
497
|
+
stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
|
498
|
+
stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
|
499
|
+
stderr.puts(" #{pre} work_queue: #{work_queue.size}")
|
500
|
+
stderr.puts("} #{pre} .")
|
501
|
+
|
502
|
+
rescue => e
|
503
|
+
|
504
|
+
stderr.puts("failure in #on_error itself:")
|
505
|
+
stderr.puts(e.inspect)
|
506
|
+
stderr.puts(e.backtrace)
|
507
|
+
|
508
|
+
ensure
|
509
|
+
|
510
|
+
stderr.flush
|
511
|
+
end
|
512
|
+
|
513
|
+
protected
|
514
|
+
|
515
|
+
# Returns [ job, job_id ]
|
516
|
+
#
|
517
|
+
def fetch(job_or_job_id)
|
518
|
+
|
519
|
+
if job_or_job_id.respond_to?(:job_id)
|
520
|
+
[ job_or_job_id, job_or_job_id.job_id ]
|
521
|
+
else
|
522
|
+
[ job(job_or_job_id), job_or_job_id ]
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
def terminate_all_jobs
|
527
|
+
|
528
|
+
jobs.each { |j| j.unschedule }
|
529
|
+
|
530
|
+
sleep 0.01 while running_jobs.size > 0
|
531
|
+
end
|
532
|
+
|
533
|
+
def join_all_work_threads
|
534
|
+
|
535
|
+
work_threads.size.times { @work_queue << :sayonara }
|
536
|
+
|
537
|
+
work_threads.each { |t| t.join }
|
538
|
+
|
539
|
+
@work_queue.clear
|
540
|
+
end
|
541
|
+
|
542
|
+
def kill_all_work_threads
|
543
|
+
|
544
|
+
work_threads.each { |t| t.kill }
|
545
|
+
end
|
546
|
+
|
547
|
+
#def free_all_work_threads
|
548
|
+
#
|
549
|
+
# work_threads.each { |t| t.raise(KillSignal) }
|
550
|
+
#end
|
551
|
+
|
552
|
+
def start
|
553
|
+
|
554
|
+
@started_at = Time.now
|
555
|
+
|
556
|
+
@thread =
|
557
|
+
Thread.new do
|
558
|
+
|
559
|
+
while @started_at do
|
560
|
+
|
561
|
+
unschedule_jobs
|
562
|
+
trigger_jobs unless @paused
|
563
|
+
timeout_jobs
|
564
|
+
|
565
|
+
sleep(@frequency)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
@thread[@thread_key] = true
|
570
|
+
@thread[:rufus_scheduler] = self
|
571
|
+
@thread[:name] = @opts[:thread_name] || "#{@thread_key}_scheduler"
|
572
|
+
end
|
573
|
+
|
574
|
+
def unschedule_jobs
|
575
|
+
|
576
|
+
@jobs.delete_unscheduled
|
577
|
+
end
|
578
|
+
|
579
|
+
def trigger_jobs
|
580
|
+
|
581
|
+
now = Time.now
|
582
|
+
|
583
|
+
@jobs.each(now) do |job|
|
584
|
+
|
585
|
+
job.trigger(now)
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
def timeout_jobs
|
590
|
+
|
591
|
+
work_threads(:active).each do |t|
|
592
|
+
|
593
|
+
job = t[:rufus_scheduler_job]
|
594
|
+
to = t[:rufus_scheduler_timeout]
|
595
|
+
|
596
|
+
next unless job && to
|
597
|
+
# thread might just have become inactive (job -> nil)
|
598
|
+
|
599
|
+
ts = t[:rufus_scheduler_time]
|
600
|
+
to = to.is_a?(Time) ? to : ts + to
|
601
|
+
|
602
|
+
next if to > Time.now
|
603
|
+
|
604
|
+
t.raise(Rufus::Scheduler::TimeoutError)
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
def do_schedule(job_type, t, callable, opts, return_job_instance, block)
|
609
|
+
|
610
|
+
fail NotRunningError.new(
|
611
|
+
'cannot schedule, scheduler is down or shutting down'
|
612
|
+
) if @started_at == nil
|
613
|
+
|
614
|
+
callable, opts = nil, callable if callable.is_a?(Hash)
|
615
|
+
return_job_instance ||= opts[:job]
|
616
|
+
|
617
|
+
job_class =
|
618
|
+
case job_type
|
619
|
+
when :once
|
620
|
+
opts[:_t] ||= Rufus::Scheduler.parse(t, opts)
|
621
|
+
opts[:_t].is_a?(Time) ? AtJob : InJob
|
622
|
+
when :every
|
623
|
+
EveryJob
|
624
|
+
when :interval
|
625
|
+
IntervalJob
|
626
|
+
when :cron
|
627
|
+
CronJob
|
628
|
+
end
|
629
|
+
|
630
|
+
job = job_class.new(self, t, opts, block || callable)
|
631
|
+
|
632
|
+
raise ArgumentError.new(
|
633
|
+
"job frequency (#{job.frequency}) is higher than " +
|
634
|
+
"scheduler frequency (#{@frequency})"
|
635
|
+
) if job.respond_to?(:frequency) && job.frequency < @frequency
|
636
|
+
|
637
|
+
@jobs.push(job)
|
638
|
+
|
639
|
+
return_job_instance ? job : job.job_id
|
640
|
+
end
|
60
641
|
end
|
61
642
|
end
|
62
643
|
|
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,10 +26,12 @@ 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
|
+
s.add_development_dependency 'chronic'
|
34
|
+
s.add_development_dependency 'tzinfo'
|
33
35
|
|
34
36
|
s.require_path = 'lib'
|
35
37
|
end
|