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
@@ -0,0 +1,633 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
|
26
|
+
module Rufus
|
27
|
+
|
28
|
+
class Scheduler
|
29
|
+
|
30
|
+
#--
|
31
|
+
# job classes
|
32
|
+
#++
|
33
|
+
|
34
|
+
class Job
|
35
|
+
|
36
|
+
#
|
37
|
+
# Used by Job#kill
|
38
|
+
#
|
39
|
+
class KillSignal < StandardError; end
|
40
|
+
|
41
|
+
attr_reader :id
|
42
|
+
attr_reader :opts
|
43
|
+
attr_reader :original
|
44
|
+
attr_reader :scheduled_at
|
45
|
+
attr_reader :last_time
|
46
|
+
attr_reader :unscheduled_at
|
47
|
+
attr_reader :tags
|
48
|
+
attr_reader :count
|
49
|
+
attr_reader :last_work_time
|
50
|
+
attr_reader :mean_work_time
|
51
|
+
|
52
|
+
# next trigger time
|
53
|
+
#
|
54
|
+
attr_accessor :next_time
|
55
|
+
|
56
|
+
# anything with a #call(job[, timet]) method,
|
57
|
+
# what gets actually triggered
|
58
|
+
#
|
59
|
+
attr_reader :callable
|
60
|
+
|
61
|
+
# a reference to the instance whose call method is the @callable
|
62
|
+
#
|
63
|
+
attr_reader :handler
|
64
|
+
|
65
|
+
def initialize(scheduler, original, opts, block)
|
66
|
+
|
67
|
+
@scheduler = scheduler
|
68
|
+
@original = original
|
69
|
+
@opts = opts
|
70
|
+
|
71
|
+
@handler = block
|
72
|
+
|
73
|
+
@callable =
|
74
|
+
if block.respond_to?(:arity)
|
75
|
+
block
|
76
|
+
elsif block.respond_to?(:call)
|
77
|
+
block.method(:call)
|
78
|
+
elsif block.is_a?(Class)
|
79
|
+
@handler = block.new
|
80
|
+
@handler.method(:call) rescue nil
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
@scheduled_at = Time.now
|
86
|
+
@unscheduled_at = nil
|
87
|
+
@last_time = nil
|
88
|
+
|
89
|
+
@locals = {}
|
90
|
+
@local_mutex = Mutex.new
|
91
|
+
|
92
|
+
@id = determine_id
|
93
|
+
|
94
|
+
raise(
|
95
|
+
ArgumentError,
|
96
|
+
'missing block or callable to schedule',
|
97
|
+
caller[2..-1]
|
98
|
+
) unless @callable
|
99
|
+
|
100
|
+
@tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
101
|
+
|
102
|
+
@count = 0
|
103
|
+
@last_work_time = 0.0
|
104
|
+
@mean_work_time = 0.0
|
105
|
+
|
106
|
+
# tidy up options
|
107
|
+
|
108
|
+
if @opts[:allow_overlap] == false || @opts[:allow_overlapping] == false
|
109
|
+
@opts[:overlap] = false
|
110
|
+
end
|
111
|
+
if m = @opts[:mutex]
|
112
|
+
@opts[:mutex] = Array(m)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
alias job_id id
|
117
|
+
|
118
|
+
def trigger(time)
|
119
|
+
|
120
|
+
set_next_time(time)
|
121
|
+
|
122
|
+
return if (
|
123
|
+
opts[:overlap] == false &&
|
124
|
+
running?
|
125
|
+
)
|
126
|
+
return if (
|
127
|
+
callback(:confirm_lock, time) &&
|
128
|
+
callback(:on_pre_trigger, time)
|
129
|
+
) == false
|
130
|
+
|
131
|
+
@count += 1
|
132
|
+
|
133
|
+
if opts[:blocking]
|
134
|
+
do_trigger(time)
|
135
|
+
else
|
136
|
+
do_trigger_in_thread(time)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def unschedule
|
141
|
+
|
142
|
+
@unscheduled_at = Time.now
|
143
|
+
end
|
144
|
+
|
145
|
+
def threads
|
146
|
+
|
147
|
+
Thread.list.select { |t| t[:rufus_scheduler_job] == self }
|
148
|
+
end
|
149
|
+
|
150
|
+
# Kills all the threads this Job currently has going on.
|
151
|
+
#
|
152
|
+
def kill
|
153
|
+
|
154
|
+
threads.each { |t| t.raise(KillSignal) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def running?
|
158
|
+
|
159
|
+
threads.any?
|
160
|
+
end
|
161
|
+
|
162
|
+
def scheduled?
|
163
|
+
|
164
|
+
@scheduler.scheduled?(self)
|
165
|
+
end
|
166
|
+
|
167
|
+
def []=(key, value)
|
168
|
+
|
169
|
+
@local_mutex.synchronize { @locals[key] = value }
|
170
|
+
end
|
171
|
+
|
172
|
+
def [](key)
|
173
|
+
|
174
|
+
@local_mutex.synchronize { @locals[key] }
|
175
|
+
end
|
176
|
+
|
177
|
+
def key?(key)
|
178
|
+
|
179
|
+
@local_mutex.synchronize { @locals.key?(key) }
|
180
|
+
end
|
181
|
+
|
182
|
+
def keys
|
183
|
+
|
184
|
+
@local_mutex.synchronize { @locals.keys }
|
185
|
+
end
|
186
|
+
|
187
|
+
#def hash
|
188
|
+
# self.object_id
|
189
|
+
#end
|
190
|
+
#def eql?(o)
|
191
|
+
# o.class == self.class && o.hash == self.hash
|
192
|
+
#end
|
193
|
+
#
|
194
|
+
# might be necessary at some point
|
195
|
+
|
196
|
+
# Calls the callable (usually a block) wrapped in this Job instance.
|
197
|
+
#
|
198
|
+
# Warning: error rescueing is the responsibity of the caller.
|
199
|
+
#
|
200
|
+
def call(do_rescue=false)
|
201
|
+
|
202
|
+
do_call(Time.now, do_rescue)
|
203
|
+
end
|
204
|
+
|
205
|
+
protected
|
206
|
+
|
207
|
+
def callback(meth, time)
|
208
|
+
|
209
|
+
return true unless @scheduler.respond_to?(meth)
|
210
|
+
|
211
|
+
arity = @scheduler.method(meth).arity
|
212
|
+
args = [ self, time ][0, (arity < 0 ? 2 : arity)]
|
213
|
+
|
214
|
+
@scheduler.send(meth, *args)
|
215
|
+
end
|
216
|
+
|
217
|
+
def compute_timeout
|
218
|
+
|
219
|
+
if to = @opts[:timeout]
|
220
|
+
Rufus::Scheduler.parse(to)
|
221
|
+
else
|
222
|
+
nil
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def mutex(m)
|
227
|
+
|
228
|
+
m.is_a?(Mutex) ? m : (@scheduler.mutexes[m.to_s] ||= Mutex.new)
|
229
|
+
end
|
230
|
+
|
231
|
+
def do_call(time, do_rescue)
|
232
|
+
|
233
|
+
args = [ self, time ][0, @callable.arity]
|
234
|
+
@callable.call(*args)
|
235
|
+
|
236
|
+
rescue StandardError => se
|
237
|
+
|
238
|
+
raise se unless do_rescue
|
239
|
+
|
240
|
+
return if se.is_a?(KillSignal) # discard
|
241
|
+
|
242
|
+
@scheduler.on_error(self, se)
|
243
|
+
|
244
|
+
# exceptions above StandardError do pass through
|
245
|
+
end
|
246
|
+
|
247
|
+
def do_trigger(time)
|
248
|
+
|
249
|
+
t = Time.now
|
250
|
+
# if there are mutexes, t might be really bigger than time
|
251
|
+
|
252
|
+
Thread.current[:rufus_scheduler_job] = self
|
253
|
+
Thread.current[:rufus_scheduler_time] = t
|
254
|
+
Thread.current[:rufus_scheduler_timeout] = compute_timeout
|
255
|
+
|
256
|
+
@last_time = t
|
257
|
+
|
258
|
+
do_call(time, true)
|
259
|
+
|
260
|
+
ensure
|
261
|
+
|
262
|
+
@last_work_time =
|
263
|
+
Time.now - Thread.current[:rufus_scheduler_time]
|
264
|
+
@mean_work_time =
|
265
|
+
((@count - 1) * @mean_work_time + @last_work_time) / @count
|
266
|
+
|
267
|
+
post_trigger(time)
|
268
|
+
|
269
|
+
Thread.current[:rufus_scheduler_job] = nil
|
270
|
+
Thread.current[:rufus_scheduler_time] = nil
|
271
|
+
Thread.current[:rufus_scheduler_timeout] = nil
|
272
|
+
end
|
273
|
+
|
274
|
+
def post_trigger(time)
|
275
|
+
|
276
|
+
set_next_time(time, true)
|
277
|
+
|
278
|
+
callback(:on_post_trigger, time)
|
279
|
+
end
|
280
|
+
|
281
|
+
def start_work_thread
|
282
|
+
|
283
|
+
thread =
|
284
|
+
Thread.new do
|
285
|
+
|
286
|
+
Thread.current[@scheduler.thread_key] = true
|
287
|
+
Thread.current[:rufus_scheduler_job_thread] = true
|
288
|
+
|
289
|
+
loop do
|
290
|
+
|
291
|
+
job, time = @scheduler.work_queue.pop
|
292
|
+
|
293
|
+
break if @scheduler.started_at == nil
|
294
|
+
|
295
|
+
next if job.unscheduled_at
|
296
|
+
|
297
|
+
begin
|
298
|
+
|
299
|
+
(job.opts[:mutex] || []).reduce(
|
300
|
+
lambda { job.do_trigger(time) }
|
301
|
+
) do |b, m|
|
302
|
+
lambda { mutex(m).synchronize { b.call } }
|
303
|
+
end.call
|
304
|
+
|
305
|
+
rescue KillSignal
|
306
|
+
|
307
|
+
# simply go on looping
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
thread[@scheduler.thread_key] = true
|
313
|
+
thread[:rufus_scheduler_work_thread] = true
|
314
|
+
#
|
315
|
+
# same as above (in the thead block),
|
316
|
+
# but since it has to be done as quickly as possible.
|
317
|
+
# So, whoever is running first (scheduler thread vs job thread)
|
318
|
+
# sets this information
|
319
|
+
end
|
320
|
+
|
321
|
+
def do_trigger_in_thread(time)
|
322
|
+
|
323
|
+
threads = @scheduler.work_threads
|
324
|
+
|
325
|
+
cur = threads.size
|
326
|
+
vac = threads.select { |t| t[:rufus_scheduler_job] == nil }.size
|
327
|
+
#min = @scheduler.min_work_threads
|
328
|
+
max = @scheduler.max_work_threads
|
329
|
+
que = @scheduler.work_queue.size
|
330
|
+
|
331
|
+
start_work_thread if vac - que < 1 && cur < max
|
332
|
+
|
333
|
+
@scheduler.work_queue << [ self, time ]
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
class OneTimeJob < Job
|
338
|
+
|
339
|
+
alias time next_time
|
340
|
+
|
341
|
+
def occurrences(time0, time1)
|
342
|
+
|
343
|
+
(time >= time0 && time <= time1) ? [ time ] : []
|
344
|
+
end
|
345
|
+
|
346
|
+
protected
|
347
|
+
|
348
|
+
def determine_id
|
349
|
+
|
350
|
+
[
|
351
|
+
self.class.name.split(':').last.downcase[0..-4],
|
352
|
+
@scheduled_at.to_f,
|
353
|
+
@next_time.to_f,
|
354
|
+
opts.hash.abs
|
355
|
+
].map(&:to_s).join('_')
|
356
|
+
end
|
357
|
+
|
358
|
+
# There is no next_time for one time jobs, hence the false.
|
359
|
+
#
|
360
|
+
def set_next_time(trigger_time, is_post=false)
|
361
|
+
|
362
|
+
@next_time = is_post ? nil : false
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
class AtJob < OneTimeJob
|
367
|
+
|
368
|
+
def initialize(scheduler, time, opts, block)
|
369
|
+
|
370
|
+
super(scheduler, time, opts, block)
|
371
|
+
|
372
|
+
@next_time =
|
373
|
+
opts[:_t] || Rufus::Scheduler.parse_at(time, opts)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
class InJob < OneTimeJob
|
378
|
+
|
379
|
+
def initialize(scheduler, duration, opts, block)
|
380
|
+
|
381
|
+
super(scheduler, duration, opts, block)
|
382
|
+
|
383
|
+
@next_time =
|
384
|
+
@scheduled_at +
|
385
|
+
opts[:_t] || Rufus::Scheduler.parse_in(duration, opts)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
class RepeatJob < Job
|
390
|
+
|
391
|
+
attr_reader :paused_at
|
392
|
+
|
393
|
+
attr_reader :first_at
|
394
|
+
attr_accessor :last_at
|
395
|
+
attr_accessor :times
|
396
|
+
|
397
|
+
def initialize(scheduler, duration, opts, block)
|
398
|
+
|
399
|
+
super
|
400
|
+
|
401
|
+
@paused_at = nil
|
402
|
+
|
403
|
+
@times = opts[:times]
|
404
|
+
|
405
|
+
raise ArgumentError.new(
|
406
|
+
"cannot accept :times => #{@times.inspect}, not nil or an int"
|
407
|
+
) unless @times == nil || @times.is_a?(Fixnum)
|
408
|
+
|
409
|
+
self.first_at =
|
410
|
+
opts[:first] || opts[:first_time] ||
|
411
|
+
opts[:first_at] || opts[:first_in] ||
|
412
|
+
nil
|
413
|
+
self.last_at =
|
414
|
+
opts[:last] || opts[:last_at] || opts[:last_in]
|
415
|
+
end
|
416
|
+
|
417
|
+
def first_at=(first)
|
418
|
+
|
419
|
+
return @first_at = nil if first == nil
|
420
|
+
|
421
|
+
n = Time.now
|
422
|
+
first = n + 0.003 if first == :now || first == :immediately
|
423
|
+
|
424
|
+
@first_at = Rufus::Scheduler.parse_to_time(first)
|
425
|
+
|
426
|
+
raise ArgumentError.new(
|
427
|
+
"cannot set first[_at|_in] in the past: " +
|
428
|
+
"#{first.inspect} -> #{@first_at.inspect}"
|
429
|
+
) if first != 0 && @first_at < n
|
430
|
+
end
|
431
|
+
|
432
|
+
def last_at=(last)
|
433
|
+
|
434
|
+
@last_at = last ? Rufus::Scheduler.parse_to_time(last) : nil
|
435
|
+
|
436
|
+
raise ArgumentError.new(
|
437
|
+
"cannot set last[_at|_in] in the past: " +
|
438
|
+
"#{last.inspect} -> #{@last_at.inspect}"
|
439
|
+
) if last && @last_at < Time.now
|
440
|
+
end
|
441
|
+
|
442
|
+
def trigger(time)
|
443
|
+
|
444
|
+
return if @paused_at
|
445
|
+
|
446
|
+
return (@next_time = nil) if @times && @times < 1
|
447
|
+
return (@next_time = nil) if @last_at && time >= @last_at
|
448
|
+
#
|
449
|
+
# TODO: rework that, jobs are thus kept 1 step too much in @jobs
|
450
|
+
|
451
|
+
super
|
452
|
+
|
453
|
+
@times -= 1 if @times
|
454
|
+
end
|
455
|
+
|
456
|
+
def pause
|
457
|
+
|
458
|
+
@paused_at = Time.now
|
459
|
+
end
|
460
|
+
|
461
|
+
def resume
|
462
|
+
|
463
|
+
@paused_at = nil
|
464
|
+
end
|
465
|
+
|
466
|
+
def paused?
|
467
|
+
|
468
|
+
@paused_at != nil
|
469
|
+
end
|
470
|
+
|
471
|
+
def determine_id
|
472
|
+
|
473
|
+
[
|
474
|
+
self.class.name.split(':').last.downcase[0..-4],
|
475
|
+
@scheduled_at.to_f,
|
476
|
+
opts.hash.abs
|
477
|
+
].map(&:to_s).join('_')
|
478
|
+
end
|
479
|
+
|
480
|
+
def occurrences(time0, time1)
|
481
|
+
|
482
|
+
a = []
|
483
|
+
|
484
|
+
nt = @next_time
|
485
|
+
ts = @times
|
486
|
+
|
487
|
+
loop do
|
488
|
+
|
489
|
+
break if nt > time1
|
490
|
+
break if ts && ts <= 0
|
491
|
+
|
492
|
+
a << nt if nt >= time0
|
493
|
+
|
494
|
+
nt = next_time_from(nt)
|
495
|
+
ts = ts - 1 if ts
|
496
|
+
end
|
497
|
+
|
498
|
+
a
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
#
|
503
|
+
# A parent class of EveryJob and IntervalJob
|
504
|
+
#
|
505
|
+
class EvInJob < RepeatJob
|
506
|
+
|
507
|
+
def first_at=(first)
|
508
|
+
|
509
|
+
super
|
510
|
+
|
511
|
+
@next_time = @first_at
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
class EveryJob < EvInJob
|
516
|
+
|
517
|
+
attr_reader :frequency
|
518
|
+
|
519
|
+
def initialize(scheduler, duration, opts, block)
|
520
|
+
|
521
|
+
super(scheduler, duration, opts, block)
|
522
|
+
|
523
|
+
@frequency = Rufus::Scheduler.parse_in(@original)
|
524
|
+
|
525
|
+
raise ArgumentError.new(
|
526
|
+
"cannot schedule #{self.class} with a frequency " +
|
527
|
+
"of #{@frequency.inspect} (#{@original.inspect})"
|
528
|
+
) if @frequency <= 0
|
529
|
+
|
530
|
+
set_next_time(nil)
|
531
|
+
end
|
532
|
+
|
533
|
+
protected
|
534
|
+
|
535
|
+
def set_next_time(trigger_time, is_post=false)
|
536
|
+
|
537
|
+
return if is_post
|
538
|
+
|
539
|
+
@next_time =
|
540
|
+
if @first_at == nil || @first_at < Time.now
|
541
|
+
(trigger_time || Time.now) + @frequency
|
542
|
+
else
|
543
|
+
@first_at
|
544
|
+
end
|
545
|
+
end
|
546
|
+
|
547
|
+
def next_time_from(time)
|
548
|
+
|
549
|
+
time + @frequency
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
class IntervalJob < EvInJob
|
554
|
+
|
555
|
+
attr_reader :interval
|
556
|
+
|
557
|
+
def initialize(scheduler, interval, opts, block)
|
558
|
+
|
559
|
+
super(scheduler, interval, opts, block)
|
560
|
+
|
561
|
+
@interval = Rufus::Scheduler.parse_in(@original)
|
562
|
+
|
563
|
+
raise ArgumentError.new(
|
564
|
+
"cannot schedule #{self.class} with an interval " +
|
565
|
+
"of #{@interval.inspect} (#{@original.inspect})"
|
566
|
+
) if @interval <= 0
|
567
|
+
|
568
|
+
set_next_time(nil)
|
569
|
+
end
|
570
|
+
|
571
|
+
protected
|
572
|
+
|
573
|
+
def set_next_time(trigger_time, is_post=false)
|
574
|
+
|
575
|
+
@next_time =
|
576
|
+
if is_post
|
577
|
+
Time.now + @interval
|
578
|
+
elsif trigger_time.nil?
|
579
|
+
if @first_at == nil || @first_at < Time.now
|
580
|
+
Time.now + @interval
|
581
|
+
else
|
582
|
+
@first_at
|
583
|
+
end
|
584
|
+
else
|
585
|
+
false
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
def next_time_from(time)
|
590
|
+
|
591
|
+
time + @mean_work_time + @interval
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
class CronJob < RepeatJob
|
596
|
+
|
597
|
+
def initialize(scheduler, cronline, opts, block)
|
598
|
+
|
599
|
+
super(scheduler, cronline, opts, block)
|
600
|
+
|
601
|
+
@cron_line = opts[:_t] || CronLine.new(cronline)
|
602
|
+
set_next_time(nil)
|
603
|
+
end
|
604
|
+
|
605
|
+
def frequency
|
606
|
+
|
607
|
+
@cron_line.frequency
|
608
|
+
end
|
609
|
+
|
610
|
+
def brute_frequency
|
611
|
+
|
612
|
+
@cron_line.brute_frequency
|
613
|
+
end
|
614
|
+
|
615
|
+
protected
|
616
|
+
|
617
|
+
def set_next_time(trigger_time, is_post=false)
|
618
|
+
|
619
|
+
@next_time = next_time_from(trigger_time || Time.now)
|
620
|
+
end
|
621
|
+
|
622
|
+
def next_time_from(time)
|
623
|
+
|
624
|
+
if @first_at == nil || @first_at < time
|
625
|
+
@cron_line.next_time(time)
|
626
|
+
else
|
627
|
+
@first_at
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006-2015, John Mettraux, jmettraux@gmail.com
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
#
|
22
|
+
# Made in Japan.
|
23
|
+
#++
|
24
|
+
|
25
|
+
require 'fileutils'
|
26
|
+
|
27
|
+
|
28
|
+
class Rufus::Scheduler
|
29
|
+
|
30
|
+
#
|
31
|
+
# A lock that can always be acquired
|
32
|
+
#
|
33
|
+
class NullLock
|
34
|
+
|
35
|
+
# Locking is always successful.
|
36
|
+
#
|
37
|
+
def lock; true; end
|
38
|
+
|
39
|
+
def locked?; true; end
|
40
|
+
def unlock; true; end
|
41
|
+
end
|
42
|
+
|
43
|
+
#
|
44
|
+
# The standard flock mecha, with its own class thanks to @ecin
|
45
|
+
#
|
46
|
+
class FileLock
|
47
|
+
|
48
|
+
attr_reader :path
|
49
|
+
|
50
|
+
def initialize(path)
|
51
|
+
|
52
|
+
@path = path.to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# Locking is successful if this Ruby process can create and lock
|
56
|
+
# its lockfile (at the given path).
|
57
|
+
#
|
58
|
+
def lock
|
59
|
+
|
60
|
+
return true if locked?
|
61
|
+
|
62
|
+
@lockfile = nil
|
63
|
+
|
64
|
+
FileUtils.mkdir_p(::File.dirname(@path))
|
65
|
+
|
66
|
+
file = File.new(@path, File::RDWR | File::CREAT)
|
67
|
+
locked = file.flock(File::LOCK_NB | File::LOCK_EX)
|
68
|
+
|
69
|
+
return false unless locked
|
70
|
+
|
71
|
+
now = Time.now
|
72
|
+
|
73
|
+
file.print("pid: #{$$}, ")
|
74
|
+
file.print("scheduler.object_id: #{self.object_id}, ")
|
75
|
+
file.print("time: #{now}, ")
|
76
|
+
file.print("timestamp: #{now.to_f}")
|
77
|
+
file.flush
|
78
|
+
|
79
|
+
@lockfile = file
|
80
|
+
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def unlock
|
85
|
+
|
86
|
+
!! (@lockfile && @lockfile.flock(File::LOCK_UN))
|
87
|
+
end
|
88
|
+
|
89
|
+
def locked?
|
90
|
+
|
91
|
+
!! (@lockfile && @lockfile.flock(File::LOCK_NB | File::LOCK_EX))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|