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
data/README.md CHANGED
@@ -1,15 +1,20 @@
1
1
 
2
2
  # rufus-scheduler
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/jmettraux/rufus-scheduler.png)](http://travis-ci.org/jmettraux/rufus-scheduler)
5
- [![Gem Version](https://badge.fury.io/rb/rufus-scheduler.png)](http://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
- **Note**: maybe are you looking for the [README of rufus-scheduler 2.x](https://github.com/jmettraux/rufus-scheduler/blob/two/README.rdoc)?
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
- * [whenever](https://github.com/javan/whenever) - let cron call back your Ruby code, trusted and reliable cron drives your schedule
63
- * [clockwork](https://github.com/tomykaira/clockwork) - rufus-scheduler inspired gem
64
- * [crono](https://github.com/plashchynski/crono) - an in-Rails cron scheduler
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
- Sir, I'll drive you right to the [tracks](#so-rails).
97
+ I'll drive you right to the [tracks](#so-rails).
79
98
 
80
99
 
81
- ## Notable changes:
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 on Ruby's ```sleep(100)```
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 :lockfile => true/filename mechanism to prevent multiple schedulers from executing
90
- * "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.
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](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html), twice.
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 StackOverflow
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
- I sometimes haunt #ruote on freenode.net. The channel is not dedicated to rufus-scheduler, so if you ask a question, first mention it's about rufus-scheduler.
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 note that I prefer helping over Stack Overflow because it's more searchable than the ruote IRC archive.
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](http://stackoverflow.com/questions/ask?tags=rufus-scheduler+ruby) is better.
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...](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
123
- * [I want a refund](http://blog.nodejitsu.com/getting-refunds-on-open-source-projects)
124
- * [Passenger and rufus-scheduler](http://stackoverflow.com/questions/18108719/debugging-rufus-scheduler/18156180#18156180)
125
- * [Passenger and rufus-scheduler (2)](http://stackoverflow.com/questions/21861387/rufus-cron-job-not-working-in-apache-passenger#answer-21868555)
126
- * [Unicorn and rufus-scheduler](https://jkraemer.net/running-rufus-scheduler-in-a-unicorn-rails-app)
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, 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).
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', :job => true do
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 decide based on its input. It returns the Job instance.
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 heres gives back an IntervalJob).
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 simple the Job instance involved. It might be useful if the job is to be unscheduled for some reason.
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 respond to #call(), when scheduling:
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
- ### :blocking => true
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
- By default, jobs are triggered in their own, new thread. When :blocking => true, the job is triggered in the scheduler thread (a new thread is not created). Yes, while the job triggers, the scheduler is not scheduling.
402
+ ### overlap: false
364
403
 
365
- ### :overlap => false
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
- Since, by default, jobs are triggered in their own new thread, job instances might overlap. For example, a job that takes 10 minutes and is scheduled every 7 minutes will have overlaps.
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
- To prevent overlap, one can set :overlap => false. Such a job will not trigger if one of its instance is already running.
408
+ The `:overlap` option is considered before the `:mutex` option when the scheduler is reviewing jobs for triggering.
370
409
 
371
- ### :mutex => mutex_instance / mutex_name / array of mutexes
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 to enter (it makes the other jobs wait until it exits the 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 :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.
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
- :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.
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
- Warning: creating lots of different mutexes is OK. Rufus-scheduler will place them in its Scheduler#mutexes hash... And they won't get garbage collected.
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
- ### :timeout => duration or point in time
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', :timeout => '1d' do
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, it's the time *after* which the first schedule will trigger.
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', :first_at => Time.now + 10 * 3600 do
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', :first_in => '10h' do
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 * * *', :first_in => '3d' do
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 make your schedule more readable.
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 occurence for immediate triggering. Consider:
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', :first => :now do
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 * * *', :last_in => '10d' do
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', :last_at => Time.now + 10 * 3600 do
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', :last_in => 10 * 3600 do
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 make your schedule more readable.
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
- ### :times => nb of times (before auto-unscheduling)
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', :times => 10 do
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 * * *', :times => 31 do
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 * * *', :times => nolimit ? nil : 10 do
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 :job => true also return Job instances instead of Job ids (Strings).
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', :job => true do
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', :tag => 'hello') do; end
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', :tag => 'hello') do; end
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 else.
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', :tag => 'hello') do; end
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('1d') do; end
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 a job thanks to them.
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', :tag => 'hello') do; end
794
+ job = scheduler.schedule_in('10d', tag: 'hello') do; end
712
795
  job.tags
713
796
  # => [ 'hello' ]
714
797
  ```
715
798
 
716
- ### []=, [], key? and keys
799
+ ### []=, [], key?, has_key?, keys, values, and entries
717
800
 
718
- Threads have thread-local variables. Rufus-scheduler jobs have job-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
- # => true
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
- Job-local variables are thread-safe.
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
- p [ 'error in scheduled job', job.class, job.original, err.message ]
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 to time.
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
- ### frequency
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
- Rufus::Scheduler.parse('5 23 * * *').frequency # ==> 24 * 3600
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
- It's used to determine if the job frequency is higher than the scheduler frequency (it raises an ArgumentError if that is the case).
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
- ### brute_frequency
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
- 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.
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 lookup Job instances.
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(:tag / :tags => x)
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 lookup the job later on.
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', :tag => 'main_process' do
988
+ scheduler.in '10d', tag: 'main_process' do
880
989
  # ...
881
990
  end
882
- scheduler.in '10d', :tags => [ 'main_process', 'side_dish' ] do
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(:tag => 'main_process')
997
+ jobs = scheduler.jobs(tag: 'main_process')
889
998
  # find all the jobs with the 'main_process' tag
890
999
 
891
- jobs = scheduler.jobs(:tags => [ 'main_process', 'side_dish' ]
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
- Let's the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
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 :blocking => true jobs).
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 ```{ job => [ t0, t1, ... ] }``` mapping jobs to their potential trigger time within the ```[ time0, time1 ]``` span.
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 abundent details to $stderr.
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). You can reassign $stderr for the current Ruby process
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
- ## Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
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 were the job execution really happens).
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(:frequency => 5)
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(:frequency => '2h10m')
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
- ### :lockfile => "mylockfile.txt"
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 ```: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.
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 scheduler sharing the same lockfile) is running.
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(:scheduler_lock => HostLock.new('coffee.example.com'))
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(:trigger_lock => PingLock.new('main.example.com'))
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(:max_work_threads => 77)
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 singleon instance of the scheduler, initialized the first time this class method is called.
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(:max_work_threads => 77)
1189
- Rufus::Scheduler.singleton(:max_work_threads => 277) # no effect
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 mecha prevents schedulers that have not set/created the lockfile from running.
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 situation where this is not sufficient.
1349
+ There are situations where this is not sufficient.
1204
1350
 
1205
- By overriding #lock and #unlock, one can customize how his schedulers lock.
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](http://zookeeper.apache.org/) to make sure only one scheduler in a group of distributed schedulers runs.
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 overriden and #confirm_lock is provided,
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
- 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).
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
- # => #<Rufus::Scheduler::CronLine:0x00000002be5198
1283
- # @original="* * * * *", @timezone=nil,
1284
- # @seconds=[0], @minutes=nil, @hours=nil, @days=nil, @months=nil,
1285
- # @weekdays=nil, @monthdays=nil>
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 output is a duration and a CronLine instance when the input is a cron string.
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, :drop_seconds => true)
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
- # => 2013-12-12 04:00:00 UTC
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 :text => "scheduled job #{job_id}"
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