rufus-scheduler 3.0.6 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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)
|
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>'
|