rufus-scheduler 3.0.0 → 3.0.9
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 +63 -0
- data/CREDITS.txt +16 -0
- data/LICENSE.txt +1 -1
- data/README.md +389 -7
- data/Rakefile +0 -1
- data/TODO.txt +4 -0
- data/lib/rufus/scheduler/cronline.rb +109 -28
- data/lib/rufus/scheduler/job_array.rb +8 -1
- data/lib/rufus/scheduler/jobs.rb +127 -43
- data/lib/rufus/scheduler/locks.rb +95 -0
- data/lib/rufus/scheduler/util.rb +39 -28
- data/lib/rufus/scheduler.rb +150 -46
- data/rufus-scheduler.gemspec +2 -1
- data/spec/basics_spec.rb +54 -0
- data/spec/cronline_spec.rb +349 -92
- data/spec/error_spec.rb +30 -7
- data/spec/job_array_spec.rb +2 -2
- data/spec/job_at_spec.rb +4 -4
- data/spec/job_cron_spec.rb +42 -3
- data/spec/job_every_spec.rb +24 -5
- data/spec/job_interval_spec.rb +5 -5
- data/spec/job_repeat_spec.rb +86 -38
- data/spec/job_spec.rb +172 -55
- data/spec/lock_custom_spec.rb +47 -0
- data/spec/lock_flock_spec.rb +47 -0
- data/spec/{lockfile_spec.rb → lock_lockfile_spec.rb} +5 -5
- data/spec/lock_spec.rb +59 -0
- data/spec/parse_spec.rb +138 -76
- data/spec/schedule_at_spec.rb +46 -17
- data/spec/schedule_cron_spec.rb +6 -6
- data/spec/schedule_every_spec.rb +12 -12
- data/spec/schedule_in_spec.rb +8 -8
- data/spec/schedule_interval_spec.rb +13 -13
- data/spec/scheduler_spec.rb +213 -119
- data/spec/spec_helper.rb +62 -1
- data/spec/threads_spec.rb +24 -3
- metadata +58 -37
data/CHANGELOG.txt
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
= rufus-scheduler CHANGELOG.txt
|
|
3
3
|
|
|
4
4
|
|
|
5
|
+
== rufus-scheduler - 3.0.9 released 2014/08/30
|
|
6
|
+
|
|
7
|
+
- fix TZ with underscores, thanks https://github.com/gnilrets
|
|
8
|
+
- integrate https://github.com/ecin Lock mecha
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
== rufus-scheduler - 3.0.8 released 2014/06/09
|
|
12
|
+
|
|
13
|
+
- handle TZInfo errors on DST transitions, thanks https://github.com/junhanamaki
|
|
14
|
+
- implement Scheduler#up?
|
|
15
|
+
- let schedule and schedule_at use Chronic if present
|
|
16
|
+
- let Rufus::Scheduler.parse use Chronic if present
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
== rufus-scheduler - 3.0.7 released 2014/03/18
|
|
20
|
+
|
|
21
|
+
- implement Scheduler #occurrences and #timeline, inspired by kreynolds
|
|
22
|
+
- implement Job #last_work_time and #mean_work_time
|
|
23
|
+
- implement Job#count
|
|
24
|
+
- add more info to the stderr error output (scheduler/tz info)
|
|
25
|
+
- prevent skipping a day on switch to summertime, gh-114, thanks Matteo
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
== rufus-scheduler - 3.0.6 released 2014/02/14
|
|
29
|
+
|
|
30
|
+
- avoid "can't be called from trap context" on Ruby 2.0, gh-98
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
== rufus-scheduler - 3.0.5 released 2014/01/30
|
|
34
|
+
|
|
35
|
+
- implement Job#call(do_rescue=false), gh-97
|
|
36
|
+
- implement :first => :now for repeat jobs, gh-96
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
== rufus-scheduler - 3.0.4 released 2014/01/19
|
|
40
|
+
|
|
41
|
+
- make CronLine#frequency faster (to avoid 20s schedule_cron times)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
== rufus-scheduler - 3.0.3 released 2013/12/12
|
|
45
|
+
|
|
46
|
+
- CronLine#previous_time fix by Yassen Bantchev (https://github.com/yassenb)
|
|
47
|
+
- introduce ZookeptScheduler example in the readme
|
|
48
|
+
- rename #consider_lockfile to #lock and introduce #unlock
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
== rufus-scheduler - 3.0.2 released 2013/10/22
|
|
52
|
+
|
|
53
|
+
- default :max_work_threads to 28
|
|
54
|
+
- fix "rufus rushes to create work threads" issue, thanks Gatis Tomsons
|
|
55
|
+
- introduce Rufus::Scheduler::NotRunningError, thanks Gatis Tomsons
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
== rufus-scheduler - 3.0.1 released 2013/10/19
|
|
59
|
+
|
|
60
|
+
- fix post_install_message, thanks Ted Pennings
|
|
61
|
+
- bring back .parse_time_string and .parse_duration_string
|
|
62
|
+
|
|
63
|
+
|
|
5
64
|
== rufus-scheduler - 3.0.0 released 2013/10/02
|
|
6
65
|
|
|
7
66
|
- complete rewrite.
|
|
@@ -148,5 +207,9 @@
|
|
|
148
207
|
== rufus-scheduler - 2.0.1 released 2009/05/07
|
|
149
208
|
== rufus-scheduler - 2.0.0 released 2009/05/07
|
|
150
209
|
|
|
210
|
+
...
|
|
211
|
+
|
|
151
212
|
- initial release
|
|
152
213
|
|
|
214
|
+
(was openwferu-scheduler before that)
|
|
215
|
+
|
data/CREDITS.txt
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
== Contributors
|
|
6
6
|
|
|
7
|
+
- Sterling Paramore (https://github.com/gnilrets) underscore TZ fix
|
|
8
|
+
- ecin (https://github.com/ecin) new lock mecha
|
|
9
|
+
- Adam Jonas (https://github.com/adamjonas) migrate specs to "expect"
|
|
10
|
+
- Yassen Bantchev (https://github.com/yassenb) CronLine#previous_time rewrite
|
|
11
|
+
- Eric Lindvall (https://github.com/eric) Zookeeper locked example
|
|
12
|
+
- Ted Pennings (https://github.com/tedpennings) typo in post_install_message
|
|
7
13
|
- Tobias Kraze (https://github.com/kratob) timeout vs mutex fix
|
|
8
14
|
- Patrick Farrell (https://github.com/pfarrell) pointing at deprecated start_new
|
|
9
15
|
- Thomas Sevestre (https://github.com/thomassevestre) :exception option
|
|
@@ -27,6 +33,15 @@
|
|
|
27
33
|
|
|
28
34
|
== Feedback
|
|
29
35
|
|
|
36
|
+
- junhanamaki - https://github.com/junhanamaki - #next_time and dst ambiguities
|
|
37
|
+
- kreynolds (tossrock) - inspiration for #occurrences
|
|
38
|
+
- Matteo - https://github.com/m4ce - dst and cron issue
|
|
39
|
+
- Tobias Bielohlawek - https://github.com/rngtng - missing assertion
|
|
40
|
+
- Joe Taylor and Agis Anastasopoulos -
|
|
41
|
+
http://stackoverflow.com/questions/21280870 - :first => :now and Job#call
|
|
42
|
+
- Gatis Tomsons - https://github.io/gacha - heavy work threads and lock errors
|
|
43
|
+
- https://github.com/joast - missing .to_time_string alias (code and doc)
|
|
44
|
+
- Tamir Duberstein - https://github.com/tamird - rdoc inaccuracies
|
|
30
45
|
- Kevin Bouwkamp - https://github.com/bmxpert1 - first_at issues
|
|
31
46
|
- Daniel Beauchamp - https://github.com/pushmatrix - pre/post trigger callbacks
|
|
32
47
|
- Arthur Maltson - https://github.com/amaltson - readme fixes
|
|
@@ -60,5 +75,6 @@
|
|
|
60
75
|
|
|
61
76
|
== and finally
|
|
62
77
|
|
|
78
|
+
- many thanks to the author and contributors of the tzinfo gem (http://tzinfo.github.io/)
|
|
63
79
|
- many thanks to the EventMachine team (especially Aman Gupta)
|
|
64
80
|
|
data/LICENSE.txt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
Copyright (c) 2005-
|
|
2
|
+
Copyright (c) 2005-2014, 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
|
data/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# rufus-scheduler
|
|
3
3
|
|
|
4
4
|
[](http://travis-ci.org/jmettraux/rufus-scheduler)
|
|
5
|
+
[](http://badge.fury.io/rb/rufus-scheduler)
|
|
5
6
|
|
|
6
7
|
Job scheduler for Ruby (at, cron, in and every jobs).
|
|
7
8
|
|
|
@@ -78,7 +79,7 @@ There is no EventMachine-based scheduler anymore.
|
|
|
78
79
|
* The scheduler isn't catching the whole of Exception anymore, only StandardError
|
|
79
80
|
* The error_handler is #on_error (instead of #on_exception), by default it now prints the details of the error to $stderr (used to be $stdout)
|
|
80
81
|
* Rufus::Scheduler::TimeOutError renamed to Rufus::Scheduler::TimeoutError
|
|
81
|
-
* Introduction of "interval" jobs. Whereas "every" jobs are like "every 10
|
|
82
|
+
* Introduction of "interval" jobs. Whereas "every" jobs are like "every 10 minutes, do this", interval jobs are like "do that, then wait for 10 minutes, then do that again, and so on"
|
|
82
83
|
* Introduction of a :lockfile => true/filename mechanism to prevent multiple schedulers from executing
|
|
83
84
|
* "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a ```every '10m'``` job is on, it will trigger once at wakeup, not 6 times (discard_past was false by default in rufus-scheduler 2.x). No intention to re-introduce ```:discard_past => false``` in 3.0 for now.
|
|
84
85
|
* Introduction of Scheduler #on_pre_trigger and #on_post_trigger callback points
|
|
@@ -88,6 +89,8 @@ There is no EventMachine-based scheduler anymore.
|
|
|
88
89
|
|
|
89
90
|
So you need help. People can help you, but first help them help you, and don't waste their time. Provide a complete description of the issue. If it works on A but not on B and others have to ask you: "so what is different between A and B" you are wasting everyone's time.
|
|
90
91
|
|
|
92
|
+
"hello" and "thanks" are not swear words.
|
|
93
|
+
|
|
91
94
|
Go read [how to report bugs effectively](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html), twice.
|
|
92
95
|
|
|
93
96
|
Update: [help_help.md](https://gist.github.com/jmettraux/310fed75f568fd731814) might help help you.
|
|
@@ -113,6 +116,8 @@ Yes, issues can be reported in [rufus-scheduler issues](https://github.com/jmett
|
|
|
113
116
|
* [It doesn't work...](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
|
|
114
117
|
* [I want a refund](http://blog.nodejitsu.com/getting-refunds-on-open-source-projects)
|
|
115
118
|
* [Passenger and rufus-scheduler](http://stackoverflow.com/questions/18108719/debugging-rufus-scheduler/18156180#18156180)
|
|
119
|
+
* [Passenger and rufus-scheduler (2)](http://stackoverflow.com/questions/21861387/rufus-cron-job-not-working-in-apache-passenger#answer-21868555)
|
|
120
|
+
* [Unicorn and rufus-scheduler](https://jkraemer.net/running-rufus-scheduler-in-a-unicorn-rails-app)
|
|
116
121
|
|
|
117
122
|
|
|
118
123
|
## scheduling
|
|
@@ -249,6 +254,25 @@ The second argument is "time", it's the time when the job got cleared for trigge
|
|
|
249
254
|
|
|
250
255
|
Note that time is the time when the job got cleared for triggering. If there are mutexes involved, now = mutex_wait_time + time...
|
|
251
256
|
|
|
257
|
+
#### "every" jobs and changing the next_time in-flight
|
|
258
|
+
|
|
259
|
+
It's OK to change the next_time of an every job in-flight:
|
|
260
|
+
|
|
261
|
+
```ruby
|
|
262
|
+
scheduler.every '10m' do |job|
|
|
263
|
+
|
|
264
|
+
# ...
|
|
265
|
+
|
|
266
|
+
status = determine_pie_status
|
|
267
|
+
|
|
268
|
+
job.next_time = Time.now + 30 * 60 if status == 'burnt'
|
|
269
|
+
#
|
|
270
|
+
# if burnt, wait 30 minutes for the oven to cool a bit
|
|
271
|
+
end
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
It should work as well with cron jobs, not so with interval jobs whose next_time is computed after their block ends its current run.
|
|
275
|
+
|
|
252
276
|
### scheduling handler instances
|
|
253
277
|
|
|
254
278
|
It's OK to pass any object, as long as it respond to #call(), when scheduling:
|
|
@@ -366,14 +390,14 @@ end
|
|
|
366
390
|
|
|
367
391
|
The :timeout option accepts either a duration (like "1d" or "2w3d") or a point in time (like "2013/12/12 12:00").
|
|
368
392
|
|
|
369
|
-
### :first_at, :first_in, :first
|
|
393
|
+
### :first_at, :first_in, :first, :first_time
|
|
370
394
|
|
|
371
395
|
This option is for repeat jobs (cron / every) only.
|
|
372
396
|
|
|
373
397
|
It's used to specify the first time after which the repeat job should trigger for the first time.
|
|
374
398
|
|
|
375
|
-
In the case of an "every" job, this will be the first time (
|
|
376
|
-
For a "cron" job, it's the time after which the first schedule will trigger.
|
|
399
|
+
In the case of an "every" job, this will be the first time (modulo the scheduler frequency) the job triggers.
|
|
400
|
+
For a "cron" job, it's the time *after* which the first schedule will trigger.
|
|
377
401
|
|
|
378
402
|
```ruby
|
|
379
403
|
scheduler.every '2d', :first_at => Time.now + 10 * 3600 do
|
|
@@ -383,6 +407,10 @@ end
|
|
|
383
407
|
scheduler.every '2d', :first_in => '10h' do
|
|
384
408
|
# ... every two days, but start in 10 hours
|
|
385
409
|
end
|
|
410
|
+
|
|
411
|
+
scheduler.cron '00 14 * * *', :first_in => '3d' do
|
|
412
|
+
# ... every day at 14h00, but start after 3 * 24 hours
|
|
413
|
+
end
|
|
386
414
|
```
|
|
387
415
|
|
|
388
416
|
:first, :first_at and :first_in all accept a point in time or a duration (number or time string). Use the symbol you think make your schedule more readable.
|
|
@@ -393,6 +421,34 @@ job.first_at = Time.now + 10
|
|
|
393
421
|
job.first_at = Rufus::Scheduler.parse('2029-12-12')
|
|
394
422
|
```
|
|
395
423
|
|
|
424
|
+
The first argument (in all its flavours) accepts a :now or :immediately value. That schedules the first occurence for immediate triggering. Consider:
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
require 'rufus-scheduler'
|
|
428
|
+
|
|
429
|
+
s = Rufus::Scheduler.new
|
|
430
|
+
|
|
431
|
+
n = Time.now; p [ :scheduled_at, n, n.to_f ]
|
|
432
|
+
|
|
433
|
+
s.every '3s', :first => :now do
|
|
434
|
+
n = Time.now; p [ :in, n, n.to_f ]
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
s.join
|
|
438
|
+
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
that'll output something like:
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
[:scheduled_at, 2014-01-22 22:21:21 +0900, 1390396881.344438]
|
|
445
|
+
[:in, 2014-01-22 22:21:21 +0900, 1390396881.6453865]
|
|
446
|
+
[:in, 2014-01-22 22:21:24 +0900, 1390396884.648807]
|
|
447
|
+
[:in, 2014-01-22 22:21:27 +0900, 1390396887.651686]
|
|
448
|
+
[:in, 2014-01-22 22:21:30 +0900, 1390396890.6571937]
|
|
449
|
+
...
|
|
450
|
+
```
|
|
451
|
+
|
|
396
452
|
### :last_at, :last_in, :last
|
|
397
453
|
|
|
398
454
|
This option is for repeat jobs (cron / every) only.
|
|
@@ -563,7 +619,6 @@ job.callable
|
|
|
563
619
|
# => #<MyHandler:0x0000000163ae88 @counter=0>
|
|
564
620
|
```
|
|
565
621
|
|
|
566
|
-
|
|
567
622
|
### scheduled_at
|
|
568
623
|
|
|
569
624
|
Returns the Time instance when the job got created.
|
|
@@ -585,6 +640,12 @@ job.scheduled_at
|
|
|
585
640
|
# => 2013-07-17 23:48:54 +0900
|
|
586
641
|
```
|
|
587
642
|
|
|
643
|
+
### last_work_time, mean_work_time
|
|
644
|
+
|
|
645
|
+
The job keeps track of how long its work was in the `last_work_time` attribute. For a one time job (in, at) it's probably not very useful.
|
|
646
|
+
|
|
647
|
+
The attribute `mean_work_time` contains a computed mean work time. It's recomputed after every run (if it's a repeat job).
|
|
648
|
+
|
|
588
649
|
### unschedule
|
|
589
650
|
|
|
590
651
|
Unschedule the job, preventing it from firing again and removing it from the schedule. This doesn't prevent a running thread for this job to run until its end.
|
|
@@ -671,6 +732,44 @@ job.keys
|
|
|
671
732
|
|
|
672
733
|
Job-local variables are thread-safe.
|
|
673
734
|
|
|
735
|
+
### call
|
|
736
|
+
|
|
737
|
+
Job instances have a #call method. It simply calls the scheduled block or callable immediately.
|
|
738
|
+
|
|
739
|
+
```ruby
|
|
740
|
+
job =
|
|
741
|
+
@scheduler.schedule_every '10m' do |job|
|
|
742
|
+
# ...
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
job.call
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Warning: the Scheduler#on_error handler is not involved. Error handling is the responsibility of the caller.
|
|
749
|
+
|
|
750
|
+
If the call has to be rescued by the error handler of the scheduler, ```call(true)``` might help:
|
|
751
|
+
|
|
752
|
+
```ruby
|
|
753
|
+
require 'rufus-scheduler'
|
|
754
|
+
|
|
755
|
+
s = Rufus::Scheduler.new
|
|
756
|
+
|
|
757
|
+
def s.on_error(job, err)
|
|
758
|
+
p [ 'error in scheduled job', job.class, job.original, err.message ]
|
|
759
|
+
rescue
|
|
760
|
+
p $!
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
job =
|
|
764
|
+
s.schedule_in('1d') do
|
|
765
|
+
fail 'again'
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
job.call(true)
|
|
769
|
+
#
|
|
770
|
+
# true lets the error_handler deal with error in the job call
|
|
771
|
+
```
|
|
772
|
+
|
|
674
773
|
## AtJob and InJob methods
|
|
675
774
|
|
|
676
775
|
### time
|
|
@@ -687,6 +786,10 @@ An alias to time.
|
|
|
687
786
|
|
|
688
787
|
Returns the next time the job will trigger (hopefully).
|
|
689
788
|
|
|
789
|
+
### count
|
|
790
|
+
|
|
791
|
+
Returns how many times the job fired.
|
|
792
|
+
|
|
690
793
|
## EveryJob methods
|
|
691
794
|
|
|
692
795
|
### frequency
|
|
@@ -717,16 +820,22 @@ Rufus::Scheduler.parse('* * * * * *').frequency # ==> 1
|
|
|
717
820
|
Rufus::Scheduler.parse('5 23 * * *').frequency # ==> 24 * 3600
|
|
718
821
|
Rufus::Scheduler.parse('5 * * * *').frequency # ==> 3600
|
|
719
822
|
Rufus::Scheduler.parse('10,20,30 * * * *').frequency # ==> 600
|
|
823
|
+
|
|
824
|
+
Rufus::Scheduler.parse('10,20,30 * * * * *').frequency # ==> 10
|
|
720
825
|
```
|
|
721
826
|
|
|
722
827
|
It's used to determine if the job frequency is higher than the scheduler frequency (it raises an ArgumentError if that is the case).
|
|
723
828
|
|
|
829
|
+
### brute_frequency
|
|
830
|
+
|
|
831
|
+
Cron jobs also have a ```#brute_frequency``` method that looks a one year of intervals to determine the shortest delta for the cron. This method can take between 20 to 50 seconds for cron lines that go the second level. ```#frequency``` above, when encountering second level cron lines will take a shortcut to answer as quickly as possible with a usable value.
|
|
832
|
+
|
|
724
833
|
|
|
725
834
|
## looking up jobs
|
|
726
835
|
|
|
727
836
|
### Scheduler#job(job_id)
|
|
728
837
|
|
|
729
|
-
The scheduler
|
|
838
|
+
The scheduler ```#job(job_id)``` method can be used to lookup Job instances.
|
|
730
839
|
|
|
731
840
|
```ruby
|
|
732
841
|
require 'rufus-scheduler'
|
|
@@ -802,6 +911,20 @@ Shuts down the scheduler, waits (blocks) until all the jobs cease running.
|
|
|
802
911
|
|
|
803
912
|
Kills all the job (threads) and then shuts the scheduler down. Radical.
|
|
804
913
|
|
|
914
|
+
### Scheduler#down?
|
|
915
|
+
|
|
916
|
+
Returns true if the scheduler has been shut down.
|
|
917
|
+
|
|
918
|
+
### Scheduler#started_at
|
|
919
|
+
|
|
920
|
+
Returns the Time instance at which the scheduler got started.
|
|
921
|
+
|
|
922
|
+
### Scheduler #uptime / #uptime_s
|
|
923
|
+
|
|
924
|
+
Returns since the count of seconds for which the scheduler has been running.
|
|
925
|
+
|
|
926
|
+
```#uptime_s``` returns this count in a String easier to grasp for humans, like ```"3d12m45s123"```.
|
|
927
|
+
|
|
805
928
|
### Scheduler#join
|
|
806
929
|
|
|
807
930
|
Let's the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
|
|
@@ -824,6 +947,16 @@ Note that the main schedule thread will be returned if it is currently running a
|
|
|
824
947
|
|
|
825
948
|
Returns true if the arg is a currently scheduled job (see Job#scheduled?).
|
|
826
949
|
|
|
950
|
+
### Scheduler#occurrences(time0, time1)
|
|
951
|
+
|
|
952
|
+
Returns a hash ```{ job => [ t0, t1, ... ] }``` mapping jobs to their potential trigger time within the ```[ time0, time1 ]``` span.
|
|
953
|
+
|
|
954
|
+
Please note that, for interval jobs, the ```#mean_work_time``` is used, so the result is only a prediction.
|
|
955
|
+
|
|
956
|
+
### Scheduler#timeline(time0, time1)
|
|
957
|
+
|
|
958
|
+
Like `#occurrences` but returns a list ```[ [ t0, job0 ], [ t1, job1 ], ... ]``` of time + job pairs.
|
|
959
|
+
|
|
827
960
|
|
|
828
961
|
## dealing with job errors
|
|
829
962
|
|
|
@@ -964,9 +1097,62 @@ The idea is to guarantee only one scheduler (in a group of scheduler sharing the
|
|
|
964
1097
|
|
|
965
1098
|
This is useful in environments where the Ruby process holding the scheduler gets started multiple times.
|
|
966
1099
|
|
|
1100
|
+
If the lockfile mechanism here is not sufficient, you can plug your custom mechanism. It's explained in [advanced lock schemes](#advanced-lock-schemes) below.
|
|
1101
|
+
|
|
1102
|
+
### :scheduler_lock
|
|
1103
|
+
|
|
1104
|
+
(since rufus-scheduler 3.0.9)
|
|
1105
|
+
|
|
1106
|
+
The scheduler lock is an object that responds to `#lock` and `#unlock`. The scheduler calls `#lock` when starting up. If the answer is `false`, the scheduler stops its initialization work and won't schedule anything.
|
|
1107
|
+
|
|
1108
|
+
Here is a sample of a scheduler lock that only lets the scheduler on host "coffee.example.com" start:
|
|
1109
|
+
```ruby
|
|
1110
|
+
class HostLock
|
|
1111
|
+
def initialize(lock_name)
|
|
1112
|
+
@lock_name = lock_name
|
|
1113
|
+
end
|
|
1114
|
+
def lock
|
|
1115
|
+
@lock_name == `hostname -f`.strip
|
|
1116
|
+
end
|
|
1117
|
+
def unlock
|
|
1118
|
+
true
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
scheduler =
|
|
1123
|
+
Rufus::Scheduler.new(:scheduler_lock => HostLock.new('coffee.example.com'))
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
By default, the scheduler_lock is an instance of `Rufus::Scheduler::NullLock`, with a `#lock` that returns true.
|
|
1127
|
+
|
|
1128
|
+
### :trigger_lock
|
|
1129
|
+
|
|
1130
|
+
(since rufus-scheduler 3.0.9)
|
|
1131
|
+
|
|
1132
|
+
The trigger lock in an object that responds to `#lock`. The scheduler calls that method on the job lock right before triggering any job. If the answer is false, the trigger doesn't happen, the job is not done (at least not in this scheduler).
|
|
1133
|
+
|
|
1134
|
+
Here is a (stupid) PingLock example, it'll only trigger if an "other host" is not responding to ping. Do not use that in production, you don't want to fork a ping process for each trigger attempt...
|
|
1135
|
+
```ruby
|
|
1136
|
+
class PingLock
|
|
1137
|
+
def initialize(other_host)
|
|
1138
|
+
@other_host = other_host
|
|
1139
|
+
end
|
|
1140
|
+
def lock
|
|
1141
|
+
! system("ping -c 1 #{@other_host}")
|
|
1142
|
+
end
|
|
1143
|
+
end
|
|
1144
|
+
|
|
1145
|
+
scheduler =
|
|
1146
|
+
Rufus::Scheduler.new(:trigger_lock => PingLock.new('main.example.com'))
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
By default, the trigger_lock is an instance of `Rufus::Scheduler::NullLock`, with a `#lock` that always returns true.
|
|
1150
|
+
|
|
1151
|
+
As explained in [advanced lock schemes](#advanced-lock-schemes), another way to tune that behaviour is by overriding the scheduler's `#confirm_lock` method. (You could also do that with an `#on_pre_trigger` callback).
|
|
1152
|
+
|
|
967
1153
|
### :max_work_threads
|
|
968
1154
|
|
|
969
|
-
In rufus-scheduler 2.x, by default, each job triggering received its own, new,
|
|
1155
|
+
In rufus-scheduler 2.x, by default, each job triggering received its own, brand new, thread of execution. In rufus-scheduler 3.x, execution happens in a pooled work thread. The max work thread count (the pool size) defaults to 28.
|
|
970
1156
|
|
|
971
1157
|
One can set this maximum value when starting the scheduler.
|
|
972
1158
|
|
|
@@ -1004,6 +1190,67 @@ Rufus::Scheduler.s.every '10s' { puts "hello, world!" }
|
|
|
1004
1190
|
```
|
|
1005
1191
|
|
|
1006
1192
|
|
|
1193
|
+
## advanced lock schemes
|
|
1194
|
+
|
|
1195
|
+
As seen above, rufus-scheduler proposes the [:lockfile](#lockfile--mylockfiletxt) system out of the box. If in a group of schedulers only one is supposed to run, the lockfile mecha prevents schedulers that have not set/created the lockfile from running.
|
|
1196
|
+
|
|
1197
|
+
There are situation where this is not sufficient.
|
|
1198
|
+
|
|
1199
|
+
By overriding #lock and #unlock, one can customize how his schedulers lock.
|
|
1200
|
+
|
|
1201
|
+
This example was provided by [Eric Lindvall](https://github.com/eric):
|
|
1202
|
+
|
|
1203
|
+
```ruby
|
|
1204
|
+
class ZookeptScheduler < Rufus::Scheduler
|
|
1205
|
+
|
|
1206
|
+
def initialize(zookeeper, opts={})
|
|
1207
|
+
@zk = zookeeper
|
|
1208
|
+
super(opts)
|
|
1209
|
+
end
|
|
1210
|
+
|
|
1211
|
+
def lock
|
|
1212
|
+
@zk_locker = @zk.exclusive_locker('scheduler')
|
|
1213
|
+
@zk_locker.lock # returns true if the lock was acquired, false else
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
def unlock
|
|
1217
|
+
@zk_locker.unlock
|
|
1218
|
+
end
|
|
1219
|
+
|
|
1220
|
+
def confirm_lock
|
|
1221
|
+
return false if down?
|
|
1222
|
+
@zk_locker.assert!
|
|
1223
|
+
rescue ZK::Exceptions::LockAssertionFailedError => e
|
|
1224
|
+
# we've lost the lock, shutdown (and return false to at least prevent
|
|
1225
|
+
# this job from triggering
|
|
1226
|
+
shutdown
|
|
1227
|
+
false
|
|
1228
|
+
end
|
|
1229
|
+
end
|
|
1230
|
+
```
|
|
1231
|
+
|
|
1232
|
+
This uses a [zookeeper](http://zookeeper.apache.org/) to make sure only one scheduler in a group of distributed schedulers runs.
|
|
1233
|
+
|
|
1234
|
+
The methods #lock and #unlock are overriden and #confirm_lock is provided,
|
|
1235
|
+
to make sure that the lock is still valid.
|
|
1236
|
+
|
|
1237
|
+
The #confirm_lock method is called right before a job triggers (if it is provided). The more generic callback #on_pre_trigger is called right after #confirm_lock.
|
|
1238
|
+
|
|
1239
|
+
### :scheduler_lock and :trigger_lock
|
|
1240
|
+
|
|
1241
|
+
(introduced in rufus-scheduler 3.0.9).
|
|
1242
|
+
|
|
1243
|
+
Another way of prodiving `#lock`, `#unlock` and `#confirm_lock` to a rufus-scheduler is by using the `:scheduler_lock` and `:trigger_lock` options.
|
|
1244
|
+
|
|
1245
|
+
See [:trigger_lock](#trigger_lock) and [:scheduler_lock](#scheduler_lock).
|
|
1246
|
+
|
|
1247
|
+
The scheduler lock may be used to prevent a scheduler from starting, while a trigger lock prevents individual jobs from triggering (the scheduler goes on scheduling).
|
|
1248
|
+
|
|
1249
|
+
One has to be careful with what goes in `#confirm_lock` or in a trigger lock, as it gets called before each trigger.
|
|
1250
|
+
|
|
1251
|
+
Warning: you may think you're heading towards "high availability" by using a trigger lock and having lots of schedulers at hand. It may be so if you limit yourself to scheduling the same set of jobs at scheduler startup. But if you add schedules at runtime, they stay local to their scheduler. There is no magic that propagates the jobs to all the schedulers in your pack.
|
|
1252
|
+
|
|
1253
|
+
|
|
1007
1254
|
## parsing cronlines and time strings
|
|
1008
1255
|
|
|
1009
1256
|
Rufus::Scheduler provides a class method ```.parse``` to parse time durations and cron strings. It's what it's using when receiving schedules. One can use it diectly (no need to instantiate a Scheduler).
|
|
@@ -1052,6 +1299,141 @@ Rufus::Scheduler.to_duration_hash(62.127, :drop_seconds => true)
|
|
|
1052
1299
|
# => { :m => 1 }
|
|
1053
1300
|
```
|
|
1054
1301
|
|
|
1302
|
+
### cronline notations specific to rufus-scheduler
|
|
1303
|
+
|
|
1304
|
+
#### first Monday, last Sunday et al
|
|
1305
|
+
|
|
1306
|
+
To schedule something at noon every first Monday of the month:
|
|
1307
|
+
|
|
1308
|
+
```ruby
|
|
1309
|
+
scheduler.cron('00 12 * * mon#1') do
|
|
1310
|
+
# ...
|
|
1311
|
+
end
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
To schedule something at noon the last Sunday of every month:
|
|
1315
|
+
|
|
1316
|
+
```ruby
|
|
1317
|
+
scheduler.cron('00 12 * * sun#-1') do
|
|
1318
|
+
# ...
|
|
1319
|
+
end
|
|
1320
|
+
#
|
|
1321
|
+
# OR
|
|
1322
|
+
#
|
|
1323
|
+
scheduler.cron('00 12 * * sun#L') do
|
|
1324
|
+
# ...
|
|
1325
|
+
end
|
|
1326
|
+
```
|
|
1327
|
+
|
|
1328
|
+
Such cronlines can be tested with scripts like:
|
|
1329
|
+
|
|
1330
|
+
```ruby
|
|
1331
|
+
require 'rufus-scheduler'
|
|
1332
|
+
|
|
1333
|
+
Time.now
|
|
1334
|
+
# => 2013-10-26 07:07:08 +0900
|
|
1335
|
+
Rufus::Scheduler.parse('* * * * mon#1').next_time
|
|
1336
|
+
# => 2013-11-04 00:00:00 +0900
|
|
1337
|
+
```
|
|
1338
|
+
|
|
1339
|
+
#### L (last day of month)
|
|
1340
|
+
|
|
1341
|
+
L can be used in the "day" slot:
|
|
1342
|
+
|
|
1343
|
+
In this example, the cronline is supposed to trigger every last day of the month at noon:
|
|
1344
|
+
|
|
1345
|
+
```ruby
|
|
1346
|
+
require 'rufus-scheduler'
|
|
1347
|
+
Time.now
|
|
1348
|
+
# => 2013-10-26 07:22:09 +0900
|
|
1349
|
+
Rufus::Scheduler.parse('00 12 L * *').next_time
|
|
1350
|
+
# => 2013-10-31 12:00:00 +0900
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
|
|
1354
|
+
## a note about timezones
|
|
1355
|
+
|
|
1356
|
+
Cron schedules and at schedules support the specification of a timezone.
|
|
1357
|
+
|
|
1358
|
+
```ruby
|
|
1359
|
+
scheduler.cron '0 22 * * 1-5 America/Chicago' do
|
|
1360
|
+
# the job...
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1363
|
+
scheduler.at '2013-12-12 14:00 Pacific/Samoa' do
|
|
1364
|
+
puts "it's tea time!"
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
# or even
|
|
1368
|
+
|
|
1369
|
+
Rufus::Scheduler.parse("2013-12-12 14:00 Pacific/Saipan")
|
|
1370
|
+
# => 2013-12-12 04:00:00 UTC
|
|
1371
|
+
```
|
|
1372
|
+
|
|
1373
|
+
Behind the scenes, rufus-scheduler uses [tzinfo](http://tzinfo.github.io/) to deal with timezones.
|
|
1374
|
+
|
|
1375
|
+
Here is a [list of timezones](misc/tz_all.txt) known to my Debian GNU/Linux 7. It was generated with this script:
|
|
1376
|
+
|
|
1377
|
+
```ruby
|
|
1378
|
+
require 'tzinfo'
|
|
1379
|
+
TZInfo::Timezone.all.each { |tz| puts tz.name }
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
Unknown timezones, typos, will be rejected by tzinfo thus rufus-scheduler.
|
|
1383
|
+
|
|
1384
|
+
On its own tzinfo derives the timezones from the system's information. On some system it needs some help, one can install the 'tzinfo-data' gem to provide the missing information.
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
## so Rails?
|
|
1388
|
+
|
|
1389
|
+
Yes, I know, all of the above is boring and you're only looking for a snippet to paste in your Ruby-on-Rails application to schedule...
|
|
1390
|
+
|
|
1391
|
+
Here is an example initializer:
|
|
1392
|
+
|
|
1393
|
+
```ruby
|
|
1394
|
+
#
|
|
1395
|
+
# config/initializers/scheduler.rb
|
|
1396
|
+
|
|
1397
|
+
require 'rufus-scheduler'
|
|
1398
|
+
|
|
1399
|
+
# Let's use the rufus-scheduler singleton
|
|
1400
|
+
#
|
|
1401
|
+
s = Rufus::Scheduler.singleton
|
|
1402
|
+
|
|
1403
|
+
|
|
1404
|
+
# Stupid recurrent task...
|
|
1405
|
+
#
|
|
1406
|
+
s.every '1m' do
|
|
1407
|
+
|
|
1408
|
+
Rails.logger.info "hello, it's #{Time.now}"
|
|
1409
|
+
end
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
And now you tell me that this is good, but you want to schedule stuff from your controller.
|
|
1413
|
+
|
|
1414
|
+
Maybe:
|
|
1415
|
+
|
|
1416
|
+
```ruby
|
|
1417
|
+
class ScheController < ApplicationController
|
|
1418
|
+
|
|
1419
|
+
# GET /sche/
|
|
1420
|
+
#
|
|
1421
|
+
def index
|
|
1422
|
+
|
|
1423
|
+
job_id =
|
|
1424
|
+
Rufus::Scheduler.singleton.in '5s' do
|
|
1425
|
+
Rails.logger.info "time flies, it's now #{Time.now}"
|
|
1426
|
+
end
|
|
1427
|
+
|
|
1428
|
+
render :text => "scheduled job #{job_id}"
|
|
1429
|
+
end
|
|
1430
|
+
end
|
|
1431
|
+
```
|
|
1432
|
+
|
|
1433
|
+
The rufus-scheduler singleton is instantiated in the ```config/initializers/scheduler.rb``` file, it's then available throughout the webapp via ```Rufus::Scheduler.singleton```.
|
|
1434
|
+
|
|
1435
|
+
*Warning*: this works well with single-process Ruby servers like Webrick and Thin. Using rufus-scheduler with Passenger or Unicorn requires a bit more knowledge and tuning, gently provided by a bit of googling and reading, see [Faq](#faq) above.
|
|
1436
|
+
|
|
1055
1437
|
|
|
1056
1438
|
## support
|
|
1057
1439
|
|
data/Rakefile
CHANGED