rsched 0.1.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/README.rdoc +93 -0
- data/bin/rsched +6 -0
- data/lib/rsched/command/rsched.rb +535 -0
- metadata +100 -0
data/README.rdoc
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
= rsched
|
2
|
+
|
3
|
+
A Generic Reliable Scheduler. It's like cron, but rsched supports redundancy using multiple servers.
|
4
|
+
|
5
|
+
|
6
|
+
== Architecture
|
7
|
+
|
8
|
+
1. rsched virtually locks a record on a RDBMS.
|
9
|
+
2. if the schedule is not finished and not locked by other node, run it.
|
10
|
+
3. if it succeeded, mark it finished.
|
11
|
+
4. if it failed, unlock it and expect to be retried.
|
12
|
+
|
13
|
+
|
14
|
+
== Install
|
15
|
+
|
16
|
+
$ gem install rsched
|
17
|
+
$ gem install dbd-mysql # to use MySQL as a lock database
|
18
|
+
$ gem install dbd-sqlite3 # to use SQLite3 as a lock database
|
19
|
+
|
20
|
+
|
21
|
+
== Schedule
|
22
|
+
|
23
|
+
A schedule consists of ident, time and action. Ident describes unique identifier of the schedule that. Time describes when it should be scheduled. Action is an description of the schedule.
|
24
|
+
|
25
|
+
Format of the time is same as cron. See `man 5 cron` for details.
|
26
|
+
|
27
|
+
_Example:_
|
28
|
+
|
29
|
+
# Run every minutes
|
30
|
+
$ rsched -a 'mysched * * * * * my descriptoin of this action' ...
|
31
|
+
|
32
|
+
# Run every day at 00:00
|
33
|
+
$ rsched -a 'mywork 0 0 * * * aaaa uuu ee' ...
|
34
|
+
|
35
|
+
|
36
|
+
== Usage
|
37
|
+
|
38
|
+
Usage: rsched [options] [-- <ARGV-for-exec-or-run>]
|
39
|
+
--configure PATH.yaml Write configuration file
|
40
|
+
--exec COMMAND Execute command
|
41
|
+
--run SCRIPT.rb Run method named 'run' defined in the script
|
42
|
+
-a, --add EXPR Add an execution schedule
|
43
|
+
-t, --timeout SEC Retry timeout (default: 600)
|
44
|
+
-r, --resume SEC Limit time to resume tasks (default: 3600)
|
45
|
+
-E, --delete SEC Limit time to delete tasks (default: 2592000)
|
46
|
+
-n, --name NAME Unique name of this node (default: PID.HOSTNAME)
|
47
|
+
-w, --delay SEC Delay time before running a task (default: 0)
|
48
|
+
-F, --from UNIX_TIME_OR_now Time to start scheduling
|
49
|
+
-i, --interval SEC Scheduling interval (default: 10)
|
50
|
+
-T, --type TYPE Lock database type (default: mysql)
|
51
|
+
-D, --database DB Database name
|
52
|
+
-H, --host HOST[:PORT] Database host
|
53
|
+
-u, --user NAME Database user name
|
54
|
+
-p, --password PASSWORD Database password
|
55
|
+
-d, --daemon PIDFILE Daemonize (default: foreground)
|
56
|
+
-f, --file PATH.yaml Read configuration file
|
57
|
+
|
58
|
+
Lock database (-T) is used to synchronize scheduling status over multiple servers. Rsched supports following database types:
|
59
|
+
|
60
|
+
* *mysql* uses MySQL as a lock database. Note that 'dbd-mysql' gem must be installed.
|
61
|
+
* *sqlite3* uses SQLite3 as a lock database. Note that 'dbd-sqlite3' gem must be installed.
|
62
|
+
|
63
|
+
|
64
|
+
One of --exec, --run or --configure is required. The behavior of the commands is described below:
|
65
|
+
|
66
|
+
|
67
|
+
=== exec
|
68
|
+
|
69
|
+
Execute a command when an action is scheduled. ident, time and action is passed to the stdin with tab-separated format. The command have to exit with status code 0 when it succeeded.
|
70
|
+
|
71
|
+
_Example:_
|
72
|
+
|
73
|
+
#!/usr/bin/env ruby
|
74
|
+
ident, time, action = STDIN.read.split("\t", 3)
|
75
|
+
t = Time.at(time.to_i)
|
76
|
+
puts "scheduled on #{t} ident=#{ident} action=#{action}"
|
77
|
+
|
78
|
+
# $ rsched -a 'mysched * * * * * des' -T sqlite3 -D test.db -F now --exec ./this_file
|
79
|
+
|
80
|
+
|
81
|
+
=== run
|
82
|
+
|
83
|
+
This is same as 'exec' except that this calls a method named 'run' defined in the file instead of executing the file. It is assumed it succeeded if the method doesn't any raise errors.
|
84
|
+
|
85
|
+
_Example:_
|
86
|
+
|
87
|
+
def run(ident, time, action)
|
88
|
+
t = Time.at(time)
|
89
|
+
puts "scheduled on #{t} ident=#{ident} action=#{action}"
|
90
|
+
end
|
91
|
+
|
92
|
+
# $ rsched -a 'mysched * * * * * des' -T sqlite3 -D test.db -F now --exec ./this_file.rb
|
93
|
+
|
data/bin/rsched
ADDED
@@ -0,0 +1,535 @@
|
|
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
|
+
require 'optparse'
|
282
|
+
|
283
|
+
op = OptionParser.new
|
284
|
+
|
285
|
+
op.banner += " [-- <ARGV-for-exec-or-run>]"
|
286
|
+
|
287
|
+
confout = nil
|
288
|
+
schedule = []
|
289
|
+
|
290
|
+
defaults = {
|
291
|
+
:timeout => 600,
|
292
|
+
:resume => 3600,
|
293
|
+
:delete => 2592000,
|
294
|
+
:delay => 0,
|
295
|
+
:interval => 10,
|
296
|
+
:type => 'mysql',
|
297
|
+
:name => "#{Process.pid}.#{`hostname`.strip}",
|
298
|
+
}
|
299
|
+
|
300
|
+
conf = { }
|
301
|
+
|
302
|
+
op.on('--configure PATH.yaml', 'Write configuration file') {|s|
|
303
|
+
confout = s
|
304
|
+
}
|
305
|
+
|
306
|
+
op.on('--exec COMMAND', 'Execute command') {|s|
|
307
|
+
conf[:exec] = s
|
308
|
+
}
|
309
|
+
|
310
|
+
op.on('--run SCRIPT.rb', 'Run method named \'run\' defined in the script') {|s|
|
311
|
+
conf[:run] = s
|
312
|
+
}
|
313
|
+
|
314
|
+
op.on('-a', '--add EXPR', 'Add an execution schedule') {|s|
|
315
|
+
schedule << s
|
316
|
+
}
|
317
|
+
|
318
|
+
op.on('-t', '--timeout SEC', 'Retry timeout (default: 600)', Integer) {|i|
|
319
|
+
conf[:timeout] = i
|
320
|
+
}
|
321
|
+
|
322
|
+
op.on('-r', '--resume SEC', 'Limit time to resume tasks (default: 3600)', Integer) {|i|
|
323
|
+
conf[:resume] = i
|
324
|
+
}
|
325
|
+
|
326
|
+
op.on('-E', '--delete SEC', 'Limit time to delete tasks (default: 2592000)', Integer) {|i|
|
327
|
+
conf[:delete] = i
|
328
|
+
}
|
329
|
+
|
330
|
+
op.on('-n', '--name NAME', 'Unique name of this node (default: PID.HOSTNAME)') {|s|
|
331
|
+
conf[:name] = s
|
332
|
+
}
|
333
|
+
|
334
|
+
op.on('-w', '--delay SEC', 'Delay time before running a task (default: 0)', Integer) {|i|
|
335
|
+
conf[:delay] = i
|
336
|
+
}
|
337
|
+
|
338
|
+
op.on('-F', '--from YYYY-mm-dd_OR_now', 'Time to start scheduling') {|s|
|
339
|
+
if s == "now"
|
340
|
+
conf[:from] = Time.now.to_i
|
341
|
+
else
|
342
|
+
conf[:from] = Time.parse(s).to_i
|
343
|
+
end
|
344
|
+
}
|
345
|
+
|
346
|
+
#op.on('-x', '--kill-timeout SEC', 'Threashold time before killing process (default: timeout * 5)', Integer) {|i|
|
347
|
+
# conf[:kill_timeout] = i
|
348
|
+
#}
|
349
|
+
|
350
|
+
op.on('-i', '--interval SEC', 'Scheduling interval (default: 10)', Integer) {|i|
|
351
|
+
conf[:interval] = i
|
352
|
+
}
|
353
|
+
|
354
|
+
op.on('-T', '--type TYPE', 'Lock database type (default: mysql)') {|s|
|
355
|
+
conf[:type] = s
|
356
|
+
}
|
357
|
+
|
358
|
+
op.on('-D', '--database DB', 'Database name') {|s|
|
359
|
+
conf[:db_database] = s
|
360
|
+
}
|
361
|
+
|
362
|
+
op.on('-H', '--host HOST[:PORT]', 'Database host') {|s|
|
363
|
+
conf[:db_host] = s
|
364
|
+
}
|
365
|
+
|
366
|
+
op.on('-u', '--user NAME', 'Database user name') {|s|
|
367
|
+
conf[:db_user] = s
|
368
|
+
}
|
369
|
+
|
370
|
+
op.on('-p', '--password PASSWORD', 'Database password') {|s|
|
371
|
+
conf[:db_password] = s
|
372
|
+
}
|
373
|
+
|
374
|
+
op.on('-d', '--daemon PIDFILE', 'Daemonize (default: foreground)') {|s|
|
375
|
+
conf[:daemon] = s
|
376
|
+
}
|
377
|
+
|
378
|
+
op.on('-f', '--file PATH.yaml', 'Read configuration file') {|s|
|
379
|
+
conf[:file] = s
|
380
|
+
}
|
381
|
+
|
382
|
+
|
383
|
+
(class<<self;self;end).module_eval do
|
384
|
+
define_method(:usage) do |msg|
|
385
|
+
puts op.to_s
|
386
|
+
puts "error: #{msg}" if msg
|
387
|
+
exit 1
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
|
392
|
+
begin
|
393
|
+
if eqeq = ARGV.index('--')
|
394
|
+
argv = ARGV.slice!(0, eqeq)
|
395
|
+
ARGV.slice!(0)
|
396
|
+
else
|
397
|
+
argv = ARGV.slice!(0..-1)
|
398
|
+
end
|
399
|
+
op.parse!(argv)
|
400
|
+
|
401
|
+
if argv.length != 0
|
402
|
+
usage nil
|
403
|
+
end
|
404
|
+
|
405
|
+
if conf[:file]
|
406
|
+
require 'yaml'
|
407
|
+
yaml = YAML.load File.read(conf[:file])
|
408
|
+
y = {}
|
409
|
+
yaml.each_pair {|k,v| y[k.to_sym] = v }
|
410
|
+
|
411
|
+
conf = defaults.merge(y).merge(conf)
|
412
|
+
|
413
|
+
if conf[:schedule]
|
414
|
+
schedule = conf[:schedule] + schedule
|
415
|
+
end
|
416
|
+
|
417
|
+
if ARGV.empty? && conf[:args]
|
418
|
+
ARGV.clear
|
419
|
+
ARGV.concat conf[:args]
|
420
|
+
end
|
421
|
+
|
422
|
+
else
|
423
|
+
conf = defaults.merge(conf)
|
424
|
+
end
|
425
|
+
|
426
|
+
if conf[:run]
|
427
|
+
type = :run
|
428
|
+
elsif conf[:exec]
|
429
|
+
type = :exec
|
430
|
+
else
|
431
|
+
raise "--exec, --run or --configure is required"
|
432
|
+
end
|
433
|
+
|
434
|
+
if conf[:resume] <= conf[:timeout]
|
435
|
+
raise "resume time (-r) must be larger than timeout (-t)"
|
436
|
+
end
|
437
|
+
|
438
|
+
if conf[:delete] <= conf[:resume]
|
439
|
+
raise "delete time (-E) must be larger than resume time (-r)"
|
440
|
+
end
|
441
|
+
|
442
|
+
case conf[:type]
|
443
|
+
when 'mysql'
|
444
|
+
if !conf[:db_database] || !conf[:db_host] || !conf[:db_user]
|
445
|
+
raise "--database, --host and --user are required for mysql"
|
446
|
+
end
|
447
|
+
dbi = "DBI:Mysql:#{conf[:db_database]}:#{conf[:db_host]}"
|
448
|
+
|
449
|
+
when 'sqlite3'
|
450
|
+
if !conf[:db_database]
|
451
|
+
raise "--database is required for sqlite3"
|
452
|
+
end
|
453
|
+
dbi = "DBI:SQLite3:#{conf[:db_database]}"
|
454
|
+
|
455
|
+
else
|
456
|
+
raise "Unknown lock server type '#{conf[:type]}'"
|
457
|
+
end
|
458
|
+
|
459
|
+
rescue
|
460
|
+
usage $!.to_s
|
461
|
+
end
|
462
|
+
|
463
|
+
|
464
|
+
if confout
|
465
|
+
require 'yaml'
|
466
|
+
|
467
|
+
conf.delete(:file)
|
468
|
+
conf[:schedule] = schedule
|
469
|
+
conf[:args] = ARGV
|
470
|
+
|
471
|
+
y = {}
|
472
|
+
conf.each_pair {|k,v| y[k.to_s] = v }
|
473
|
+
|
474
|
+
File.open(confout, "w") {|f|
|
475
|
+
f.write y.to_yaml
|
476
|
+
}
|
477
|
+
exit 0
|
478
|
+
end
|
479
|
+
|
480
|
+
|
481
|
+
if schedule.empty?
|
482
|
+
usage "At least one -a is required"
|
483
|
+
end
|
484
|
+
|
485
|
+
|
486
|
+
puts "Using node name #{conf[:name]}"
|
487
|
+
|
488
|
+
|
489
|
+
if conf[:daemon]
|
490
|
+
exit!(0) if fork
|
491
|
+
Process.setsid
|
492
|
+
exit!(0) if fork
|
493
|
+
File.umask(0)
|
494
|
+
STDIN.reopen("/dev/null")
|
495
|
+
STDOUT.reopen("/dev/null", "w")
|
496
|
+
STDERR.reopen("/dev/null", "w")
|
497
|
+
File.open(conf[:daemon], "w") {|f|
|
498
|
+
f.write Process.pid.to_s
|
499
|
+
}
|
500
|
+
end
|
501
|
+
|
502
|
+
|
503
|
+
lock = RSched::DBLock.new(conf[:name], conf[:timeout], dbi, conf[:db_user].to_s, conf[:db_password].to_s)
|
504
|
+
worker = RSched::Engine.new(lock, conf)
|
505
|
+
|
506
|
+
schedule.each {|e|
|
507
|
+
tabs = e.split(/\s+/, 7)
|
508
|
+
ident = tabs.shift
|
509
|
+
action = tabs.pop
|
510
|
+
time = tabs.join(' ')
|
511
|
+
puts "Adding schedule ident='#{ident}' time='#{time}' action='#{action}'"
|
512
|
+
worker.set_sched(ident, action, time)
|
513
|
+
}
|
514
|
+
|
515
|
+
|
516
|
+
trap :INT do
|
517
|
+
puts "shutting down..."
|
518
|
+
worker.shutdown
|
519
|
+
end
|
520
|
+
|
521
|
+
trap :TERM do
|
522
|
+
puts "shutting down..."
|
523
|
+
worker.shutdown
|
524
|
+
end
|
525
|
+
|
526
|
+
|
527
|
+
if type == :run
|
528
|
+
load File.expand_path(conf[:run])
|
529
|
+
run_proc = method(:run)
|
530
|
+
else
|
531
|
+
run_proc = RSched::ExecRunner.new(conf[:exec])
|
532
|
+
end
|
533
|
+
|
534
|
+
worker.run(run_proc)
|
535
|
+
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rsched
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Sadayuki Furuhashi
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-07-11 00:00:00 +09:00
|
19
|
+
default_executable: rsched
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: cron-spec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - "="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 31
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 1
|
33
|
+
- 2
|
34
|
+
version: 0.1.2
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: dbi
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 5
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
- 4
|
49
|
+
- 5
|
50
|
+
version: 0.4.5
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description:
|
54
|
+
email: frsyuki@gmail.com
|
55
|
+
executables:
|
56
|
+
- rsched
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README.rdoc
|
61
|
+
files:
|
62
|
+
- bin/rsched
|
63
|
+
- lib/rsched/command/rsched.rb
|
64
|
+
- README.rdoc
|
65
|
+
has_rdoc: true
|
66
|
+
homepage:
|
67
|
+
licenses: []
|
68
|
+
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options:
|
71
|
+
- --charset=UTF-8
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
hash: 3
|
89
|
+
segments:
|
90
|
+
- 0
|
91
|
+
version: "0"
|
92
|
+
requirements: []
|
93
|
+
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.3.7
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Generic Reliable Scheduler
|
99
|
+
test_files: []
|
100
|
+
|