rq 0.1.7
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/DEPENDS +5 -0
- data/HISTORY +26 -0
- data/README +552 -0
- data/TODO +13 -0
- data/VERSION +1 -0
- data/bin/rq +391 -0
- data/bin/rq-0.1.7 +410 -0
- data/install.rb +143 -0
- data/lib/rq-0.1.7.rb +82 -0
- data/lib/rq-0.1.7/backer.rb +27 -0
- data/lib/rq-0.1.7/configfile.rb +78 -0
- data/lib/rq-0.1.7/configurator.rb +36 -0
- data/lib/rq-0.1.7/creator.rb +23 -0
- data/lib/rq-0.1.7/defaultconfig.txt +5 -0
- data/lib/rq-0.1.7/deleter.rb +39 -0
- data/lib/rq-0.1.7/executor.rb +41 -0
- data/lib/rq-0.1.7/feeder.rb +367 -0
- data/lib/rq-0.1.7/job.rb +51 -0
- data/lib/rq-0.1.7/jobqueue.rb +432 -0
- data/lib/rq-0.1.7/jobrunner.rb +63 -0
- data/lib/rq-0.1.7/jobrunnerdaemon.rb +179 -0
- data/lib/rq-0.1.7/lister.rb +22 -0
- data/lib/rq-0.1.7/locker.rb +37 -0
- data/lib/rq-0.1.7/logging.rb +117 -0
- data/lib/rq-0.1.7/mainhelper.rb +53 -0
- data/lib/rq-0.1.7/qdb.rb +634 -0
- data/lib/rq-0.1.7/querier.rb +33 -0
- data/lib/rq-0.1.7/refresher.rb +72 -0
- data/lib/rq-0.1.7/sleepcycle.rb +46 -0
- data/lib/rq-0.1.7/snapshotter.rb +25 -0
- data/lib/rq-0.1.7/statuslister.rb +22 -0
- data/lib/rq-0.1.7/submitter.rb +90 -0
- data/lib/rq-0.1.7/updater.rb +95 -0
- data/lib/rq-0.1.7/usage.rb +609 -0
- data/lib/rq-0.1.7/util.rb +286 -0
- data/lib/rq.rb +84 -0
- data/rdoc.cmd +2 -0
- data/rq +2 -0
- data/rq.gemspec +36 -0
- data/rq.help +552 -0
- data/white_box/crontab +2 -0
- data/white_box/killrq +18 -0
- data/white_box/rq_killer +27 -0
- metadata +126 -0
@@ -0,0 +1,367 @@
|
|
1
|
+
unless defined? $__rq_feeder__
|
2
|
+
module RQ
|
3
|
+
#{{{
|
4
|
+
LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
5
|
+
defined? LIBDIR
|
6
|
+
|
7
|
+
require LIBDIR + 'mainhelper'
|
8
|
+
require LIBDIR + 'job'
|
9
|
+
require LIBDIR + 'jobrunner'
|
10
|
+
require LIBDIR + 'jobrunnerdaemon'
|
11
|
+
require LIBDIR + 'jobqueue'
|
12
|
+
|
13
|
+
class Feeder < MainHelper
|
14
|
+
#{{{
|
15
|
+
DEFAULT_MIN_SLEEP = 42
|
16
|
+
DEFAULT_MAX_SLEEP = 240
|
17
|
+
DEFAULT_FEED = 2
|
18
|
+
|
19
|
+
class << self
|
20
|
+
#{{{
|
21
|
+
attr :min_sleep, true
|
22
|
+
attr :max_sleep, true
|
23
|
+
attr :feed, true
|
24
|
+
#}}}
|
25
|
+
end
|
26
|
+
|
27
|
+
def feed
|
28
|
+
#{{{
|
29
|
+
daemon do
|
30
|
+
gen_pidfile
|
31
|
+
@main.init_logging
|
32
|
+
@logger = @main.logger
|
33
|
+
set_q
|
34
|
+
|
35
|
+
@pid = Process::pid
|
36
|
+
@cmd = @main.cmd
|
37
|
+
@started = Util::timestamp
|
38
|
+
@min_sleep = Integer(@options['min_sleep'] || defval('min_sleep'))
|
39
|
+
@max_sleep = Integer(@options['max_sleep'] || defval('max_sleep'))
|
40
|
+
@max_feed = Integer(@options['feed'] || defval('feed'))
|
41
|
+
@children = Hash::new
|
42
|
+
@jrd = JobRunnerDaemon::daemon
|
43
|
+
|
44
|
+
install_signal_handlers
|
45
|
+
|
46
|
+
info{ "** STARTED **" }
|
47
|
+
info{ "version <#{ RQ::VERSION }>" }
|
48
|
+
info{ "cmd <#{ @cmd }>" }
|
49
|
+
info{ "pid <#{ @pid }>" }
|
50
|
+
info{ "pidfile <#{ @pidfile.path }>" }
|
51
|
+
info{ "jobrunnerdaemon uri <#{ @jrd.uri }> pid <#{ @jrd.pid }>" }
|
52
|
+
info{ "qpath <#{ @qpath }>" }
|
53
|
+
info{ "mode <#{ @mode }>" }
|
54
|
+
debug{ "min_sleep <#{ @min_sleep }>" }
|
55
|
+
debug{ "max_sleep <#{ @max_sleep }>" }
|
56
|
+
|
57
|
+
fill_morgue
|
58
|
+
|
59
|
+
loop do
|
60
|
+
handle_signal if $rq_signaled
|
61
|
+
throttle(@min_sleep) do
|
62
|
+
start_jobs unless busy?
|
63
|
+
if nothing_running?
|
64
|
+
relax
|
65
|
+
else
|
66
|
+
reap_jobs
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
#}}}
|
72
|
+
end
|
73
|
+
def daemon
|
74
|
+
#{{{
|
75
|
+
if @options['daemon']
|
76
|
+
fork do
|
77
|
+
Process::setsid
|
78
|
+
fork do
|
79
|
+
Dir::chdir(Util.realpath('~'))
|
80
|
+
File::umask 0
|
81
|
+
open('/dev/null','r+') do |f|
|
82
|
+
STDIN.reopen f
|
83
|
+
STDOUT.reopen f
|
84
|
+
STDERR.reopen f
|
85
|
+
end
|
86
|
+
@daemon = true
|
87
|
+
yield
|
88
|
+
exit EXIT_SUCCESS
|
89
|
+
end
|
90
|
+
exit!
|
91
|
+
end
|
92
|
+
exit!
|
93
|
+
else
|
94
|
+
@daemon = false
|
95
|
+
yield
|
96
|
+
exit EXIT_SUCCESS
|
97
|
+
end
|
98
|
+
#}}}
|
99
|
+
end
|
100
|
+
def gen_pidfile name = nil
|
101
|
+
#{{{
|
102
|
+
name ||= gen_feeder_name(@options['name'] || @qpath)
|
103
|
+
@pidfile =
|
104
|
+
begin
|
105
|
+
open name, File::CREAT | File::EXCL | File::RDWR
|
106
|
+
rescue
|
107
|
+
open name, File::RDWR
|
108
|
+
end
|
109
|
+
unless @pidfile and @pidfile.posixlock(File::LOCK_EX | File::LOCK_NB)
|
110
|
+
pid = IO::read(name) rescue nil
|
111
|
+
pid ||= 'unknown'
|
112
|
+
if @options['quiet']
|
113
|
+
exit EXIT_FAILURE
|
114
|
+
else
|
115
|
+
raise "process <#{ pid }> is already feeding from this queue"
|
116
|
+
end
|
117
|
+
else
|
118
|
+
@pidfile.rewind
|
119
|
+
@pidfile.sync = true
|
120
|
+
@pidfile.print Process::pid
|
121
|
+
@pidfile.truncate @pidfile.pos
|
122
|
+
at_exit{ FileUtils::rm_f name rescue nil }
|
123
|
+
end
|
124
|
+
#}}}
|
125
|
+
end
|
126
|
+
def gen_feeder_name path
|
127
|
+
#{{{
|
128
|
+
path = Util::realpath(path).gsub(%r|/|o, '_')
|
129
|
+
File::join(Util::realpath('~'), ".#{ path }.feeder")
|
130
|
+
#}}}
|
131
|
+
end
|
132
|
+
def install_signal_handlers
|
133
|
+
#{{{
|
134
|
+
if @daemon
|
135
|
+
$rq_signaled = false
|
136
|
+
$rq_sighup = false
|
137
|
+
$rq_sigterm = false
|
138
|
+
$rq_sigint = false
|
139
|
+
trap('SIGHUP') do
|
140
|
+
$rq_signaled = $rq_sighup = 'SIGHUP'
|
141
|
+
warn{ "signal <SIGHUP>" }
|
142
|
+
warn{ "finishing running jobs before handling signal" }
|
143
|
+
end
|
144
|
+
trap('SIGTERM') do
|
145
|
+
$rq_signaled = $rq_sigterm = 'SIGTERM'
|
146
|
+
warn{ "signal <SIGTERM>" }
|
147
|
+
warn{ "finishing running jobs before handling signal" }
|
148
|
+
end
|
149
|
+
trap('SIGINT') do
|
150
|
+
$rq_signaled = $rq_sigint = 'SIGINT'
|
151
|
+
warn{ "signal <SIGINT>" }
|
152
|
+
warn{ "finishing running jobs before handling signal" }
|
153
|
+
end
|
154
|
+
@jrd.install_signal_handlers
|
155
|
+
end
|
156
|
+
#}}}
|
157
|
+
end
|
158
|
+
def fill_morgue
|
159
|
+
#{{{
|
160
|
+
debug{ "filling morgue" }
|
161
|
+
sql = <<-sql
|
162
|
+
select * from jobs where state = 'running' and runner = '#{ Util::hostname }' and started <= '#{ @started }'
|
163
|
+
sql
|
164
|
+
db = @q.qdb
|
165
|
+
transaction do
|
166
|
+
tuples = db.execute sql
|
167
|
+
tuples.each do |tuple|
|
168
|
+
jid = tuple['jid']
|
169
|
+
if jid
|
170
|
+
sql = "update jobs set state='dead' where jid='#{ jid }'"
|
171
|
+
db.execute sql
|
172
|
+
info{ "burried job <#{ jid }>" }
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
debug{ "filled morgue" }
|
177
|
+
#}}}
|
178
|
+
end
|
179
|
+
def handle_signal
|
180
|
+
#{{{
|
181
|
+
if $rq_sigterm or $rq_sigint
|
182
|
+
reap_jobs(reap_only = true) until nothing_running?
|
183
|
+
info{ "** STOPPING **" }
|
184
|
+
@jrd.shutdown rescue nil
|
185
|
+
@pidfile.posixlock File::LOCK_UN
|
186
|
+
exit EXIT_SUCCESS
|
187
|
+
end
|
188
|
+
|
189
|
+
if $rq_sighup
|
190
|
+
reap_jobs(reap_only = true) until nothing_running?
|
191
|
+
info{ "** RESTARTING **" }
|
192
|
+
@jrd.shutdown rescue nil
|
193
|
+
Util::uncache __FILE__
|
194
|
+
@pidfile.posixlock File::LOCK_UN
|
195
|
+
Util::exec @cmd
|
196
|
+
end
|
197
|
+
#}}}
|
198
|
+
end
|
199
|
+
def throttle rate = @min_sleep
|
200
|
+
#{{{
|
201
|
+
if Numeric === rate and rate > 0
|
202
|
+
if defined? @last_throttle_time and @last_throttle_time
|
203
|
+
elapsed = Time.now - @last_throttle_time
|
204
|
+
timeout = rate - elapsed
|
205
|
+
if timeout > 0
|
206
|
+
timeout = timeout + rand(rate * 0.10)
|
207
|
+
debug{ "throttle rate of <#{ rate }> exceeded - sleeping <#{ timeout }>" }
|
208
|
+
sleep timeout
|
209
|
+
end
|
210
|
+
end
|
211
|
+
@last_throttle_time = Time.now
|
212
|
+
end
|
213
|
+
yield
|
214
|
+
#}}}
|
215
|
+
end
|
216
|
+
def start_jobs
|
217
|
+
#{{{
|
218
|
+
n_started = 0
|
219
|
+
transaction do
|
220
|
+
until busy?
|
221
|
+
break unless((job = @q.getjob))
|
222
|
+
start_job job
|
223
|
+
n_started += 1
|
224
|
+
end
|
225
|
+
end
|
226
|
+
debug{ "<#{ n_started }> jobs started" }
|
227
|
+
n_started
|
228
|
+
#}}}
|
229
|
+
end
|
230
|
+
def start_job job
|
231
|
+
#{{{
|
232
|
+
jid, command = job['jid'], job['command']
|
233
|
+
|
234
|
+
#
|
235
|
+
# we setup state slightly prematurely so jobrunner will have it availible
|
236
|
+
#
|
237
|
+
job['state'] = 'running'
|
238
|
+
job['started'] = Util::timestamp Time::now
|
239
|
+
job['runner'] = Util::hostname
|
240
|
+
|
241
|
+
jr = @jrd.runner job
|
242
|
+
cid = jr.cid
|
243
|
+
|
244
|
+
if jr and cid
|
245
|
+
jr.run
|
246
|
+
job['pid'] = cid
|
247
|
+
@children[cid] = job
|
248
|
+
@q.jobisrunning job
|
249
|
+
info{ "started - jid <#{ job['jid'] }> pid <#{ job['pid'] }> command <#{ job['command'] }>" }
|
250
|
+
else
|
251
|
+
error{ "not started - jid <#{ job['jid'] }> command <#{ job['command'] }>" }
|
252
|
+
end
|
253
|
+
|
254
|
+
cid
|
255
|
+
#}}}
|
256
|
+
end
|
257
|
+
def nothing_running?
|
258
|
+
#{{{
|
259
|
+
@children.size == 0
|
260
|
+
#}}}
|
261
|
+
end
|
262
|
+
def reap_jobs reap_only = false, blocking = true
|
263
|
+
#{{{
|
264
|
+
reaped = []
|
265
|
+
|
266
|
+
cid = status = nil
|
267
|
+
|
268
|
+
if blocking
|
269
|
+
if busy? or reap_only
|
270
|
+
cid, status = @jrd.waitpid2 -1, Process::WUNTRACED
|
271
|
+
else
|
272
|
+
loop do
|
273
|
+
debug{ "not busy wait loop" }
|
274
|
+
cid, status = @jrd.waitpid2 -1, Process::WNOHANG | Process::WUNTRACED
|
275
|
+
break if cid
|
276
|
+
start_jobs unless $rq_signaled
|
277
|
+
break if busy?
|
278
|
+
cid, status = @jrd.waitpid2 -1, Process::WNOHANG | Process::WUNTRACED
|
279
|
+
break if cid
|
280
|
+
sleep 4.2
|
281
|
+
end
|
282
|
+
cid, status = @jrd.waitpid2 -1, Process::WUNTRACED unless cid
|
283
|
+
end
|
284
|
+
else
|
285
|
+
cid, status = @jrd.waitpid2 -1, Process::WNOHANG | Process::WUNTRACED
|
286
|
+
end
|
287
|
+
|
288
|
+
if cid and status
|
289
|
+
job = @children[cid]
|
290
|
+
finish_job job, status
|
291
|
+
|
292
|
+
transaction do
|
293
|
+
loopno = 0
|
294
|
+
loop do
|
295
|
+
@q.jobisdone job
|
296
|
+
@children.delete cid
|
297
|
+
reaped << cid
|
298
|
+
|
299
|
+
start_jobs unless reap_only or $rq_signaled
|
300
|
+
|
301
|
+
if @children.size == 0 or loopno > 42
|
302
|
+
break
|
303
|
+
else
|
304
|
+
sleep 0.1
|
305
|
+
cid, status = @jrd.waitpid2 -1, Process::WNOHANG | Process::WUNTRACED
|
306
|
+
break unless cid and status
|
307
|
+
job = @children[cid]
|
308
|
+
finish_job job, status
|
309
|
+
end
|
310
|
+
loopno += 1
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
debug{ "<#{ reaped.size }> jobs reaped" }
|
316
|
+
|
317
|
+
reaped
|
318
|
+
#}}}
|
319
|
+
end
|
320
|
+
def finish_job job, status
|
321
|
+
#{{{
|
322
|
+
job['finished'] = Util::timestamp(Time::now)
|
323
|
+
job['elapsed'] = Util::stamptime(job['finished']) - Util::stamptime(job['started'])
|
324
|
+
t = status.exitstatus rescue nil
|
325
|
+
job['exit_status'] = t
|
326
|
+
job['state'] = 'finished'
|
327
|
+
if t and t == 0
|
328
|
+
info{ "finished - jid <#{ job['jid'] }> pid <#{ job['pid'] }> exit_status <#{ job['exit_status'] }>" }
|
329
|
+
else
|
330
|
+
warn{ "finished - jid <#{ job['jid'] }> pid <#{ job['pid'] }> exit_status <#{ job['exit_status'] }>" }
|
331
|
+
end
|
332
|
+
#}}}
|
333
|
+
end
|
334
|
+
def transaction
|
335
|
+
#{{{
|
336
|
+
ret = nil
|
337
|
+
if @in_transaction
|
338
|
+
ret = yield
|
339
|
+
else
|
340
|
+
begin
|
341
|
+
@in_transaction = true
|
342
|
+
@q.transaction{ ret = yield }
|
343
|
+
ensure
|
344
|
+
@in_transaction = false
|
345
|
+
end
|
346
|
+
end
|
347
|
+
ret
|
348
|
+
#}}}
|
349
|
+
end
|
350
|
+
def busy?
|
351
|
+
#{{{
|
352
|
+
@children.size >= @max_feed
|
353
|
+
#}}}
|
354
|
+
end
|
355
|
+
def relax
|
356
|
+
#{{{
|
357
|
+
seconds = rand(@max_sleep - @min_sleep + 1) + @min_sleep
|
358
|
+
debug{ "relaxing <#{ seconds }>" }
|
359
|
+
sleep seconds
|
360
|
+
#}}}
|
361
|
+
end
|
362
|
+
#}}}
|
363
|
+
end # class Feeder
|
364
|
+
#}}}
|
365
|
+
end # module RQ
|
366
|
+
$__rq_feeder__ = __FILE__
|
367
|
+
end
|
data/lib/rq-0.1.7/job.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
unless defined? $__rq_job__
|
2
|
+
module RQ
|
3
|
+
#{{{
|
4
|
+
LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
5
|
+
defined? LIBDIR
|
6
|
+
|
7
|
+
require LIBDIR + 'util'
|
8
|
+
require LIBDIR + 'qdb'
|
9
|
+
|
10
|
+
class Job
|
11
|
+
#{{{
|
12
|
+
module Methods
|
13
|
+
#{{{
|
14
|
+
def method_missing(meth, *args, &block)
|
15
|
+
#{{{
|
16
|
+
setpat = %r/=$/o
|
17
|
+
meth = "#{ meth }"
|
18
|
+
setter = meth =~ setpat
|
19
|
+
meth.gsub! setpat, ''
|
20
|
+
if fields.include? "#{ meth }"
|
21
|
+
if setter
|
22
|
+
self.send('[]=', meth, *args, &block)
|
23
|
+
else
|
24
|
+
self.send('[]', meth, *args, &block)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
super
|
28
|
+
end
|
29
|
+
#}}}
|
30
|
+
end
|
31
|
+
#}}}
|
32
|
+
end
|
33
|
+
class << self
|
34
|
+
#{{{
|
35
|
+
def new(*a, &b)
|
36
|
+
#{{{
|
37
|
+
t = QDB::tuple
|
38
|
+
kvs = Util::hashify(*a)
|
39
|
+
kvs.each{|k,v| t[k] = v}
|
40
|
+
t.extend Methods
|
41
|
+
t
|
42
|
+
#}}}
|
43
|
+
end
|
44
|
+
#}}}
|
45
|
+
end
|
46
|
+
#}}}
|
47
|
+
end # class Job
|
48
|
+
#}}}
|
49
|
+
end # module RQ
|
50
|
+
$__rq_job__ = __FILE__
|
51
|
+
end
|
@@ -0,0 +1,432 @@
|
|
1
|
+
unless defined? $__rq_jobqueue__
|
2
|
+
module RQ
|
3
|
+
#{{{
|
4
|
+
LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
5
|
+
defined? LIBDIR
|
6
|
+
|
7
|
+
require LIBDIR + 'util'
|
8
|
+
require LIBDIR + 'logging'
|
9
|
+
require LIBDIR + 'qdb'
|
10
|
+
|
11
|
+
class JobQueue
|
12
|
+
#{{{
|
13
|
+
include Logging
|
14
|
+
include Util
|
15
|
+
class Error < StandardError; end
|
16
|
+
|
17
|
+
MAX_JID = 2 ** 20
|
18
|
+
|
19
|
+
class << self
|
20
|
+
#{{{
|
21
|
+
def create path, opts = {}
|
22
|
+
#{{{
|
23
|
+
FileUtils::rm_rf path
|
24
|
+
FileUtils::mkdir_p path
|
25
|
+
db = File::join path, 'db'
|
26
|
+
qdb = QDB.create db, opts
|
27
|
+
opts['qdb'] = qdb
|
28
|
+
q = new path, opts
|
29
|
+
q
|
30
|
+
#}}}
|
31
|
+
end
|
32
|
+
#}}}
|
33
|
+
end
|
34
|
+
attr :path
|
35
|
+
attr :opts
|
36
|
+
attr :qdb
|
37
|
+
alias :db :qdb
|
38
|
+
def initialize path, opts = {}
|
39
|
+
#{{{
|
40
|
+
@path = path
|
41
|
+
@opts = opts
|
42
|
+
raise "q <#{ @path }> does not exist" unless test ?e, @path
|
43
|
+
raise "q <#{ @path }> is not a directory" unless test ?d, @path
|
44
|
+
@basename = File::basename(@path)
|
45
|
+
@dirname = File::dirname(@path)
|
46
|
+
@logger = getopt('logger', opts) || Logger::new(STDERR)
|
47
|
+
@qdb = getopt('qdb', opts) || QDB::new(File::join(@path, 'db'), 'logger' => @logger)
|
48
|
+
#}}}
|
49
|
+
end
|
50
|
+
def submit(*jobs)
|
51
|
+
#{{{
|
52
|
+
now = Util::timestamp Time.now
|
53
|
+
tuples = nil
|
54
|
+
sql = ''
|
55
|
+
|
56
|
+
jobs.each do |job|
|
57
|
+
raise "no command for job <#{ job.inspect }>" unless job['command']
|
58
|
+
|
59
|
+
tuple = QDB::tuple
|
60
|
+
|
61
|
+
tuple['command'] = job['command']
|
62
|
+
tuple['priority'] = job['priority'] || 0
|
63
|
+
tuple['tag'] = job['tag']
|
64
|
+
tuple['state'] = 'pending'
|
65
|
+
tuple['submitted'] = now
|
66
|
+
tuple['submitter'] = Util::hostname
|
67
|
+
|
68
|
+
values = QDB::q tuple
|
69
|
+
|
70
|
+
sql << "insert into jobs values (#{ values.join ',' });\n"
|
71
|
+
end
|
72
|
+
|
73
|
+
tuples = nil
|
74
|
+
transaction do
|
75
|
+
execute(sql){}
|
76
|
+
if block_given?
|
77
|
+
sql = "select * from jobs where submitted = '#{ now }'"
|
78
|
+
tuples = execute sql
|
79
|
+
end
|
80
|
+
end
|
81
|
+
tuples.each{|t| yield t} if tuples
|
82
|
+
tuples = nil
|
83
|
+
|
84
|
+
self
|
85
|
+
#}}}
|
86
|
+
end
|
87
|
+
def list(*whats)
|
88
|
+
#{{{
|
89
|
+
whats.replace(['pending', 'running', 'finished', 'dead']) if
|
90
|
+
whats.empty? or whats.include?('all')
|
91
|
+
|
92
|
+
whats.map! do |what|
|
93
|
+
case what
|
94
|
+
when %r/^\s*p/io
|
95
|
+
'pending'
|
96
|
+
when %r/^\s*r/io
|
97
|
+
'running'
|
98
|
+
when %r/^\s*f/io
|
99
|
+
'finished'
|
100
|
+
when %r/^\s*d/io
|
101
|
+
'dead'
|
102
|
+
else
|
103
|
+
what
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
where_clauses = []
|
108
|
+
|
109
|
+
whats.each do |what|
|
110
|
+
if what =~ %r/^\s*\d+\s*$/o
|
111
|
+
where_clauses << "jid=#{ QDB::q what }\n"
|
112
|
+
else
|
113
|
+
where_clauses << "state=#{ QDB::q what }\n"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
where_clause = where_clauses.join(" or \n")
|
118
|
+
|
119
|
+
sql = <<-sql
|
120
|
+
select * from jobs
|
121
|
+
where #{ where_clause }
|
122
|
+
sql
|
123
|
+
|
124
|
+
tuples = ro_transaction{ execute sql }
|
125
|
+
puts '---'
|
126
|
+
fields = @qdb.fields
|
127
|
+
tuples.each do |tuple|
|
128
|
+
puts '-'
|
129
|
+
fields.each{|f| puts " #{ f }: #{ tuple[f] }" }
|
130
|
+
end
|
131
|
+
tuples = nil
|
132
|
+
|
133
|
+
self
|
134
|
+
#}}}
|
135
|
+
end
|
136
|
+
def status(*whats)
|
137
|
+
#{{{
|
138
|
+
whats.replace(['pending', 'running', 'finished', 'dead']) if
|
139
|
+
whats.empty? or whats.include?('all')
|
140
|
+
|
141
|
+
whats.map! do |what|
|
142
|
+
case what
|
143
|
+
when %r/^\s*p/io
|
144
|
+
'pending'
|
145
|
+
when %r/^\s*r/io
|
146
|
+
'running'
|
147
|
+
when %r/^\s*f/io
|
148
|
+
'finished'
|
149
|
+
when %r/^\s*d/io
|
150
|
+
'dead'
|
151
|
+
else
|
152
|
+
what
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
puts '---'
|
157
|
+
ro_transaction do
|
158
|
+
whats.each do |what|
|
159
|
+
sql = <<-sql
|
160
|
+
select count(*) from jobs where state = '#{ what }';
|
161
|
+
sql
|
162
|
+
tuples = execute sql
|
163
|
+
tuple = tuples.first
|
164
|
+
count = (tuple ? tuple.first : 0)
|
165
|
+
puts "#{ what } : #{ count }"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
self
|
170
|
+
#}}}
|
171
|
+
end
|
172
|
+
def query where_clause = nil
|
173
|
+
#{{{
|
174
|
+
sql =
|
175
|
+
if where_clause
|
176
|
+
|
177
|
+
#
|
178
|
+
# turn =~ into like clauses
|
179
|
+
#
|
180
|
+
#where_clause.gsub!(/(=~\s*([^\s')(=]+))/om){q = $2.gsub(%r/'+|\s+/o,''); "like '%#{ q }%'"}
|
181
|
+
#
|
182
|
+
# quote everything on the rhs of an '=' sign - helps with shell problems...
|
183
|
+
#
|
184
|
+
#where_clause.gsub!(/(==?\s*([^\s')(=]+))/om){q = $2.gsub(%r/'+|\s+/o,''); "='#{ q }'"}
|
185
|
+
|
186
|
+
"select * from jobs where #{ where_clause };"
|
187
|
+
else
|
188
|
+
"select * from jobs;"
|
189
|
+
end
|
190
|
+
|
191
|
+
tuples = ro_transaction{ execute sql }
|
192
|
+
|
193
|
+
puts '---'
|
194
|
+
fields = @qdb.fields
|
195
|
+
tuples.each do |tuple|
|
196
|
+
puts '-'
|
197
|
+
fields.each{|f| puts " #{ f }: #{ tuple[f] }"}
|
198
|
+
end
|
199
|
+
tuples = nil
|
200
|
+
|
201
|
+
self
|
202
|
+
#}}}
|
203
|
+
end
|
204
|
+
def delete(*jids)
|
205
|
+
#{{{
|
206
|
+
what = jids.first || 'all'
|
207
|
+
|
208
|
+
case what
|
209
|
+
when String
|
210
|
+
sql =
|
211
|
+
case what
|
212
|
+
when %r/^\s*p/io
|
213
|
+
"delete from jobs where state = 'pending';"
|
214
|
+
when %r/^\s*r/io
|
215
|
+
"delete from jobs where state = 'running';"
|
216
|
+
when %r/^\s*f/io
|
217
|
+
"delete from jobs where state = 'finished';"
|
218
|
+
when %r/^\s*d/io
|
219
|
+
"delete from jobs where state = 'dead';"
|
220
|
+
when %r/^\s*a/io
|
221
|
+
"delete from jobs;"
|
222
|
+
else
|
223
|
+
raise ArgumentError, "cannot delete <#{ what }>"
|
224
|
+
end
|
225
|
+
else
|
226
|
+
sql = ''
|
227
|
+
jids.each do |jid|
|
228
|
+
jid = Integer jid
|
229
|
+
sql << "delete from jobs where jid = #{ jid };"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
transaction{ execute sql }
|
233
|
+
sql = nil
|
234
|
+
@qdb.vacuum
|
235
|
+
self
|
236
|
+
#}}}
|
237
|
+
end
|
238
|
+
def update(kvs,*jids)
|
239
|
+
#{{{
|
240
|
+
allowed = %w(priority command tag)
|
241
|
+
kvs.keys.each do |key|
|
242
|
+
raise "update of <#{ key }> not allowed" unless
|
243
|
+
allowed.include? key
|
244
|
+
end
|
245
|
+
|
246
|
+
raise "no jids" if jids.empty?
|
247
|
+
jids.sort!
|
248
|
+
jids.uniq!
|
249
|
+
|
250
|
+
pending = jids.delete 'pending'
|
251
|
+
|
252
|
+
update_clause = kvs.map{|k,v| "#{ k }='#{ v }'"}.join(",\n")
|
253
|
+
|
254
|
+
if pending
|
255
|
+
update_sql =
|
256
|
+
"update jobs\n" <<
|
257
|
+
"set\n#{ update_clause }\n" <<
|
258
|
+
"where\nstate='pending'"
|
259
|
+
select_sql = "select * from jobs where state='pending'"
|
260
|
+
else
|
261
|
+
where_clause = jids.map{|jid| "jid=#{ jid }"}.join(" or\n")
|
262
|
+
update_sql =
|
263
|
+
"update jobs\n" <<
|
264
|
+
"set\n#{ update_clause }\n" <<
|
265
|
+
"where\nstate='pending' and\n#{ where_clause }"
|
266
|
+
select_sql = "select * from jobs where #{ where_clause }"
|
267
|
+
end
|
268
|
+
|
269
|
+
tuples = nil
|
270
|
+
transaction do
|
271
|
+
execute update_sql
|
272
|
+
tuples = execute select_sql
|
273
|
+
end
|
274
|
+
tuples or []
|
275
|
+
#}}}
|
276
|
+
end
|
277
|
+
def transaction(*args, &block)
|
278
|
+
#{{{
|
279
|
+
@qdb.transaction(*args, &block)
|
280
|
+
#}}}
|
281
|
+
end
|
282
|
+
def ro_transaction &block
|
283
|
+
#{{{
|
284
|
+
@qdb.ro_transaction &block
|
285
|
+
#}}}
|
286
|
+
end
|
287
|
+
def execute(*args, &block)
|
288
|
+
#{{{
|
289
|
+
@qdb.execute(*args, &block)
|
290
|
+
#}}}
|
291
|
+
end
|
292
|
+
def integrity_check(*args, &block)
|
293
|
+
#{{{
|
294
|
+
@qdb.integrity_check(*args, &block)
|
295
|
+
#}}}
|
296
|
+
end
|
297
|
+
def snapshot qtmp = "#{ @basename }.snapshot", retries = nil
|
298
|
+
#{{{
|
299
|
+
qtmp ||= "#{ @basename }.snapshot"
|
300
|
+
debug{ "snapshot <#{ @path }> -> <#{ qtmp }>" }
|
301
|
+
retries = Integer(retries || 16)
|
302
|
+
debug{ "retries <#{ retries }>" }
|
303
|
+
|
304
|
+
qss = nil
|
305
|
+
loopno = 0
|
306
|
+
|
307
|
+
take_snapshot = lambda do
|
308
|
+
FileUtils::rm_rf qtmp
|
309
|
+
FileUtils::mkdir_p qtmp
|
310
|
+
%w(db db.schema lock).each do |base|
|
311
|
+
src, dest = File::join(@path, base), File::join(qtmp, base)
|
312
|
+
debug{ "cp <#{ src }> -> <#{ dest }>" }
|
313
|
+
FileUtils::cp(src, dest)
|
314
|
+
end
|
315
|
+
ss = klass::new qtmp, @opts
|
316
|
+
if ss.integrity_check
|
317
|
+
ss
|
318
|
+
else
|
319
|
+
nil
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
loop do
|
324
|
+
break if loopno >= retries
|
325
|
+
if((ss = take_snapshot.call))
|
326
|
+
debug{ "snapshot <#{ qtmp }> created" }
|
327
|
+
qss = ss
|
328
|
+
break
|
329
|
+
else
|
330
|
+
debug{ "failure <#{ loopno + 1}> of <#{ retries }> attempts to create snapshot <#{ qtmp }> - retrying..." }
|
331
|
+
end
|
332
|
+
loopno += 1
|
333
|
+
end
|
334
|
+
|
335
|
+
unless qss
|
336
|
+
debug{ "locking <#{ @path }> as last resort" }
|
337
|
+
@qdb.write_lock do
|
338
|
+
if((ss = take_snapshot.call))
|
339
|
+
debug{ "snapshot <#{ qtmp }> created" }
|
340
|
+
qss = ss
|
341
|
+
else
|
342
|
+
raise "failed <#{ loopno }> times to create snapshot <#{ qtmp }>"
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
qss
|
348
|
+
#}}}
|
349
|
+
end
|
350
|
+
def lock(*args, &block)
|
351
|
+
#{{{
|
352
|
+
@qdb.lock(*args, &block)
|
353
|
+
#}}}
|
354
|
+
end
|
355
|
+
def getjob
|
356
|
+
#{{{
|
357
|
+
sql = <<-sql
|
358
|
+
select * from jobs
|
359
|
+
where state = 'pending'
|
360
|
+
order by priority desc, submitted asc, jid asc
|
361
|
+
limit 1;
|
362
|
+
sql
|
363
|
+
tuples = execute sql
|
364
|
+
job = tuples.first
|
365
|
+
job
|
366
|
+
#}}}
|
367
|
+
end
|
368
|
+
def jobisrunning job
|
369
|
+
#{{{
|
370
|
+
sql = <<-sql
|
371
|
+
update jobs
|
372
|
+
set
|
373
|
+
pid = '#{ job['pid'] }',
|
374
|
+
state = '#{ job['state'] }',
|
375
|
+
started = '#{ job['started'] }',
|
376
|
+
runner = '#{ job['runner'] }'
|
377
|
+
where jid = #{ job['jid'] };
|
378
|
+
sql
|
379
|
+
execute sql
|
380
|
+
#}}}
|
381
|
+
end
|
382
|
+
def jobisdone job
|
383
|
+
#{{{
|
384
|
+
sql = <<-sql
|
385
|
+
update jobs
|
386
|
+
set
|
387
|
+
state = '#{ job['state'] }',
|
388
|
+
exit_status = '#{ job['exit_status'] }',
|
389
|
+
finished = '#{ job['finished'] }',
|
390
|
+
elapsed = '#{ job['elapsed'] }'
|
391
|
+
where jid = #{ job['jid'] };
|
392
|
+
sql
|
393
|
+
execute sql
|
394
|
+
#}}}
|
395
|
+
end
|
396
|
+
def mtime
|
397
|
+
#{{{
|
398
|
+
File::stat(@path).mtime
|
399
|
+
#}}}
|
400
|
+
end
|
401
|
+
def []= key, value
|
402
|
+
#{{{
|
403
|
+
sql = "select count(*) from attributes where key='#{ key }';"
|
404
|
+
tuples = @qdb.execute sql
|
405
|
+
tuple = tuples.first
|
406
|
+
count = Integer tuple['count(*)']
|
407
|
+
case count
|
408
|
+
when 0
|
409
|
+
sql = "insert into attributes values('#{ key }','#{ value }');"
|
410
|
+
@qdb.execute sql
|
411
|
+
when 1
|
412
|
+
sql = "update attributes set key='#{ key }', value='#{ value }' where key='#{ key }';"
|
413
|
+
@qdb.execute sql
|
414
|
+
else
|
415
|
+
raise "key <#{ key }> has become corrupt!"
|
416
|
+
end
|
417
|
+
#}}}
|
418
|
+
end
|
419
|
+
def attributes
|
420
|
+
#{{{
|
421
|
+
h = {}
|
422
|
+
tuples = @qdb.execute "select * from attributes;"
|
423
|
+
tuples.map!{|t| h[t['key']] = t['value']}
|
424
|
+
h
|
425
|
+
#}}}
|
426
|
+
end
|
427
|
+
#}}}
|
428
|
+
end # class JobQueue
|
429
|
+
#}}}
|
430
|
+
end # module RQ
|
431
|
+
$__rq_jobqueue__ = __FILE__
|
432
|
+
end
|