rsched 0.1.0 → 0.2.0
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/lib/rsched/command/rsched.rb +1 -280
- data/lib/rsched/dblock.rb +78 -0
- data/lib/rsched/engine.rb +186 -0
- data/lib/rsched/lock.rb +27 -0
- data/lib/rsched/version.rb +5 -0
- data/test/cat.sh +2 -0
- data/test/dblock_test.rb +154 -0
- data/test/exec_test.rb +57 -0
- data/test/fail.sh +2 -0
- data/test/huge.sh +2 -0
- data/test/sched_test.rb +59 -0
- data/test/success.sh +2 -0
- data/test/test_helper.rb +14 -0
- metadata +25 -6
@@ -1,284 +1,5 @@
|
|
1
|
-
require 'thread'
|
2
|
-
require 'time'
|
3
|
-
|
4
|
-
module RSched
|
5
|
-
|
6
|
-
|
7
|
-
class Lock
|
8
|
-
def initialize(hostname, timeout)
|
9
|
-
end
|
10
|
-
|
11
|
-
# acquired=token, locked=false, finished=nil
|
12
|
-
def aquire(ident, time)
|
13
|
-
end
|
14
|
-
|
15
|
-
def release(token)
|
16
|
-
end
|
17
|
-
|
18
|
-
def finish(token)
|
19
|
-
end
|
20
|
-
|
21
|
-
def delete_before(ident, time)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
class DBLock < Lock
|
27
|
-
def initialize(hostname, timeout, uri, user, pass)
|
28
|
-
require 'dbi'
|
29
|
-
@hostname = hostname
|
30
|
-
@timeout = timeout
|
31
|
-
@db = DBI.connect(uri, user, pass)
|
32
|
-
init_db
|
33
|
-
end
|
34
|
-
|
35
|
-
def init_db
|
36
|
-
sql = ''
|
37
|
-
sql << 'CREATE TABLE IF NOT EXISTS rsched ('
|
38
|
-
sql << ' ident VARCHAR(256) NOT NULL,'
|
39
|
-
sql << ' time INT NOT NULL,'
|
40
|
-
sql << ' host VARCHAR(256),'
|
41
|
-
sql << ' timeout INT,'
|
42
|
-
sql << ' finish INT,'
|
43
|
-
sql << ' PRIMARY KEY (ident, time));'
|
44
|
-
@db.execute(sql)
|
45
|
-
end
|
46
|
-
|
47
|
-
def aquire(ident, time)
|
48
|
-
now = Time.now.to_i
|
49
|
-
if try_insert(ident, time, now) || try_update(ident, time, now)
|
50
|
-
return [ident, time]
|
51
|
-
elsif check_finished(ident, time)
|
52
|
-
return nil
|
53
|
-
else
|
54
|
-
return false
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def release(token)
|
59
|
-
ident, time = *token
|
60
|
-
n = @db.do('UPDATE rsched SET timeout=? WHERE ident = ? AND time = ? AND host = ?;',
|
61
|
-
0, ident, time, @hostname)
|
62
|
-
return n > 0
|
63
|
-
end
|
64
|
-
|
65
|
-
def finish(token)
|
66
|
-
ident, time = *token
|
67
|
-
now = Time.now.to_i
|
68
|
-
n = @db.do('UPDATE rsched SET finish=? WHERE ident = ? AND time = ? AND host = ?;',
|
69
|
-
now, ident, time, @hostname)
|
70
|
-
return n > 0
|
71
|
-
end
|
72
|
-
|
73
|
-
def delete_before(ident, time)
|
74
|
-
@db.do('DELETE FROM rsched WHERE ident = ? AND time < ? AND finish IS NOT NULL;', ident, time)
|
75
|
-
end
|
76
|
-
|
77
|
-
private
|
78
|
-
def try_insert(ident, time, now)
|
79
|
-
n = @db.do('INSERT INTO rsched (ident, time, host, timeout) VALUES (?, ?, ?, ?);',
|
80
|
-
ident, time, @hostname, now+@timeout)
|
81
|
-
return n > 0
|
82
|
-
rescue # TODO unique error
|
83
|
-
return false
|
84
|
-
end
|
85
|
-
|
86
|
-
def try_update(ident, time, now)
|
87
|
-
n = @db.do('UPDATE rsched SET host=?, timeout=? WHERE ident = ? AND time = ? AND finish IS NULL AND (timeout < ? OR host = ?);',
|
88
|
-
@hostname, now+@timeout, ident, time, now, @hostname)
|
89
|
-
return n > 0
|
90
|
-
end
|
91
|
-
|
92
|
-
def check_finished(ident, time)
|
93
|
-
x = @db.select_one('SELECT finish FROM rsched WHERE ident = ? AND time = ? AND finish IS NOT NULL;',
|
94
|
-
ident, time)
|
95
|
-
return x != nil
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
class Engine
|
101
|
-
class Sched
|
102
|
-
def initialize(cron, action, sched_start, from=Time.now.to_i, to=Time.now.to_i)
|
103
|
-
@tab = CronSpec::CronSpecification.new(cron)
|
104
|
-
@action = action
|
105
|
-
@sched_start = sched_start
|
106
|
-
@queue = []
|
107
|
-
@last_time = from
|
108
|
-
sched(to)
|
109
|
-
end
|
110
|
-
|
111
|
-
attr_reader :queue, :action
|
112
|
-
|
113
|
-
def sched(now)
|
114
|
-
while @last_time <= now
|
115
|
-
t = Time.at(@last_time).utc
|
116
|
-
if @tab.is_specification_in_effect?(t)
|
117
|
-
time = create_time_key(t)
|
118
|
-
@queue << time if time >= @sched_start
|
119
|
-
end
|
120
|
-
@last_time += 60
|
121
|
-
end
|
122
|
-
@queue.uniq!
|
123
|
-
end
|
124
|
-
|
125
|
-
private
|
126
|
-
if Time.respond_to?(:strptime)
|
127
|
-
def create_time_key(t)
|
128
|
-
Time.strptime(t.strftime('%Y%m%d%H%M00UTC'), '%Y%m%d%H%M%S%Z').to_i
|
129
|
-
end
|
130
|
-
else
|
131
|
-
require 'date'
|
132
|
-
def create_time_key(t)
|
133
|
-
Time.parse(DateTime.strptime(t.strftime('%Y%m%d%H%M00UTC'), '%Y%m%d%H%M%S').to_s).to_i
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
def initialize(lock, conf)
|
139
|
-
require 'cron-spec'
|
140
|
-
@lock = lock
|
141
|
-
@resume = conf[:resume]
|
142
|
-
@delay = conf[:delay]
|
143
|
-
@interval = conf[:interval]
|
144
|
-
@delete = conf[:delete]
|
145
|
-
@sched_start = conf[:from] || 0
|
146
|
-
@finished = false
|
147
|
-
@ss = {}
|
148
|
-
|
149
|
-
@mutex = Mutex.new
|
150
|
-
@cond = ConditionVariable.new
|
151
|
-
end
|
152
|
-
|
153
|
-
# {cron => (ident,action)}
|
154
|
-
def set_sched(ident, action, cron)
|
155
|
-
now = Time.now.to_i
|
156
|
-
@ss[ident] = Sched.new(cron, action, @sched_start, now-@resume, now-@delay)
|
157
|
-
end
|
158
|
-
|
159
|
-
def run(run_proc)
|
160
|
-
until @finished
|
161
|
-
one = false
|
162
|
-
|
163
|
-
now = Time.now.to_i - @delay
|
164
|
-
@ss.each_pair {|ident,s|
|
165
|
-
|
166
|
-
s.sched(now)
|
167
|
-
s.queue.delete_if {|time|
|
168
|
-
next if @finished
|
169
|
-
|
170
|
-
x = @lock.aquire(ident, time)
|
171
|
-
case x
|
172
|
-
when nil
|
173
|
-
# already finished
|
174
|
-
true
|
175
|
-
|
176
|
-
when false
|
177
|
-
# not finished but already locked
|
178
|
-
false
|
179
|
-
|
180
|
-
else
|
181
|
-
one = true
|
182
|
-
if process(ident, time, s.action, run_proc)
|
183
|
-
# success
|
184
|
-
@lock.finish(x)
|
185
|
-
try_delete(ident)
|
186
|
-
true
|
187
|
-
else
|
188
|
-
# fail
|
189
|
-
@lock.release(x)
|
190
|
-
false
|
191
|
-
end
|
192
|
-
end
|
193
|
-
}
|
194
|
-
|
195
|
-
break if @finished
|
196
|
-
}
|
197
|
-
|
198
|
-
return if @finished
|
199
|
-
|
200
|
-
unless one
|
201
|
-
cond_wait(@interval)
|
202
|
-
end
|
203
|
-
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
def shutdown
|
208
|
-
@finished = true
|
209
|
-
@mutex.synchronize {
|
210
|
-
@cond.broadcast
|
211
|
-
}
|
212
|
-
end
|
213
|
-
|
214
|
-
private
|
215
|
-
if ConditionVariable.new.method(:wait).arity == 1
|
216
|
-
require 'timeout'
|
217
|
-
def cond_wait(sec)
|
218
|
-
@mutex.synchronize {
|
219
|
-
Timeout.timeout(sec) {
|
220
|
-
@cond.wait(@mutex)
|
221
|
-
}
|
222
|
-
}
|
223
|
-
rescue Timeout::Error
|
224
|
-
end
|
225
|
-
else
|
226
|
-
def cond_wait(sec)
|
227
|
-
@mutex.synchronize {
|
228
|
-
@cond.wait(@mutex, sec)
|
229
|
-
}
|
230
|
-
end
|
231
|
-
end
|
232
|
-
|
233
|
-
def process(ident, time, action, run_proc)
|
234
|
-
begin
|
235
|
-
run_proc.call(ident, time, action)
|
236
|
-
return true
|
237
|
-
rescue
|
238
|
-
puts "failed ident=#{ident} time=#{time}: #{$!}"
|
239
|
-
$!.backtrace.each {|bt|
|
240
|
-
puts " #{bt}"
|
241
|
-
}
|
242
|
-
return false
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def try_delete(ident)
|
247
|
-
@lock.delete_before(ident, Time.now.to_i-@delete)
|
248
|
-
end
|
249
|
-
end
|
250
|
-
|
251
|
-
|
252
|
-
class ExecRunner
|
253
|
-
def initialize(cmd)
|
254
|
-
@cmd = cmd + ' ' + ARGV.map {|a| Shellwords.escape(a) }.join(' ')
|
255
|
-
@iobuf = ''
|
256
|
-
end
|
257
|
-
|
258
|
-
def call(ident, time, action)
|
259
|
-
message = [ident, time, action].join("\t")
|
260
|
-
IO.popen(@cmd, "r+") {|io|
|
261
|
-
io.write(message) rescue nil
|
262
|
-
io.close_write
|
263
|
-
begin
|
264
|
-
while true
|
265
|
-
io.sysread(1024, @iobuf)
|
266
|
-
print @iobuf
|
267
|
-
end
|
268
|
-
rescue EOFError
|
269
|
-
end
|
270
|
-
}
|
271
|
-
if $?.to_i != 0
|
272
|
-
raise "Command failed"
|
273
|
-
end
|
274
|
-
end
|
275
|
-
end
|
276
|
-
|
277
|
-
|
278
|
-
end # module RSched
|
279
|
-
|
280
|
-
|
281
1
|
require 'optparse'
|
2
|
+
require 'rsched/engine'
|
282
3
|
|
283
4
|
op = OptionParser.new
|
284
5
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
module RSched
|
3
|
+
|
4
|
+
|
5
|
+
class DBLock < Lock
|
6
|
+
def initialize(hostname, timeout, uri, user, pass)
|
7
|
+
require 'dbi'
|
8
|
+
@hostname = hostname
|
9
|
+
@timeout = timeout
|
10
|
+
@db = DBI.connect(uri, user, pass)
|
11
|
+
init_db
|
12
|
+
end
|
13
|
+
|
14
|
+
def init_db
|
15
|
+
sql = ''
|
16
|
+
sql << 'CREATE TABLE IF NOT EXISTS rsched ('
|
17
|
+
sql << ' ident VARCHAR(256) NOT NULL,'
|
18
|
+
sql << ' time INT NOT NULL,'
|
19
|
+
sql << ' host VARCHAR(256),'
|
20
|
+
sql << ' timeout INT,'
|
21
|
+
sql << ' finish INT,'
|
22
|
+
sql << ' PRIMARY KEY (ident, time));'
|
23
|
+
@db.execute(sql)
|
24
|
+
end
|
25
|
+
|
26
|
+
def acquire(ident, time, now=Time.now.to_i)
|
27
|
+
if try_insert(ident, time, now) || try_update(ident, time, now)
|
28
|
+
return [ident, time]
|
29
|
+
elsif check_finished(ident, time)
|
30
|
+
return nil
|
31
|
+
else
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def release(token)
|
37
|
+
ident, time = *token
|
38
|
+
n = @db.do('UPDATE rsched SET timeout=? WHERE ident = ? AND time = ? AND host = ?;',
|
39
|
+
0, ident, time, @hostname)
|
40
|
+
return n > 0
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish(token, now=Time.now.to_i)
|
44
|
+
ident, time = *token
|
45
|
+
n = @db.do('UPDATE rsched SET finish=? WHERE ident = ? AND time = ? AND host = ?;',
|
46
|
+
now, ident, time, @hostname)
|
47
|
+
return n > 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def delete_before(ident, time)
|
51
|
+
@db.do('DELETE FROM rsched WHERE ident = ? AND time < ? AND finish IS NOT NULL;', ident, time)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def try_insert(ident, time, now)
|
56
|
+
n = @db.do('INSERT INTO rsched (ident, time, host, timeout) VALUES (?, ?, ?, ?);',
|
57
|
+
ident, time, @hostname, now+@timeout)
|
58
|
+
return n > 0
|
59
|
+
rescue # TODO unique error
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
|
63
|
+
def try_update(ident, time, now)
|
64
|
+
n = @db.do('UPDATE rsched SET host=?, timeout=? WHERE ident = ? AND time = ? AND finish IS NULL AND (timeout < ? OR host = ?);',
|
65
|
+
@hostname, now+@timeout, ident, time, now, @hostname)
|
66
|
+
return n > 0
|
67
|
+
end
|
68
|
+
|
69
|
+
def check_finished(ident, time)
|
70
|
+
x = @db.select_one('SELECT finish FROM rsched WHERE ident = ? AND time = ? AND finish IS NOT NULL;',
|
71
|
+
ident, time)
|
72
|
+
return x != nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'time'
|
3
|
+
require 'rsched/lock'
|
4
|
+
|
5
|
+
module RSched
|
6
|
+
|
7
|
+
|
8
|
+
class Engine
|
9
|
+
class Sched
|
10
|
+
def initialize(cron, action, sched_start, from=Time.now.to_i, to=Time.now.to_i)
|
11
|
+
require 'cron-spec'
|
12
|
+
@tab = CronSpec::CronSpecification.new(cron)
|
13
|
+
@action = action
|
14
|
+
@sched_start = sched_start
|
15
|
+
@queue = []
|
16
|
+
@last_time = from
|
17
|
+
sched(to)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :queue, :action
|
21
|
+
|
22
|
+
def sched(now)
|
23
|
+
while @last_time <= now
|
24
|
+
t = Time.at(@last_time).utc
|
25
|
+
if @tab.is_specification_in_effect?(t)
|
26
|
+
time = create_time_key(t)
|
27
|
+
@queue << time if time >= @sched_start
|
28
|
+
end
|
29
|
+
@last_time += 60
|
30
|
+
end
|
31
|
+
@queue.uniq!
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
if Time.respond_to?(:strptime)
|
36
|
+
def create_time_key(t)
|
37
|
+
Time.strptime(t.strftime('%Y%m%d%H%M00UTC'), '%Y%m%d%H%M%S%Z').to_i
|
38
|
+
end
|
39
|
+
else
|
40
|
+
require 'date'
|
41
|
+
def create_time_key(t)
|
42
|
+
Time.parse(DateTime.strptime(t.strftime('%Y%m%d%H%M00UTC'), '%Y%m%d%H%M%S').to_s).to_i
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(lock, conf)
|
48
|
+
@lock = lock
|
49
|
+
@resume = conf[:resume]
|
50
|
+
@delay = conf[:delay]
|
51
|
+
@interval = conf[:interval]
|
52
|
+
@delete = conf[:delete]
|
53
|
+
@sched_start = conf[:from] || 0
|
54
|
+
@finished = false
|
55
|
+
@ss = {}
|
56
|
+
|
57
|
+
@mutex = Mutex.new
|
58
|
+
@cond = ConditionVariable.new
|
59
|
+
end
|
60
|
+
|
61
|
+
# {cron => (ident,action)}
|
62
|
+
def set_sched(ident, action, cron)
|
63
|
+
now = Time.now.to_i
|
64
|
+
@ss[ident] = Sched.new(cron, action, @sched_start, now-@resume, now-@delay)
|
65
|
+
end
|
66
|
+
|
67
|
+
def run(run_proc)
|
68
|
+
until @finished
|
69
|
+
one = false
|
70
|
+
|
71
|
+
now = Time.now.to_i - @delay
|
72
|
+
@ss.each_pair {|ident,s|
|
73
|
+
|
74
|
+
s.sched(now)
|
75
|
+
s.queue.delete_if {|time|
|
76
|
+
next if @finished
|
77
|
+
|
78
|
+
x = @lock.acquire(ident, time)
|
79
|
+
case x
|
80
|
+
when nil
|
81
|
+
# already finished
|
82
|
+
true
|
83
|
+
|
84
|
+
when false
|
85
|
+
# not finished but already locked
|
86
|
+
false
|
87
|
+
|
88
|
+
else
|
89
|
+
one = true
|
90
|
+
if process(ident, time, s.action, run_proc)
|
91
|
+
# success
|
92
|
+
@lock.finish(x)
|
93
|
+
try_delete(ident)
|
94
|
+
true
|
95
|
+
else
|
96
|
+
# fail
|
97
|
+
@lock.release(x)
|
98
|
+
false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
}
|
102
|
+
|
103
|
+
break if @finished
|
104
|
+
}
|
105
|
+
|
106
|
+
return if @finished
|
107
|
+
|
108
|
+
unless one
|
109
|
+
cond_wait(@interval)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def shutdown
|
116
|
+
@finished = true
|
117
|
+
@mutex.synchronize {
|
118
|
+
@cond.broadcast
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
if ConditionVariable.new.method(:wait).arity == 1
|
124
|
+
require 'timeout'
|
125
|
+
def cond_wait(sec)
|
126
|
+
@mutex.synchronize {
|
127
|
+
Timeout.timeout(sec) {
|
128
|
+
@cond.wait(@mutex)
|
129
|
+
}
|
130
|
+
}
|
131
|
+
rescue Timeout::Error
|
132
|
+
end
|
133
|
+
else
|
134
|
+
def cond_wait(sec)
|
135
|
+
@mutex.synchronize {
|
136
|
+
@cond.wait(@mutex, sec)
|
137
|
+
}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def process(ident, time, action, run_proc)
|
142
|
+
begin
|
143
|
+
run_proc.call(ident, time, action)
|
144
|
+
return true
|
145
|
+
rescue
|
146
|
+
puts "failed ident=#{ident} time=#{time}: #{$!}"
|
147
|
+
$!.backtrace.each {|bt|
|
148
|
+
puts " #{bt}"
|
149
|
+
}
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def try_delete(ident)
|
155
|
+
@lock.delete_before(ident, Time.now.to_i-@delete)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
class ExecRunner
|
161
|
+
def initialize(cmd)
|
162
|
+
@cmd = cmd + ' ' + ARGV.map {|a| Shellwords.escape(a) }.join(' ')
|
163
|
+
@iobuf = ''
|
164
|
+
end
|
165
|
+
|
166
|
+
def call(ident, time, action)
|
167
|
+
message = [ident, time, action].join("\t")
|
168
|
+
IO.popen(@cmd, "r+") {|io|
|
169
|
+
io.write(message) rescue nil
|
170
|
+
io.close_write
|
171
|
+
begin
|
172
|
+
while true
|
173
|
+
io.sysread(1024, @iobuf)
|
174
|
+
print @iobuf
|
175
|
+
end
|
176
|
+
rescue EOFError
|
177
|
+
end
|
178
|
+
}
|
179
|
+
if $?.to_i != 0
|
180
|
+
raise "Command failed"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
end # module RSched
|
data/lib/rsched/lock.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
module RSched
|
3
|
+
|
4
|
+
|
5
|
+
class Lock
|
6
|
+
def initialize(hostname, timeout)
|
7
|
+
end
|
8
|
+
|
9
|
+
# acquired=token, locked=false, finished=nil
|
10
|
+
def acquire(ident, time)
|
11
|
+
end
|
12
|
+
|
13
|
+
def release(token)
|
14
|
+
end
|
15
|
+
|
16
|
+
def finish(token)
|
17
|
+
end
|
18
|
+
|
19
|
+
def delete_before(ident, time)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rsched/dblock'
|
27
|
+
|
data/test/cat.sh
ADDED
data/test/dblock_test.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper'
|
2
|
+
|
3
|
+
class DBLockTest < Test::Unit::TestCase
|
4
|
+
TIMEOUT = 10
|
5
|
+
DB_PATH = File.dirname(__FILE__)+'/test.db'
|
6
|
+
DB_URI = "DBI:SQLite3:#{DB_PATH}"
|
7
|
+
|
8
|
+
def clean
|
9
|
+
FileUtils.rm_f DB_PATH
|
10
|
+
end
|
11
|
+
|
12
|
+
def test1_db
|
13
|
+
RSched::DBLock.new('test1', TIMEOUT, DB_URI, '', '')
|
14
|
+
end
|
15
|
+
|
16
|
+
def test2_db
|
17
|
+
RSched::DBLock.new('test2', TIMEOUT, DB_URI, '', '')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'acquire' do
|
21
|
+
clean
|
22
|
+
|
23
|
+
now = Time.now.to_i
|
24
|
+
time = Time.now.to_i / 60 * 60
|
25
|
+
|
26
|
+
db1 = test1_db
|
27
|
+
db2 = test2_db
|
28
|
+
|
29
|
+
token = db1.acquire('ident1', time, now)
|
30
|
+
assert_not_equal nil, token
|
31
|
+
assert_not_equal false, token
|
32
|
+
|
33
|
+
# different host can't lock
|
34
|
+
token = db2.acquire('ident1', time, now)
|
35
|
+
assert_equal false, token
|
36
|
+
|
37
|
+
# same host can relock
|
38
|
+
token = db1.acquire('ident1', time, now)
|
39
|
+
assert_not_equal nil, token
|
40
|
+
assert_not_equal false, token
|
41
|
+
|
42
|
+
# different identifier
|
43
|
+
token = db2.acquire('ident2', time, now)
|
44
|
+
assert_not_equal nil, token
|
45
|
+
assert_not_equal false, token
|
46
|
+
|
47
|
+
# different time
|
48
|
+
token = db2.acquire('ident1', time+60, now)
|
49
|
+
assert_not_equal nil, token
|
50
|
+
assert_not_equal false, token
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'release' do
|
54
|
+
clean
|
55
|
+
|
56
|
+
now = Time.now.to_i
|
57
|
+
time = Time.now.to_i / 60 * 60
|
58
|
+
|
59
|
+
db1 = test1_db
|
60
|
+
db2 = test2_db
|
61
|
+
|
62
|
+
token = db1.acquire('ident1', time, now)
|
63
|
+
assert_not_equal nil, token
|
64
|
+
assert_not_equal false, token
|
65
|
+
|
66
|
+
db1.release(token)
|
67
|
+
|
68
|
+
# released
|
69
|
+
token = db2.acquire('ident1', time, now)
|
70
|
+
assert_not_equal nil, token
|
71
|
+
assert_not_equal false, token
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'finish' do
|
75
|
+
clean
|
76
|
+
|
77
|
+
now = Time.now.to_i
|
78
|
+
time = Time.now.to_i / 60 * 60
|
79
|
+
|
80
|
+
db1 = test1_db
|
81
|
+
db2 = test2_db
|
82
|
+
|
83
|
+
token = db1.acquire('ident1', time, now)
|
84
|
+
assert_not_equal nil, token
|
85
|
+
assert_not_equal false, token
|
86
|
+
|
87
|
+
db1.finish(token, now)
|
88
|
+
|
89
|
+
# finished
|
90
|
+
token_ = db1.acquire('ident1', time, now)
|
91
|
+
assert_equal nil, token_
|
92
|
+
|
93
|
+
# finished
|
94
|
+
token_ = db2.acquire('ident1', time, now)
|
95
|
+
assert_equal nil, token_
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'timeout' do
|
99
|
+
clean
|
100
|
+
|
101
|
+
now = Time.now.to_i
|
102
|
+
time = Time.now.to_i / 60 * 60
|
103
|
+
|
104
|
+
db1 = test1_db
|
105
|
+
db2 = test2_db
|
106
|
+
|
107
|
+
token = db1.acquire('ident1', time, now)
|
108
|
+
assert_not_equal nil, token
|
109
|
+
assert_not_equal false, token
|
110
|
+
|
111
|
+
# not timed out
|
112
|
+
token_ = db2.acquire('ident1', time, now+TIMEOUT)
|
113
|
+
assert_equal false, token_
|
114
|
+
|
115
|
+
# timed out
|
116
|
+
token = db2.acquire('ident1', time, now+TIMEOUT+1)
|
117
|
+
assert_not_equal nil, token
|
118
|
+
assert_not_equal false, token
|
119
|
+
|
120
|
+
# taken
|
121
|
+
token = db1.acquire('ident1', time, now+TIMEOUT+1)
|
122
|
+
assert_equal false, token
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'extend' do
|
126
|
+
clean
|
127
|
+
|
128
|
+
now = Time.now.to_i
|
129
|
+
time = Time.now.to_i / 60 * 60
|
130
|
+
|
131
|
+
db1 = test1_db
|
132
|
+
db2 = test2_db
|
133
|
+
|
134
|
+
token = db1.acquire('ident1', time, now)
|
135
|
+
assert_not_equal nil, token
|
136
|
+
assert_not_equal false, token
|
137
|
+
|
138
|
+
# same host can relock
|
139
|
+
# + extend timeout
|
140
|
+
token = db1.acquire('ident1', time, now+TIMEOUT)
|
141
|
+
assert_not_equal nil, token
|
142
|
+
assert_not_equal false, token
|
143
|
+
|
144
|
+
# timeout is extended
|
145
|
+
token_ = db2.acquire('ident1', time, now+TIMEOUT+1)
|
146
|
+
assert_equal false, token_
|
147
|
+
|
148
|
+
# extended timeout is expired
|
149
|
+
token = db2.acquire('ident1', time, now+TIMEOUT*2+1)
|
150
|
+
assert_not_equal nil, token
|
151
|
+
assert_not_equal false, token
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
data/test/exec_test.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper'
|
2
|
+
|
3
|
+
class ExecTest < Test::Unit::TestCase
|
4
|
+
it 'success' do
|
5
|
+
success_sh = File.expand_path File.dirname(__FILE__)+"/success.sh"
|
6
|
+
e = RSched::ExecRunner.new(success_sh)
|
7
|
+
|
8
|
+
ident = 'ident'
|
9
|
+
time = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
10
|
+
action = 'act'
|
11
|
+
|
12
|
+
assert_nothing_raised do
|
13
|
+
e.call(ident, time, action)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'fail' do
|
18
|
+
fail_sh = File.expand_path File.dirname(__FILE__)+"/fail.sh"
|
19
|
+
e = RSched::ExecRunner.new(fail_sh)
|
20
|
+
|
21
|
+
ident = 'ident'
|
22
|
+
time = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
23
|
+
action = 'act'
|
24
|
+
|
25
|
+
assert_raise(RuntimeError) do
|
26
|
+
e.call(ident, time, action)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'stdin' do
|
31
|
+
cat_sh = File.expand_path File.dirname(__FILE__)+"/cat.sh"
|
32
|
+
out_tmp = File.expand_path File.dirname(__FILE__)+"/cat.sh.tmp"
|
33
|
+
e = RSched::ExecRunner.new("#{cat_sh} #{out_tmp}")
|
34
|
+
|
35
|
+
ident = 'ident'
|
36
|
+
time = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
37
|
+
action = 'act'
|
38
|
+
|
39
|
+
e.call(ident, time, action)
|
40
|
+
|
41
|
+
assert_equal [ident, time, action].join("\t"), File.read(out_tmp)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'huge' do
|
45
|
+
huge_sh = File.expand_path File.dirname(__FILE__)+"/huge.sh"
|
46
|
+
e = RSched::ExecRunner.new("#{huge_sh}")
|
47
|
+
|
48
|
+
ident = 'ident'
|
49
|
+
time = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
50
|
+
action = 'act'
|
51
|
+
|
52
|
+
e.call(ident, time, action)
|
53
|
+
|
54
|
+
# should finish
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
data/test/fail.sh
ADDED
data/test/huge.sh
ADDED
data/test/sched_test.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.dirname(__FILE__)+'/test_helper'
|
2
|
+
|
3
|
+
class SchedTest < Test::Unit::TestCase
|
4
|
+
MINUTE = 60
|
5
|
+
HOUR = 60*60
|
6
|
+
DAY = 60*60*24
|
7
|
+
|
8
|
+
it 'sched minutes' do
|
9
|
+
now = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
10
|
+
sched_start = now
|
11
|
+
|
12
|
+
sched = RSched::Engine::Sched.new("* * * * *", "act", sched_start, now, now)
|
13
|
+
assert_equal [now], sched.queue
|
14
|
+
|
15
|
+
sched = RSched::Engine::Sched.new("* * * * *", "act", sched_start, now, now+MINUTE*2)
|
16
|
+
assert_equal [now, now+MINUTE, now+MINUTE*2], sched.queue
|
17
|
+
|
18
|
+
sched = RSched::Engine::Sched.new("*/2 * * * *", "act", sched_start, now, now+MINUTE*2)
|
19
|
+
assert_equal [now, now+MINUTE*2], sched.queue
|
20
|
+
|
21
|
+
sched = RSched::Engine::Sched.new("0,1,3 * * * *", "act", sched_start, now, now+MINUTE*4)
|
22
|
+
assert_equal [now, now+MINUTE, now+MINUTE*3], sched.queue
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'sched hours' do
|
26
|
+
now = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
27
|
+
sched_start = now
|
28
|
+
|
29
|
+
sched = RSched::Engine::Sched.new("0 * * * *", "act", sched_start, now, now)
|
30
|
+
assert_equal [now], sched.queue
|
31
|
+
|
32
|
+
sched = RSched::Engine::Sched.new("0 * * * *", "act", sched_start, now, now+HOUR*2)
|
33
|
+
assert_equal [now, now+HOUR, now+HOUR*2], sched.queue
|
34
|
+
|
35
|
+
sched = RSched::Engine::Sched.new("0 */2 * * *", "act", sched_start, now, now+HOUR*2)
|
36
|
+
assert_equal [now, now+HOUR*2], sched.queue
|
37
|
+
|
38
|
+
sched = RSched::Engine::Sched.new("0 0,1,3 * * *", "act", sched_start, now, now+HOUR*4)
|
39
|
+
assert_equal [now, now+HOUR, now+HOUR*3], sched.queue
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'sched hours' do
|
43
|
+
now = Time.parse("2010-02-02 00:00:00 UTC").to_i
|
44
|
+
sched_start = now
|
45
|
+
|
46
|
+
sched = RSched::Engine::Sched.new("0 0 * * *", "act", sched_start, now, now)
|
47
|
+
assert_equal [now], sched.queue
|
48
|
+
|
49
|
+
sched = RSched::Engine::Sched.new("0 0 * * *", "act", sched_start, now, now+DAY*2)
|
50
|
+
assert_equal [now, now+DAY, now+DAY*2], sched.queue
|
51
|
+
|
52
|
+
sched = RSched::Engine::Sched.new("0 0 */2 * *", "act", sched_start, now, now+DAY*2)
|
53
|
+
assert_equal [now, now+DAY*2], sched.queue
|
54
|
+
|
55
|
+
sched = RSched::Engine::Sched.new("0 0 2,3,5 * *", "act", sched_start, now, now+DAY*4)
|
56
|
+
assert_equal [now, now+DAY, now+DAY*3], sched.queue
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/test/success.sh
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
$LOAD_PATH << File.dirname(__FILE__)+"/../lib"
|
3
|
+
require 'rsched/engine'
|
4
|
+
require 'fileutils'
|
5
|
+
|
6
|
+
class Test::Unit::TestCase
|
7
|
+
#class << self
|
8
|
+
# alias_method :it, :test
|
9
|
+
#end
|
10
|
+
def self.it(name, &block)
|
11
|
+
define_method("test_#{name}", &block)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rsched
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 2
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Sadayuki Furuhashi
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-07-
|
18
|
+
date: 2011-07-12 00:00:00 +09:00
|
19
19
|
default_executable: rsched
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -61,7 +61,19 @@ extra_rdoc_files:
|
|
61
61
|
files:
|
62
62
|
- bin/rsched
|
63
63
|
- lib/rsched/command/rsched.rb
|
64
|
+
- lib/rsched/dblock.rb
|
65
|
+
- lib/rsched/engine.rb
|
66
|
+
- lib/rsched/lock.rb
|
67
|
+
- lib/rsched/version.rb
|
68
|
+
- test/dblock_test.rb
|
69
|
+
- test/exec_test.rb
|
70
|
+
- test/sched_test.rb
|
71
|
+
- test/test_helper.rb
|
64
72
|
- README.rdoc
|
73
|
+
- test/cat.sh
|
74
|
+
- test/fail.sh
|
75
|
+
- test/huge.sh
|
76
|
+
- test/success.sh
|
65
77
|
has_rdoc: true
|
66
78
|
homepage:
|
67
79
|
licenses: []
|
@@ -96,5 +108,12 @@ rubygems_version: 1.3.7
|
|
96
108
|
signing_key:
|
97
109
|
specification_version: 3
|
98
110
|
summary: Generic Reliable Scheduler
|
99
|
-
test_files:
|
100
|
-
|
111
|
+
test_files:
|
112
|
+
- test/dblock_test.rb
|
113
|
+
- test/exec_test.rb
|
114
|
+
- test/sched_test.rb
|
115
|
+
- test/test_helper.rb
|
116
|
+
- test/cat.sh
|
117
|
+
- test/fail.sh
|
118
|
+
- test/huge.sh
|
119
|
+
- test/success.sh
|