rsched 0.2.0 → 0.3.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/ChangeLog +18 -0
- data/lib/rsched/command/rsched.rb +35 -6
- data/lib/rsched/dblock.rb +10 -4
- data/lib/rsched/engine.rb +130 -19
- data/lib/rsched/lock.rb +10 -3
- data/lib/rsched/version.rb +1 -1
- data/test/dblock_test.rb +14 -11
- metadata +6 -4
data/ChangeLog
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
== 2011-07-19 version 0.3.0
|
3
|
+
|
4
|
+
* Changed not to release a lock when a task is failed
|
5
|
+
* Added -E, --release-on-fail option
|
6
|
+
* Added respectable timeout implementation
|
7
|
+
|
8
|
+
|
9
|
+
== 2011-07-11 version 0.2.0
|
10
|
+
|
11
|
+
* Splits single bin file into multiple files
|
12
|
+
* Added test codes
|
13
|
+
|
14
|
+
|
15
|
+
== 2011-07-10 version 0.1.0
|
16
|
+
|
17
|
+
* Initial release
|
18
|
+
|
@@ -9,13 +9,15 @@ confout = nil
|
|
9
9
|
schedule = []
|
10
10
|
|
11
11
|
defaults = {
|
12
|
-
:timeout =>
|
12
|
+
:timeout => 30,
|
13
13
|
:resume => 3600,
|
14
14
|
:delete => 2592000,
|
15
15
|
:delay => 0,
|
16
16
|
:interval => 10,
|
17
17
|
:type => 'mysql',
|
18
18
|
:name => "#{Process.pid}.#{`hostname`.strip}",
|
19
|
+
:kill_retry => 60,
|
20
|
+
:release_on_fail => false,
|
19
21
|
}
|
20
22
|
|
21
23
|
conf = { }
|
@@ -36,7 +38,7 @@ op.on('-a', '--add EXPR', 'Add an execution schedule') {|s|
|
|
36
38
|
schedule << s
|
37
39
|
}
|
38
40
|
|
39
|
-
op.on('-t', '--timeout SEC', 'Retry timeout (default:
|
41
|
+
op.on('-t', '--timeout SEC', 'Retry timeout (default: 30)', Integer) {|i|
|
40
42
|
conf[:timeout] = i
|
41
43
|
}
|
42
44
|
|
@@ -64,14 +66,26 @@ op.on('-F', '--from YYYY-mm-dd_OR_now', 'Time to start scheduling') {|s|
|
|
64
66
|
end
|
65
67
|
}
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
op.on('-e', '--extend-timeout SEC', 'Threashold time before extending visibility timeout (default: timeout * 3/4)', Integer) {|i|
|
70
|
+
conf[:extend_timeout] = i
|
71
|
+
}
|
72
|
+
|
73
|
+
op.on('-x', '--kill-timeout SEC', 'Threashold time before killing process (default: timeout * 10)', Integer) {|i|
|
74
|
+
conf[:kill_timeout] = i
|
75
|
+
}
|
76
|
+
|
77
|
+
op.on('-X', '--kill-retry SEC', 'Threashold time before retrying killing process (default: 60)', Integer) {|i|
|
78
|
+
conf[:kill_retry] = i
|
79
|
+
}
|
70
80
|
|
71
81
|
op.on('-i', '--interval SEC', 'Scheduling interval (default: 10)', Integer) {|i|
|
72
82
|
conf[:interval] = i
|
73
83
|
}
|
74
84
|
|
85
|
+
op.on('-E', '--release-on-fail', 'Releases lock if task failed so that other node can retry immediately', TrueClass) {|b|
|
86
|
+
conf[:release_on_fail] = b
|
87
|
+
}
|
88
|
+
|
75
89
|
op.on('-T', '--type TYPE', 'Lock database type (default: mysql)') {|s|
|
76
90
|
conf[:type] = s
|
77
91
|
}
|
@@ -177,6 +191,14 @@ begin
|
|
177
191
|
raise "Unknown lock server type '#{conf[:type]}'"
|
178
192
|
end
|
179
193
|
|
194
|
+
unless conf[:extend_timeout]
|
195
|
+
conf[:extend_timeout] = conf[:timeout] / 4 * 3
|
196
|
+
end
|
197
|
+
|
198
|
+
unless conf[:kill_timeout]
|
199
|
+
conf[:kill_timeout] = conf[:timeout] * 10
|
200
|
+
end
|
201
|
+
|
180
202
|
rescue
|
181
203
|
usage $!.to_s
|
182
204
|
end
|
@@ -248,9 +270,16 @@ end
|
|
248
270
|
if type == :run
|
249
271
|
load File.expand_path(conf[:run])
|
250
272
|
run_proc = method(:run)
|
273
|
+
if defined? terminate
|
274
|
+
kill_proc = method(:terminate)
|
275
|
+
else
|
276
|
+
kill_proc = Proc.new { }
|
277
|
+
end
|
251
278
|
else
|
252
279
|
run_proc = RSched::ExecRunner.new(conf[:exec])
|
280
|
+
kill_proc = run_proc.method(:terminate)
|
253
281
|
end
|
254
282
|
|
255
|
-
worker.
|
283
|
+
worker.init_proc(run_proc, kill_proc)
|
284
|
+
worker.run
|
256
285
|
|
data/lib/rsched/dblock.rb
CHANGED
@@ -4,9 +4,8 @@ module RSched
|
|
4
4
|
|
5
5
|
class DBLock < Lock
|
6
6
|
def initialize(hostname, timeout, uri, user, pass)
|
7
|
+
super(hostname, timeout)
|
7
8
|
require 'dbi'
|
8
|
-
@hostname = hostname
|
9
|
-
@timeout = timeout
|
10
9
|
@db = DBI.connect(uri, user, pass)
|
11
10
|
init_db
|
12
11
|
end
|
@@ -47,6 +46,13 @@ class DBLock < Lock
|
|
47
46
|
return n > 0
|
48
47
|
end
|
49
48
|
|
49
|
+
def extend_timeout(token, timeout=Time.now.to_i+@timeout)
|
50
|
+
ident, time = *token
|
51
|
+
n = @db.do('UPDATE rsched SET timeout=? WHERE ident = ? AND time = ? AND host = ?;',
|
52
|
+
timeout, ident, time, @hostname)
|
53
|
+
return n > 0
|
54
|
+
end
|
55
|
+
|
50
56
|
def delete_before(ident, time)
|
51
57
|
@db.do('DELETE FROM rsched WHERE ident = ? AND time < ? AND finish IS NOT NULL;', ident, time)
|
52
58
|
end
|
@@ -61,8 +67,8 @@ class DBLock < Lock
|
|
61
67
|
end
|
62
68
|
|
63
69
|
def try_update(ident, time, now)
|
64
|
-
n = @db.do('UPDATE rsched SET host=?, timeout=? WHERE ident = ? AND time = ? AND finish IS NULL AND
|
65
|
-
@hostname, now+@timeout, ident, time, now
|
70
|
+
n = @db.do('UPDATE rsched SET host=?, timeout=? WHERE ident = ? AND time = ? AND finish IS NULL AND timeout < ?;',
|
71
|
+
@hostname, now+@timeout, ident, time, now)
|
66
72
|
return n > 0
|
67
73
|
end
|
68
74
|
|
data/lib/rsched/engine.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thread'
|
2
|
+
require 'monitor'
|
2
3
|
require 'time'
|
3
4
|
require 'rsched/lock'
|
4
5
|
|
@@ -50,10 +51,16 @@ class Engine
|
|
50
51
|
@delay = conf[:delay]
|
51
52
|
@interval = conf[:interval]
|
52
53
|
@delete = conf[:delete]
|
54
|
+
@extend_timeout = conf[:extend_timeout]
|
55
|
+
@kill_timeout = conf[:kill_timeout]
|
56
|
+
@kill_retry = conf[:kill_retry]
|
53
57
|
@sched_start = conf[:from] || 0
|
58
|
+
@release_on_fail = conf[:release_on_fail]
|
54
59
|
@finished = false
|
55
60
|
@ss = {}
|
56
61
|
|
62
|
+
@extender = TimerThread.new(@lock, @extend_timeout, @kill_timeout, @kill_retry)
|
63
|
+
|
57
64
|
@mutex = Mutex.new
|
58
65
|
@cond = ConditionVariable.new
|
59
66
|
end
|
@@ -64,7 +71,13 @@ class Engine
|
|
64
71
|
@ss[ident] = Sched.new(cron, action, @sched_start, now-@resume, now-@delay)
|
65
72
|
end
|
66
73
|
|
67
|
-
def
|
74
|
+
def init_proc(run_proc, kill_proc)
|
75
|
+
@run_proc = run_proc
|
76
|
+
@extender.init_proc(kill_proc)
|
77
|
+
end
|
78
|
+
|
79
|
+
def run
|
80
|
+
@extender.start
|
68
81
|
until @finished
|
69
82
|
one = false
|
70
83
|
|
@@ -75,8 +88,8 @@ class Engine
|
|
75
88
|
s.queue.delete_if {|time|
|
76
89
|
next if @finished
|
77
90
|
|
78
|
-
|
79
|
-
case
|
91
|
+
token = @lock.acquire(ident, time)
|
92
|
+
case token
|
80
93
|
when nil
|
81
94
|
# already finished
|
82
95
|
true
|
@@ -87,16 +100,7 @@ class Engine
|
|
87
100
|
|
88
101
|
else
|
89
102
|
one = true
|
90
|
-
|
91
|
-
# success
|
92
|
-
@lock.finish(x)
|
93
|
-
try_delete(ident)
|
94
|
-
true
|
95
|
-
else
|
96
|
-
# fail
|
97
|
-
@lock.release(x)
|
98
|
-
false
|
99
|
-
end
|
103
|
+
process(token, ident, time, s.action)
|
100
104
|
end
|
101
105
|
}
|
102
106
|
|
@@ -114,6 +118,7 @@ class Engine
|
|
114
118
|
|
115
119
|
def shutdown
|
116
120
|
@finished = true
|
121
|
+
@extender.shutdown
|
117
122
|
@mutex.synchronize {
|
118
123
|
@cond.broadcast
|
119
124
|
}
|
@@ -138,22 +143,120 @@ class Engine
|
|
138
143
|
end
|
139
144
|
end
|
140
145
|
|
141
|
-
def process(ident, time, action
|
146
|
+
def process(token, ident, time, action)
|
147
|
+
puts "started token=#{token.inspect} time=#{time}"
|
148
|
+
|
149
|
+
@extender.set_token(token)
|
150
|
+
|
151
|
+
success = false
|
142
152
|
begin
|
143
|
-
run_proc.call(ident, time, action)
|
144
|
-
|
153
|
+
@run_proc.call(ident, time, action)
|
154
|
+
puts "finished token=#{token.inspect}"
|
155
|
+
success = true
|
145
156
|
rescue
|
146
|
-
puts "failed
|
157
|
+
puts "failed token=#{token.inspect} time=#{time}: #{$!}"
|
147
158
|
$!.backtrace.each {|bt|
|
148
159
|
puts " #{bt}"
|
149
160
|
}
|
150
|
-
|
161
|
+
end
|
162
|
+
|
163
|
+
@extender.reset_token
|
164
|
+
|
165
|
+
if success
|
166
|
+
@lock.finish(token)
|
167
|
+
cleanup_old_entries(ident)
|
168
|
+
true
|
169
|
+
else
|
170
|
+
if @release_on_fail
|
171
|
+
@lock.release(token)
|
172
|
+
end
|
173
|
+
false
|
151
174
|
end
|
152
175
|
end
|
153
176
|
|
154
|
-
def
|
177
|
+
def cleanup_old_entries(ident)
|
155
178
|
@lock.delete_before(ident, Time.now.to_i-@delete)
|
156
179
|
end
|
180
|
+
|
181
|
+
class TimerThread
|
182
|
+
include MonitorMixin
|
183
|
+
|
184
|
+
def initialize(lock, extend_timeout, kill_timeout, kill_retry)
|
185
|
+
super()
|
186
|
+
@lock = lock
|
187
|
+
@extend_timeout = extend_timeout
|
188
|
+
@kill_timeout = kill_timeout
|
189
|
+
@kill_retry = kill_retry
|
190
|
+
@kill_time = nil
|
191
|
+
@kill_proc = nil
|
192
|
+
@extend_time = nil
|
193
|
+
@token = nil
|
194
|
+
@finished = false
|
195
|
+
end
|
196
|
+
|
197
|
+
def init_proc(kill_proc)
|
198
|
+
@kill_proc = kill_proc
|
199
|
+
end
|
200
|
+
|
201
|
+
def start
|
202
|
+
@thread = Thread.new(&method(:run))
|
203
|
+
end
|
204
|
+
|
205
|
+
def join
|
206
|
+
@thread.join
|
207
|
+
end
|
208
|
+
|
209
|
+
def set_token(token)
|
210
|
+
synchronize do
|
211
|
+
now = Time.now.to_i
|
212
|
+
@extend_time = now + @extend_timeout
|
213
|
+
@kill_time = now + @kill_timeout
|
214
|
+
@token = token
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def reset_token
|
219
|
+
synchronize do
|
220
|
+
@token = nil
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def shutdown
|
225
|
+
@finished = true
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
def run
|
230
|
+
until @finished
|
231
|
+
sleep 1
|
232
|
+
synchronize do
|
233
|
+
if @token
|
234
|
+
now = Time.now.to_i
|
235
|
+
try_kill(now, @token)
|
236
|
+
try_extend(now, @token)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def try_extend(now, token)
|
243
|
+
if now > @extend_time
|
244
|
+
puts "extending token=#{token.inspect}"
|
245
|
+
@lock.extend_timeout(token, now)
|
246
|
+
@extend_time = now + @extend_timeout
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def try_kill(now, token)
|
251
|
+
if now > @kill_time
|
252
|
+
if @kill_proc
|
253
|
+
puts "killing #{token.inspect}..."
|
254
|
+
@kill_proc.call rescue nil
|
255
|
+
end
|
256
|
+
@kill_time = now + @kill_retry
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
157
260
|
end
|
158
261
|
|
159
262
|
|
@@ -161,11 +264,14 @@ class ExecRunner
|
|
161
264
|
def initialize(cmd)
|
162
265
|
@cmd = cmd + ' ' + ARGV.map {|a| Shellwords.escape(a) }.join(' ')
|
163
266
|
@iobuf = ''
|
267
|
+
@pid = nil
|
268
|
+
@next_kill = :TERM
|
164
269
|
end
|
165
270
|
|
166
271
|
def call(ident, time, action)
|
167
272
|
message = [ident, time, action].join("\t")
|
168
273
|
IO.popen(@cmd, "r+") {|io|
|
274
|
+
@pid = io.pid
|
169
275
|
io.write(message) rescue nil
|
170
276
|
io.close_write
|
171
277
|
begin
|
@@ -180,6 +286,11 @@ class ExecRunner
|
|
180
286
|
raise "Command failed"
|
181
287
|
end
|
182
288
|
end
|
289
|
+
|
290
|
+
def terminate
|
291
|
+
Process.kill(@next_kill, @pid)
|
292
|
+
@next_kill = :KILL
|
293
|
+
end
|
183
294
|
end
|
184
295
|
|
185
296
|
|
data/lib/rsched/lock.rb
CHANGED
@@ -4,16 +4,23 @@ module RSched
|
|
4
4
|
|
5
5
|
class Lock
|
6
6
|
def initialize(hostname, timeout)
|
7
|
+
@hostname = hostname
|
8
|
+
@timeout = timeout
|
7
9
|
end
|
8
10
|
|
11
|
+
attr_reader :hostname, :timeout
|
12
|
+
|
9
13
|
# acquired=token, locked=false, finished=nil
|
10
|
-
def acquire(ident, time)
|
14
|
+
def acquire(ident, time, now=Time.now.to_i)
|
15
|
+
end
|
16
|
+
|
17
|
+
def release(token, next_timeout=Time.now.to_i)
|
11
18
|
end
|
12
19
|
|
13
|
-
def
|
20
|
+
def finish(token, now=Time.now.to_i)
|
14
21
|
end
|
15
22
|
|
16
|
-
def
|
23
|
+
def extend_timeout(token, timeout=Time.now.to_i+@timeout)
|
17
24
|
end
|
18
25
|
|
19
26
|
def delete_before(ident, time)
|
data/lib/rsched/version.rb
CHANGED
data/test/dblock_test.rb
CHANGED
@@ -34,10 +34,11 @@ class DBLockTest < Test::Unit::TestCase
|
|
34
34
|
token = db2.acquire('ident1', time, now)
|
35
35
|
assert_equal false, token
|
36
36
|
|
37
|
-
# same host can relock
|
38
|
-
|
39
|
-
|
40
|
-
assert_not_equal
|
37
|
+
# v0.4.0: same host can't relock
|
38
|
+
## same host can relock
|
39
|
+
#token = db1.acquire('ident1', time, now)
|
40
|
+
#assert_not_equal nil, token
|
41
|
+
#assert_not_equal false, token
|
41
42
|
|
42
43
|
# different identifier
|
43
44
|
token = db2.acquire('ident2', time, now)
|
@@ -135,17 +136,19 @@ class DBLockTest < Test::Unit::TestCase
|
|
135
136
|
assert_not_equal nil, token
|
136
137
|
assert_not_equal false, token
|
137
138
|
|
138
|
-
#
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
139
|
+
# different host can't extend (even if same token)
|
140
|
+
ok = db2.extend_timeout(token, now+TIMEOUT*2)
|
141
|
+
assert_equal false, ok
|
142
|
+
|
143
|
+
# same host can extend timeout
|
144
|
+
ok = db1.extend_timeout(token, now+TIMEOUT*2)
|
145
|
+
assert_equal true, ok
|
143
146
|
|
144
|
-
# timeout is extended
|
147
|
+
# timeout is extended; different host can't lock
|
145
148
|
token_ = db2.acquire('ident1', time, now+TIMEOUT+1)
|
146
149
|
assert_equal false, token_
|
147
150
|
|
148
|
-
# extended timeout is expired
|
151
|
+
# extended timeout is expired; different host can lock
|
149
152
|
token = db2.acquire('ident1', time, now+TIMEOUT*2+1)
|
150
153
|
assert_not_equal nil, token
|
151
154
|
assert_not_equal false, token
|
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: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 3
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.3.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-18 00:00:00 +09:00
|
19
19
|
default_executable: rsched
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -57,6 +57,7 @@ executables:
|
|
57
57
|
extensions: []
|
58
58
|
|
59
59
|
extra_rdoc_files:
|
60
|
+
- ChangeLog
|
60
61
|
- README.rdoc
|
61
62
|
files:
|
62
63
|
- bin/rsched
|
@@ -69,6 +70,7 @@ files:
|
|
69
70
|
- test/exec_test.rb
|
70
71
|
- test/sched_test.rb
|
71
72
|
- test/test_helper.rb
|
73
|
+
- ChangeLog
|
72
74
|
- README.rdoc
|
73
75
|
- test/cat.sh
|
74
76
|
- test/fail.sh
|