perfectsched 0.7.19 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/ChangeLog +0 -36
- data/Gemfile +3 -0
- data/README.md +219 -0
- data/Rakefile +19 -0
- data/lib/perfectsched/application.rb +25 -0
- data/lib/perfectsched/backend/rdb_compat.rb +254 -0
- data/lib/perfectsched/backend.rb +44 -82
- data/lib/perfectsched/blocking_flag.rb +25 -0
- data/lib/perfectsched/client.rb +129 -0
- data/lib/perfectsched/command/perfectsched.rb +103 -290
- data/lib/perfectsched/daemons_logger.rb +24 -0
- data/lib/perfectsched/engine.rb +54 -54
- data/lib/perfectsched/error.rb +43 -0
- data/lib/perfectsched/model.rb +37 -0
- data/lib/perfectsched/runner.rb +35 -0
- data/lib/perfectsched/schedule.rb +65 -0
- data/lib/perfectsched/schedule_collection.rb +62 -0
- data/lib/perfectsched/schedule_metadata.rb +76 -0
- data/lib/perfectsched/signal_queue.rb +25 -0
- data/lib/perfectsched/task.rb +46 -0
- data/lib/perfectsched/version.rb +1 -3
- data/lib/perfectsched/worker.rb +163 -0
- data/lib/perfectsched.rb +98 -4
- data/perfectsched.gemspec +27 -0
- data/spec/schedule_collection_spec.rb +172 -0
- data/spec/spec_helper.rb +46 -0
- data/spec/worker_spec.rb +51 -0
- metadata +98 -52
- checksums.yaml +0 -7
- data/README.rdoc +0 -137
- data/lib/perfectsched/backend/null.rb +0 -45
- data/lib/perfectsched/backend/rdb.rb +0 -165
- data/lib/perfectsched/backend/simpledb.rb +0 -174
- data/lib/perfectsched/croncalc.rb +0 -29
- data/test/backend_test.rb +0 -217
- data/test/test_helper.rb +0 -19
@@ -0,0 +1,25 @@
|
|
1
|
+
#
|
2
|
+
# PerfectSched
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectSched
|
20
|
+
|
21
|
+
require 'perfectqueue/signal_queue'
|
22
|
+
SignalQueue = PerfectQueue::SignalQueue
|
23
|
+
|
24
|
+
end
|
25
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#
|
2
|
+
# PerfectSched
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectSched
|
20
|
+
|
21
|
+
class Task < ScheduleWithMetadata
|
22
|
+
include Model
|
23
|
+
|
24
|
+
def initialize(client, key, attributes, scheduled_time, task_token)
|
25
|
+
super(client, key, attributes)
|
26
|
+
@scheduled_time = scheduled_time
|
27
|
+
@task_token = task_token
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :scheduled_time
|
31
|
+
|
32
|
+
def release!(options={})
|
33
|
+
@client.release(@task_token, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def retry!(options={})
|
37
|
+
@client.retry(@task_token, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def finish!(options={})
|
41
|
+
@client.finish(@task_token, options)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
data/lib/perfectsched/version.rb
CHANGED
@@ -0,0 +1,163 @@
|
|
1
|
+
#
|
2
|
+
# PerfectSched
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectSched
|
20
|
+
|
21
|
+
class Worker
|
22
|
+
def self.run(runner, config=nil, &block)
|
23
|
+
new(runner, config, &block).run
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(runner, config=nil, &block)
|
27
|
+
# initial logger
|
28
|
+
STDERR.sync = true
|
29
|
+
@log = DaemonsLogger.new(STDERR)
|
30
|
+
|
31
|
+
@runner = runner
|
32
|
+
block = Proc.new { config } if config
|
33
|
+
@config_load_proc = block
|
34
|
+
@finished = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def run
|
38
|
+
@sig = install_signal_handlers
|
39
|
+
begin
|
40
|
+
@engine = Engine.new(@runner, load_config)
|
41
|
+
begin
|
42
|
+
until @finished
|
43
|
+
@engine.run
|
44
|
+
end
|
45
|
+
ensure
|
46
|
+
@engine.shutdown
|
47
|
+
end
|
48
|
+
ensure
|
49
|
+
@sig.shutdown
|
50
|
+
end
|
51
|
+
return nil
|
52
|
+
rescue
|
53
|
+
@log.error "#{$!.class}: #{$!}"
|
54
|
+
$!.backtrace.each {|x| @log.error " #{x}" }
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def stop
|
59
|
+
@log.info "stop"
|
60
|
+
begin
|
61
|
+
@finished = true
|
62
|
+
@engine.stop if @engine
|
63
|
+
rescue
|
64
|
+
@log.error "failed to stop: #{$!}"
|
65
|
+
$!.backtrace.each {|bt| @log.warn "\t#{bt}" }
|
66
|
+
return false
|
67
|
+
end
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
|
71
|
+
def restart
|
72
|
+
@log.info "Received restart"
|
73
|
+
begin
|
74
|
+
engine = Engine.new(@runner, load_config)
|
75
|
+
current = @engine
|
76
|
+
@engine = engine
|
77
|
+
current.shutdown
|
78
|
+
rescue
|
79
|
+
@log.error "failed to restart: #{$!}"
|
80
|
+
$!.backtrace.each {|bt| @log.warn "\t#{bt}" }
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
return true
|
84
|
+
end
|
85
|
+
|
86
|
+
def replace(command=[$0]+ARGV)
|
87
|
+
@log.info "replace"
|
88
|
+
begin
|
89
|
+
return if @replaced_pid
|
90
|
+
stop
|
91
|
+
@replaced_pid = Process.fork do
|
92
|
+
exec(*command)
|
93
|
+
exit!(127)
|
94
|
+
end
|
95
|
+
rescue
|
96
|
+
@log.error "failed to replace: #{$!}"
|
97
|
+
$!.backtrace.each {|bt| @log.warn "\t#{bt}" }
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def logrotated
|
104
|
+
@log.info "reopen a log file"
|
105
|
+
begin
|
106
|
+
@log.reopen!
|
107
|
+
rescue
|
108
|
+
@log.error "failed to restart: #{$!}"
|
109
|
+
$!.backtrace.each {|bt| @log.warn "\t#{bt}" }
|
110
|
+
return false
|
111
|
+
end
|
112
|
+
return true
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
def load_config
|
117
|
+
raw_config = @config_load_proc.call
|
118
|
+
config = {}
|
119
|
+
raw_config.each_pair {|k,v| config[k.to_sym] = v }
|
120
|
+
|
121
|
+
log = DaemonsLogger.new(config[:log] || STDERR)
|
122
|
+
if old_log = @log
|
123
|
+
old_log.close
|
124
|
+
end
|
125
|
+
@log = log
|
126
|
+
|
127
|
+
config[:logger] = log
|
128
|
+
|
129
|
+
return config
|
130
|
+
end
|
131
|
+
|
132
|
+
def install_signal_handlers(&block)
|
133
|
+
SignalQueue.start do |sig|
|
134
|
+
sig.trap :TERM do
|
135
|
+
stop
|
136
|
+
end
|
137
|
+
sig.trap :INT do
|
138
|
+
stop
|
139
|
+
end
|
140
|
+
|
141
|
+
sig.trap :QUIT do
|
142
|
+
stop
|
143
|
+
end
|
144
|
+
|
145
|
+
sig.trap :USR1 do
|
146
|
+
restart
|
147
|
+
end
|
148
|
+
|
149
|
+
sig.trap :HUP do
|
150
|
+
restart
|
151
|
+
end
|
152
|
+
|
153
|
+
sig.trap :USR2 do
|
154
|
+
logrotated
|
155
|
+
end
|
156
|
+
|
157
|
+
trap :CHLD, "SIG_IGN"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
|
data/lib/perfectsched.rb
CHANGED
@@ -1,4 +1,98 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
#
|
2
|
+
# PerfectSched
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectSched
|
20
|
+
require 'json'
|
21
|
+
require 'thread' # Mutex, CoditionVariable
|
22
|
+
|
23
|
+
{
|
24
|
+
:Application => 'perfectsched/application',
|
25
|
+
:Backend => 'perfectsched/backend',
|
26
|
+
:BackendHelper => 'perfectsched/backend',
|
27
|
+
:BlockingFlag => 'perfectsched/blocking_flag',
|
28
|
+
:Client => 'perfectsched/client',
|
29
|
+
:DaemonsLogger => 'perfectsched/daemons_logger',
|
30
|
+
:Engine => 'perfectsched/engine',
|
31
|
+
:Model => 'perfectsched/model',
|
32
|
+
:Runner => 'perfectsched/runner',
|
33
|
+
:ScheduleCollection => 'perfectsched/schedule_collection',
|
34
|
+
:Schedule => 'perfectsched/schedule',
|
35
|
+
:ScheduleWithMetadata => 'perfectsched/schedule',
|
36
|
+
:ScheduleMetadata => 'perfectsched/schedule_metadata',
|
37
|
+
:ScheduleMetadataAccessors => 'perfectsched/schedule_metadata',
|
38
|
+
:SignalQueue => 'perfectsched/signal_queue',
|
39
|
+
:Task => 'perfectsched/task',
|
40
|
+
:Worker => 'perfectsched/worker',
|
41
|
+
}.each_pair {|k,v|
|
42
|
+
autoload k, File.expand_path(v, File.dirname(__FILE__))
|
43
|
+
}
|
44
|
+
[
|
45
|
+
'perfectsched/version',
|
46
|
+
'perfectsched/error',
|
47
|
+
].each {|v|
|
48
|
+
require File.expand_path(v, File.dirname(__FILE__))
|
49
|
+
}
|
50
|
+
|
51
|
+
require 'cron-spec'
|
52
|
+
require 'tzinfo'
|
53
|
+
|
54
|
+
def self.open(config, &block)
|
55
|
+
c = Client.new(config)
|
56
|
+
begin
|
57
|
+
q = ScheduleCollection.new(c)
|
58
|
+
if block
|
59
|
+
block.call(q)
|
60
|
+
else
|
61
|
+
c = nil
|
62
|
+
return q
|
63
|
+
end
|
64
|
+
ensure
|
65
|
+
c.close if c
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.cron_time(cron, timestamp, timezone)
|
70
|
+
begin
|
71
|
+
tab = CronSpec::CronSpecification.new(cron)
|
72
|
+
rescue
|
73
|
+
raise ArgumentError, "invalid cron format: #{$!}: #{cron.inspect}"
|
74
|
+
end
|
75
|
+
|
76
|
+
begin
|
77
|
+
tz = TZInfo::Timezone.get(timezone) if timezone
|
78
|
+
rescue
|
79
|
+
raise ArgumentError, "unknown timezone: #{$!}: #{timezone.inspect}"
|
80
|
+
end
|
81
|
+
|
82
|
+
ts = (timestamp + 59) / 60 * 60
|
83
|
+
while true
|
84
|
+
t = Time.at(ts).utc
|
85
|
+
t = tz.utc_to_local(t) if tz
|
86
|
+
if tab.is_specification_in_effect?(t)
|
87
|
+
return ts
|
88
|
+
end
|
89
|
+
ts += 60
|
90
|
+
# FIXME break
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.next_time(cron, before_timestamp, timezone)
|
95
|
+
cron_time(cron, before_timestamp+1, timezone)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'perfectsched/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "perfectsched"
|
7
|
+
gem.description = "Highly available distributed cron built on RDBMS"
|
8
|
+
gem.homepage = "https://github.com/treasure-data/perfectsched"
|
9
|
+
gem.summary = gem.description
|
10
|
+
gem.version = PerfectSched::VERSION
|
11
|
+
gem.authors = ["Sadayuki Furuhashi"]
|
12
|
+
gem.email = "frsyuki@gmail.com"
|
13
|
+
gem.has_rdoc = false
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
|
19
|
+
gem.add_dependency "cron-spec", [">= 0.1.2", "<= 0.1.2"]
|
20
|
+
gem.add_dependency "sequel", "~> 3.26.0"
|
21
|
+
gem.add_dependency "tzinfo", "~> 0.3.29"
|
22
|
+
gem.add_dependency "perfectqueue", "~> 0.8.0"
|
23
|
+
gem.add_development_dependency "rake", "~> 0.9.2"
|
24
|
+
gem.add_development_dependency "rspec", "~> 2.10.0"
|
25
|
+
gem.add_development_dependency "simplecov", "~> 0.5.4"
|
26
|
+
gem.add_development_dependency "sqlite3", "~> 1.3.3"
|
27
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
|
2
|
+
describe ScheduleCollection do
|
3
|
+
before do
|
4
|
+
@sc = create_test_sched
|
5
|
+
end
|
6
|
+
|
7
|
+
after do
|
8
|
+
@sc.client.close
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'is a ScheduleCollection' do
|
12
|
+
@sc.class.should == PerfectSched::ScheduleCollection
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'succeess add' do
|
16
|
+
@sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'fail duplicated add' do
|
20
|
+
@sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
|
21
|
+
|
22
|
+
lambda {
|
23
|
+
@sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
|
24
|
+
}.should raise_error AlreadyExistsError
|
25
|
+
|
26
|
+
@sc['sched01'].delete!
|
27
|
+
|
28
|
+
@sc.add('sched01', 't01', {:cron=>'* * * * *', :timezone=>'UTC'})
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'acquire' do
|
32
|
+
time = 1323820800 # 2011-12-14 00:00:00 UTC
|
33
|
+
|
34
|
+
s01 = @sc.add('sched01', 't01', :cron=>"* * * * *", :data=>{'k'=>1}, :next_time=>time)
|
35
|
+
|
36
|
+
t01 = @sc.poll(:alive_time=>120, :now=>time)
|
37
|
+
t01.key.should == 'sched01'
|
38
|
+
t01.type.should == 't01'
|
39
|
+
t01.cron.should == "* * * * *"
|
40
|
+
t01.delay.should == 0
|
41
|
+
t01.data.should == {'k'=>1}
|
42
|
+
t01.scheduled_time.should == Time.at(time).utc
|
43
|
+
|
44
|
+
t02 = @sc.poll(:alive_time=>120, :now=>time+60)
|
45
|
+
t02.should == nil
|
46
|
+
|
47
|
+
t01.finish!
|
48
|
+
|
49
|
+
t04 = @sc.poll(:alive_time=>120, :now=>time+60)
|
50
|
+
t04.key.should == 'sched01'
|
51
|
+
t01.type.should == 't01'
|
52
|
+
t04.cron.should == "* * * * *"
|
53
|
+
t04.delay.should == 0
|
54
|
+
t04.data.should == {'k'=>1}
|
55
|
+
t04.scheduled_time.should == Time.at(time+60).utc
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'timezone' do
|
59
|
+
time = 1323820800 # 2011-12-14 00:00:00 UTC
|
60
|
+
|
61
|
+
s01 = @sc.add('sched01', 't01', :cron=>"0 0 * * *", :next_time=>time-60, :timezone=>'UTC')
|
62
|
+
#s01.class.should == Schedule
|
63
|
+
#s01.key.should == 'sched01'
|
64
|
+
|
65
|
+
s02 = @sc.add('sched02', 't01', :cron=>"0 0 * * *", :next_time=>time-60, :timezone=>'Asia/Tokyo')
|
66
|
+
#s02.class.should == Schedule
|
67
|
+
#s02.key.should == 'sched02'
|
68
|
+
|
69
|
+
t01 = @sc.poll(:alive_time=>86400, :now=>time)
|
70
|
+
t01.class.should == Task
|
71
|
+
t01.key.should == 'sched01'
|
72
|
+
t01.type.should == 't01'
|
73
|
+
t01.scheduled_time.should == Time.at(time).utc
|
74
|
+
|
75
|
+
t02 = @sc.poll(:alive_time=>86400, :now=>time+54000)
|
76
|
+
t02.class.should == Task
|
77
|
+
t02.key.should == 'sched02'
|
78
|
+
t02.type.should == 't01'
|
79
|
+
t02.scheduled_time.should == Time.at(time+54000).utc
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'delay' do
|
83
|
+
time = 1323820800 # 2011-12-14 00:00:00 UTC
|
84
|
+
|
85
|
+
s01 = @sc.add('sched01', 't01', :cron=>"0 * * * *", :delay=>30, :next_time=>time, :timezone=>'UTC')
|
86
|
+
|
87
|
+
t01 = @sc.poll(:alive_time=>86400, :now=>time)
|
88
|
+
t01.should == nil
|
89
|
+
|
90
|
+
t02 = @sc.poll(:alive_time=>86400, :now=>time+30)
|
91
|
+
t02.class.should == Task
|
92
|
+
t02.key.should == 'sched01'
|
93
|
+
t02.type.should == 't01'
|
94
|
+
t02.scheduled_time.should == Time.at(time).utc
|
95
|
+
t02.delay.should == 30
|
96
|
+
|
97
|
+
t02.finish!
|
98
|
+
|
99
|
+
t03 = @sc.poll(:alive_time=>86400, :now=>time+3600)
|
100
|
+
t03.should == nil
|
101
|
+
|
102
|
+
t04 = @sc.poll(:alive_time=>86400, :now=>time+3630)
|
103
|
+
t04.class.should == Task
|
104
|
+
t04.key.should == 'sched01'
|
105
|
+
t04.type.should == 't01'
|
106
|
+
t04.scheduled_time.should == Time.at(time+3600).utc
|
107
|
+
t04.delay.should == 30
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'invalid cron format' do
|
111
|
+
lambda {
|
112
|
+
@sc.add('sched01', 't01', :cron=>'???')
|
113
|
+
}.should raise_error ArgumentError
|
114
|
+
|
115
|
+
lambda {
|
116
|
+
@sc.add('sched01', 't01', :cron=>'* * * * * *')
|
117
|
+
}.should raise_error ArgumentError
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'fail duplicated add' do
|
121
|
+
@sc.add('sched01', 't01', :cron=>"0 * * * *")
|
122
|
+
lambda {
|
123
|
+
@sc.add('sched01', 't01', :cron=>"0 * * * *")
|
124
|
+
}.should raise_error AlreadyExistsError
|
125
|
+
|
126
|
+
@sc['sched01'].delete!
|
127
|
+
|
128
|
+
@sc.add('sched01', 't01', :cron=>"0 * * * *")
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'list' do
|
132
|
+
time = 1323820800 # 2011-12-14 00:00:00 UTC
|
133
|
+
|
134
|
+
@sc.add('sched01', 't01', :cron=>"0 * * * *", :next_time=>time, :delay=>1)
|
135
|
+
@sc.add('sched02', 't02', :cron=>"0 * * * *", :next_time=>time, :delay=>2)
|
136
|
+
@sc.add('sched03', 't03', :cron=>"0 * * * *", :next_time=>time, :delay=>3, :next_run_time=>time+3600)
|
137
|
+
|
138
|
+
a = []
|
139
|
+
@sc.list {|s|
|
140
|
+
a << s
|
141
|
+
}
|
142
|
+
a.sort_by! {|s| s.key }
|
143
|
+
|
144
|
+
s01 = a.shift
|
145
|
+
s01.class.should == ScheduleWithMetadata
|
146
|
+
s01.key.should == 'sched01'
|
147
|
+
s01.type.should == 't01'
|
148
|
+
s01.cron.should == '0 * * * *'
|
149
|
+
s01.delay.should == 1
|
150
|
+
s01.next_time.should == Time.at(time).utc
|
151
|
+
s01.next_run_time.should == Time.at(time+1).utc
|
152
|
+
|
153
|
+
s02 = a.shift
|
154
|
+
s02.class.should == ScheduleWithMetadata
|
155
|
+
s02.key.should == 'sched02'
|
156
|
+
s02.type.should == 't02'
|
157
|
+
s02.cron.should == '0 * * * *'
|
158
|
+
s02.delay.should == 2
|
159
|
+
s02.next_time.should == Time.at(time).utc
|
160
|
+
s02.next_run_time.should == Time.at(time+2).utc
|
161
|
+
|
162
|
+
s03 = a.shift
|
163
|
+
s03.class.should == ScheduleWithMetadata
|
164
|
+
s03.key.should == 'sched03'
|
165
|
+
s03.type.should == 't03'
|
166
|
+
s03.cron.should == '0 * * * *'
|
167
|
+
s03.delay.should == 3
|
168
|
+
s03.next_time.should == Time.at(time).utc
|
169
|
+
s03.next_run_time.should == Time.at(time+3600).utc
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|