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 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, hthread of execution. In rufus-scheduler 3.x, execution happens in a work thread and the max work thread count defaults to 28.
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
@@ -3,7 +3,6 @@ require 'rubygems'
3
3
 
4
4
  require 'rake'
5
5
  require 'rake/clean'
6
- #require 'rake/rdoctask'
7
6
  require 'rdoc/task'
8
7
 
9
8
 
@@ -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.6'
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
- time += (24 - time.hour) * 3600 - time.min * 60 - time.sec; next
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
@@ -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 = @times - 1 if @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
@@ -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
@@ -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
@@ -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
 
@@ -23,9 +23,14 @@ describe Rufus::Scheduler do
23
23
  t[:rufus_scheduler].should == scheduler
24
24
  end
25
25
 
26
- it 'sets a :rufus_scheduler thread local var' do
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.6
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-02-13 00:00:00.000000000 Z
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.6\n\nIt
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>'