rufus-scheduler 3.1.4 → 3.8.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +410 -0
  3. data/CREDITS.md +142 -0
  4. data/LICENSE.txt +1 -1
  5. data/Makefile +27 -0
  6. data/README.md +407 -143
  7. data/lib/rufus/scheduler/job_array.rb +36 -66
  8. data/lib/rufus/scheduler/jobs_core.rb +369 -0
  9. data/lib/rufus/scheduler/jobs_one_time.rb +53 -0
  10. data/lib/rufus/scheduler/jobs_repeat.rb +335 -0
  11. data/lib/rufus/scheduler/locks.rb +41 -67
  12. data/lib/rufus/scheduler/util.rb +89 -179
  13. data/lib/rufus/scheduler.rb +545 -453
  14. data/rufus-scheduler.gemspec +22 -11
  15. metadata +44 -85
  16. data/CHANGELOG.txt +0 -243
  17. data/CREDITS.txt +0 -88
  18. data/Rakefile +0 -83
  19. data/TODO.txt +0 -151
  20. data/lib/rufus/scheduler/cronline.rb +0 -470
  21. data/lib/rufus/scheduler/jobs.rb +0 -633
  22. data/lib/rufus/scheduler/zones.rb +0 -174
  23. data/lib/rufus/scheduler/zotime.rb +0 -155
  24. data/spec/basics_spec.rb +0 -54
  25. data/spec/cronline_spec.rb +0 -915
  26. data/spec/error_spec.rb +0 -139
  27. data/spec/job_array_spec.rb +0 -39
  28. data/spec/job_at_spec.rb +0 -58
  29. data/spec/job_cron_spec.rb +0 -128
  30. data/spec/job_every_spec.rb +0 -104
  31. data/spec/job_in_spec.rb +0 -20
  32. data/spec/job_interval_spec.rb +0 -68
  33. data/spec/job_repeat_spec.rb +0 -357
  34. data/spec/job_spec.rb +0 -631
  35. data/spec/lock_custom_spec.rb +0 -47
  36. data/spec/lock_flock_spec.rb +0 -47
  37. data/spec/lock_lockfile_spec.rb +0 -61
  38. data/spec/lock_spec.rb +0 -59
  39. data/spec/parse_spec.rb +0 -263
  40. data/spec/schedule_at_spec.rb +0 -158
  41. data/spec/schedule_cron_spec.rb +0 -66
  42. data/spec/schedule_every_spec.rb +0 -109
  43. data/spec/schedule_in_spec.rb +0 -80
  44. data/spec/schedule_interval_spec.rb +0 -128
  45. data/spec/scheduler_spec.rb +0 -1067
  46. data/spec/spec_helper.rb +0 -126
  47. data/spec/threads_spec.rb +0 -96
  48. data/spec/zotime_spec.rb +0 -396
@@ -1,633 +0,0 @@
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_work_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
-