rufus-scheduler 2.0.3 → 2.0.4
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 +6 -1
- data/CREDITS.txt +1 -0
- data/LICENSE.txt +1 -1
- data/Rakefile +82 -0
- data/lib/rufus/sc/cronline.rb +2 -1
- data/lib/rufus/sc/jobqueues.rb +1 -1
- data/lib/rufus/sc/jobs.rb +10 -8
- data/lib/rufus/sc/rtime.rb +1 -1
- data/lib/rufus/sc/scheduler.rb +2 -2
- data/lib/rufus/scheduler.rb +1 -1
- data/misc/cronline_next_time_cost.rb +14 -0
- data/rufus-scheduler.gemspec +87 -0
- data/spec/at_in_spec.rb +48 -0
- data/spec/at_spec.rb +121 -0
- data/spec/blocking_spec.rb +54 -0
- data/spec/cron_spec.rb +122 -0
- data/spec/cronline_spec.rb +67 -0
- data/spec/every_spec.rb +193 -0
- data/spec/exception_spec.rb +77 -0
- data/spec/in_spec.rb +165 -0
- data/spec/rtime_spec.rb +88 -0
- data/spec/schedulable_spec.rb +79 -0
- data/spec/scheduler_spec.rb +81 -0
- data/spec/spec_base.rb +82 -0
- data/spec/stress_schedule_unschedule_spec.rb +155 -0
- data/spec/timeout_spec.rb +103 -0
- data/test/kjw.rb +113 -0
- data/test/t.rb +20 -0
- metadata +61 -14
@@ -0,0 +1,155 @@
|
|
1
|
+
|
2
|
+
$LOAD_PATH << 'lib'
|
3
|
+
require 'rufus/sc/scheduler'
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'eventmachine'
|
7
|
+
|
8
|
+
#require 'spec/autorun'
|
9
|
+
require File.dirname(__FILE__) + '/spec_base'
|
10
|
+
|
11
|
+
JOB_COUNT = 500 # 1000
|
12
|
+
JOB_IDS = (1..JOB_COUNT).to_a
|
13
|
+
NUM_RESCHEDULES = 5 # 10
|
14
|
+
TRIGGER_DELAY = 4 # 15
|
15
|
+
|
16
|
+
describe SCHEDULER_CLASS do
|
17
|
+
|
18
|
+
# helper methods
|
19
|
+
|
20
|
+
#
|
21
|
+
# Wait for a variable to become a certain value.
|
22
|
+
# This method returns a block which loops waiting for the passed in
|
23
|
+
# block paramter to have value 'target'.
|
24
|
+
#
|
25
|
+
def eventually(timeout = TRIGGER_DELAY * 2, precision = 1)
|
26
|
+
lambda { |target|
|
27
|
+
value = nil
|
28
|
+
(timeout/precision).to_i.times do
|
29
|
+
value = yield # read variable once
|
30
|
+
# puts "got #{value}, expected #{target}"
|
31
|
+
break if target == value
|
32
|
+
sleep precision
|
33
|
+
end
|
34
|
+
target == value
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
def benchmark
|
39
|
+
now = Time.now
|
40
|
+
yield
|
41
|
+
benchmark = Time.now - now
|
42
|
+
print " (scheduling took #{benchmark}s)"
|
43
|
+
if benchmark > TRIGGER_DELAY
|
44
|
+
puts "\nTEST RESULT INVALID/UNRELIABLE"
|
45
|
+
puts "Scheduling took longer than TRIGGER_DELAY (#{TRIGGER_DELAY}s)."
|
46
|
+
puts "Increase TRIGGER_DELAY to a value larger than largest scheduling time."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def schedule_unschedule_same_ids_spec (mode)
|
51
|
+
scheduler = SCHEDULER_CLASS.start_new
|
52
|
+
benchmark { schedule_unschedule(scheduler, mode, NUM_RESCHEDULES) }
|
53
|
+
JOB_COUNT.should.satisfy &eventually { scheduler.all_jobs.size }
|
54
|
+
JOB_IDS.sort.should.equal(scheduler.find_jobs.map{ |job| job.job_id }.sort)
|
55
|
+
JOB_COUNT.should.satisfy &eventually { @trigger_queue.size }
|
56
|
+
@trigger_queue.size.should.equal(JOB_COUNT)
|
57
|
+
scheduler.stop
|
58
|
+
end
|
59
|
+
|
60
|
+
def schedule_unschedule_unique_ids_spec (mode)
|
61
|
+
scheduler = SCHEDULER_CLASS.start_new
|
62
|
+
job_ids = []
|
63
|
+
benchmark { job_ids = schedule_unschedule(scheduler, mode, NUM_RESCHEDULES, true) }
|
64
|
+
JOB_COUNT.should.satisfy &eventually { scheduler.all_jobs.size }
|
65
|
+
job_ids.sort.should.equal(scheduler.find_jobs.map{ |job| job.job_id }.sort)
|
66
|
+
JOB_COUNT.should.satisfy &eventually { @trigger_queue.size }
|
67
|
+
@trigger_queue.size.should.equal(JOB_COUNT)
|
68
|
+
scheduler.stop
|
69
|
+
end
|
70
|
+
|
71
|
+
def scheduler_counts(scheduler)
|
72
|
+
"all:%d at:%d cron:%d every:%d pending:%d" % [
|
73
|
+
scheduler.all_jobs.size,
|
74
|
+
scheduler.at_job_count,
|
75
|
+
scheduler.cron_job_count,
|
76
|
+
scheduler.every_job_count,
|
77
|
+
scheduler.pending_job_count]
|
78
|
+
end
|
79
|
+
|
80
|
+
def schedule_unschedule(scheduler, mode, num_reschedules, generate_ids = false)
|
81
|
+
job_ids = schedule_jobs(scheduler, mode, generate_ids)
|
82
|
+
1.upto(num_reschedules) do
|
83
|
+
unschedule_jobs(scheduler, job_ids)
|
84
|
+
job_ids = schedule_jobs(scheduler, mode, generate_ids)
|
85
|
+
end
|
86
|
+
job_ids
|
87
|
+
end
|
88
|
+
|
89
|
+
def schedule_jobs(scheduler, mode, generate_ids = false)
|
90
|
+
job_ids = []
|
91
|
+
JOB_IDS.each do |job_id|
|
92
|
+
case mode
|
93
|
+
when :cron
|
94
|
+
job_ids << scheduler.cron("%d * * * * *" % @cron_trigger,
|
95
|
+
{ :job_id => (generate_ids ? nil : job_id) },
|
96
|
+
&@trigger_proc).job_id
|
97
|
+
when :at
|
98
|
+
job_ids << scheduler.at(@at_trigger,
|
99
|
+
{ :job_id => (generate_ids ? nil : job_id) },
|
100
|
+
&@trigger_proc).job_id
|
101
|
+
when :every
|
102
|
+
job_ids << scheduler.every(@every_trigger,
|
103
|
+
{ :job_id => (generate_ids ? nil : job_id) },
|
104
|
+
&@trigger_proc).job_id
|
105
|
+
else
|
106
|
+
raise ArgumentError
|
107
|
+
end
|
108
|
+
end
|
109
|
+
job_ids
|
110
|
+
end
|
111
|
+
|
112
|
+
def unschedule_jobs(scheduler, job_ids)
|
113
|
+
job_ids.each { |job_id| scheduler.unschedule(job_id) }
|
114
|
+
end
|
115
|
+
|
116
|
+
# the actual tests
|
117
|
+
|
118
|
+
before do #(:each) do
|
119
|
+
@trigger_queue = Queue.new
|
120
|
+
@cron_trigger = ((Time.now.to_i%60) + TRIGGER_DELAY) % 60 # 30 seconds from now
|
121
|
+
@at_trigger = Time.now + TRIGGER_DELAY
|
122
|
+
@every_trigger = "#{TRIGGER_DELAY}s"
|
123
|
+
@trigger_proc = lambda { |job| @trigger_queue << job.job_id }
|
124
|
+
end
|
125
|
+
|
126
|
+
after do #(:each) do
|
127
|
+
@trigger_queue = nil
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should sustain frequent schedule/unschedule 'cron' jobs with same ids" do
|
131
|
+
schedule_unschedule_same_ids_spec(:cron)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should sustain frequent schedule/unschedule 'at' jobs with same ids" do
|
135
|
+
schedule_unschedule_same_ids_spec(:at)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should sustain frequent schedule/unschedule 'every' jobs same ids" do
|
139
|
+
schedule_unschedule_same_ids_spec(:every)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should sustain frequent schedule/unschedule 'cron' jobs with unique ids" do
|
143
|
+
schedule_unschedule_unique_ids_spec(:cron)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should sustain frequent schedule/unschedule 'at' jobs with unique ids" do
|
147
|
+
schedule_unschedule_unique_ids_spec(:at)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should sustain frequent schedule/unschedule 'every' jobs unique ids" do
|
151
|
+
schedule_unschedule_unique_ids_spec(:every)
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Specifying rufus-scheduler
|
4
|
+
#
|
5
|
+
# Sun May 3 15:44:28 JST 2009
|
6
|
+
#
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/spec_base'
|
9
|
+
|
10
|
+
|
11
|
+
describe "#{SCHEDULER_CLASS} timeouts" do
|
12
|
+
|
13
|
+
before do
|
14
|
+
@s = start_scheduler
|
15
|
+
end
|
16
|
+
after do
|
17
|
+
stop_scheduler(@s)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should should refuse to schedule a job with :timeout and :blocking' do
|
21
|
+
|
22
|
+
lambda {
|
23
|
+
@s.in '1s', :timeout => '3s', :blocking => true do
|
24
|
+
end
|
25
|
+
}.should.raise(ArgumentError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should schedule a dedicated job for the timeout' do
|
29
|
+
|
30
|
+
@s.in '1s', :timeout => '3s' do
|
31
|
+
sleep 5
|
32
|
+
end
|
33
|
+
|
34
|
+
@s.jobs.size.should.equal(1)
|
35
|
+
|
36
|
+
# the timeout job is left
|
37
|
+
|
38
|
+
sleep 2
|
39
|
+
@s.jobs.size.should.equal(1)
|
40
|
+
@s.find_by_tag('timeout').size.should.equal(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should time out' do
|
44
|
+
|
45
|
+
var = nil
|
46
|
+
timedout = false
|
47
|
+
|
48
|
+
@s.in '1s', :timeout => '1s' do
|
49
|
+
begin
|
50
|
+
sleep 2
|
51
|
+
var = true
|
52
|
+
rescue Rufus::Scheduler::TimeOutError => e
|
53
|
+
timedout = true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
sleep 4
|
58
|
+
|
59
|
+
var.should.be.nil
|
60
|
+
@s.jobs.size.should.equal(0)
|
61
|
+
timedout.should.be.true
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should die silently if job finished before timeout' do
|
65
|
+
|
66
|
+
var = nil
|
67
|
+
timedout = false
|
68
|
+
|
69
|
+
@s.in '1s', :timeout => '1s' do
|
70
|
+
begin
|
71
|
+
var = true
|
72
|
+
rescue Rufus::Scheduler::TimeOutError => e
|
73
|
+
timedout = true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
sleep 3
|
78
|
+
|
79
|
+
var.should.be.true
|
80
|
+
@s.jobs.size.should.equal(0)
|
81
|
+
timedout.should.be.false
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should not timeout other jobs (in case of every)' do
|
85
|
+
|
86
|
+
timeouts = []
|
87
|
+
|
88
|
+
@s.every '1s', :timeout => '1s500' do
|
89
|
+
start = Time.now
|
90
|
+
begin
|
91
|
+
sleep 2.0
|
92
|
+
rescue Rufus::Scheduler::TimeOutError => e
|
93
|
+
timeouts << (Time.now - start)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
sleep 5
|
98
|
+
|
99
|
+
timeouts.size.should.equal(3)
|
100
|
+
timeouts.each { |to| (to * 10).to_i.should.equal(16) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
data/test/kjw.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
#
|
4
|
+
# A test by http://twitter.com/kjw
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'test/unit'
|
8
|
+
$LOAD_PATH << 'lib'
|
9
|
+
require 'rufus/sc/scheduler'
|
10
|
+
|
11
|
+
require 'rubygems'
|
12
|
+
require 'eventmachine'
|
13
|
+
|
14
|
+
class CronTest < Test::Unit::TestCase
|
15
|
+
|
16
|
+
#
|
17
|
+
# Stress test program for rufus-scheduler
|
18
|
+
#
|
19
|
+
|
20
|
+
SECONDS_FROM_NOW = 30
|
21
|
+
MODE = :cron
|
22
|
+
JOB_COUNT = 1000
|
23
|
+
JOB_IDS = (1..JOB_COUNT).to_a
|
24
|
+
NUM_RESCHEDULES = 20
|
25
|
+
|
26
|
+
def setup
|
27
|
+
@trigger_queue = Queue.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_stress_schedule_unschedule_plain_cron
|
31
|
+
stress_schedule_unschedule(:cron, Rufus::Scheduler::PlainScheduler.start_new)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_stress_schedule_unschedule_plain_at
|
35
|
+
stress_schedule_unschedule(:at, Rufus::Scheduler::PlainScheduler.start_new)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_stress_schedule_unschedule_plain_every
|
39
|
+
stress_schedule_unschedule(:every, Rufus::Scheduler::PlainScheduler.start_new)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_stress_schedule_unschedule_em_cron
|
43
|
+
stress_schedule_unschedule(:cron, Rufus::Scheduler::EmScheduler.start_new)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_stress_schedule_unschedule_em_at
|
47
|
+
stress_schedule_unschedule(:at, Rufus::Scheduler::EmScheduler.start_new)
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_stress_schedule_unschedule_em_every
|
51
|
+
stress_schedule_unschedule(:every, Rufus::Scheduler::EmScheduler.start_new)
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def stress_schedule_unschedule(mode, scheduler)
|
57
|
+
|
58
|
+
# Schedule all jobs, then unschedule and (re)schedule a number of times
|
59
|
+
schedule_unschedule(scheduler, mode, NUM_RESCHEDULES)
|
60
|
+
|
61
|
+
# give scheduler thread 10 seconds to process the schedule and unschedule requests
|
62
|
+
# but don't wait for the jobs to trigger (which is in less than 30 seconds)
|
63
|
+
sleep 10
|
64
|
+
# print_scheduler_counts(scheduler, 10)
|
65
|
+
|
66
|
+
# by now the scheduler should have processed everything, check
|
67
|
+
assert(JOB_IDS.sort == scheduler.find_jobs.map{ |job| job.job_id }.sort)
|
68
|
+
sleep SECONDS_FROM_NOW # wait for jobs to trigger
|
69
|
+
assert(JOB_COUNT == @trigger_queue.size)
|
70
|
+
end
|
71
|
+
|
72
|
+
def schedule_unschedule(scheduler, mode, num_reschedules)
|
73
|
+
schedule_jobs(scheduler, mode)
|
74
|
+
1.upto(num_reschedules) do
|
75
|
+
sleep 0.01 # cause schedule's to happen before unscheduling
|
76
|
+
unschedule_jobs(scheduler)
|
77
|
+
schedule_jobs(scheduler, mode)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def print_scheduler_counts(scheduler, seconds)
|
82
|
+
1.upto(seconds) do
|
83
|
+
puts "all:%d at:%d cron:%d every:%d pending:%d" % [
|
84
|
+
scheduler.all_jobs.size,
|
85
|
+
scheduler.at_job_count,
|
86
|
+
scheduler.cron_job_count,
|
87
|
+
scheduler.every_job_count,
|
88
|
+
scheduler.pending_job_count]
|
89
|
+
sleep 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def schedule_jobs(scheduler, mode)
|
94
|
+
trigger_proc = lambda { |params| @trigger_queue << params[:job_id] }
|
95
|
+
JOB_IDS.each do |job_id|
|
96
|
+
case mode
|
97
|
+
when :at
|
98
|
+
scheduler.at(Time.now + SECONDS_FROM_NOW, {:job_id => job_id}, &trigger_proc)
|
99
|
+
when :every
|
100
|
+
scheduler.every("#{SECONDS_FROM_NOW}s", {:job_id => job_id}, &trigger_proc)
|
101
|
+
when :cron
|
102
|
+
scheduler.cron("%d * * * * *" % ((Time.now.to_i%60) + SECONDS_FROM_NOW) % 60,
|
103
|
+
{:job_id => job_id}, &trigger_proc)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def unschedule_jobs(scheduler)
|
109
|
+
JOB_IDS.each { |job_id| scheduler.unschedule(job_id) }
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
data/test/t.rb
ADDED
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: 2.0.
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Mettraux
|
@@ -9,10 +9,39 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-02-12 00:00:00 +09:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: yard
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: bacon
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: jeweler
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
16
45
|
description: "\n job scheduler for Ruby (at, cron, in and every jobs).\n\n By default uses a Ruby thread, if EventMachine is present, it will rely on it.\n "
|
17
46
|
email: jmettraux@gmail.com
|
18
47
|
executables: []
|
@@ -20,11 +49,16 @@ executables: []
|
|
20
49
|
extensions: []
|
21
50
|
|
22
51
|
extra_rdoc_files:
|
52
|
+
- LICENSE.txt
|
23
53
|
- README.rdoc
|
54
|
+
files:
|
24
55
|
- CHANGELOG.txt
|
25
56
|
- CREDITS.txt
|
26
57
|
- LICENSE.txt
|
27
|
-
|
58
|
+
- README.rdoc
|
59
|
+
- Rakefile
|
60
|
+
- TODO.txt
|
61
|
+
- lib/rufus-scheduler.rb
|
28
62
|
- lib/rufus/otime.rb
|
29
63
|
- lib/rufus/sc/cronline.rb
|
30
64
|
- lib/rufus/sc/jobqueues.rb
|
@@ -32,19 +66,32 @@ files:
|
|
32
66
|
- lib/rufus/sc/rtime.rb
|
33
67
|
- lib/rufus/sc/scheduler.rb
|
34
68
|
- lib/rufus/scheduler.rb
|
35
|
-
-
|
36
|
-
-
|
37
|
-
-
|
38
|
-
-
|
39
|
-
-
|
40
|
-
-
|
69
|
+
- misc/cronline_next_time_cost.rb
|
70
|
+
- rufus-scheduler.gemspec
|
71
|
+
- spec/at_in_spec.rb
|
72
|
+
- spec/at_spec.rb
|
73
|
+
- spec/blocking_spec.rb
|
74
|
+
- spec/cron_spec.rb
|
75
|
+
- spec/cronline_spec.rb
|
76
|
+
- spec/every_spec.rb
|
77
|
+
- spec/exception_spec.rb
|
78
|
+
- spec/in_spec.rb
|
79
|
+
- spec/rtime_spec.rb
|
80
|
+
- spec/schedulable_spec.rb
|
81
|
+
- spec/scheduler_spec.rb
|
82
|
+
- spec/spec.rb
|
83
|
+
- spec/spec_base.rb
|
84
|
+
- spec/stress_schedule_unschedule_spec.rb
|
85
|
+
- spec/timeout_spec.rb
|
86
|
+
- test/kjw.rb
|
87
|
+
- test/t.rb
|
41
88
|
has_rdoc: true
|
42
|
-
homepage: http://github.com/jmettraux/rufus-scheduler
|
89
|
+
homepage: http://github.com/jmettraux/rufus-scheduler/
|
43
90
|
licenses: []
|
44
91
|
|
45
92
|
post_install_message:
|
46
|
-
rdoc_options:
|
47
|
-
|
93
|
+
rdoc_options:
|
94
|
+
- --charset=UTF-8
|
48
95
|
require_paths:
|
49
96
|
- lib
|
50
97
|
required_ruby_version: !ruby/object:Gem::Requirement
|