rufus-scheduler 3.0.6 → 3.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +9 -0
- data/CREDITS.txt +3 -0
- data/README.md +104 -2
- data/Rakefile +0 -1
- data/lib/rufus/scheduler.rb +54 -1
- data/lib/rufus/scheduler/cronline.rb +4 -1
- data/lib/rufus/scheduler/jobs.rb +56 -1
- data/spec/cronline_spec.rb +96 -0
- data/spec/error_spec.rb +23 -0
- data/spec/job_every_spec.rb +19 -0
- data/spec/job_repeat_spec.rb +19 -0
- data/spec/job_spec.rb +53 -0
- data/spec/scheduler_spec.rb +76 -1
- data/spec/spec_helper.rb +12 -0
- metadata +9 -4
- data/out.txt +0 -12
data/CHANGELOG.txt
CHANGED
@@ -2,6 +2,15 @@
|
|
2
2
|
= rufus-scheduler CHANGELOG.txt
|
3
3
|
|
4
4
|
|
5
|
+
== rufus-scheduler - 3.0.7 released 2014/03/18
|
6
|
+
|
7
|
+
- implement Scheduler #occurrences and #timeline, inspired by kreynolds
|
8
|
+
- implement Job #last_work_time and #mean_work_time
|
9
|
+
- implement Job#count
|
10
|
+
- add more info to the stderr error output (scheduler/tz info)
|
11
|
+
- prevent skipping a day on swith to summertime, gh-114, thanks Matteo
|
12
|
+
|
13
|
+
|
5
14
|
== rufus-scheduler - 3.0.6 released 2014/02/14
|
6
15
|
|
7
16
|
- avoid "can't be called from trap context" on Ruby 2.0, gh-98
|
data/CREDITS.txt
CHANGED
@@ -30,6 +30,9 @@
|
|
30
30
|
|
31
31
|
== Feedback
|
32
32
|
|
33
|
+
- kreynolds (tossrock) - inspiration for #occurrences
|
34
|
+
- Matteo - https://github.com/m4ce - dst and cron issue
|
35
|
+
- Tobias Bielohlawek - https://github.com/rngtng - missing assertion
|
33
36
|
- Joe Taylor and Agis Anastasopoulos -
|
34
37
|
http://stackoverflow.com/questions/21280870 - :first => :now and Job#call
|
35
38
|
- Gatis Tomsons - https://github.io/gacha - heavy work threads and lock errors
|
data/README.md
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# rufus-scheduler
|
3
3
|
|
4
4
|
[](http://travis-ci.org/jmettraux/rufus-scheduler)
|
5
|
+
[](http://badge.fury.io/rb/rufus-scheduler)
|
5
6
|
|
6
7
|
Job scheduler for Ruby (at, cron, in and every jobs).
|
7
8
|
|
@@ -113,6 +114,8 @@ Yes, issues can be reported in [rufus-scheduler issues](https://github.com/jmett
|
|
113
114
|
* [It doesn't work...](http://www.chiark.greenend.org.uk/~sgtatham/bugs.html)
|
114
115
|
* [I want a refund](http://blog.nodejitsu.com/getting-refunds-on-open-source-projects)
|
115
116
|
* [Passenger and rufus-scheduler](http://stackoverflow.com/questions/18108719/debugging-rufus-scheduler/18156180#18156180)
|
117
|
+
* [Passenger and rufus-scheduler (2)](http://stackoverflow.com/questions/21861387/rufus-cron-job-not-working-in-apache-passenger#answer-21868555)
|
118
|
+
* [Unicorn and rufus-scheduler](https://jkraemer.net/running-rufus-scheduler-in-a-unicorn-rails-app)
|
116
119
|
|
117
120
|
|
118
121
|
## scheduling
|
@@ -249,6 +252,25 @@ The second argument is "time", it's the time when the job got cleared for trigge
|
|
249
252
|
|
250
253
|
Note that time is the time when the job got cleared for triggering. If there are mutexes involved, now = mutex_wait_time + time...
|
251
254
|
|
255
|
+
#### "every" jobs and changing the next_time in-flight
|
256
|
+
|
257
|
+
It's OK to change the next_time of an every job in-flight:
|
258
|
+
|
259
|
+
```ruby
|
260
|
+
scheduler.every '10m' do |job|
|
261
|
+
|
262
|
+
# ...
|
263
|
+
|
264
|
+
status = determine_pie_status
|
265
|
+
|
266
|
+
job.next_time = Time.now + 30 * 60 if status == 'burnt'
|
267
|
+
#
|
268
|
+
# if burnt, wait 30 minutes for the oven to cool a bit
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
It should work as well with cron jobs, not so with interval jobs whose next_time is computed after their block ends its current run.
|
273
|
+
|
252
274
|
### scheduling handler instances
|
253
275
|
|
254
276
|
It's OK to pass any object, as long as it respond to #call(), when scheduling:
|
@@ -591,7 +613,6 @@ job.callable
|
|
591
613
|
# => #<MyHandler:0x0000000163ae88 @counter=0>
|
592
614
|
```
|
593
615
|
|
594
|
-
|
595
616
|
### scheduled_at
|
596
617
|
|
597
618
|
Returns the Time instance when the job got created.
|
@@ -613,6 +634,12 @@ job.scheduled_at
|
|
613
634
|
# => 2013-07-17 23:48:54 +0900
|
614
635
|
```
|
615
636
|
|
637
|
+
### last_work_time, mean_work_time
|
638
|
+
|
639
|
+
The job keeps track of how long its work was in the `last_work_time` attribute. For a one time job (in, at) it's probably not very useful.
|
640
|
+
|
641
|
+
The attribute `mean_work_time` contains a computed mean work time. It's recomputed after every run (if it's a repeat job).
|
642
|
+
|
616
643
|
### unschedule
|
617
644
|
|
618
645
|
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.
|
@@ -753,6 +780,10 @@ An alias to time.
|
|
753
780
|
|
754
781
|
Returns the next time the job will trigger (hopefully).
|
755
782
|
|
783
|
+
### count
|
784
|
+
|
785
|
+
Returns how many times the job fired.
|
786
|
+
|
756
787
|
## EveryJob methods
|
757
788
|
|
758
789
|
### frequency
|
@@ -878,6 +909,16 @@ Kills all the job (threads) and then shuts the scheduler down. Radical.
|
|
878
909
|
|
879
910
|
Returns true if the scheduler has been shut down.
|
880
911
|
|
912
|
+
### Scheduler#started_at
|
913
|
+
|
914
|
+
Returns the Time instance at which the scheduler got started.
|
915
|
+
|
916
|
+
### Scheduler #uptime / #uptime_s
|
917
|
+
|
918
|
+
Returns since the count of seconds for which the scheduler has been running.
|
919
|
+
|
920
|
+
```#uptime_s``` returns this count in a String easier to grasp for humans, like ```"3d12m45s123"```.
|
921
|
+
|
881
922
|
### Scheduler#join
|
882
923
|
|
883
924
|
Let's the current thread join the scheduling thread in rufus-scheduler. The thread comes back when the scheduler gets shut down.
|
@@ -900,6 +941,16 @@ Note that the main schedule thread will be returned if it is currently running a
|
|
900
941
|
|
901
942
|
Returns true if the arg is a currently scheduled job (see Job#scheduled?).
|
902
943
|
|
944
|
+
### Scheduler#occurrences(time0, time1)
|
945
|
+
|
946
|
+
Returns a hash ```{ job => [ t0, t1, ... ] }``` mapping jobs to their potential trigger time within the ```[ time0, time1 ]``` span.
|
947
|
+
|
948
|
+
Please note that, for interval jobs, the ```#mean_work_time``` is used, so the result is only a prediction.
|
949
|
+
|
950
|
+
### Scheduler#timeline(time0, time1)
|
951
|
+
|
952
|
+
Like `#occurrences` but returns a list ```[ [ t0, job0 ], [ t1, job1 ], ... ]``` of time + job pairs.
|
953
|
+
|
903
954
|
|
904
955
|
## dealing with job errors
|
905
956
|
|
@@ -1044,7 +1095,7 @@ If the lockfile mechanism here is not sufficient, you can plug your custom mecha
|
|
1044
1095
|
|
1045
1096
|
### :max_work_threads
|
1046
1097
|
|
1047
|
-
In rufus-scheduler 2.x, by default, each job triggering received its own, new,
|
1098
|
+
In rufus-scheduler 2.x, by default, each job triggering received its own, brand new, thread of execution. In rufus-scheduler 3.x, execution happens in a pooled work thread. The max work thread count (the pool size) defaults to 28.
|
1048
1099
|
|
1049
1100
|
One can set this maximum value when starting the scheduler.
|
1050
1101
|
|
@@ -1262,6 +1313,57 @@ Unknown timezones, typos, will be rejected by tzinfo thus rufus-scheduler.
|
|
1262
1313
|
On its own tzinfo derives the timezones from the system's information. On some system it needs some help, one can install the 'tzinfo-data' gem to provide the missing information.
|
1263
1314
|
|
1264
1315
|
|
1316
|
+
## so Rails?
|
1317
|
+
|
1318
|
+
Yes, I know, all of the above is boring and you're only looking for a snippet to paste in your Ruby-on-Rails application to schedule...
|
1319
|
+
|
1320
|
+
Here is an example initializer:
|
1321
|
+
|
1322
|
+
```ruby
|
1323
|
+
#
|
1324
|
+
# config/initializers/scheduler.rb
|
1325
|
+
|
1326
|
+
require 'rufus-scheduler'
|
1327
|
+
|
1328
|
+
# Let's use the rufus-scheduler singleton
|
1329
|
+
#
|
1330
|
+
s = Rufus::Scheduler.singleton
|
1331
|
+
|
1332
|
+
|
1333
|
+
# Stupid recurrent task...
|
1334
|
+
#
|
1335
|
+
s.every '1m' do
|
1336
|
+
|
1337
|
+
Rails.logger.info "hello, it's #{Time.now}"
|
1338
|
+
end
|
1339
|
+
```
|
1340
|
+
|
1341
|
+
And now you tell me that this is good, but you want to schedule stuff from your controller.
|
1342
|
+
|
1343
|
+
Maybe:
|
1344
|
+
|
1345
|
+
```ruby
|
1346
|
+
class ScheController < ApplicationController
|
1347
|
+
|
1348
|
+
# GET /sche/
|
1349
|
+
#
|
1350
|
+
def index
|
1351
|
+
|
1352
|
+
job_id =
|
1353
|
+
Rufus::Scheduler.singleton.in '5s' do
|
1354
|
+
Rails.logger.info "time flies, it's now #{Time.now}"
|
1355
|
+
end
|
1356
|
+
|
1357
|
+
render :text => "scheduled job #{job_id}"
|
1358
|
+
end
|
1359
|
+
end
|
1360
|
+
```
|
1361
|
+
|
1362
|
+
The rufus-scheduler singleton is instantiated in the ```config/initializers/scheduler.rb``` file, it's then available throughout the webapp via ```Rufus::Scheduler.singleton```.
|
1363
|
+
|
1364
|
+
*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.
|
1365
|
+
|
1366
|
+
|
1265
1367
|
## support
|
1266
1368
|
|
1267
1369
|
see [getting help](#getting-help) above.
|
data/Rakefile
CHANGED
data/lib/rufus/scheduler.rb
CHANGED
@@ -38,7 +38,7 @@ module Rufus
|
|
38
38
|
require 'rufus/scheduler/cronline'
|
39
39
|
require 'rufus/scheduler/job_array'
|
40
40
|
|
41
|
-
VERSION = '3.0.
|
41
|
+
VERSION = '3.0.7'
|
42
42
|
|
43
43
|
#
|
44
44
|
# A common error class for rufus-scheduler
|
@@ -370,13 +370,39 @@ module Rufus
|
|
370
370
|
jobs(opts.merge(:running => true))
|
371
371
|
end
|
372
372
|
|
373
|
+
def occurrences(time0, time1, format=:per_job)
|
374
|
+
|
375
|
+
h = {}
|
376
|
+
|
377
|
+
jobs.each do |j|
|
378
|
+
os = j.occurrences(time0, time1)
|
379
|
+
h[j] = os if os.any?
|
380
|
+
end
|
381
|
+
|
382
|
+
if format == :timeline
|
383
|
+
a = []
|
384
|
+
h.each { |j, ts| ts.each { |t| a << [ t, j ] } }
|
385
|
+
a.sort_by { |(t, j)| t }
|
386
|
+
else
|
387
|
+
h
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
def timeline(time0, time1)
|
392
|
+
|
393
|
+
occurrences(time0, time1, :timeline)
|
394
|
+
end
|
395
|
+
|
373
396
|
def on_error(job, err)
|
374
397
|
|
375
398
|
pre = err.object_id.to_s
|
376
399
|
|
400
|
+
ms = {}; mutexes.each { |k, v| ms[k] = v.locked? }
|
401
|
+
|
377
402
|
stderr.puts("{ #{pre} rufus-scheduler intercepted an error:")
|
378
403
|
stderr.puts(" #{pre} job:")
|
379
404
|
stderr.puts(" #{pre} #{job.class} #{job.original.inspect} #{job.opts.inspect}")
|
405
|
+
# TODO: eventually use a Job#detail or something like that
|
380
406
|
stderr.puts(" #{pre} error:")
|
381
407
|
stderr.puts(" #{pre} #{err.object_id}")
|
382
408
|
stderr.puts(" #{pre} #{err.class}")
|
@@ -384,6 +410,33 @@ module Rufus
|
|
384
410
|
err.backtrace.each do |l|
|
385
411
|
stderr.puts(" #{pre} #{l}")
|
386
412
|
end
|
413
|
+
stderr.puts(" #{pre} tz:")
|
414
|
+
stderr.puts(" #{pre} ENV['TZ']: #{ENV['TZ']}")
|
415
|
+
stderr.puts(" #{pre} Time.now: #{Time.now}")
|
416
|
+
stderr.puts(" #{pre} scheduler:")
|
417
|
+
stderr.puts(" #{pre} object_id: #{object_id}")
|
418
|
+
stderr.puts(" #{pre} opts:")
|
419
|
+
stderr.puts(" #{pre} #{@opts.inspect}")
|
420
|
+
stderr.puts(" #{pre} frequency: #{self.frequency}")
|
421
|
+
stderr.puts(" #{pre} lockfile: #{@lockfile.inspect}")
|
422
|
+
stderr.puts(" #{pre} uptime: #{uptime} (#{uptime_s})")
|
423
|
+
stderr.puts(" #{pre} down?: #{down?}")
|
424
|
+
stderr.puts(" #{pre} threads: #{self.threads.size}")
|
425
|
+
stderr.puts(" #{pre} thread: #{self.thread}")
|
426
|
+
stderr.puts(" #{pre} thread_key: #{self.thread_key}")
|
427
|
+
stderr.puts(" #{pre} work_threads: #{work_threads.size}")
|
428
|
+
stderr.puts(" #{pre} active: #{work_threads(:active).size}")
|
429
|
+
stderr.puts(" #{pre} vacant: #{work_threads(:vacant).size}")
|
430
|
+
stderr.puts(" #{pre} max_work_threads: #{max_work_threads}")
|
431
|
+
stderr.puts(" #{pre} mutexes: #{ms.inspect}")
|
432
|
+
stderr.puts(" #{pre} jobs: #{jobs.size}")
|
433
|
+
stderr.puts(" #{pre} at_jobs: #{at_jobs.size}")
|
434
|
+
stderr.puts(" #{pre} in_jobs: #{in_jobs.size}")
|
435
|
+
stderr.puts(" #{pre} every_jobs: #{every_jobs.size}")
|
436
|
+
stderr.puts(" #{pre} interval_jobs: #{interval_jobs.size}")
|
437
|
+
stderr.puts(" #{pre} cron_jobs: #{cron_jobs.size}")
|
438
|
+
stderr.puts(" #{pre} running_jobs: #{running_jobs.size}")
|
439
|
+
stderr.puts(" #{pre} work_queue: #{work_queue.size}")
|
387
440
|
stderr.puts("} #{pre} .")
|
388
441
|
|
389
442
|
rescue => e
|
@@ -130,7 +130,10 @@ class Rufus::Scheduler
|
|
130
130
|
|
131
131
|
loop do
|
132
132
|
unless date_match?(time)
|
133
|
-
|
133
|
+
dst = time.isdst
|
134
|
+
time += (24 - time.hour) * 3600 - time.min * 60 - time.sec
|
135
|
+
time -= 3600 if time.isdst != dst # not necessary for winter, but...
|
136
|
+
next
|
134
137
|
end
|
135
138
|
unless sub_match?(time, :hour, @hours)
|
136
139
|
time += (60 - time.min) * 60 - time.sec; next
|
data/lib/rufus/scheduler/jobs.rb
CHANGED
@@ -45,6 +45,9 @@ module Rufus
|
|
45
45
|
attr_reader :last_time
|
46
46
|
attr_reader :unscheduled_at
|
47
47
|
attr_reader :tags
|
48
|
+
attr_reader :count
|
49
|
+
attr_reader :last_work_time
|
50
|
+
attr_reader :mean_work_time
|
48
51
|
|
49
52
|
# next trigger time
|
50
53
|
#
|
@@ -96,6 +99,10 @@ module Rufus
|
|
96
99
|
|
97
100
|
@tags = Array(opts[:tag] || opts[:tags]).collect { |t| t.to_s }
|
98
101
|
|
102
|
+
@count = 0
|
103
|
+
@last_work_time = 0.0
|
104
|
+
@mean_work_time = 0.0
|
105
|
+
|
99
106
|
# tidy up options
|
100
107
|
|
101
108
|
if @opts[:allow_overlap] == false || @opts[:allow_overlapping] == false
|
@@ -120,6 +127,8 @@ module Rufus
|
|
120
127
|
|
121
128
|
return if r == false
|
122
129
|
|
130
|
+
@count += 1
|
131
|
+
|
123
132
|
if opts[:blocking]
|
124
133
|
do_trigger(time)
|
125
134
|
else
|
@@ -249,6 +258,11 @@ module Rufus
|
|
249
258
|
|
250
259
|
ensure
|
251
260
|
|
261
|
+
@last_work_time =
|
262
|
+
Time.now - Thread.current[:rufus_scheduler_time]
|
263
|
+
@mean_work_time =
|
264
|
+
((@count - 1) * @mean_work_time + @last_work_time) / @count
|
265
|
+
|
252
266
|
post_trigger(time)
|
253
267
|
|
254
268
|
Thread.current[:rufus_scheduler_job] = nil
|
@@ -323,6 +337,11 @@ module Rufus
|
|
323
337
|
|
324
338
|
alias time next_time
|
325
339
|
|
340
|
+
def occurrences(time0, time1)
|
341
|
+
|
342
|
+
time >= time0 && time <= time1 ? [ time ] : []
|
343
|
+
end
|
344
|
+
|
326
345
|
protected
|
327
346
|
|
328
347
|
def determine_id
|
@@ -428,7 +447,7 @@ module Rufus
|
|
428
447
|
|
429
448
|
super
|
430
449
|
|
431
|
-
@times
|
450
|
+
@times -= 1 if @times
|
432
451
|
end
|
433
452
|
|
434
453
|
def pause
|
@@ -454,6 +473,27 @@ module Rufus
|
|
454
473
|
opts.hash.abs
|
455
474
|
].map(&:to_s).join('_')
|
456
475
|
end
|
476
|
+
|
477
|
+
def occurrences(time0, time1)
|
478
|
+
|
479
|
+
a = []
|
480
|
+
|
481
|
+
nt = @next_time
|
482
|
+
ts = @times
|
483
|
+
|
484
|
+
loop do
|
485
|
+
|
486
|
+
break if nt > time1
|
487
|
+
break if ts && ts <= 0
|
488
|
+
|
489
|
+
a << nt if nt >= time0
|
490
|
+
|
491
|
+
nt = next_time_from(nt)
|
492
|
+
ts = ts - 1 if ts
|
493
|
+
end
|
494
|
+
|
495
|
+
a
|
496
|
+
end
|
457
497
|
end
|
458
498
|
|
459
499
|
#
|
@@ -502,6 +542,11 @@ module Rufus
|
|
502
542
|
@first_at
|
503
543
|
end
|
504
544
|
end
|
545
|
+
|
546
|
+
def next_time_from(time)
|
547
|
+
|
548
|
+
time + @frequency
|
549
|
+
end
|
505
550
|
end
|
506
551
|
|
507
552
|
class IntervalJob < EvInJob
|
@@ -539,6 +584,11 @@ module Rufus
|
|
539
584
|
false
|
540
585
|
end
|
541
586
|
end
|
587
|
+
|
588
|
+
def next_time_from(time)
|
589
|
+
|
590
|
+
time + @mean_work_time + @interval
|
591
|
+
end
|
542
592
|
end
|
543
593
|
|
544
594
|
class CronJob < RepeatJob
|
@@ -567,6 +617,11 @@ module Rufus
|
|
567
617
|
|
568
618
|
@next_time = @cron_line.next_time
|
569
619
|
end
|
620
|
+
|
621
|
+
def next_time_from(time)
|
622
|
+
|
623
|
+
@cron_line.next_time(time)
|
624
|
+
end
|
570
625
|
end
|
571
626
|
end
|
572
627
|
end
|
data/spec/cronline_spec.rb
CHANGED
@@ -116,6 +116,13 @@ describe Rufus::Scheduler::CronLine do
|
|
116
116
|
[ [0], [ 0, 10, 20, 30, 40, 50], nil, nil, nil, nil, nil, nil ])
|
117
117
|
end
|
118
118
|
|
119
|
+
it 'rejects / for days (every other wednesday)' do
|
120
|
+
|
121
|
+
lambda {
|
122
|
+
Rufus::Scheduler::CronLine.new('* * * * wed/2')
|
123
|
+
}.should raise_error(ArgumentError)
|
124
|
+
end
|
125
|
+
|
119
126
|
it 'does not support ranges for monthdays (sun#1-sun#2)' do
|
120
127
|
|
121
128
|
lambda {
|
@@ -476,5 +483,94 @@ describe Rufus::Scheduler::CronLine do
|
|
476
483
|
# takes > 20s ...
|
477
484
|
end
|
478
485
|
end
|
486
|
+
|
487
|
+
context 'summer time' do
|
488
|
+
|
489
|
+
# let's assume summer time jumps always occur on sundays
|
490
|
+
|
491
|
+
# cf gh-114
|
492
|
+
#
|
493
|
+
it 'schedules correctly through a switch into summer time' do
|
494
|
+
|
495
|
+
#j = `zdump -v Europe/Berlin | grep "Sun Mar" | grep 2014`.split("\n")[0]
|
496
|
+
#j = j.match(/^.+ (Sun Mar .+ UTC) /)[1]
|
497
|
+
# only works on system that have a zdump...
|
498
|
+
|
499
|
+
in_zone 'Europe/Berlin' do
|
500
|
+
|
501
|
+
# find the summer jump
|
502
|
+
|
503
|
+
j = Time.parse('2014-02-28 12:00')
|
504
|
+
loop do
|
505
|
+
jj = j + 24 * 3600
|
506
|
+
break if jj.isdst
|
507
|
+
j = jj
|
508
|
+
end
|
509
|
+
|
510
|
+
# test
|
511
|
+
|
512
|
+
friday = j - 24 * 3600 # one day before
|
513
|
+
|
514
|
+
# verify the playground...
|
515
|
+
#
|
516
|
+
friday.isdst.should == false
|
517
|
+
(friday + 24 * 3600 * 3).isdst.should == true
|
518
|
+
|
519
|
+
cl0 = Rufus::Scheduler::CronLine.new('02 00 * * 1,2,3,4,5')
|
520
|
+
cl1 = Rufus::Scheduler::CronLine.new('45 08 * * 1,2,3,4,5')
|
521
|
+
|
522
|
+
n0 = cl0.next_time(friday)
|
523
|
+
n1 = cl1.next_time(friday)
|
524
|
+
|
525
|
+
n0.strftime('%H:%M:%S %^a').should == '00:02:00 MON'
|
526
|
+
n1.strftime('%H:%M:%S %^a').should == '08:45:00 MON'
|
527
|
+
|
528
|
+
n0.isdst.should == true
|
529
|
+
n1.isdst.should == true
|
530
|
+
|
531
|
+
(n0 - 24 * 3600 * 3).strftime('%H:%M:%S %^a').should == '23:02:00 THU'
|
532
|
+
(n1 - 24 * 3600 * 3).strftime('%H:%M:%S %^a').should == '07:45:00 FRI'
|
533
|
+
end
|
534
|
+
end
|
535
|
+
|
536
|
+
it 'schedules correctly through a switch out of summer time' do
|
537
|
+
|
538
|
+
in_zone 'Europe/Berlin' do
|
539
|
+
|
540
|
+
# find the winter jump
|
541
|
+
|
542
|
+
j = Time.parse('2014-08-31 12:00')
|
543
|
+
loop do
|
544
|
+
jj = j + 24 * 3600
|
545
|
+
break if jj.isdst == false
|
546
|
+
j = jj
|
547
|
+
end
|
548
|
+
|
549
|
+
# test
|
550
|
+
|
551
|
+
friday = j - 24 * 3600 # one day before
|
552
|
+
|
553
|
+
# verify the playground...
|
554
|
+
#
|
555
|
+
friday.isdst.should == true
|
556
|
+
(friday + 24 * 3600 * 3).isdst.should == false
|
557
|
+
|
558
|
+
cl0 = Rufus::Scheduler::CronLine.new('02 00 * * 1,2,3,4,5')
|
559
|
+
cl1 = Rufus::Scheduler::CronLine.new('45 08 * * 1,2,3,4,5')
|
560
|
+
|
561
|
+
n0 = cl0.next_time(friday)
|
562
|
+
n1 = cl1.next_time(friday)
|
563
|
+
|
564
|
+
n0.strftime('%H:%M:%S %^a').should == '00:02:00 MON'
|
565
|
+
n1.strftime('%H:%M:%S %^a').should == '08:45:00 MON'
|
566
|
+
|
567
|
+
n0.isdst.should == false
|
568
|
+
n1.isdst.should == false
|
569
|
+
|
570
|
+
(n0 - 24 * 3600 * 3).strftime('%H:%M:%S %^a').should == '01:02:00 FRI'
|
571
|
+
(n1 - 24 * 3600 * 3).strftime('%H:%M:%S %^a').should == '09:45:00 FRI'
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
479
575
|
end
|
480
576
|
|
data/spec/error_spec.rb
CHANGED
@@ -91,6 +91,29 @@ describe Rufus::Scheduler do
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
context 'error information' do
|
95
|
+
|
96
|
+
it 'contains information about the error, the job and the scheduler' do
|
97
|
+
|
98
|
+
@scheduler.stderr = StringIO.new
|
99
|
+
|
100
|
+
@scheduler.in('0s') do
|
101
|
+
fail 'miserably'
|
102
|
+
end
|
103
|
+
|
104
|
+
sleep 0.5
|
105
|
+
|
106
|
+
s = @scheduler.stderr.string
|
107
|
+
#puts s
|
108
|
+
|
109
|
+
s.should match(/ENV\['TZ'\]:/)
|
110
|
+
s.should match(/down\?: false/)
|
111
|
+
s.should match(/work_threads: 1/)
|
112
|
+
s.should match(/running_jobs: 1/)
|
113
|
+
s.should match(/uptime: \d/)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
94
117
|
context 'Rufus::Scheduler#on_error(&block)' do
|
95
118
|
|
96
119
|
it 'intercepts all StandardError instances' do
|
data/spec/job_every_spec.rb
CHANGED
@@ -30,6 +30,25 @@ describe Rufus::Scheduler::EveryJob do
|
|
30
30
|
[ 2, 3 ].should include(counter)
|
31
31
|
end
|
32
32
|
|
33
|
+
it 'lets its @next_time change in-flight' do
|
34
|
+
|
35
|
+
times = []
|
36
|
+
|
37
|
+
@scheduler.every '1s' do |job|
|
38
|
+
times << Time.now
|
39
|
+
job.next_time = Time.now + 3 if times.count == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
sleep 0.3 while times.count < 3
|
43
|
+
|
44
|
+
#p [ times[1] - times[0], times[2] - times[1] ]
|
45
|
+
|
46
|
+
(times[1] - times[0]).should > 1.0
|
47
|
+
(times[1] - times[0]).should < 1.4
|
48
|
+
(times[2] - times[1]).should > 3.0
|
49
|
+
(times[2] - times[1]).should < 3.4
|
50
|
+
end
|
51
|
+
|
33
52
|
context 'first_at/in' do
|
34
53
|
|
35
54
|
it 'triggers for the first time at first_at' do
|
data/spec/job_repeat_spec.rb
CHANGED
@@ -332,5 +332,24 @@ describe Rufus::Scheduler::RepeatJob do
|
|
332
332
|
job.last_at.strftime('%c').should == 'Thu Dec 12 12:00:30 2030'
|
333
333
|
end
|
334
334
|
end
|
335
|
+
|
336
|
+
describe '#count' do
|
337
|
+
|
338
|
+
it 'starts at 0' do
|
339
|
+
|
340
|
+
job = @scheduler.schedule_every '5m' do; end
|
341
|
+
|
342
|
+
job.count.should == 0
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'keeps track of how many times the job fired' do
|
346
|
+
|
347
|
+
job = @scheduler.schedule_every '0.5s' do; end
|
348
|
+
|
349
|
+
sleep(2.0)
|
350
|
+
|
351
|
+
job.count.should == 3
|
352
|
+
end
|
353
|
+
end
|
335
354
|
end
|
336
355
|
|
data/spec/job_spec.rb
CHANGED
@@ -574,5 +574,58 @@ describe Rufus::Scheduler::Job do
|
|
574
574
|
counter.should > 1
|
575
575
|
end
|
576
576
|
end
|
577
|
+
|
578
|
+
context 'work time' do
|
579
|
+
|
580
|
+
describe '#last_work_time' do
|
581
|
+
|
582
|
+
it 'starts at 0' do
|
583
|
+
|
584
|
+
job = @scheduler.schedule_every '5m' do; end
|
585
|
+
|
586
|
+
job.last_work_time.should == 0.0
|
587
|
+
end
|
588
|
+
|
589
|
+
it 'keeps track of how long the work was upon last trigger' do
|
590
|
+
|
591
|
+
job =
|
592
|
+
@scheduler.schedule_in '0.5s' do
|
593
|
+
sleep 0.7
|
594
|
+
end
|
595
|
+
|
596
|
+
sleep 2
|
597
|
+
|
598
|
+
job.last_work_time.should >= 0.7
|
599
|
+
job.last_work_time.should < 0.8
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
describe '#mean_work_time' do
|
604
|
+
|
605
|
+
it 'starts at 0' do
|
606
|
+
|
607
|
+
job = @scheduler.schedule_every '5m' do; end
|
608
|
+
|
609
|
+
job.mean_work_time.should == 0.0
|
610
|
+
end
|
611
|
+
|
612
|
+
it 'gathers work times and computes the mean' do
|
613
|
+
|
614
|
+
job =
|
615
|
+
@scheduler.schedule_every '0.5s' do |j|
|
616
|
+
#p j.last_work_time
|
617
|
+
#p j.mean_work_time
|
618
|
+
sleep 0.01 * (j.count + 1)
|
619
|
+
end
|
620
|
+
|
621
|
+
sleep 4.6
|
622
|
+
|
623
|
+
job.last_work_time.should >= 0.08
|
624
|
+
job.last_work_time.should < 0.099
|
625
|
+
job.mean_work_time.should > 0.05
|
626
|
+
job.mean_work_time.should < 0.06
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
577
630
|
end
|
578
631
|
|
data/spec/scheduler_spec.rb
CHANGED
@@ -23,9 +23,14 @@ describe Rufus::Scheduler do
|
|
23
23
|
t[:rufus_scheduler].should == scheduler
|
24
24
|
end
|
25
25
|
|
26
|
-
it 'sets
|
26
|
+
it 'sets :name and :rufus_scheduler in the scheduler thread local vars' do
|
27
27
|
|
28
28
|
scheduler = Rufus::Scheduler.new
|
29
|
+
|
30
|
+
scheduler.thread[:name].should ==
|
31
|
+
"rufus_scheduler_#{scheduler.object_id}_scheduler"
|
32
|
+
scheduler.thread[:rufus_scheduler].should ==
|
33
|
+
scheduler
|
29
34
|
end
|
30
35
|
|
31
36
|
it 'accepts a :frequency => integer option' do
|
@@ -544,6 +549,76 @@ describe Rufus::Scheduler do
|
|
544
549
|
end
|
545
550
|
end
|
546
551
|
|
552
|
+
describe '#occurrences(time0, time1)' do
|
553
|
+
|
554
|
+
it 'returns a { job => [ times ] } of job occurrences' do
|
555
|
+
|
556
|
+
j0 = @scheduler.schedule_in '7m' do; end
|
557
|
+
j1 = @scheduler.schedule_at '10m' do; end
|
558
|
+
j2 = @scheduler.schedule_every '5m' do; end
|
559
|
+
j3 = @scheduler.schedule_interval '5m' do; end
|
560
|
+
j4 = @scheduler.schedule_cron '* * * * *' do; end
|
561
|
+
|
562
|
+
h = @scheduler.occurrences(Time.now + 4 * 60, Time.now + 11 * 60)
|
563
|
+
|
564
|
+
h.size.should == 5
|
565
|
+
h[j0].should == [ j0.next_time ]
|
566
|
+
h[j1].should == [ j1.next_time ]
|
567
|
+
h[j2].size.should == 2
|
568
|
+
h[j3].size.should == 2
|
569
|
+
h[j4].size.should == 7
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'returns a [ [ time, job ], ... ] of job occurrences when :timeline' do
|
573
|
+
|
574
|
+
j0 = @scheduler.schedule_in '5m' do; end
|
575
|
+
j1 = @scheduler.schedule_in '10m' do; end
|
576
|
+
|
577
|
+
a =
|
578
|
+
@scheduler.occurrences(Time.now + 4 * 60, Time.now + 11 * 60, :timeline)
|
579
|
+
|
580
|
+
a[0][0].should be_within_1s_of(Time.now + 5 * 60)
|
581
|
+
a[0][1].should == j0
|
582
|
+
a[1][0].should be_within_1s_of(Time.now + 10 * 60)
|
583
|
+
a[1][1].should == j1
|
584
|
+
end
|
585
|
+
|
586
|
+
it 'respects :first_at for repeat jobs' do
|
587
|
+
|
588
|
+
j0 = @scheduler.schedule_every '5m', :first_in => '10m' do; end
|
589
|
+
|
590
|
+
h = @scheduler.occurrences(Time.now + 4 * 60, Time.now + 16 * 60)
|
591
|
+
|
592
|
+
h[j0][0].should be_within_1s_of(Time.now + 10 * 60)
|
593
|
+
h[j0][1].should be_within_1s_of(Time.now + 15 * 60)
|
594
|
+
end
|
595
|
+
|
596
|
+
it 'respects :times for repeat jobs' do
|
597
|
+
|
598
|
+
j0 = @scheduler.schedule_every '1m', :times => 10 do; end
|
599
|
+
|
600
|
+
h = @scheduler.occurrences(Time.now + 4 * 60, Time.now + 16 * 60)
|
601
|
+
|
602
|
+
h[j0].size.should == 6
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
describe '#timeline(time0, time1)' do
|
607
|
+
|
608
|
+
it 'returns a [ [ time, job ], ... ] of job occurrences' do
|
609
|
+
|
610
|
+
j0 = @scheduler.schedule_in '5m' do; end
|
611
|
+
j1 = @scheduler.schedule_in '10m' do; end
|
612
|
+
|
613
|
+
a = @scheduler.timeline(Time.now + 4 * 60, Time.now + 11 * 60)
|
614
|
+
|
615
|
+
a[0][0].should be_within_1s_of(Time.now + 5 * 60)
|
616
|
+
a[0][1].should == j0
|
617
|
+
a[1][0].should be_within_1s_of(Time.now + 10 * 60)
|
618
|
+
a[1][1].should == j1
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
547
622
|
#--
|
548
623
|
# management methods
|
549
624
|
#++
|
data/spec/spec_helper.rb
CHANGED
@@ -35,6 +35,18 @@ def sleep_until_next_second
|
|
35
35
|
while Time.now.sec == sec; sleep 0.2; end
|
36
36
|
end
|
37
37
|
|
38
|
+
def in_zone(zone_name, &block)
|
39
|
+
|
40
|
+
prev_tz = ENV['TZ']
|
41
|
+
ENV['TZ'] = zone_name
|
42
|
+
|
43
|
+
block.call
|
44
|
+
|
45
|
+
ensure
|
46
|
+
|
47
|
+
ENV['TZ'] = prev_tz
|
48
|
+
end
|
49
|
+
|
38
50
|
|
39
51
|
#require 'rspec/expectations'
|
40
52
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rufus-scheduler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: tzinfo
|
@@ -98,13 +98,12 @@ files:
|
|
98
98
|
- CHANGELOG.txt
|
99
99
|
- TODO.txt
|
100
100
|
- LICENSE.txt
|
101
|
-
- out.txt
|
102
101
|
- CREDITS.txt
|
103
102
|
- README.md
|
104
103
|
homepage: http://github.com/jmettraux/rufus-scheduler
|
105
104
|
licenses:
|
106
105
|
- MIT
|
107
|
-
post_install_message: ! "\n***\n\nThanks for installing rufus-scheduler 3.0.
|
106
|
+
post_install_message: ! "\n***\n\nThanks for installing rufus-scheduler 3.0.7\n\nIt
|
108
107
|
might not be 100% compatible with rufus-scheduler 2.x.\n\nIf you encounter issues
|
109
108
|
with this new rufus-scheduler, especially\nif your app worked fine with previous
|
110
109
|
versions of it, you can\n\nA) Forget it and peg your Gemfile to rufus-scheduler
|
@@ -120,12 +119,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
119
|
- - ! '>='
|
121
120
|
- !ruby/object:Gem::Version
|
122
121
|
version: '0'
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
hash: -1141686396098710527
|
123
125
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
126
|
none: false
|
125
127
|
requirements:
|
126
128
|
- - ! '>='
|
127
129
|
- !ruby/object:Gem::Version
|
128
130
|
version: '0'
|
131
|
+
segments:
|
132
|
+
- 0
|
133
|
+
hash: -1141686396098710527
|
129
134
|
requirements: []
|
130
135
|
rubyforge_project: rufus
|
131
136
|
rubygems_version: 1.8.23
|
data/out.txt
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
4408
|
2
|
-
:hello
|
3
|
-
:hello
|
4
|
-
:hello
|
5
|
-
/home/jmettraux/w/rufus-scheduler/lib/rufus/scheduler/job_array.rb:74:in `synchronize': can't be called from trap context (ThreadError)
|
6
|
-
from /home/jmettraux/w/rufus-scheduler/lib/rufus/scheduler/job_array.rb:74:in `to_a'
|
7
|
-
from /home/jmettraux/w/rufus-scheduler/lib/rufus/scheduler.rb:276:in `jobs'
|
8
|
-
from /home/jmettraux/w/rufus-scheduler/lib/rufus/scheduler.rb:127:in `shutdown'
|
9
|
-
from t.rb:8:in `block in <main>'
|
10
|
-
from t.rb:18:in `call'
|
11
|
-
from t.rb:18:in `sleep'
|
12
|
-
from t.rb:18:in `<main>'
|