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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +410 -0
- data/CREDITS.md +142 -0
- data/LICENSE.txt +1 -1
- data/Makefile +27 -0
- data/README.md +407 -143
- data/lib/rufus/scheduler/job_array.rb +36 -66
- data/lib/rufus/scheduler/jobs_core.rb +369 -0
- data/lib/rufus/scheduler/jobs_one_time.rb +53 -0
- data/lib/rufus/scheduler/jobs_repeat.rb +335 -0
- data/lib/rufus/scheduler/locks.rb +41 -67
- data/lib/rufus/scheduler/util.rb +89 -179
- data/lib/rufus/scheduler.rb +545 -453
- data/rufus-scheduler.gemspec +22 -11
- metadata +44 -85
- data/CHANGELOG.txt +0 -243
- data/CREDITS.txt +0 -88
- data/Rakefile +0 -83
- data/TODO.txt +0 -151
- data/lib/rufus/scheduler/cronline.rb +0 -470
- data/lib/rufus/scheduler/jobs.rb +0 -633
- data/lib/rufus/scheduler/zones.rb +0 -174
- data/lib/rufus/scheduler/zotime.rb +0 -155
- data/spec/basics_spec.rb +0 -54
- data/spec/cronline_spec.rb +0 -915
- data/spec/error_spec.rb +0 -139
- data/spec/job_array_spec.rb +0 -39
- data/spec/job_at_spec.rb +0 -58
- data/spec/job_cron_spec.rb +0 -128
- data/spec/job_every_spec.rb +0 -104
- data/spec/job_in_spec.rb +0 -20
- data/spec/job_interval_spec.rb +0 -68
- data/spec/job_repeat_spec.rb +0 -357
- data/spec/job_spec.rb +0 -631
- data/spec/lock_custom_spec.rb +0 -47
- data/spec/lock_flock_spec.rb +0 -47
- data/spec/lock_lockfile_spec.rb +0 -61
- data/spec/lock_spec.rb +0 -59
- data/spec/parse_spec.rb +0 -263
- data/spec/schedule_at_spec.rb +0 -158
- data/spec/schedule_cron_spec.rb +0 -66
- data/spec/schedule_every_spec.rb +0 -109
- data/spec/schedule_in_spec.rb +0 -80
- data/spec/schedule_interval_spec.rb +0 -128
- data/spec/scheduler_spec.rb +0 -1067
- data/spec/spec_helper.rb +0 -126
- data/spec/threads_spec.rb +0 -96
- data/spec/zotime_spec.rb +0 -396
data/README.md
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
|
2
2
|
# rufus-scheduler
|
3
3
|
|
4
|
-
[![
|
5
|
-
[![Gem Version](https://badge.fury.io/rb/rufus-scheduler.
|
4
|
+
[![tests](https://github.com/jmettraux/rufus-scheduler/workflows/test/badge.svg)](https://github.com/jmettraux/rufus-scheduler/actions)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/rufus-scheduler.svg)](https://badge.fury.io/rb/rufus-scheduler)
|
6
|
+
[![Join the chat at https://gitter.im/floraison/fugit](https://badges.gitter.im/floraison/fugit.svg)](https://gitter.im/floraison/fugit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
6
7
|
|
7
8
|
Job scheduler for Ruby (at, cron, in and every jobs).
|
8
9
|
|
9
|
-
|
10
|
+
It uses threads.
|
11
|
+
|
12
|
+
**Note**: maybe are you looking for the [README of rufus-scheduler 2.x](https://github.com/jmettraux/rufus-scheduler/blob/two/README.rdoc)? (especially if you're using [Dashing](https://github.com/Shopify/dashing) which is [stuck](https://github.com/Shopify/dashing/blob/master/dashing.gemspec) on rufus-scheduler 2.0.24)
|
10
13
|
|
11
14
|
Quickstart:
|
12
15
|
```ruby
|
16
|
+
# quickstart.rb
|
17
|
+
|
13
18
|
require 'rufus-scheduler'
|
14
19
|
|
15
20
|
scheduler = Rufus::Scheduler.new
|
@@ -19,8 +24,13 @@ scheduler.in '3s' do
|
|
19
24
|
end
|
20
25
|
|
21
26
|
scheduler.join
|
27
|
+
#
|
22
28
|
# let the current thread join the scheduler thread
|
29
|
+
#
|
30
|
+
# (please note that this join should be removed when scheduling
|
31
|
+
# in a web application (Rails and friends) initializer)
|
23
32
|
```
|
33
|
+
(run with `ruby quickstart.rb`)
|
24
34
|
|
25
35
|
Various forms of scheduling are supported:
|
26
36
|
```ruby
|
@@ -41,6 +51,9 @@ end
|
|
41
51
|
scheduler.every '3h' do
|
42
52
|
# do something every 3 hours
|
43
53
|
end
|
54
|
+
scheduler.every '3h10m' do
|
55
|
+
# do something every 3 hours and 10 minutes
|
56
|
+
end
|
44
57
|
|
45
58
|
scheduler.cron '5 0 * * *' do
|
46
59
|
# do something every day, five minutes after midnight
|
@@ -50,18 +63,24 @@ end
|
|
50
63
|
# ...
|
51
64
|
```
|
52
65
|
|
66
|
+
Rufus-scheduler uses [fugit](https://github.com/floraison/fugit) for parsing time strings, [et-orbi](https://github.com/floraison/et-orbi) for pairing time and [tzinfo](https://github.com/tzinfo/tzinfo) timezones.
|
67
|
+
|
53
68
|
## non-features
|
54
69
|
|
55
|
-
Rufus-scheduler (out of the box) is an in-process, in-memory scheduler.
|
70
|
+
Rufus-scheduler (out of the box) is an in-process, in-memory scheduler. It uses threads.
|
56
71
|
|
57
72
|
It does not persist your schedules. When the process is gone and the scheduler instance with it, the schedules are gone.
|
58
73
|
|
74
|
+
A rufus-scheduler instance will go on scheduling while it is present among the objects in a Ruby process. To make it stop scheduling you have to call its [`#shutdown` method](#schedulershutdown).
|
75
|
+
|
59
76
|
|
60
77
|
## related and similar gems
|
61
78
|
|
62
|
-
* [
|
63
|
-
* [
|
64
|
-
* [
|
79
|
+
* [Whenever](https://github.com/javan/whenever) - let cron call back your Ruby code, trusted and reliable cron drives your schedule
|
80
|
+
* [ruby-clock](https://github.com/jjb/ruby-clock) - a clock process / job scheduler for Ruby
|
81
|
+
* [Clockwork](https://github.com/Rykian/clockwork) - rufus-scheduler inspired gem
|
82
|
+
* [Crono](https://github.com/plashchynski/crono) - an in-Rails cron scheduler
|
83
|
+
* [PerfectSched](https://github.com/treasure-data/perfectsched) - highly available distributed cron built on [Sequel](https://sequel.jeremyevans.net) and more
|
65
84
|
|
66
85
|
(please note: rufus-scheduler is not a cron replacement)
|
67
86
|
|
@@ -75,19 +94,19 @@ There is no EventMachine-based scheduler anymore.
|
|
75
94
|
|
76
95
|
## I don't know what this Ruby thing is, where are my Rails?
|
77
96
|
|
78
|
-
|
97
|
+
I'll drive you right to the [tracks](#so-rails).
|
79
98
|
|
80
99
|
|
81
|
-
##
|
100
|
+
## notable changes:
|
82
101
|
|
83
102
|
* As said, no more EventMachine-based scheduler
|
84
|
-
* ```scheduler.every('100') {``` will schedule every 100 seconds (previously, it would have been 0.1s). This aligns rufus-scheduler
|
103
|
+
* ```scheduler.every('100') {``` will schedule every 100 seconds (previously, it would have been 0.1s). This aligns rufus-scheduler with Ruby's ```sleep(100)```
|
85
104
|
* The scheduler isn't catching the whole of Exception anymore, only StandardError
|
86
|
-
* 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)
|
105
|
+
* The error_handler is [#on_error](#rufusscheduleron_errorjob-error) (instead of #on_exception), by default it now prints the details of the error to $stderr (used to be $stdout)
|
87
106
|
* Rufus::Scheduler::TimeOutError renamed to Rufus::Scheduler::TimeoutError
|
88
107
|
* 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"
|
89
|
-
* Introduction of a :
|
90
|
-
* "discard_past" is on by default. If the scheduler (its host) sleeps for 1 hour and a
|
108
|
+
* Introduction of a lockfile: true/filename mechanism to prevent multiple schedulers from executing
|
109
|
+
* "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.
|
91
110
|
* Introduction of Scheduler #on_pre_trigger and #on_post_trigger callback points
|
92
111
|
|
93
112
|
|
@@ -95,35 +114,34 @@ Sir, I'll drive you right to the [tracks](#so-rails).
|
|
95
114
|
|
96
115
|
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.
|
97
116
|
|
98
|
-
"hello" and "thanks" are not swear words.
|
117
|
+
"hello", "please" and "thanks" are not swear words.
|
99
118
|
|
100
|
-
Go read [how to report bugs effectively](
|
119
|
+
Go read [how to report bugs effectively](https://www.chiark.greenend.org.uk/~sgtatham/bugs.html), twice.
|
101
120
|
|
102
121
|
Update: [help_help.md](https://gist.github.com/jmettraux/310fed75f568fd731814) might help help you.
|
103
122
|
|
104
|
-
### on
|
105
|
-
|
106
|
-
Use this [form](http://stackoverflow.com/questions/ask?tags=rufus-scheduler+ruby). It'll create a question flagged "rufus-scheduler" and "ruby".
|
107
|
-
|
108
|
-
Here are the [questions tagged rufus-scheduler](http://stackoverflow.com/questions/tagged/rufus-scheduler).
|
109
|
-
|
110
|
-
### on IRC
|
123
|
+
### on Gitter
|
111
124
|
|
112
|
-
|
125
|
+
You can find help via chat over at [https://gitter.im/floraison/fugit](https://gitter.im/floraison/fugit). It's [fugit](https://github.com/floraison/fugit), [et-orbi](https://github.com/floraison/et-orbi), and rufus-scheduler combined chat room.
|
113
126
|
|
114
|
-
Please
|
127
|
+
Please be courteous.
|
115
128
|
|
116
129
|
### issues
|
117
130
|
|
118
|
-
Yes, issues can be reported in [rufus-scheduler issues](https://github.com/jmettraux/rufus-scheduler/issues), I'd actually prefer bugs in there. If there is nothing wrong with rufus-scheduler, a [Stack Overflow question](
|
131
|
+
Yes, issues can be reported in [rufus-scheduler issues](https://github.com/jmettraux/rufus-scheduler/issues), I'd actually prefer bugs in there. If there is nothing wrong with rufus-scheduler, a [Stack Overflow question](https://stackoverflow.com/questions/ask?tags=rufus-scheduler+ruby) is better.
|
119
132
|
|
120
133
|
### faq
|
121
134
|
|
122
|
-
* [It doesn't work...](
|
123
|
-
* [I want a refund](http://blog.nodejitsu.com/getting-refunds-on-open-source-projects)
|
124
|
-
* [Passenger and rufus-scheduler](
|
125
|
-
* [Passenger and rufus-scheduler (2)](
|
126
|
-
* [
|
135
|
+
* [It doesn't work...](https://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
|
136
|
+
* [I want a refund](https://web.archive.org/web/20160425034214/http://blog.nodejitsu.com/getting-refunds-on-open-source-projects/)
|
137
|
+
* [Passenger and rufus-scheduler](https://stackoverflow.com/questions/18108719/debugging-rufus-scheduler/18156180#18156180)
|
138
|
+
* [Passenger and rufus-scheduler (2)](https://stackoverflow.com/questions/21861387/rufus-cron-job-not-working-in-apache-passenger#answer-21868555)
|
139
|
+
* [Passenger in-depth spawn methods](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/)
|
140
|
+
* [Passenger in-depth spawn methods (smart spawning)](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/#smart-spawning-hooks)
|
141
|
+
* [The scheduler comes up when running the Rails console or a Rake task](https://github.com/jmettraux/rufus-scheduler#avoid-scheduling-when-running-the-ruby-on-rails-console)
|
142
|
+
* [The job triggers twice](https://github.com/jmettraux/rufus-scheduler#lockfile--mylockfiletxt)
|
143
|
+
* [I don't get any of this, I just want it to work in my Rails application](#so-rails)
|
144
|
+
* [I get "zotime.rb:41:in `initialize': cannot determine timezone from nil"](#i-get-zotimerb41in-initialize-cannot-determine-timezone-from-nil)
|
127
145
|
|
128
146
|
|
129
147
|
## scheduling
|
@@ -176,10 +194,12 @@ end
|
|
176
194
|
|
177
195
|
Every jobs try hard to trigger following the frequency they were scheduled with.
|
178
196
|
|
179
|
-
Interval jobs
|
197
|
+
Interval jobs trigger, execute and then trigger again after the interval elapsed. (every jobs time between trigger times, interval jobs time between trigger termination and the next trigger start).
|
180
198
|
|
181
199
|
Cron jobs are based on the venerable cron utility (```man 5 crontab```). They trigger following a pattern given in (almost) the same language cron uses.
|
182
200
|
|
201
|
+
####
|
202
|
+
|
183
203
|
### #schedule_x vs #x
|
184
204
|
|
185
205
|
schedule_in, schedule_at, schedule_cron, etc will return the new Job instance.
|
@@ -203,7 +223,7 @@ job =
|
|
203
223
|
# also
|
204
224
|
|
205
225
|
job =
|
206
|
-
scheduler.in '10d', :
|
226
|
+
scheduler.in '10d', job: true do
|
207
227
|
# ...
|
208
228
|
end
|
209
229
|
```
|
@@ -212,7 +232,7 @@ job =
|
|
212
232
|
|
213
233
|
Sometimes it pays to be less verbose.
|
214
234
|
|
215
|
-
The ```#schedule``` methods schedules an at, in or cron job. It just
|
235
|
+
The ```#schedule``` methods schedules an at, in or cron job. It just decides based on its input. It returns the Job instance.
|
216
236
|
|
217
237
|
```ruby
|
218
238
|
scheduler.schedule '10d' do; end.class
|
@@ -235,13 +255,13 @@ scheduler.repeat '* * * * *' do; end.class
|
|
235
255
|
# => Rufus::Scheduler::CronJob
|
236
256
|
```
|
237
257
|
|
238
|
-
(Yes, no combination
|
258
|
+
(Yes, no combination here gives back an IntervalJob).
|
239
259
|
|
240
260
|
### schedule blocks arguments (job, time)
|
241
261
|
|
242
262
|
A schedule block may be given 0, 1 or 2 arguments.
|
243
263
|
|
244
|
-
The first argument is "job", it's
|
264
|
+
The first argument is "job", it's simply the Job instance involved. It might be useful if the job is to be unscheduled for some reason.
|
245
265
|
|
246
266
|
```ruby
|
247
267
|
scheduler.every '10m' do |job|
|
@@ -281,7 +301,7 @@ It should work as well with cron jobs, not so with interval jobs whose next_time
|
|
281
301
|
|
282
302
|
### scheduling handler instances
|
283
303
|
|
284
|
-
It's OK to pass any object, as long as it
|
304
|
+
It's OK to pass any object, as long as it responds to #call(), when scheduling:
|
285
305
|
|
286
306
|
```ruby
|
287
307
|
class Handler
|
@@ -358,34 +378,55 @@ While paused, the scheduler still accepts schedules, but no schedule will get tr
|
|
358
378
|
|
359
379
|
## job options
|
360
380
|
|
361
|
-
### :
|
381
|
+
### name: string
|
382
|
+
|
383
|
+
Sets the name of the job.
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
scheduler.cron '*/15 8 * * *', name: 'Robert' do |job|
|
387
|
+
puts "A, it's #{Time.now} and my name is #{job.name}"
|
388
|
+
end
|
389
|
+
|
390
|
+
job1 =
|
391
|
+
scheduler.schedule_cron '*/30 9 * * *', n: 'temporary' do |job|
|
392
|
+
puts "B, it's #{Time.now} and my name is #{job.name}"
|
393
|
+
end
|
394
|
+
# ...
|
395
|
+
job1.name = 'Beowulf'
|
396
|
+
```
|
397
|
+
|
398
|
+
### blocking: true
|
399
|
+
|
400
|
+
By default, jobs are triggered in their own, new threads. When `blocking: true`, the job is triggered in the scheduler thread (a new thread is not created). Yes, while a blocking job is running, the scheduler is not scheduling.
|
362
401
|
|
363
|
-
|
402
|
+
### overlap: false
|
364
403
|
|
365
|
-
|
404
|
+
Since, by default, jobs are triggered in their own new threads, job instances might overlap. For example, a job that takes 10 minutes and is scheduled every 7 minutes will have overlaps.
|
366
405
|
|
367
|
-
|
406
|
+
To prevent overlap, one can set `overlap: false`. Such a job will not trigger if one of its instances is already running.
|
368
407
|
|
369
|
-
|
408
|
+
The `:overlap` option is considered before the `:mutex` option when the scheduler is reviewing jobs for triggering.
|
370
409
|
|
371
|
-
### :
|
410
|
+
### mutex: mutex_instance / mutex_name / array of mutexes
|
372
411
|
|
373
|
-
When a job with a mutex triggers, the job's block is executed with the mutex around it, preventing other jobs with the same mutex
|
412
|
+
When a job with a mutex triggers, the job's block is executed with the mutex around it, preventing other jobs with the same mutex from entering (it makes the other jobs wait until it exits the mutex).
|
374
413
|
|
375
|
-
This is different from :
|
414
|
+
This is different from `overlap: false`, which is, first, limited to instances of the same job, and, second, doesn't make the incoming job instance block/wait but give up.
|
376
415
|
|
377
|
-
|
416
|
+
`:mutex` accepts a mutex instance or a mutex name (String). It also accept an array of mutex names / mutex instances. It allows for complex relations between jobs.
|
378
417
|
|
379
418
|
Array of mutexes: original idea and implementation by [Rainux Luo](https://github.com/rainux)
|
380
419
|
|
381
|
-
|
420
|
+
Note: creating lots of different mutexes is OK. Rufus-scheduler will place them in its Scheduler#mutexes hash... And they won't get garbage collected.
|
421
|
+
|
422
|
+
The `:overlap` option is considered before the `:mutex` option when the scheduler is reviewing jobs for triggering.
|
382
423
|
|
383
|
-
### :
|
424
|
+
### timeout: duration or point in time
|
384
425
|
|
385
426
|
It's OK to specify a timeout when scheduling some work. After the time specified, it gets interrupted via a Rufus::Scheduler::TimeoutError.
|
386
427
|
|
387
428
|
```ruby
|
388
|
-
scheduler.in '10d', :
|
429
|
+
scheduler.in '10d', timeout: '1d' do
|
389
430
|
begin
|
390
431
|
# ... do something
|
391
432
|
rescue Rufus::Scheduler::TimeoutError
|
@@ -403,23 +444,23 @@ This option is for repeat jobs (cron / every) only.
|
|
403
444
|
It's used to specify the first time after which the repeat job should trigger for the first time.
|
404
445
|
|
405
446
|
In the case of an "every" job, this will be the first time (modulo the scheduler frequency) the job triggers.
|
406
|
-
For a "cron" job,
|
447
|
+
For a "cron" job as well, the :first will point to the first time the job has to trigger, the following trigger times are then determined by the cron string.
|
407
448
|
|
408
449
|
```ruby
|
409
|
-
scheduler.every '2d', :
|
450
|
+
scheduler.every '2d', first_at: Time.now + 10 * 3600 do
|
410
451
|
# ... every two days, but start in 10 hours
|
411
452
|
end
|
412
453
|
|
413
|
-
scheduler.every '2d', :
|
454
|
+
scheduler.every '2d', first_in: '10h' do
|
414
455
|
# ... every two days, but start in 10 hours
|
415
456
|
end
|
416
457
|
|
417
|
-
scheduler.cron '00 14 * * *', :
|
458
|
+
scheduler.cron '00 14 * * *', first_in: '3d' do
|
418
459
|
# ... every day at 14h00, but start after 3 * 24 hours
|
419
460
|
end
|
420
461
|
```
|
421
462
|
|
422
|
-
:first, :first_at and :first_in all accept a point in time or a duration (number or time string). Use the symbol you think
|
463
|
+
:first, :first_at and :first_in all accept a point in time or a duration (number or time string). Use the symbol you think makes your schedule more readable.
|
423
464
|
|
424
465
|
Note: it's OK to change the first_at (a Time instance) directly:
|
425
466
|
```ruby
|
@@ -427,7 +468,7 @@ job.first_at = Time.now + 10
|
|
427
468
|
job.first_at = Rufus::Scheduler.parse('2029-12-12')
|
428
469
|
```
|
429
470
|
|
430
|
-
The first argument (in all its flavours) accepts a :now or :immediately value. That schedules the first
|
471
|
+
The first argument (in all its flavours) accepts a :now or :immediately value. That schedules the first occurrence for immediate triggering. Consider:
|
431
472
|
|
432
473
|
```ruby
|
433
474
|
require 'rufus-scheduler'
|
@@ -436,12 +477,11 @@ s = Rufus::Scheduler.new
|
|
436
477
|
|
437
478
|
n = Time.now; p [ :scheduled_at, n, n.to_f ]
|
438
479
|
|
439
|
-
s.every '3s', :
|
480
|
+
s.every '3s', first: :now do
|
440
481
|
n = Time.now; p [ :in, n, n.to_f ]
|
441
482
|
end
|
442
483
|
|
443
484
|
s.join
|
444
|
-
|
445
485
|
```
|
446
486
|
|
447
487
|
that'll output something like:
|
@@ -462,19 +502,19 @@ This option is for repeat jobs (cron / every) only.
|
|
462
502
|
It indicates the point in time after which the job should unschedule itself.
|
463
503
|
|
464
504
|
```ruby
|
465
|
-
scheduler.cron '5 23 * * *', :
|
505
|
+
scheduler.cron '5 23 * * *', last_in: '10d' do
|
466
506
|
# ... do something every evening at 23:05 for 10 days
|
467
507
|
end
|
468
508
|
|
469
|
-
scheduler.every '10m', :
|
509
|
+
scheduler.every '10m', last_at: Time.now + 10 * 3600 do
|
470
510
|
# ... do something every 10 minutes for 10 hours
|
471
511
|
end
|
472
512
|
|
473
|
-
scheduler.every '10m', :
|
513
|
+
scheduler.every '10m', last_in: 10 * 3600 do
|
474
514
|
# ... do something every 10 minutes for 10 hours
|
475
515
|
end
|
476
516
|
```
|
477
|
-
:last, :last_at and :last_in all accept a point in time or a duration (number or time string). Use the symbol you think
|
517
|
+
:last, :last_at and :last_in all accept a point in time or a duration (number or time string). Use the symbol you think makes your schedule more readable.
|
478
518
|
|
479
519
|
Note: it's OK to change the last_at (nil or a Time instance) directly:
|
480
520
|
```ruby
|
@@ -485,16 +525,16 @@ job.last_at = Rufus::Scheduler.parse('2029-12-12')
|
|
485
525
|
# set the last bound
|
486
526
|
```
|
487
527
|
|
488
|
-
### :
|
528
|
+
### times: nb of times (before auto-unscheduling)
|
489
529
|
|
490
530
|
One can tell how many times a repeat job (CronJob or EveryJob) is to execute before unscheduling by itself.
|
491
531
|
|
492
532
|
```ruby
|
493
|
-
scheduler.every '2d', :
|
533
|
+
scheduler.every '2d', times: 10 do
|
494
534
|
# ... do something every two days, but not more than 10 times
|
495
535
|
end
|
496
536
|
|
497
|
-
scheduler.cron '0 23 * * *', :
|
537
|
+
scheduler.cron '0 23 * * *', times: 31 do
|
498
538
|
# ... do something every day at 23:00 but do it no more than 31 times
|
499
539
|
end
|
500
540
|
```
|
@@ -502,7 +542,7 @@ end
|
|
502
542
|
It's OK to assign nil to :times to make sure the repeat job is not limited. It's useful when the :times is determined at scheduling time.
|
503
543
|
|
504
544
|
```ruby
|
505
|
-
scheduler.cron '0 23 * * *', :
|
545
|
+
scheduler.cron '0 23 * * *', times: (nolimit ? nil : 10) do
|
506
546
|
# ...
|
507
547
|
end
|
508
548
|
```
|
@@ -524,7 +564,7 @@ job.times = 10
|
|
524
564
|
|
525
565
|
## Job methods
|
526
566
|
|
527
|
-
When calling a schedule method, the id (String) of the job is returned. Longer schedule methods return Job instances directly. Calling the shorter schedule methods with the :
|
567
|
+
When calling a schedule method, the id (String) of the job is returned. Longer schedule methods return Job instances directly. Calling the shorter schedule methods with the `job: true` also returns Job instances instead of Job ids (Strings).
|
528
568
|
|
529
569
|
```ruby
|
530
570
|
require 'rufus-scheduler'
|
@@ -542,7 +582,7 @@ When calling a schedule method, the id (String) of the job is returned. Longer s
|
|
542
582
|
end
|
543
583
|
|
544
584
|
job =
|
545
|
-
scheduler.in '1w', :
|
585
|
+
scheduler.in '1w', job: true do
|
546
586
|
# ...
|
547
587
|
end
|
548
588
|
```
|
@@ -568,7 +608,7 @@ Returns the scheduler instance itself.
|
|
568
608
|
Returns the options passed at the Job creation.
|
569
609
|
|
570
610
|
```ruby
|
571
|
-
job = scheduler.schedule_in('10d', :
|
611
|
+
job = scheduler.schedule_in('10d', tag: 'hello') do; end
|
572
612
|
job.opts
|
573
613
|
# => { :tag => 'hello' }
|
574
614
|
```
|
@@ -578,7 +618,7 @@ job.opts
|
|
578
618
|
Returns the original schedule.
|
579
619
|
|
580
620
|
```ruby
|
581
|
-
job = scheduler.schedule_in('10d', :
|
621
|
+
job = scheduler.schedule_in('10d', tag: 'hello') do; end
|
582
622
|
job.original
|
583
623
|
# => '10d'
|
584
624
|
```
|
@@ -587,7 +627,7 @@ job.original
|
|
587
627
|
|
588
628
|
callable() returns the scheduled block (or the call method of the callable object passed in lieu of a block)
|
589
629
|
|
590
|
-
handler() returns nil if a block was scheduled and the instance scheduled
|
630
|
+
handler() returns nil if a block was scheduled and the instance scheduled otherwise.
|
591
631
|
|
592
632
|
```ruby
|
593
633
|
# when passing a block
|
@@ -625,12 +665,30 @@ job.callable
|
|
625
665
|
# => #<MyHandler:0x0000000163ae88 @counter=0>
|
626
666
|
```
|
627
667
|
|
668
|
+
### source_location
|
669
|
+
|
670
|
+
Added to rufus-scheduler 3.8.0.
|
671
|
+
|
672
|
+
Returns the array `[ 'path/to/file.rb', 123 ]` like `Proc#source_location` does.
|
673
|
+
|
674
|
+
```ruby
|
675
|
+
require 'rufus-scheduler'
|
676
|
+
|
677
|
+
scheduler = Rufus::Scheduler.new
|
678
|
+
|
679
|
+
job = scheduler.schedule_every('2h') { p Time.now }
|
680
|
+
|
681
|
+
p job.source_location
|
682
|
+
# ==> [ '/home/jmettraux/rufus-scheduler/test.rb', 6 ]
|
683
|
+
|
684
|
+
```
|
685
|
+
|
628
686
|
### scheduled_at
|
629
687
|
|
630
688
|
Returns the Time instance when the job got created.
|
631
689
|
|
632
690
|
```ruby
|
633
|
-
job = scheduler.schedule_in('10d', :
|
691
|
+
job = scheduler.schedule_in('10d', tag: 'hello') do; end
|
634
692
|
job.scheduled_at
|
635
693
|
# => 2013-07-17 23:48:54 +0900
|
636
694
|
```
|
@@ -638,12 +696,31 @@ job.scheduled_at
|
|
638
696
|
### last_time
|
639
697
|
|
640
698
|
Returns the last time the job triggered (is usually nil for AtJob and InJob).
|
641
|
-
k
|
642
699
|
```ruby
|
643
|
-
job = scheduler.schedule_every('
|
644
|
-
|
700
|
+
job = scheduler.schedule_every('10s') do; end
|
701
|
+
|
645
702
|
job.scheduled_at
|
646
703
|
# => 2013-07-17 23:48:54 +0900
|
704
|
+
job.last_time
|
705
|
+
# => nil (since we've just scheduled it)
|
706
|
+
|
707
|
+
# after 10 seconds
|
708
|
+
|
709
|
+
job.scheduled_at
|
710
|
+
# => 2013-07-17 23:48:54 +0900 (same as above)
|
711
|
+
job.last_time
|
712
|
+
# => 2013-07-17 23:49:04 +0900
|
713
|
+
```
|
714
|
+
|
715
|
+
### previous_time
|
716
|
+
|
717
|
+
Returns the previous `#next_time`
|
718
|
+
```ruby
|
719
|
+
scheduler.every('10s') do |job|
|
720
|
+
puts "job scheduled for #{job.previous_time} triggered at #{Time.now}"
|
721
|
+
puts "next time will be around #{job.next_time}"
|
722
|
+
puts "."
|
723
|
+
end
|
647
724
|
```
|
648
725
|
|
649
726
|
### last_work_time, mean_work_time
|
@@ -652,6 +729,12 @@ The job keeps track of how long its work was in the `last_work_time` attribute.
|
|
652
729
|
|
653
730
|
The attribute `mean_work_time` contains a computed mean work time. It's recomputed after every run (if it's a repeat job).
|
654
731
|
|
732
|
+
### next_times(n)
|
733
|
+
|
734
|
+
Returns an array of `EtOrbi::EoTime` instances (Time instances with a designated time zone), listing the `n` next occurrences for this job.
|
735
|
+
|
736
|
+
Please note that for "interval" jobs, a mean work time is computed each time and it's used by this `#next_times(n)` method to approximate the next times beyond the immediate next time.
|
737
|
+
|
655
738
|
### unschedule
|
656
739
|
|
657
740
|
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.
|
@@ -678,7 +761,7 @@ Returns true if the job is scheduled (is due to trigger). For repeat jobs it sho
|
|
678
761
|
|
679
762
|
### pause, resume, paused?, paused_at
|
680
763
|
|
681
|
-
These four methods are only available to CronJob, EveryJob and IntervalJob instances. One can pause or resume such
|
764
|
+
These four methods are only available to CronJob, EveryJob and IntervalJob instances. One can pause or resume such jobs thanks to these methods.
|
682
765
|
|
683
766
|
```ruby
|
684
767
|
job =
|
@@ -708,14 +791,14 @@ job = scheduler.schedule_in('10d') do; end
|
|
708
791
|
job.tags
|
709
792
|
# => []
|
710
793
|
|
711
|
-
job = scheduler.schedule_in('10d', :
|
794
|
+
job = scheduler.schedule_in('10d', tag: 'hello') do; end
|
712
795
|
job.tags
|
713
796
|
# => [ 'hello' ]
|
714
797
|
```
|
715
798
|
|
716
|
-
### []=, [], key
|
799
|
+
### []=, [], key?, has_key?, keys, values, and entries
|
717
800
|
|
718
|
-
Threads have thread-local variables
|
801
|
+
Threads have thread-local variables, similarly Rufus-scheduler jobs have job-local variables. Those are more like a dict with thread-safe access.
|
719
802
|
|
720
803
|
```ruby
|
721
804
|
job =
|
@@ -730,13 +813,29 @@ sleep 3.6
|
|
730
813
|
job[:counter]
|
731
814
|
# => 3
|
732
815
|
|
733
|
-
job.key?(:timestamp)
|
734
|
-
|
735
|
-
job.keys
|
736
|
-
# => [ :timestamp, :counter ]
|
816
|
+
job.key?(:timestamp) # => true
|
817
|
+
job.has_key?(:timestamp) # => true
|
818
|
+
job.keys # => [ :timestamp, :counter ]
|
737
819
|
```
|
738
820
|
|
739
|
-
|
821
|
+
Locals can be set at schedule time:
|
822
|
+
```ruby
|
823
|
+
job0 =
|
824
|
+
@scheduler.schedule_cron '*/15 12 * * *', locals: { a: 0 } do
|
825
|
+
# ...
|
826
|
+
end
|
827
|
+
job1 =
|
828
|
+
@scheduler.schedule_cron '*/15 13 * * *', l: { a: 1 } do
|
829
|
+
# ...
|
830
|
+
end
|
831
|
+
```
|
832
|
+
|
833
|
+
One can fetch the Hash directly with `Job#locals`. Of course, direct manipulation is not thread-safe.
|
834
|
+
```ruby
|
835
|
+
job.locals.entries do |k, v|
|
836
|
+
p "#{k}: #{v}"
|
837
|
+
end
|
838
|
+
```
|
740
839
|
|
741
840
|
### call
|
742
841
|
|
@@ -751,7 +850,7 @@ job =
|
|
751
850
|
job.call
|
752
851
|
```
|
753
852
|
|
754
|
-
Warning: the Scheduler#on_error handler is not involved. Error handling is the responsibility of the caller.
|
853
|
+
Warning: the Scheduler[#on_error](#rufusscheduleron_errorjob-error) handler is not involved. Error handling is the responsibility of the caller.
|
755
854
|
|
756
855
|
If the call has to be rescued by the error handler of the scheduler, ```call(true)``` might help:
|
757
856
|
|
@@ -761,7 +860,11 @@ require 'rufus-scheduler'
|
|
761
860
|
s = Rufus::Scheduler.new
|
762
861
|
|
763
862
|
def s.on_error(job, err)
|
764
|
-
|
863
|
+
if job
|
864
|
+
p [ 'error in scheduled job', job.class, job.original, err.message ]
|
865
|
+
else
|
866
|
+
p [ 'error while scheduling', err.message ]
|
867
|
+
end
|
765
868
|
rescue
|
766
869
|
p $!
|
767
870
|
end
|
@@ -784,7 +887,7 @@ Returns when the job will trigger (hopefully).
|
|
784
887
|
|
785
888
|
### next_time
|
786
889
|
|
787
|
-
An alias
|
890
|
+
An alias for time.
|
788
891
|
|
789
892
|
## EveryJob, IntervalJob and CronJob methods
|
790
893
|
|
@@ -814,34 +917,40 @@ Every jobs use a time duration between each start of their execution, while inte
|
|
814
917
|
|
815
918
|
## CronJob methods
|
816
919
|
|
817
|
-
###
|
818
|
-
|
819
|
-
It returns the shortest interval of time between two potential occurences of the job.
|
820
|
-
|
821
|
-
For instance:
|
822
|
-
```ruby
|
823
|
-
Rufus::Scheduler.parse('* * * * *').frequency # ==> 60
|
824
|
-
Rufus::Scheduler.parse('* * * * * *').frequency # ==> 1
|
920
|
+
### brute_frequency
|
825
921
|
|
826
|
-
|
827
|
-
Rufus::Scheduler.parse('5 * * * *').frequency # ==> 3600
|
828
|
-
Rufus::Scheduler.parse('10,20,30 * * * *').frequency # ==> 600
|
922
|
+
An expensive method to run, it's brute. It caches its results. By default it runs for 2017 (a non leap-year).
|
829
923
|
|
830
|
-
Rufus::Scheduler.parse('10,20,30 * * * * *').frequency # ==> 10
|
831
924
|
```
|
925
|
+
require 'rufus-scheduler'
|
832
926
|
|
833
|
-
|
927
|
+
Rufus::Scheduler.parse('* * * * *').brute_frequency
|
928
|
+
#
|
929
|
+
# => #<Fugit::Cron::Frequency:0x00007fdf4520c5e8
|
930
|
+
# @span=31536000.0, @delta_min=60, @delta_max=60,
|
931
|
+
# @occurrences=525600, @span_years=1.0, @yearly_occurrences=525600.0>
|
932
|
+
#
|
933
|
+
# Occurs 525600 times in a span of 1 year (2017) and 1 day.
|
934
|
+
# There are least 60 seconds between "triggers" and at most 60 seconds.
|
834
935
|
|
835
|
-
|
936
|
+
Rufus::Scheduler.parse('0 12 * * *').brute_frequency
|
937
|
+
# => #<Fugit::Cron::Frequency:0x00007fdf451ec6d0
|
938
|
+
# @span=31536000.0, @delta_min=86400, @delta_max=86400,
|
939
|
+
# @occurrences=365, @span_years=1.0, @yearly_occurrences=365.0>
|
940
|
+
Rufus::Scheduler.parse('0 12 * * *').brute_frequency.to_debug_s
|
941
|
+
# => "dmin: 1D, dmax: 1D, ocs: 365, spn: 52W1D, spnys: 1, yocs: 365"
|
942
|
+
#
|
943
|
+
# 365 occurrences, at most 1 day between each, at least 1 day.
|
944
|
+
```
|
836
945
|
|
837
|
-
|
946
|
+
The `CronJob#frequency` method found in rufus-scheduler < 3.5 has been retired.
|
838
947
|
|
839
948
|
|
840
949
|
## looking up jobs
|
841
950
|
|
842
951
|
### Scheduler#job(job_id)
|
843
952
|
|
844
|
-
The scheduler ```#job(job_id)``` method can be used to
|
953
|
+
The scheduler ```#job(job_id)``` method can be used to look up Job instances.
|
845
954
|
|
846
955
|
```ruby
|
847
956
|
require 'rufus-scheduler'
|
@@ -871,24 +980,24 @@ Here is an example:
|
|
871
980
|
scheduler.at_jobs.each(&:unschedule)
|
872
981
|
```
|
873
982
|
|
874
|
-
### Scheduler#jobs(:
|
983
|
+
### Scheduler#jobs(tag: / tags: x)
|
875
984
|
|
876
|
-
When scheduling a job, one can specify one or more tags attached to the job. These can be used to
|
985
|
+
When scheduling a job, one can specify one or more tags attached to the job. These can be used to look up the job later on.
|
877
986
|
|
878
987
|
```ruby
|
879
|
-
scheduler.in '10d', :
|
988
|
+
scheduler.in '10d', tag: 'main_process' do
|
880
989
|
# ...
|
881
990
|
end
|
882
|
-
scheduler.in '10d', :
|
991
|
+
scheduler.in '10d', tags: [ 'main_process', 'side_dish' ] do
|
883
992
|
# ...
|
884
993
|
end
|
885
994
|
|
886
995
|
# ...
|
887
996
|
|
888
|
-
jobs = scheduler.jobs(:
|
997
|
+
jobs = scheduler.jobs(tag: 'main_process')
|
889
998
|
# find all the jobs with the 'main_process' tag
|
890
999
|
|
891
|
-
jobs = scheduler.jobs(:
|
1000
|
+
jobs = scheduler.jobs(tags: [ 'main_process', 'side_dish' ]
|
892
1001
|
# find all the jobs with the 'main_process' AND 'side_dish' tags
|
893
1002
|
```
|
894
1003
|
|
@@ -913,6 +1022,10 @@ Shuts down the scheduler, ceases any scheduler/triggering activity.
|
|
913
1022
|
|
914
1023
|
Shuts down the scheduler, waits (blocks) until all the jobs cease running.
|
915
1024
|
|
1025
|
+
### Scheduler#shutdown(wait: n)
|
1026
|
+
|
1027
|
+
Shuts down the scheduler, waits (blocks) at most n seconds until all the jobs cease running. (Jobs are killed after n seconds have elapsed).
|
1028
|
+
|
916
1029
|
### Scheduler#shutdown(:kill)
|
917
1030
|
|
918
1031
|
Kills all the job (threads) and then shuts the scheduler down. Radical.
|
@@ -933,7 +1046,9 @@ Returns since the count of seconds for which the scheduler has been running.
|
|
933
1046
|
|
934
1047
|
### Scheduler#join
|
935
1048
|
|
936
|
-
|
1049
|
+
Lets the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
|
1050
|
+
|
1051
|
+
`#join` is mostly used in standalone scheduling script (or tiny one file examples). Calling `#join` from a web application initializer will probably hijack the main thread and prevent the web application from being served. Do not put a `#join` in such a web application initializer file.
|
937
1052
|
|
938
1053
|
### Scheduler#threads
|
939
1054
|
|
@@ -947,7 +1062,7 @@ Lists the work threads associated with the scheduler. The query option defaults
|
|
947
1062
|
* :active : all the work threads currently running a Job
|
948
1063
|
* :vacant : all the work threads currently not running a Job
|
949
1064
|
|
950
|
-
Note that the main schedule thread will be returned if it is currently running a Job (ie one of those :
|
1065
|
+
Note that the main schedule thread will be returned if it is currently running a Job (ie one of those `blocking: true` jobs).
|
951
1066
|
|
952
1067
|
### Scheduler#scheduled?(job_or_job_id)
|
953
1068
|
|
@@ -955,7 +1070,7 @@ Returns true if the arg is a currently scheduled job (see Job#scheduled?).
|
|
955
1070
|
|
956
1071
|
### Scheduler#occurrences(time0, time1)
|
957
1072
|
|
958
|
-
Returns a hash
|
1073
|
+
Returns a hash `{ job => [ t0, t1, ... ] }` mapping jobs to their potential trigger time within the `[ time0, time1 ]` span.
|
959
1074
|
|
960
1075
|
Please note that, for interval jobs, the ```#mean_work_time``` is used, so the result is only a prediction.
|
961
1076
|
|
@@ -1008,9 +1123,9 @@ TODO: talk about callable#on_error (if implemented)
|
|
1008
1123
|
|
1009
1124
|
### Rufus::Scheduler#stderr=
|
1010
1125
|
|
1011
|
-
By default, rufus-scheduler intercepts all errors (that inherit from StandardError) and dumps
|
1126
|
+
By default, rufus-scheduler intercepts all errors (that inherit from StandardError) and dumps abundant details to $stderr.
|
1012
1127
|
|
1013
|
-
If, for example, you'd like to divert that flow to another file (descriptor)
|
1128
|
+
If, for example, you'd like to divert that flow to another file (descriptor), you can reassign $stderr for the current Ruby process
|
1014
1129
|
|
1015
1130
|
```ruby
|
1016
1131
|
$stderr = File.open('/var/log/myapplication.log', 'ab')
|
@@ -1033,7 +1148,23 @@ def scheduler.on_error(job, error)
|
|
1033
1148
|
end
|
1034
1149
|
```
|
1035
1150
|
|
1036
|
-
|
1151
|
+
On Rails, the `on_error` method redefinition might look like:
|
1152
|
+
```ruby
|
1153
|
+
def scheduler.on_error(job, error)
|
1154
|
+
|
1155
|
+
Rails.logger.error(
|
1156
|
+
"err#{error.object_id} rufus-scheduler intercepted #{error.inspect}" +
|
1157
|
+
" in job #{job.inspect}")
|
1158
|
+
error.backtrace.each_with_index do |line, i|
|
1159
|
+
Rails.logger.error(
|
1160
|
+
"err#{error.object_id} #{i}: #{line}")
|
1161
|
+
end
|
1162
|
+
end
|
1163
|
+
```
|
1164
|
+
|
1165
|
+
## Callbacks
|
1166
|
+
|
1167
|
+
### Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
|
1037
1168
|
|
1038
1169
|
One can bind callbacks before and after jobs trigger:
|
1039
1170
|
|
@@ -1055,13 +1186,28 @@ end
|
|
1055
1186
|
|
1056
1187
|
The ```trigger_time``` is the time at which the job triggers. It might be a bit before ```Time.now```.
|
1057
1188
|
|
1058
|
-
Warning: these two callbacks are executed in the scheduler thread, not in the work threads (the threads
|
1189
|
+
Warning: these two callbacks are executed in the scheduler thread, not in the work threads (the threads where the job execution really happens).
|
1190
|
+
|
1191
|
+
### Rufus::Scheduler#around_trigger
|
1192
|
+
|
1193
|
+
One can create an around callback which will wrap a job:
|
1194
|
+
|
1195
|
+
```ruby
|
1196
|
+
def s.around_trigger(job)
|
1197
|
+
t = Time.now
|
1198
|
+
puts "Starting job #{job.id}..."
|
1199
|
+
yield
|
1200
|
+
puts "job #{job.id} finished in #{Time.now-t} seconds."
|
1201
|
+
end
|
1202
|
+
```
|
1203
|
+
|
1204
|
+
The around callback is executed in the thread.
|
1059
1205
|
|
1060
1206
|
### Rufus::Scheduler#on_pre_trigger as a guard
|
1061
1207
|
|
1062
1208
|
Returning ```false``` in on_pre_trigger will prevent the job from triggering. Returning anything else (nil, -1, true, ...) will let the job trigger.
|
1063
1209
|
|
1064
|
-
Note: your business logic should go in the scheduled block itself (or the scheduled instance). Don't put business logic in on_pre_trigger. Return false for admin reasons (backend down, etc) not for business reasons that are tied to the job itself.
|
1210
|
+
Note: your business logic should go in the scheduled block itself (or the scheduled instance). Don't put business logic in on_pre_trigger. Return false for admin reasons (backend down, etc), not for business reasons that are tied to the job itself.
|
1065
1211
|
|
1066
1212
|
```ruby
|
1067
1213
|
def s.on_pre_trigger(job, trigger_time)
|
@@ -1081,25 +1227,25 @@ By default, rufus-scheduler sleeps 0.300 second between every step. At each step
|
|
1081
1227
|
The :frequency option lets you change that 0.300 second to something else.
|
1082
1228
|
|
1083
1229
|
```ruby
|
1084
|
-
scheduler = Rufus::Scheduler.new(:
|
1230
|
+
scheduler = Rufus::Scheduler.new(frequency: 5)
|
1085
1231
|
```
|
1086
1232
|
|
1087
1233
|
It's OK to use a time string to specify the frequency.
|
1088
1234
|
|
1089
1235
|
```ruby
|
1090
|
-
scheduler = Rufus::Scheduler.new(:
|
1236
|
+
scheduler = Rufus::Scheduler.new(frequency: '2h10m')
|
1091
1237
|
# this scheduler will sleep 2 hours and 10 minutes between every "step"
|
1092
1238
|
```
|
1093
1239
|
|
1094
1240
|
Use with care.
|
1095
1241
|
|
1096
|
-
### :
|
1242
|
+
### lockfile: "mylockfile.txt"
|
1097
1243
|
|
1098
1244
|
This feature only works on OSes that support the flock (man 2 flock) call.
|
1099
1245
|
|
1100
|
-
Starting the scheduler with
|
1246
|
+
Starting the scheduler with ```lockfile: '.rufus-scheduler.lock'``` will make the scheduler attempt to create and lock the file ```.rufus-scheduler.lock``` in the current working directory. If that fails, the scheduler will not start.
|
1101
1247
|
|
1102
|
-
The idea is to guarantee only one scheduler (in a group of
|
1248
|
+
The idea is to guarantee only one scheduler (in a group of schedulers sharing the same lockfile) is running.
|
1103
1249
|
|
1104
1250
|
This is useful in environments where the Ruby process holding the scheduler gets started multiple times.
|
1105
1251
|
|
@@ -1126,7 +1272,7 @@ class HostLock
|
|
1126
1272
|
end
|
1127
1273
|
|
1128
1274
|
scheduler =
|
1129
|
-
Rufus::Scheduler.new(:
|
1275
|
+
Rufus::Scheduler.new(scheduler_lock: HostLock.new('coffee.example.com'))
|
1130
1276
|
```
|
1131
1277
|
|
1132
1278
|
By default, the scheduler_lock is an instance of `Rufus::Scheduler::NullLock`, with a `#lock` that returns true.
|
@@ -1149,7 +1295,7 @@ class PingLock
|
|
1149
1295
|
end
|
1150
1296
|
|
1151
1297
|
scheduler =
|
1152
|
-
Rufus::Scheduler.new(:
|
1298
|
+
Rufus::Scheduler.new(trigger_lock: PingLock.new('main.example.com'))
|
1153
1299
|
```
|
1154
1300
|
|
1155
1301
|
By default, the trigger_lock is an instance of `Rufus::Scheduler::NullLock`, with a `#lock` that always returns true.
|
@@ -1163,7 +1309,7 @@ In rufus-scheduler 2.x, by default, each job triggering received its own, brand
|
|
1163
1309
|
One can set this maximum value when starting the scheduler.
|
1164
1310
|
|
1165
1311
|
```ruby
|
1166
|
-
scheduler = Rufus::Scheduler.new(:
|
1312
|
+
scheduler = Rufus::Scheduler.new(max_work_threads: 77)
|
1167
1313
|
```
|
1168
1314
|
|
1169
1315
|
It's OK to increase the :max_work_threads of a running scheduler.
|
@@ -1176,7 +1322,7 @@ scheduler.max_work_threads += 10
|
|
1176
1322
|
## Rufus::Scheduler.singleton
|
1177
1323
|
|
1178
1324
|
Do not want to store a reference to your rufus-scheduler instance?
|
1179
|
-
Then ```Rufus::Scheduler.singleton``` can help, it returns a
|
1325
|
+
Then ```Rufus::Scheduler.singleton``` can help, it returns a singleton instance of the scheduler, initialized the first time this class method is called.
|
1180
1326
|
|
1181
1327
|
```ruby
|
1182
1328
|
Rufus::Scheduler.singleton.every '10s' { puts "hello, world!" }
|
@@ -1185,8 +1331,8 @@ Rufus::Scheduler.singleton.every '10s' { puts "hello, world!" }
|
|
1185
1331
|
It's OK to pass initialization arguments (like :frequency or :max_work_threads) but they will only be taken into account the first time ```.singleton``` is called.
|
1186
1332
|
|
1187
1333
|
```ruby
|
1188
|
-
Rufus::Scheduler.singleton(:
|
1189
|
-
Rufus::Scheduler.singleton(:
|
1334
|
+
Rufus::Scheduler.singleton(max_work_threads: 77)
|
1335
|
+
Rufus::Scheduler.singleton(max_work_threads: 277) # no effect
|
1190
1336
|
```
|
1191
1337
|
|
1192
1338
|
The ```.s``` is a shortcut for ```.singleton```.
|
@@ -1198,11 +1344,11 @@ Rufus::Scheduler.s.every '10s' { puts "hello, world!" }
|
|
1198
1344
|
|
1199
1345
|
## advanced lock schemes
|
1200
1346
|
|
1201
|
-
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
|
1347
|
+
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 mechanism prevents schedulers that have not set/created the lockfile from running.
|
1202
1348
|
|
1203
|
-
There are
|
1349
|
+
There are situations where this is not sufficient.
|
1204
1350
|
|
1205
|
-
By overriding #lock and #unlock, one can customize how
|
1351
|
+
By overriding #lock and #unlock, one can customize how schedulers lock.
|
1206
1352
|
|
1207
1353
|
This example was provided by [Eric Lindvall](https://github.com/eric):
|
1208
1354
|
|
@@ -1235,9 +1381,9 @@ class ZookeptScheduler < Rufus::Scheduler
|
|
1235
1381
|
end
|
1236
1382
|
```
|
1237
1383
|
|
1238
|
-
This uses a [zookeeper](
|
1384
|
+
This uses a [zookeeper](https://zookeeper.apache.org/) to make sure only one scheduler in a group of distributed schedulers runs.
|
1239
1385
|
|
1240
|
-
The methods #lock and #unlock are
|
1386
|
+
The methods #lock and #unlock are overridden and #confirm_lock is provided,
|
1241
1387
|
to make sure that the lock is still valid.
|
1242
1388
|
|
1243
1389
|
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.
|
@@ -1259,7 +1405,9 @@ Warning: you may think you're heading towards "high availability" by using a tri
|
|
1259
1405
|
|
1260
1406
|
## parsing cronlines and time strings
|
1261
1407
|
|
1262
|
-
|
1408
|
+
(Please note that [fugit](https://github.com/floraison/fugit) does the heavy-lifting parsing work for rufus-scheduler).
|
1409
|
+
|
1410
|
+
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 directly (no need to instantiate a Scheduler).
|
1263
1411
|
|
1264
1412
|
```ruby
|
1265
1413
|
require 'rufus-scheduler'
|
@@ -1279,13 +1427,13 @@ Rufus::Scheduler.parse(0.1)
|
|
1279
1427
|
# => 0.1
|
1280
1428
|
|
1281
1429
|
Rufus::Scheduler.parse('* * * * *')
|
1282
|
-
# => #<
|
1283
|
-
#
|
1284
|
-
#
|
1285
|
-
#
|
1430
|
+
# => #<Fugit::Cron:0x00007fb7a3045508
|
1431
|
+
# @original="* * * * *", @cron_s=nil,
|
1432
|
+
# @seconds=[0], @minutes=nil, @hours=nil, @monthdays=nil, @months=nil,
|
1433
|
+
# @weekdays=nil, @zone=nil, @timezone=nil>
|
1286
1434
|
```
|
1287
1435
|
|
1288
|
-
It returns a number when the
|
1436
|
+
It returns a number when the input is a duration and a Fugit::Cron instance when the input is a cron string.
|
1289
1437
|
|
1290
1438
|
It will raise an ArgumentError if it can't parse the input.
|
1291
1439
|
|
@@ -1301,7 +1449,7 @@ Rufus::Scheduler.to_duration_hash(60)
|
|
1301
1449
|
Rufus::Scheduler.to_duration_hash(62.127)
|
1302
1450
|
# => { :m => 1, :s => 2, :ms => 127 }
|
1303
1451
|
|
1304
|
-
Rufus::Scheduler.to_duration_hash(62.127, :
|
1452
|
+
Rufus::Scheduler.to_duration_hash(62.127, drop_seconds: true)
|
1305
1453
|
# => { :m => 1 }
|
1306
1454
|
```
|
1307
1455
|
|
@@ -1338,7 +1486,7 @@ require 'rufus-scheduler'
|
|
1338
1486
|
|
1339
1487
|
Time.now
|
1340
1488
|
# => 2013-10-26 07:07:08 +0900
|
1341
|
-
Rufus::Scheduler.parse('* * * * mon#1').next_time
|
1489
|
+
Rufus::Scheduler.parse('* * * * mon#1').next_time.to_s
|
1342
1490
|
# => 2013-11-04 00:00:00 +0900
|
1343
1491
|
```
|
1344
1492
|
|
@@ -1347,15 +1495,29 @@ Rufus::Scheduler.parse('* * * * mon#1').next_time
|
|
1347
1495
|
L can be used in the "day" slot:
|
1348
1496
|
|
1349
1497
|
In this example, the cronline is supposed to trigger every last day of the month at noon:
|
1350
|
-
|
1351
1498
|
```ruby
|
1352
1499
|
require 'rufus-scheduler'
|
1353
1500
|
Time.now
|
1354
1501
|
# => 2013-10-26 07:22:09 +0900
|
1355
|
-
Rufus::Scheduler.parse('00 12 L * *').next_time
|
1502
|
+
Rufus::Scheduler.parse('00 12 L * *').next_time.to_s
|
1356
1503
|
# => 2013-10-31 12:00:00 +0900
|
1357
1504
|
```
|
1358
1505
|
|
1506
|
+
#### negative day (x days before the end of the month)
|
1507
|
+
|
1508
|
+
It's OK to pass negative values in the "day" slot:
|
1509
|
+
```ruby
|
1510
|
+
scheduler.cron '0 0 -5 * *' do
|
1511
|
+
# do it at 00h00 5 days before the end of the month...
|
1512
|
+
end
|
1513
|
+
```
|
1514
|
+
|
1515
|
+
Negative ranges (`-10--5-`: 10 days before the end of the month to 5 days before the end of the month) are OK, but mixed positive / negative ranges will raise an `ArgumentError`.
|
1516
|
+
|
1517
|
+
Negative ranges with increments (`-10---2/2`) are accepted as well.
|
1518
|
+
|
1519
|
+
Descending day ranges are not accepted (`10-8` or `-8--10` for example).
|
1520
|
+
|
1359
1521
|
|
1360
1522
|
## a note about timezones
|
1361
1523
|
|
@@ -1373,7 +1535,58 @@ end
|
|
1373
1535
|
# or even
|
1374
1536
|
|
1375
1537
|
Rufus::Scheduler.parse("2013-12-12 14:00 Pacific/Saipan")
|
1376
|
-
# =>
|
1538
|
+
# => #<Rufus::Scheduler::ZoTime:0x007fb424abf4e8 @seconds=1386820800.0, @zone=#<TZInfo::DataTimezone: Pacific/Saipan>, @time=nil>
|
1539
|
+
```
|
1540
|
+
|
1541
|
+
### I get "zotime.rb:41:in `initialize': cannot determine timezone from nil"
|
1542
|
+
|
1543
|
+
For when you see an error like:
|
1544
|
+
```
|
1545
|
+
rufus-scheduler/lib/rufus/scheduler/zotime.rb:41:
|
1546
|
+
in `initialize':
|
1547
|
+
cannot determine timezone from nil (etz:nil,tnz:"中国标准时间",tzid:nil)
|
1548
|
+
(ArgumentError)
|
1549
|
+
from rufus-scheduler/lib/rufus/scheduler/zotime.rb:198:in `new'
|
1550
|
+
from rufus-scheduler/lib/rufus/scheduler/zotime.rb:198:in `now'
|
1551
|
+
from rufus-scheduler/lib/rufus/scheduler.rb:561:in `start'
|
1552
|
+
...
|
1553
|
+
```
|
1554
|
+
|
1555
|
+
It may happen on Windows or on systems that poorly hint to Ruby which timezone to use. It should be solved by setting explicitly the `ENV['TZ']` before the scheduler instantiation:
|
1556
|
+
```ruby
|
1557
|
+
ENV['TZ'] = 'Asia/Shanghai'
|
1558
|
+
scheduler = Rufus::Scheduler.new
|
1559
|
+
scheduler.every '2s' do
|
1560
|
+
puts "#{Time.now} Hello #{ENV['TZ']}!"
|
1561
|
+
end
|
1562
|
+
```
|
1563
|
+
|
1564
|
+
On Rails you might want to try with:
|
1565
|
+
```ruby
|
1566
|
+
ENV['TZ'] = Time.zone.name # Rails only
|
1567
|
+
scheduler = Rufus::Scheduler.new
|
1568
|
+
scheduler.every '2s' do
|
1569
|
+
puts "#{Time.now} Hello #{ENV['TZ']}!"
|
1570
|
+
end
|
1571
|
+
```
|
1572
|
+
(Hat tip to Alexander in [gh-230](https://github.com/jmettraux/rufus-scheduler/issues/230))
|
1573
|
+
|
1574
|
+
Rails sets its timezone under `config/application.rb`.
|
1575
|
+
|
1576
|
+
Rufus-Scheduler 3.3.3 detects the presence of Rails and uses its timezone setting (tested with Rails 4), so setting `ENV['TZ']` should not be necessary.
|
1577
|
+
|
1578
|
+
The value can be determined thanks to [https://en.wikipedia.org/wiki/List_of_tz_database_time_zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
|
1579
|
+
|
1580
|
+
Use a "continent/city" identifier (for example "Asia/Shanghai"). Do not use an abbreviation (not "CST") and do not use a local time zone name (not "中国标准时间" nor "Eastern Standard Time" which, for instance, points to a time zone in America and to another one in Australia...).
|
1581
|
+
|
1582
|
+
If the error persists (and especially on Windows), try to add the `tzinfo-data` to your Gemfile, as in:
|
1583
|
+
```ruby
|
1584
|
+
gem 'tzinfo-data'
|
1585
|
+
```
|
1586
|
+
or by manually requiring it before requiring rufus-scheduler (if you don't use Bundler):
|
1587
|
+
```ruby
|
1588
|
+
require 'tzinfo/data'
|
1589
|
+
require 'rufus-scheduler'
|
1377
1590
|
```
|
1378
1591
|
|
1379
1592
|
|
@@ -1399,6 +1612,7 @@ s = Rufus::Scheduler.singleton
|
|
1399
1612
|
s.every '1m' do
|
1400
1613
|
|
1401
1614
|
Rails.logger.info "hello, it's #{Time.now}"
|
1615
|
+
Rails.logger.flush
|
1402
1616
|
end
|
1403
1617
|
```
|
1404
1618
|
|
@@ -1418,7 +1632,7 @@ class ScheController < ApplicationController
|
|
1418
1632
|
Rails.logger.info "time flies, it's now #{Time.now}"
|
1419
1633
|
end
|
1420
1634
|
|
1421
|
-
render :
|
1635
|
+
render text: "scheduled job #{job_id}"
|
1422
1636
|
end
|
1423
1637
|
end
|
1424
1638
|
```
|
@@ -1427,6 +1641,56 @@ The rufus-scheduler singleton is instantiated in the ```config/initializers/sche
|
|
1427
1641
|
|
1428
1642
|
*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.
|
1429
1643
|
|
1644
|
+
### avoid scheduling when running the Ruby on Rails console
|
1645
|
+
|
1646
|
+
(Written in reply to [gh-186](https://github.com/jmettraux/rufus-scheduler/issues/186))
|
1647
|
+
|
1648
|
+
If you don't want rufus-scheduler to trigger anything while running the Ruby on Rails console, running for tests/specs, or running from a Rake task, you can insert a conditional return statement before jobs are added to the scheduler instance:
|
1649
|
+
|
1650
|
+
```ruby
|
1651
|
+
#
|
1652
|
+
# config/initializers/scheduler.rb
|
1653
|
+
|
1654
|
+
require 'rufus-scheduler'
|
1655
|
+
|
1656
|
+
return if defined?(Rails::Console) || Rails.env.test? || File.split($PROGRAM_NAME).last == 'rake'
|
1657
|
+
#
|
1658
|
+
# do not schedule when Rails is run from its console, for a test/spec, or
|
1659
|
+
# from a Rake task
|
1660
|
+
|
1661
|
+
# return if $PROGRAM_NAME.include?('spring')
|
1662
|
+
#
|
1663
|
+
# see https://github.com/jmettraux/rufus-scheduler/issues/186
|
1664
|
+
|
1665
|
+
s = Rufus::Scheduler.singleton
|
1666
|
+
|
1667
|
+
s.every '1m' do
|
1668
|
+
Rails.logger.info "hello, it's #{Time.now}"
|
1669
|
+
Rails.logger.flush
|
1670
|
+
end
|
1671
|
+
```
|
1672
|
+
|
1673
|
+
(Beware later version of Rails where Spring takes care pre-running the initializers. Running `spring stop` or disabling Spring might be necessary in some cases to see changes to initializers being taken into account.)
|
1674
|
+
|
1675
|
+
|
1676
|
+
### rails server -d
|
1677
|
+
|
1678
|
+
(Written in reply to https://github.com/jmettraux/rufus-scheduler/issues/165 )
|
1679
|
+
|
1680
|
+
There is the handy `rails server -d` that starts a development Rails as a daemon. The annoying thing is that the scheduler as seen above is started in the main process that then gets forked and daemonized. The rufus-scheduler thread (and any other thread) gets lost, no scheduling happens.
|
1681
|
+
|
1682
|
+
I avoid running `-d` in development mode and bother about daemonizing only for production deployment.
|
1683
|
+
|
1684
|
+
These are two well crafted articles on process daemonization, please read them:
|
1685
|
+
|
1686
|
+
* https://www.mikeperham.com/2014/09/22/dont-daemonize-your-daemons/
|
1687
|
+
* https://www.mikeperham.com/2014/07/07/use-runit/
|
1688
|
+
|
1689
|
+
If, anyway, you need something like `rails server -d`, why not try `bundle exec unicorn -D` instead? In my (limited) experience, it worked out of the box (well, had to add `gem 'unicorn'` to `Gemfile` first).
|
1690
|
+
|
1691
|
+
### executor / reloader
|
1692
|
+
|
1693
|
+
You might benefit from wraping your scheduled code in the executor or reloader. Read more here: https://guides.rubyonrails.org/threading_and_code_execution.html
|
1430
1694
|
|
1431
1695
|
## support
|
1432
1696
|
|