rufus-scheduler 3.4.2 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2005-2017, John Mettraux, jmettraux@gmail.com
2
+ Copyright (c) 2005-2020, John Mettraux, jmettraux@gmail.com
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
data/Makefile CHANGED
@@ -4,6 +4,10 @@ NAME = \
4
4
  VERSION = \
5
5
  $(shell ruby -e "s = eval(File.read(Dir['*.gemspec'][0])); puts s.version")
6
6
 
7
+ count_lines:
8
+ find lib -name "*.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }"
9
+ find spec -name "*_spec.rb" | xargs cat | ruby -e "p STDIN.readlines.count { |l| l = l.strip; l[0, 1] != '#' && l != '' }"
10
+ cl: count_lines
7
11
 
8
12
  gemspec_validate:
9
13
  @echo "---"
@@ -19,5 +23,5 @@ build: gemspec_validate
19
23
  mv $(NAME)-$(VERSION).gem pkg/
20
24
 
21
25
  push: build
22
- gem push pkg/$(NAME)-$(VERSION).gem
26
+ gem push --otp "$(OTP)" pkg/$(NAME)-$(VERSION).gem
23
27
 
data/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
 
2
2
  # rufus-scheduler
3
3
 
4
- [![Build Status](https://secure.travis-ci.org/jmettraux/rufus-scheduler.svg)](http://travis-ci.org/jmettraux/rufus-scheduler)
5
- [![Gem Version](https://badge.fury.io/rb/rufus-scheduler.svg)](http://badge.fury.io/rb/rufus-scheduler)
4
+ [![Build Status](https://secure.travis-ci.org/jmettraux/rufus-scheduler.svg)](https://travis-ci.org/jmettraux/rufus-scheduler)
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
 
@@ -23,7 +24,11 @@ scheduler.in '3s' do
23
24
  end
24
25
 
25
26
  scheduler.join
27
+ #
26
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)
27
32
  ```
28
33
  (run with `ruby quickstart.rb`)
29
34
 
@@ -46,6 +51,9 @@ end
46
51
  scheduler.every '3h' do
47
52
  # do something every 3 hours
48
53
  end
54
+ scheduler.every '3h10m' do
55
+ # do something every 3 hours and 10 minutes
56
+ end
49
57
 
50
58
  scheduler.cron '5 0 * * *' do
51
59
  # do something every day, five minutes after midnight
@@ -55,21 +63,24 @@ end
55
63
  # ...
56
64
  ```
57
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
+
58
68
  ## non-features
59
69
 
60
70
  Rufus-scheduler (out of the box) is an in-process, in-memory scheduler. It uses threads.
61
71
 
62
72
  It does not persist your schedules. When the process is gone and the scheduler instance with it, the schedules are gone.
63
73
 
64
- A rufus-scheduler instance will go on scheduling while it is present among the object in a Ruby process. To make it stop scheduling you have to call its [`#shutdown` method](#schedulershutdown).
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).
65
75
 
66
76
 
67
77
  ## related and similar gems
68
78
 
69
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
70
81
  * [Clockwork](https://github.com/Rykian/clockwork) - rufus-scheduler inspired gem
71
82
  * [Crono](https://github.com/plashchynski/crono) - an in-Rails cron scheduler
72
- * [PerfectSched](https://github.com/treasure-data/perfectsched) - highly available distributed cron built on [Sequel](http://sequel.jeremyevans.net) and more
83
+ * [PerfectSched](https://github.com/treasure-data/perfectsched) - highly available distributed cron built on [Sequel](https://sequel.jeremyevans.net) and more
73
84
 
74
85
  (please note: rufus-scheduler is not a cron replacement)
75
86
 
@@ -86,16 +97,16 @@ There is no EventMachine-based scheduler anymore.
86
97
  I'll drive you right to the [tracks](#so-rails).
87
98
 
88
99
 
89
- ## Notable changes:
100
+ ## notable changes:
90
101
 
91
102
  * As said, no more EventMachine-based scheduler
92
- * ```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)```
93
104
  * The scheduler isn't catching the whole of Exception anymore, only StandardError
94
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)
95
106
  * Rufus::Scheduler::TimeOutError renamed to Rufus::Scheduler::TimeoutError
96
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"
97
- * Introduction of a :lockfile => true/filename mechanism to prevent multiple schedulers from executing
98
- * "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.
99
110
  * Introduction of Scheduler #on_pre_trigger and #on_post_trigger callback points
100
111
 
101
112
 
@@ -105,29 +116,30 @@ So you need help. People can help you, but first help them help you, and don't w
105
116
 
106
117
  "hello", "please" and "thanks" are not swear words.
107
118
 
108
- 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.
109
120
 
110
121
  Update: [help_help.md](https://gist.github.com/jmettraux/310fed75f568fd731814) might help help you.
111
122
 
112
- ### on IRC
123
+ ### on Gitter
113
124
 
114
- 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.
115
126
 
116
- Please note that I prefer helping over Stack Overflow because it's more searchable than the ruote IRC archive.
127
+ Please be courteous.
117
128
 
118
129
  ### issues
119
130
 
120
- 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.
121
132
 
122
133
  ### faq
123
134
 
124
- * [It doesn't work...](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
125
- * [I want a refund](http://blog.nodejitsu.com/getting-refunds-on-open-source-projects)
126
- * [Passenger and rufus-scheduler](http://stackoverflow.com/questions/18108719/debugging-rufus-scheduler/18156180#18156180)
127
- * [Passenger and rufus-scheduler (2)](http://stackoverflow.com/questions/21861387/rufus-cron-job-not-working-in-apache-passenger#answer-21868555)
135
+ * [It doesn't work...](https://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
136
+ * [I want a refund](https://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)
128
139
  * [Passenger in-depth spawn methods](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/)
129
140
  * [Passenger in-depth spawn methods (smart spawning)](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/#smart-spawning-hooks)
130
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)
131
143
  * [I don't get any of this, I just want it to work in my Rails application](#so-rails)
132
144
  * [I get "zotime.rb:41:in `initialize': cannot determine timezone from nil"](#i-get-zotimerb41in-initialize-cannot-determine-timezone-from-nil)
133
145
 
@@ -182,7 +194,7 @@ end
182
194
 
183
195
  Every jobs try hard to trigger following the frequency they were scheduled with.
184
196
 
185
- 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).
186
198
 
187
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.
188
200
 
@@ -220,7 +232,7 @@ job =
220
232
 
221
233
  Sometimes it pays to be less verbose.
222
234
 
223
- 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.
224
236
 
225
237
  ```ruby
226
238
  scheduler.schedule '10d' do; end.class
@@ -243,13 +255,13 @@ scheduler.repeat '* * * * *' do; end.class
243
255
  # => Rufus::Scheduler::CronJob
244
256
  ```
245
257
 
246
- (Yes, no combination heres gives back an IntervalJob).
258
+ (Yes, no combination here gives back an IntervalJob).
247
259
 
248
260
  ### schedule blocks arguments (job, time)
249
261
 
250
262
  A schedule block may be given 0, 1 or 2 arguments.
251
263
 
252
- 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.
253
265
 
254
266
  ```ruby
255
267
  scheduler.every '10m' do |job|
@@ -289,7 +301,7 @@ It should work as well with cron jobs, not so with interval jobs whose next_time
289
301
 
290
302
  ### scheduling handler instances
291
303
 
292
- 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:
293
305
 
294
306
  ```ruby
295
307
  class Handler
@@ -366,29 +378,46 @@ While paused, the scheduler still accepts schedules, but no schedule will get tr
366
378
 
367
379
  ## job options
368
380
 
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
+
369
398
  ### :blocking => true
370
399
 
371
- 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.
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.
372
401
 
373
402
  ### :overlap => false
374
403
 
375
- 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.
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.
376
405
 
377
- To prevent overlap, one can set :overlap => false. Such a job will not trigger if one of its instance is already running.
406
+ To prevent overlap, one can set `:overlap => false`. Such a job will not trigger if one of its instances is already running.
378
407
 
379
408
  The `:overlap` option is considered before the `:mutex` option when the scheduler is reviewing jobs for triggering.
380
409
 
381
410
  ### :mutex => mutex_instance / mutex_name / array of mutexes
382
411
 
383
- 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).
384
413
 
385
- 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.
386
415
 
387
- :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.
388
417
 
389
418
  Array of mutexes: original idea and implementation by [Rainux Luo](https://github.com/rainux)
390
419
 
391
- 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.
392
421
 
393
422
  The `:overlap` option is considered before the `:mutex` option when the scheduler is reviewing jobs for triggering.
394
423
 
@@ -415,7 +444,7 @@ This option is for repeat jobs (cron / every) only.
415
444
  It's used to specify the first time after which the repeat job should trigger for the first time.
416
445
 
417
446
  In the case of an "every" job, this will be the first time (modulo the scheduler frequency) the job triggers.
418
- 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.
419
448
 
420
449
  ```ruby
421
450
  scheduler.every '2d', :first_at => Time.now + 10 * 3600 do
@@ -431,7 +460,7 @@ scheduler.cron '00 14 * * *', :first_in => '3d' do
431
460
  end
432
461
  ```
433
462
 
434
- :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.
435
464
 
436
465
  Note: it's OK to change the first_at (a Time instance) directly:
437
466
  ```ruby
@@ -453,7 +482,6 @@ s.every '3s', :first => :now do
453
482
  end
454
483
 
455
484
  s.join
456
-
457
485
  ```
458
486
 
459
487
  that'll output something like:
@@ -486,7 +514,7 @@ scheduler.every '10m', :last_in => 10 * 3600 do
486
514
  # ... do something every 10 minutes for 10 hours
487
515
  end
488
516
  ```
489
- :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.
490
518
 
491
519
  Note: it's OK to change the last_at (nil or a Time instance) directly:
492
520
  ```ruby
@@ -536,7 +564,7 @@ job.times = 10
536
564
 
537
565
  ## Job methods
538
566
 
539
- 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).
540
568
 
541
569
  ```ruby
542
570
  require 'rufus-scheduler'
@@ -599,7 +627,7 @@ job.original
599
627
 
600
628
  callable() returns the scheduled block (or the call method of the callable object passed in lieu of a block)
601
629
 
602
- 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.
603
631
 
604
632
  ```ruby
605
633
  # when passing a block
@@ -683,6 +711,12 @@ The job keeps track of how long its work was in the `last_work_time` attribute.
683
711
 
684
712
  The attribute `mean_work_time` contains a computed mean work time. It's recomputed after every run (if it's a repeat job).
685
713
 
714
+ ### next_times(n)
715
+
716
+ Returns an array of `EtOrbi::EoTime` instances (Time instances with a designated time zone), listing the `n` next occurrences for this job.
717
+
718
+ 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.
719
+
686
720
  ### unschedule
687
721
 
688
722
  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.
@@ -709,7 +743,7 @@ Returns true if the job is scheduled (is due to trigger). For repeat jobs it sho
709
743
 
710
744
  ### pause, resume, paused?, paused_at
711
745
 
712
- These four methods are only available to CronJob, EveryJob and IntervalJob instances. One can pause or resume such a job thanks to them.
746
+ These four methods are only available to CronJob, EveryJob and IntervalJob instances. One can pause or resume such jobs thanks to these methods.
713
747
 
714
748
  ```ruby
715
749
  job =
@@ -744,9 +778,9 @@ job.tags
744
778
  # => [ 'hello' ]
745
779
  ```
746
780
 
747
- ### []=, [], key? and keys
781
+ ### []=, [], key?, has_key?, keys, values, and entries
748
782
 
749
- Threads have thread-local variables. Rufus-scheduler jobs have job-local variables.
783
+ Threads have thread-local variables, similarly Rufus-scheduler jobs have job-local variables. Those are more like a dict with thread-safe access.
750
784
 
751
785
  ```ruby
752
786
  job =
@@ -761,13 +795,29 @@ sleep 3.6
761
795
  job[:counter]
762
796
  # => 3
763
797
 
764
- job.key?(:timestamp)
765
- # => true
766
- job.keys
767
- # => [ :timestamp, :counter ]
798
+ job.key?(:timestamp) # => true
799
+ job.has_key?(:timestamp) # => true
800
+ job.keys # => [ :timestamp, :counter ]
801
+ ```
802
+
803
+ Locals can be set at schedule time:
804
+ ```ruby
805
+ job0 =
806
+ @scheduler.schedule_cron '*/15 12 * * *', locals: { a: 0 } do
807
+ # ...
808
+ end
809
+ job1 =
810
+ @scheduler.schedule_cron '*/15 13 * * *', l: { a: 1 } do
811
+ # ...
812
+ end
768
813
  ```
769
814
 
770
- Job-local variables are thread-safe.
815
+ One can fetch the Hash directly with `Job#locals`. Of course, direct manipulation is not thread-safe.
816
+ ```ruby
817
+ job.locals.entries do |k, v|
818
+ p "#{k}: #{v}"
819
+ end
820
+ ```
771
821
 
772
822
  ### call
773
823
 
@@ -815,7 +865,7 @@ Returns when the job will trigger (hopefully).
815
865
 
816
866
  ### next_time
817
867
 
818
- An alias to time.
868
+ An alias for time.
819
869
 
820
870
  ## EveryJob, IntervalJob and CronJob methods
821
871
 
@@ -845,34 +895,40 @@ Every jobs use a time duration between each start of their execution, while inte
845
895
 
846
896
  ## CronJob methods
847
897
 
848
- ### frequency
849
-
850
- It returns the shortest interval of time between two potential occurrences of the job.
851
-
852
- For instance:
853
- ```ruby
854
- Rufus::Scheduler.parse('* * * * *').frequency # ==> 60
855
- Rufus::Scheduler.parse('* * * * * *').frequency # ==> 1
898
+ ### brute_frequency
856
899
 
857
- Rufus::Scheduler.parse('5 23 * * *').frequency # ==> 24 * 3600
858
- Rufus::Scheduler.parse('5 * * * *').frequency # ==> 3600
859
- Rufus::Scheduler.parse('10,20,30 * * * *').frequency # ==> 600
900
+ An expensive method to run, it's brute. It caches its results. By default it runs for 2017 (a non leap-year).
860
901
 
861
- Rufus::Scheduler.parse('10,20,30 * * * * *').frequency # ==> 10
862
902
  ```
903
+ require 'rufus-scheduler'
863
904
 
864
- It's used to determine if the job frequency is higher than the scheduler frequency (it raises an ArgumentError if that is the case).
905
+ Rufus::Scheduler.parse('* * * * *').brute_frequency
906
+ #
907
+ # => #<Fugit::Cron::Frequency:0x00007fdf4520c5e8
908
+ # @span=31536000.0, @delta_min=60, @delta_max=60,
909
+ # @occurrences=525600, @span_years=1.0, @yearly_occurrences=525600.0>
910
+ #
911
+ # Occurs 525600 times in a span of 1 year (2017) and 1 day.
912
+ # There are least 60 seconds between "triggers" and at most 60 seconds.
865
913
 
866
- ### brute_frequency
914
+ Rufus::Scheduler.parse('0 12 * * *').brute_frequency
915
+ # => #<Fugit::Cron::Frequency:0x00007fdf451ec6d0
916
+ # @span=31536000.0, @delta_min=86400, @delta_max=86400,
917
+ # @occurrences=365, @span_years=1.0, @yearly_occurrences=365.0>
918
+ Rufus::Scheduler.parse('0 12 * * *').brute_frequency.to_debug_s
919
+ # => "dmin: 1D, dmax: 1D, ocs: 365, spn: 52W1D, spnys: 1, yocs: 365"
920
+ #
921
+ # 365 occurrences, at most 1 day between each, at least 1 day.
922
+ ```
867
923
 
868
- 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.
924
+ The `CronJob#frequency` method found in rufus-scheduler < 3.5 has been retired.
869
925
 
870
926
 
871
927
  ## looking up jobs
872
928
 
873
929
  ### Scheduler#job(job_id)
874
930
 
875
- The scheduler ```#job(job_id)``` method can be used to lookup Job instances.
931
+ The scheduler ```#job(job_id)``` method can be used to look up Job instances.
876
932
 
877
933
  ```ruby
878
934
  require 'rufus-scheduler'
@@ -904,7 +960,7 @@ Here is an example:
904
960
 
905
961
  ### Scheduler#jobs(:tag / :tags => x)
906
962
 
907
- 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.
963
+ 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.
908
964
 
909
965
  ```ruby
910
966
  scheduler.in '10d', :tag => 'main_process' do
@@ -944,6 +1000,10 @@ Shuts down the scheduler, ceases any scheduler/triggering activity.
944
1000
 
945
1001
  Shuts down the scheduler, waits (blocks) until all the jobs cease running.
946
1002
 
1003
+ ### Scheduler#shutdown(wait: n)
1004
+
1005
+ Shuts down the scheduler, waits (blocks) at most n seconds until all the jobs cease running. (Jobs are killed after n seconds have elapsed).
1006
+
947
1007
  ### Scheduler#shutdown(:kill)
948
1008
 
949
1009
  Kills all the job (threads) and then shuts the scheduler down. Radical.
@@ -964,7 +1024,9 @@ Returns since the count of seconds for which the scheduler has been running.
964
1024
 
965
1025
  ### Scheduler#join
966
1026
 
967
- Let's the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
1027
+ Lets the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
1028
+
1029
+ `#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.
968
1030
 
969
1031
  ### Scheduler#threads
970
1032
 
@@ -1039,9 +1101,9 @@ TODO: talk about callable#on_error (if implemented)
1039
1101
 
1040
1102
  ### Rufus::Scheduler#stderr=
1041
1103
 
1042
- By default, rufus-scheduler intercepts all errors (that inherit from StandardError) and dumps abundent details to $stderr.
1104
+ By default, rufus-scheduler intercepts all errors (that inherit from StandardError) and dumps abundant details to $stderr.
1043
1105
 
1044
- If, for example, you'd like to divert that flow to another file (descriptor). You can reassign $stderr for the current Ruby process
1106
+ If, for example, you'd like to divert that flow to another file (descriptor), you can reassign $stderr for the current Ruby process
1045
1107
 
1046
1108
  ```ruby
1047
1109
  $stderr = File.open('/var/log/myapplication.log', 'ab')
@@ -1078,7 +1140,9 @@ def scheduler.on_error(job, error)
1078
1140
  end
1079
1141
  ```
1080
1142
 
1081
- ## Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
1143
+ ## Callbacks
1144
+
1145
+ ### Rufus::Scheduler #on_pre_trigger and #on_post_trigger callbacks
1082
1146
 
1083
1147
  One can bind callbacks before and after jobs trigger:
1084
1148
 
@@ -1100,13 +1164,28 @@ end
1100
1164
 
1101
1165
  The ```trigger_time``` is the time at which the job triggers. It might be a bit before ```Time.now```.
1102
1166
 
1103
- Warning: these two callbacks are executed in the scheduler thread, not in the work threads (the threads were the job execution really happens).
1167
+ Warning: these two callbacks are executed in the scheduler thread, not in the work threads (the threads where the job execution really happens).
1168
+
1169
+ ### Rufus::Scheduler#around_trigger
1170
+
1171
+ One can create an around callback which will wrap a job:
1172
+
1173
+ ```ruby
1174
+ def s.around_trigger(job)
1175
+ t = Time.now
1176
+ puts "Starting job #{job.id}..."
1177
+ yield
1178
+ puts "job #{job.id} finished in #{Time.now-t} seconds."
1179
+ end
1180
+ ```
1181
+
1182
+ The around callback is executed in the thread.
1104
1183
 
1105
1184
  ### Rufus::Scheduler#on_pre_trigger as a guard
1106
1185
 
1107
1186
  Returning ```false``` in on_pre_trigger will prevent the job from triggering. Returning anything else (nil, -1, true, ...) will let the job trigger.
1108
1187
 
1109
- 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.
1188
+ 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.
1110
1189
 
1111
1190
  ```ruby
1112
1191
  def s.on_pre_trigger(job, trigger_time)
@@ -1144,7 +1223,7 @@ This feature only works on OSes that support the flock (man 2 flock) call.
1144
1223
 
1145
1224
  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.
1146
1225
 
1147
- The idea is to guarantee only one scheduler (in a group of scheduler sharing the same lockfile) is running.
1226
+ The idea is to guarantee only one scheduler (in a group of schedulers sharing the same lockfile) is running.
1148
1227
 
1149
1228
  This is useful in environments where the Ruby process holding the scheduler gets started multiple times.
1150
1229
 
@@ -1221,7 +1300,7 @@ scheduler.max_work_threads += 10
1221
1300
  ## Rufus::Scheduler.singleton
1222
1301
 
1223
1302
  Do not want to store a reference to your rufus-scheduler instance?
1224
- Then ```Rufus::Scheduler.singleton``` can help, it returns a singleon instance of the scheduler, initialized the first time this class method is called.
1303
+ Then ```Rufus::Scheduler.singleton``` can help, it returns a singleton instance of the scheduler, initialized the first time this class method is called.
1225
1304
 
1226
1305
  ```ruby
1227
1306
  Rufus::Scheduler.singleton.every '10s' { puts "hello, world!" }
@@ -1243,11 +1322,11 @@ Rufus::Scheduler.s.every '10s' { puts "hello, world!" }
1243
1322
 
1244
1323
  ## advanced lock schemes
1245
1324
 
1246
- 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.
1325
+ 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.
1247
1326
 
1248
- There are situation where this is not sufficient.
1327
+ There are situations where this is not sufficient.
1249
1328
 
1250
- By overriding #lock and #unlock, one can customize how his schedulers lock.
1329
+ By overriding #lock and #unlock, one can customize how schedulers lock.
1251
1330
 
1252
1331
  This example was provided by [Eric Lindvall](https://github.com/eric):
1253
1332
 
@@ -1280,7 +1359,7 @@ class ZookeptScheduler < Rufus::Scheduler
1280
1359
  end
1281
1360
  ```
1282
1361
 
1283
- This uses a [zookeeper](http://zookeeper.apache.org/) to make sure only one scheduler in a group of distributed schedulers runs.
1362
+ This uses a [zookeeper](https://zookeeper.apache.org/) to make sure only one scheduler in a group of distributed schedulers runs.
1284
1363
 
1285
1364
  The methods #lock and #unlock are overridden and #confirm_lock is provided,
1286
1365
  to make sure that the lock is still valid.
@@ -1304,7 +1383,9 @@ Warning: you may think you're heading towards "high availability" by using a tri
1304
1383
 
1305
1384
  ## parsing cronlines and time strings
1306
1385
 
1307
- 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).
1386
+ (Please note that [fugit](https://github.com/floraison/fugit) does the heavy-lifting parsing work for rufus-scheduler).
1387
+
1388
+ 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).
1308
1389
 
1309
1390
  ```ruby
1310
1391
  require 'rufus-scheduler'
@@ -1324,13 +1405,13 @@ Rufus::Scheduler.parse(0.1)
1324
1405
  # => 0.1
1325
1406
 
1326
1407
  Rufus::Scheduler.parse('* * * * *')
1327
- # => #<Rufus::Scheduler::CronLine:0x00000002be5198
1328
- # @original="* * * * *", @timezone=nil,
1329
- # @seconds=[0], @minutes=nil, @hours=nil, @days=nil, @months=nil,
1330
- # @weekdays=nil, @monthdays=nil>
1408
+ # => #<Fugit::Cron:0x00007fb7a3045508
1409
+ # @original="* * * * *", @cron_s=nil,
1410
+ # @seconds=[0], @minutes=nil, @hours=nil, @monthdays=nil, @months=nil,
1411
+ # @weekdays=nil, @zone=nil, @timezone=nil>
1331
1412
  ```
1332
1413
 
1333
- It returns a number when the output is a duration and a CronLine instance when the input is a cron string.
1414
+ It returns a number when the input is a duration and a Fugit::Cron instance when the input is a cron string.
1334
1415
 
1335
1416
  It will raise an ArgumentError if it can't parse the input.
1336
1417
 
@@ -1383,7 +1464,7 @@ require 'rufus-scheduler'
1383
1464
 
1384
1465
  Time.now
1385
1466
  # => 2013-10-26 07:07:08 +0900
1386
- Rufus::Scheduler.parse('* * * * mon#1').next_time
1467
+ Rufus::Scheduler.parse('* * * * mon#1').next_time.to_s
1387
1468
  # => 2013-11-04 00:00:00 +0900
1388
1469
  ```
1389
1470
 
@@ -1396,7 +1477,7 @@ In this example, the cronline is supposed to trigger every last day of the month
1396
1477
  require 'rufus-scheduler'
1397
1478
  Time.now
1398
1479
  # => 2013-10-26 07:22:09 +0900
1399
- Rufus::Scheduler.parse('00 12 L * *').next_time
1480
+ Rufus::Scheduler.parse('00 12 L * *').next_time.to_s
1400
1481
  # => 2013-10-31 12:00:00 +0900
1401
1482
  ```
1402
1483
 
@@ -1449,7 +1530,7 @@ rufus-scheduler/lib/rufus/scheduler/zotime.rb:41:
1449
1530
  ...
1450
1531
  ```
1451
1532
 
1452
- It may happen on Windows or on systems that poorly hints to Ruby on which timezone to use. It should be solved by setting explicitly the `ENV['TZ']` before the scheduler instantiation:
1533
+ 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:
1453
1534
  ```ruby
1454
1535
  ENV['TZ'] = 'Asia/Shanghai'
1455
1536
  scheduler = Rufus::Scheduler.new
@@ -1540,9 +1621,9 @@ The rufus-scheduler singleton is instantiated in the ```config/initializers/sche
1540
1621
 
1541
1622
  ### avoid scheduling when running the Ruby on Rails console
1542
1623
 
1543
- (Written in reply to https://github.com/jmettraux/rufus-scheduler/issues/186 )
1624
+ (Written in reply to [gh-186](https://github.com/jmettraux/rufus-scheduler/issues/186))
1544
1625
 
1545
- If you don't want rufus-scheduler to kick in when running the Ruby on Rails console or invoking a rake task, you can wrap your initializer in a conditional:
1626
+ 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:
1546
1627
 
1547
1628
  ```ruby
1548
1629
  #
@@ -1552,21 +1633,19 @@ require 'rufus-scheduler'
1552
1633
 
1553
1634
  s = Rufus::Scheduler.singleton
1554
1635
 
1636
+ return if defined?(Rails::Console) || Rails.env.test? || File.split($0).last == 'rake'
1637
+ # return if $PROGRAM_NAME.include?('spring')
1638
+ # see https://github.com/jmettraux/rufus-scheduler/issues/186
1555
1639
 
1556
- unless defined?(Rails::Console) || File.split($0).last == 'rake'
1557
-
1558
- # only schedule when not running from the Ruby on Rails console
1559
- # or from a rake task
1640
+ # do not schedule when Rails is run from its console, for a test/spec, or
1641
+ # from a Rake task
1560
1642
 
1561
- s.every '1m' do
1562
-
1563
- Rails.logger.info "hello, it's #{Time.now}"
1564
- Rails.logger.flush
1565
- end
1643
+ s.every '1m' do
1644
+ Rails.logger.info "hello, it's #{Time.now}"
1645
+ Rails.logger.flush
1566
1646
  end
1567
1647
  ```
1568
1648
 
1569
- It should work for Ruby on Rails 3 and 4.
1570
1649
 
1571
1650
  ### rails server -d
1572
1651
 
@@ -1578,8 +1657,8 @@ I avoid running `-d` in development mode and bother about daemonizing only for p
1578
1657
 
1579
1658
  These are two well crafted articles on process daemonization, please read them:
1580
1659
 
1581
- * http://www.mikeperham.com/2014/09/22/dont-daemonize-your-daemons/
1582
- * http://www.mikeperham.com/2014/07/07/use-runit/
1660
+ * https://www.mikeperham.com/2014/09/22/dont-daemonize-your-daemons/
1661
+ * https://www.mikeperham.com/2014/07/07/use-runit/
1583
1662
 
1584
1663
  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).
1585
1664