rq-ruby1.8 3.4.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/Gemfile +22 -0
  2. data/Gemfile.lock +22 -0
  3. data/INSTALL +166 -0
  4. data/LICENSE +10 -0
  5. data/Makefile +6 -0
  6. data/README +1183 -0
  7. data/Rakefile +37 -0
  8. data/TODO +24 -0
  9. data/TUTORIAL +230 -0
  10. data/VERSION +1 -0
  11. data/bin/rq +902 -0
  12. data/bin/rqmailer +865 -0
  13. data/example/a.rb +7 -0
  14. data/extconf.rb +198 -0
  15. data/gemspec.rb +40 -0
  16. data/install.rb +210 -0
  17. data/lib/rq.rb +155 -0
  18. data/lib/rq/arrayfields.rb +371 -0
  19. data/lib/rq/backer.rb +31 -0
  20. data/lib/rq/configfile.rb +82 -0
  21. data/lib/rq/configurator.rb +40 -0
  22. data/lib/rq/creator.rb +54 -0
  23. data/lib/rq/cron.rb +144 -0
  24. data/lib/rq/defaultconfig.txt +5 -0
  25. data/lib/rq/deleter.rb +51 -0
  26. data/lib/rq/executor.rb +40 -0
  27. data/lib/rq/feeder.rb +527 -0
  28. data/lib/rq/ioviewer.rb +48 -0
  29. data/lib/rq/job.rb +51 -0
  30. data/lib/rq/jobqueue.rb +947 -0
  31. data/lib/rq/jobrunner.rb +110 -0
  32. data/lib/rq/jobrunnerdaemon.rb +193 -0
  33. data/lib/rq/lister.rb +47 -0
  34. data/lib/rq/locker.rb +43 -0
  35. data/lib/rq/lockfile.rb +564 -0
  36. data/lib/rq/logging.rb +124 -0
  37. data/lib/rq/mainhelper.rb +189 -0
  38. data/lib/rq/orderedautohash.rb +39 -0
  39. data/lib/rq/orderedhash.rb +240 -0
  40. data/lib/rq/qdb.rb +733 -0
  41. data/lib/rq/querier.rb +98 -0
  42. data/lib/rq/rails.rb +80 -0
  43. data/lib/rq/recoverer.rb +28 -0
  44. data/lib/rq/refresher.rb +80 -0
  45. data/lib/rq/relayer.rb +283 -0
  46. data/lib/rq/resource.rb +22 -0
  47. data/lib/rq/resourcemanager.rb +40 -0
  48. data/lib/rq/resubmitter.rb +100 -0
  49. data/lib/rq/rotater.rb +98 -0
  50. data/lib/rq/sleepcycle.rb +46 -0
  51. data/lib/rq/snapshotter.rb +40 -0
  52. data/lib/rq/sqlite.rb +286 -0
  53. data/lib/rq/statuslister.rb +48 -0
  54. data/lib/rq/submitter.rb +113 -0
  55. data/lib/rq/toucher.rb +182 -0
  56. data/lib/rq/updater.rb +94 -0
  57. data/lib/rq/usage.rb +1222 -0
  58. data/lib/rq/util.rb +304 -0
  59. data/rdoc.sh +17 -0
  60. data/rq-ruby1.8.gemspec +120 -0
  61. data/test/.gitignore +1 -0
  62. data/test/test_rq.rb +145 -0
  63. data/white_box/crontab +2 -0
  64. data/white_box/joblist +8 -0
  65. data/white_box/killrq +18 -0
  66. data/white_box/rq_killer +27 -0
  67. metadata +208 -0
@@ -0,0 +1,48 @@
1
+ unless defined? $__rq_ioviewer__
2
+ module RQ
3
+ #--{{{
4
+ LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
5
+ defined? LIBDIR
6
+
7
+ require LIBDIR + 'mainhelper'
8
+
9
+ #
10
+ # the IOViewer class spawns an external editor command to view the
11
+ # stdin/stdout/stderr of a jid(s)
12
+ #
13
+ class IOViewer < MainHelper
14
+ #--{{{
15
+ def ioview
16
+ #--{{{
17
+ @infile = @options['infile']
18
+ debug{ "infile <#{ @infile }>" }
19
+
20
+ jobs = []
21
+ if @infile
22
+ open(@infile) do |f|
23
+ debug{ "reading jobs from <#{ @infile }>" }
24
+ loadio f, @infile, jobs
25
+ end
26
+ end
27
+ if stdin?
28
+ debug{ "reading jobs from <stdin>" }
29
+ loadio stdin, 'stdin', jobs
30
+ end
31
+ jobs.each{|job| @argv << Integer(job['jid'])}
32
+
33
+ editor = @options['editor'] || ENV['RQ_EDITOR'] || ENV['RQ_IOVIEW'] || 'vim -R -o'
34
+ @argv.each do |jid|
35
+ jid = Integer jid
36
+ ios = %w( stdin stdout stderr ).map{|d| File.join @qpath, d, jid.to_s}
37
+ command = "#{ editor } #{ ios.join ' ' }"
38
+ system(command) #or error{ "command <#{ command }> failed with <#{ $? }>" }
39
+ end
40
+ self
41
+ #--}}}
42
+ end
43
+ #--}}}
44
+ end # class IOViewer
45
+ #--}}}
46
+ end # module RQ
47
+ $__rq_ioviewer__ = __FILE__
48
+ end
@@ -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 'arrayfields'
8
+ require 'yaml'
9
+
10
+ require LIBDIR + 'util'
11
+ require LIBDIR + 'qdb'
12
+
13
+ #
14
+ # Job is a convenience class which stamps out a QDB::tuple and extends it
15
+ # with methods that give accessor methods for each field in the hash
16
+ #
17
+ class Job < Array
18
+ #--{{{
19
+ include ArrayFields
20
+ def initialize kvs = {}
21
+ #--{{{
22
+ self.fields = QDB::FIELDS
23
+ (kvs.keys - self.fields).each{|k| self[k] = kvs[k]}
24
+ #--}}}
25
+ end
26
+ def method_missing(meth, *args, &block)
27
+ #--{{{
28
+ setpat = %r/=$/o
29
+ meth = "#{ meth }"
30
+ setter = meth =~ setpat
31
+ meth.gsub! setpat, ''
32
+ if fields.include? "#{ meth }"
33
+ if setter
34
+ self.send('[]=', meth, *args, &block)
35
+ else
36
+ self.send('[]', meth, *args, &block)
37
+ end
38
+ else
39
+ super
40
+ end
41
+ #--}}}
42
+ end
43
+ def to_yaml
44
+ to_hash.to_yaml
45
+ end
46
+ #--}}}
47
+ end # class Job
48
+ #--}}}
49
+ end # module RQ
50
+ $__rq_job__ = __FILE__
51
+ end
@@ -0,0 +1,947 @@
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 'tempfile'
8
+
9
+ require LIBDIR + 'util'
10
+ require LIBDIR + 'logging'
11
+ require LIBDIR + 'qdb'
12
+ require LIBDIR + 'orderedhash'
13
+ require LIBDIR + 'orderedautohash'
14
+
15
+ #
16
+ # the JobQueue class is responsible for high level access to the job queue
17
+ #
18
+ class JobQueue
19
+ #--{{{
20
+ include Logging
21
+ include Util
22
+ class Error < StandardError; end
23
+
24
+ MAX_JID = 2 ** 20
25
+
26
+ class << self
27
+ #--{{{
28
+ def create path, opts = {}
29
+ #--{{{
30
+ FileUtils::rm_rf path
31
+ FileUtils::mkdir_p path
32
+ db = File::join path, 'db'
33
+ qdb = QDB.create db, opts
34
+ opts['qdb'] = qdb
35
+ q = new path, opts
36
+ FileUtils::mkdir_p q.bin
37
+ FileUtils::mkdir_p q.stdin
38
+ FileUtils::mkdir_p q.stdout
39
+ FileUtils::mkdir_p q.stderr
40
+ FileUtils::mkdir_p q.data
41
+ q
42
+ #--}}}
43
+ end
44
+ #--}}}
45
+ end
46
+
47
+ attr :path
48
+ attr :bin
49
+ attr :stdin
50
+ attr :stdout
51
+ attr :stderr
52
+ attr :data
53
+ attr :opts
54
+ attr :qdb
55
+ alias :db :qdb
56
+
57
+ def initialize path, opts = {}
58
+ #--{{{
59
+ @path = path # do NOT expand this or it'll be fubar from misc nfs mounts!!
60
+ @bin = File::join @path, 'bin'
61
+ @stdin = File::join @path, 'stdin'
62
+ @stdout = File::join @path, 'stdout'
63
+ @stderr = File::join @path, 'stderr'
64
+ @data = File::join @path, 'data'
65
+ @opts = opts
66
+ raise "q <#{ @path }> does not exist" unless test ?e, @path
67
+ raise "q <#{ @path }> is not a directory" unless test ?d, @path
68
+ @basename = File::basename(@path)
69
+ @dirname = File::dirname(@path)
70
+ @logger = getopt('logger', opts) || Logger::new(STDERR)
71
+ @qdb = getopt('qdb', opts) || QDB::new(File::join(@path, 'db'), 'logger' => @logger)
72
+ @in_transaction = false
73
+ @in_ro_transaction = false
74
+ #--}}}
75
+ end
76
+ def stdin4 jid
77
+ #--{{{
78
+ "stdin/#{ jid }"
79
+ #--}}}
80
+ end
81
+ def standard_in_4 jid
82
+ #--{{{
83
+ File::expand_path(File::join(path, stdin4(jid)))
84
+ #--}}}
85
+ end
86
+ def stdout4 jid
87
+ #--{{{
88
+ "stdout/#{ jid }"
89
+ #--}}}
90
+ end
91
+ def standard_out_4 jid
92
+ #--{{{
93
+ File::expand_path(File::join(path, stdout4(jid)))
94
+ #--}}}
95
+ end
96
+ def stderr4 jid
97
+ #--{{{
98
+ "stderr/#{ jid }"
99
+ #--}}}
100
+ end
101
+ def standard_err_4 jid
102
+ #--{{{
103
+ File::expand_path(File::join(path, stderr4(jid)))
104
+ #--}}}
105
+ end
106
+ def data4 jid
107
+ #--{{{
108
+ "data/#{ jid }"
109
+ #--}}}
110
+ end
111
+ def data_4 jid
112
+ #--{{{
113
+ File::expand_path(File::join(path, data4(jid)))
114
+ #--}}}
115
+ end
116
+ def submit(*jobs, &block)
117
+ #--{{{
118
+ if jobs.size == 1 and jobs.first.is_a?(String)
119
+ jobs = [ { "command" => jobs.to_s } ]
120
+ end
121
+
122
+ now = Util::timestamp Time::now
123
+
124
+ transaction do
125
+ sql = "select max(jid) from jobs"
126
+ tuple = execute(sql).first
127
+ jid = tuple.first || 0
128
+ jid = Integer(jid) + 1
129
+
130
+ jobs.each do |job|
131
+ command = job['command']
132
+ stdin = job['stdin']
133
+ data = job['data']
134
+
135
+ raise "no command for job <#{ job.inspect }>" unless command
136
+
137
+ tmp_stdin(stdin) do |ts|
138
+ tuple = QDB::tuple
139
+
140
+ tuple['command'] = command
141
+ tuple['priority'] = job['priority'] || 0
142
+ tuple['tag'] = job['tag']
143
+ tuple['runner'] = job['runner']
144
+ tuple['restartable'] = job['restartable']
145
+ tuple['state'] = 'pending'
146
+ tuple['submitted'] = now
147
+ tuple['submitter'] = Util::hostname
148
+ tuple['stdin'] = stdin4 jid
149
+ tuple['stdout'] = nil
150
+ tuple['stderr'] = nil
151
+ tuple['data'] = data4 jid
152
+
153
+ values = QDB::q tuple
154
+
155
+ sql = "insert into jobs values (#{ values.join ',' });\n"
156
+ execute(sql){}
157
+
158
+ FileUtils::rm_rf standard_in_4(jid)
159
+ FileUtils::rm_rf standard_out_4(jid)
160
+ FileUtils::rm_rf standard_err_4(jid)
161
+ FileUtils::rm_rf data_4(jid)
162
+ FileUtils::cp ts.path, standard_in_4(jid) if ts
163
+ if data
164
+ FileUtils::cp_r data, data_4(jid)
165
+ else
166
+ FileUtils::mkdir_p data_4(jid)
167
+ end
168
+
169
+ if block
170
+ sql = "select * from jobs where jid = '#{ jid }'"
171
+ execute(sql, &block)
172
+ end
173
+ end
174
+
175
+ jid += 1
176
+ end
177
+ end
178
+
179
+ self
180
+ #--}}}
181
+ end
182
+ def resubmit(*jobs, &block)
183
+ #--{{{
184
+ now = Util::timestamp Time::now
185
+
186
+ transaction do
187
+ jobs.each do |job|
188
+ jid = Integer job['jid']
189
+ command = job['command']
190
+ stdin = job['stdin']
191
+ data = job['data']
192
+
193
+ raise "no jid for job <#{ job.inspect }>" unless jid
194
+ raise "no command for job <#{ job.inspect }>" unless command
195
+
196
+ tmp_stdin(stdin) do |ts|
197
+ tuple = QDB::tuple
198
+
199
+ tuple['jid'] = jid
200
+ tuple['command'] = command
201
+ tuple['priority'] = job['priority'] || 0
202
+ tuple['tag'] = job['tag']
203
+ tuple['runner'] = job['runner']
204
+ tuple['restartable'] = job['restartable']
205
+ tuple['state'] = 'pending'
206
+ tuple['submitted'] = now
207
+ tuple['submitter'] = Util::hostname
208
+ tuple['stdin'] = stdin4 jid
209
+ tuple['stdout'] = nil
210
+ tuple['stderr'] = nil
211
+ tuple['data'] = data4 jid
212
+
213
+ kvs = tuple.fields[1..-1].map{|f| "#{ f }=#{ QDB::q(tuple[ f ]) }"}
214
+ sql = "update jobs set #{ kvs.join ',' } where jid=#{ jid };\n"
215
+
216
+ execute(sql){}
217
+
218
+ FileUtils::rm_rf standard_in_4(jid)
219
+ FileUtils::rm_rf standard_out_4(jid)
220
+ FileUtils::rm_rf standard_err_4(jid)
221
+ #FileUtils::rm_rf data_4(jid)
222
+ FileUtils::cp ts.path, standard_in_4(jid) if ts
223
+ if data
224
+ FileUtils::mv data, data_4(jid)
225
+ else
226
+ FileUtils::mkdir_p data_4(jid)
227
+ end
228
+
229
+ if block
230
+ sql = "select * from jobs where jid = '#{ jid }'"
231
+ execute(sql, &block)
232
+ end
233
+ end # tmp_stdin
234
+ end # jobs.each
235
+ end # transaction
236
+
237
+ self
238
+ #--}}}
239
+ end
240
+ def tmp_stdin stdin = nil
241
+ #--{{{
242
+ #stdin = nil if stdin.to_s.empty?
243
+ if stdin.to_s.empty?
244
+ return(block_given? ? yield(nil) : nil)
245
+ end
246
+ stdin = STDIN if stdin == '-'
247
+
248
+ was_opened = false
249
+
250
+ begin
251
+ unless stdin.respond_to?('read') or stdin.nil?
252
+ stdin = stdin.to_s
253
+ # relative to queue
254
+ if stdin =~ %r|^@?stdin/\d+$|
255
+ stdin.gsub! %r|^@|, ''
256
+ stdin = File::join(path, stdin)
257
+ end
258
+ stdin = File.expand_path stdin
259
+ stdin = open stdin
260
+ was_opened = true
261
+ end
262
+
263
+ tmp = Tempfile::new "#{ Process::pid }_#{ rand }"
264
+ while((buf = stdin.read(8192))); tmp.write buf; end if stdin
265
+ tmp.close
266
+
267
+ if block_given?
268
+ begin
269
+ yield tmp
270
+ ensure
271
+ tmp.close!
272
+ end
273
+ else
274
+ return tmp
275
+ end
276
+ ensure
277
+ stdin.close if was_opened rescue nil
278
+ end
279
+ #--}}}
280
+ end
281
+ def list(*whats, &block)
282
+ #--{{{
283
+ ret = nil
284
+
285
+ whats.replace(%w( pending running finished dead )) if
286
+ whats.empty? or whats.include?('all')
287
+
288
+ whats.map! do |what|
289
+ case what
290
+ when %r/^\s*p/io
291
+ 'pending'
292
+ when %r/^\s*h/io
293
+ 'holding'
294
+ when %r/^\s*r/io
295
+ 'running'
296
+ when %r/^\s*f/io
297
+ 'finished'
298
+ when %r/^\s*d/io
299
+ 'dead'
300
+ else
301
+ what
302
+ end
303
+ end
304
+
305
+ where_clauses = []
306
+
307
+ whats.each do |what|
308
+ case what
309
+ when Numeric
310
+ where_clauses << "jid=#{ what }\n"
311
+ else
312
+ what = "#{ what }"
313
+ if what.to_s =~ %r/^\s*\d+\s*$/o
314
+ where_clauses << "jid=#{ QDB::q what }\n"
315
+ else
316
+ where_clauses << "state=#{ QDB::q what }\n"
317
+ end
318
+ end
319
+ end
320
+
321
+ where_clause = where_clauses.join(" or \n")
322
+
323
+ sql = <<-sql
324
+ select * from jobs
325
+ where #{ where_clause }
326
+ sql
327
+
328
+ if block
329
+ ro_transaction{ execute(sql, &block) }
330
+ else
331
+ ret = ro_transaction{ execute(sql) }
332
+ end
333
+
334
+ ret
335
+ #--}}}
336
+ end
337
+ def status options = {}
338
+ #--{{{
339
+ stats = OrderedAutoHash::new
340
+
341
+ now = Time::now
342
+
343
+ hms = lambda do |t|
344
+ elapsed =
345
+ begin
346
+ Float t
347
+ rescue
348
+ now - Util::stamptime(t, 'local' => true)
349
+ end
350
+ sh, sm, ss = Util::hms elapsed.to_f
351
+ s = "#{ '%2.2d' % sh }h#{ '%2.2d' % sm }m#{ '%05.2f' % ss }s"
352
+ end
353
+
354
+ exit_code_map =
355
+ options[:exit_code_map] || options['exit_code_map'] || {}
356
+
357
+ ro_transaction do
358
+ #
359
+ # jobs stats
360
+ #
361
+ total = 0
362
+ %w( pending holding running finished dead ).each do |state|
363
+ sql = <<-sql
364
+ select count(*) from jobs
365
+ where
366
+ state='#{ state }'
367
+ sql
368
+ tuples = execute sql
369
+ tuple = tuples.first
370
+ count = (tuple ? Integer(tuple.first || 0) : 0)
371
+ stats['jobs'][state] = count
372
+ total += count
373
+ end
374
+ stats['jobs']['total'] = total
375
+ #
376
+ # temporal stats
377
+ #
378
+ metrics = OrderedAutoHash::new
379
+ metrics['pending'] = 'submitted'
380
+ metrics['holding'] = 'submitted'
381
+ metrics['running'] = 'started'
382
+ metrics['finished'] = 'elapsed'
383
+ metrics['dead'] = 'elapsed'
384
+
385
+ metrics.each do |state, metric|
386
+ sql =
387
+ unless metric == 'elapsed'
388
+ <<-sql
389
+ select min(#{ metric }) as max, max(#{ metric }) as min
390
+ from jobs where state='#{ state }'
391
+ sql
392
+ else
393
+ <<-sql
394
+ select min(#{ metric }) as min, max(#{ metric }) as max
395
+ from jobs where state='#{ state }'
396
+ sql
397
+ end
398
+ tuple = execute(sql).first
399
+ next unless tuple
400
+
401
+ %w( min max ).each do |time|
402
+ oh = nil
403
+ t = tuple[time]
404
+ if t
405
+ sql = <<-sql
406
+ select jid from jobs where #{ metric }='#{ t }' and state='#{ state }'
407
+ sql
408
+ which = execute(sql).first
409
+ jid = (which and which['jid']).to_i
410
+ if jid
411
+ oh = OrderedAutoHash::new
412
+ oh[jid] = hms[t]
413
+ oh.yaml_inline = true
414
+ end
415
+ stats['temporal'][state][time] = oh
416
+ end
417
+ end
418
+ #stats['temporal'][state] ||= nil
419
+ end
420
+ stats['temporal'] ||= nil
421
+ #
422
+ # generate performance stats
423
+ #
424
+ sql = <<-sql
425
+ select avg(elapsed) from jobs
426
+ where
427
+ state='finished'
428
+ sql
429
+ tuples = execute sql
430
+ tuple = tuples.first
431
+ avg = (tuple ? Float(tuple.first || 0) : 0)
432
+ stats['performance']['avg_time_per_job'] = hms[avg]
433
+
434
+ list = []
435
+ 0.step(5){|i| list << (2 ** i)}
436
+ list << 24
437
+ list.sort!
438
+
439
+ list = 1, 12, 24
440
+
441
+ list.each do |n|
442
+ ago = now - (n * 3600)
443
+ ago = Util::timestamp ago
444
+ sql = <<-sql
445
+ select count(*) from jobs
446
+ where
447
+ state = 'finished' and
448
+ finished > '#{ ago }'
449
+ sql
450
+ tuples = execute sql
451
+ tuple = tuples.first
452
+ count = (tuple ? Integer(tuple.first || 0) : 0)
453
+ #stats['performance']["n_jobs_in_last_#{ n }_hrs"] = count
454
+ stats['performance']["n_jobs_in_last_hrs"][n] = count
455
+ end
456
+
457
+ #
458
+ # generate exit_status stats
459
+ #
460
+ #stats['exit_status'] = {}
461
+ sql = <<-sql
462
+ select count(*) from jobs
463
+ where
464
+ state='finished' and
465
+ exit_status=0
466
+ sql
467
+ tuples = execute sql
468
+ tuple = tuples.first
469
+ successes = (tuple ? Integer(tuple.first || 0) : 0)
470
+ stats['exit_status']['successes'] = successes
471
+
472
+ sql = <<-sql
473
+ select count(*) from jobs
474
+ where
475
+ (state='finished' and
476
+ exit_status!=0) or
477
+ state='dead'
478
+ sql
479
+ tuples = execute sql
480
+ tuple = tuples.first
481
+ failures = (tuple ? Integer(tuple.first || 0) : 0)
482
+ stats['exit_status']['failures'] = failures
483
+
484
+ exit_code_map.each do |which, codes|
485
+ exit_status_clause = codes.map{|code| "exit_status=#{ code }"}.join(' or ')
486
+ sql = <<-sql
487
+ select count(*) from jobs
488
+ where
489
+ (state='finished' and (#{ exit_status_clause }))
490
+ sql
491
+ tuples = execute sql
492
+ tuple = tuples.first
493
+ n = (tuple ? Integer(tuple.first || 0) : 0)
494
+ stats['exit_status'][which] = n
495
+ end
496
+ end
497
+
498
+ stats
499
+ #--}}}
500
+ end
501
+ def query(where_clause = nil, &block)
502
+ #--{{{
503
+ ret = nil
504
+
505
+ sql =
506
+ if where_clause
507
+
508
+ #
509
+ # turn =~ into like clauses
510
+ #
511
+ #where_clause.gsub!(/(=~\s*([^\s')(=]+))/om){q = $2.gsub(%r/'+|\s+/o,''); "like '%#{ q }%'"}
512
+ #
513
+ # quote everything on the rhs of an '=' sign - helps with shell problems...
514
+ #
515
+ #where_clause.gsub!(/(==?\s*([^\s')(=]+))/om){q = $2.gsub(%r/'+|\s+/o,''); "='#{ q }'"}
516
+
517
+ "select * from jobs where #{ where_clause };"
518
+ else
519
+ "select * from jobs;"
520
+ end
521
+
522
+ if block
523
+ ro_transaction{ execute(sql, &block) }
524
+ else
525
+ ret = ro_transaction{ execute(sql) }
526
+ end
527
+
528
+ ret
529
+ #--}}}
530
+ end
531
+ def delete(*args, &block)
532
+ #--{{{
533
+ whats, optargs = args.partition{|arg| not Hash === arg}
534
+
535
+ opts = {}
536
+ optargs.each{|oa| opts.update oa}
537
+
538
+ force = Util::getopt 'force', opts
539
+
540
+ delete_sql, select_sql = '', ''
541
+
542
+ whats << 'all' if whats.empty?
543
+
544
+ whats.each do |what|
545
+ case "#{ what }"
546
+ when %r/^\s*\d+\s*$/io # number
547
+ delete_sql << "delete from jobs where jid=#{ what } and state!='running';\n"
548
+ select_sql << "select * from jobs where jid=#{ what } and state!='running';\n"
549
+ when %r/^\s*p/io # pending
550
+ delete_sql << "delete from jobs where state='pending';\n"
551
+ select_sql << "select * from jobs where state='pending';\n"
552
+ when %r/^\s*h/io # holding
553
+ delete_sql << "delete from jobs where state='holding';\n"
554
+ select_sql << "select * from jobs where state='holding';\n"
555
+ when %r/^\s*r/io # running
556
+ delete_sql << "delete from jobs where state='running';\n" if force
557
+ select_sql << "select * from jobs where state='running';\n" if force
558
+ when %r/^\s*f/io # finished
559
+ delete_sql << "delete from jobs where state='finished';\n"
560
+ select_sql << "select * from jobs where state='finished';\n"
561
+ when %r/^\s*d/io # dead
562
+ delete_sql << "delete from jobs where state='dead';\n"
563
+ select_sql << "select * from jobs where state='dead';\n"
564
+ when %r/^\s*a/io # all
565
+ delete_sql << "delete from jobs where state!='running';\n"
566
+ select_sql << "select * from jobs where state!='running';\n"
567
+ else
568
+ raise ArgumentError, "cannot delete <#{ what.inspect }>"
569
+ end
570
+ end
571
+
572
+ scrub = lambda do |jid|
573
+ [standard_in_4(jid), standard_out_4(jid), standard_err_4(jid), data_4(jid)].each do |path|
574
+ FileUtils::rm_rf path
575
+ end
576
+ end
577
+
578
+ tuples = []
579
+
580
+ metablock =
581
+ if block
582
+ lambda do |tuple|
583
+ jid = tuple['jid']
584
+ block[tuple]
585
+ scrub[jid]
586
+ end
587
+ else
588
+ lambda do |tuple|
589
+ jid = tuple['jid']
590
+ scrub[jid]
591
+ tuples << tuple
592
+ end
593
+ end
594
+
595
+ # TODO - make file deletion transactional too
596
+
597
+ transaction do
598
+ execute(select_sql, &metablock)
599
+ execute(delete_sql){}
600
+ end
601
+
602
+ delete_sql = nil
603
+ select_sql = nil
604
+
605
+ block ? nil : tuples
606
+ #--}}}
607
+ end
608
+ def vacuum
609
+ #--{{{
610
+ @qdb.vacuum
611
+ #--}}}
612
+ end
613
+ def update(kvs, *jids, &block)
614
+ #--{{{
615
+ ret = nil
616
+ #
617
+ # yank out stdin - which we allow as a key
618
+ #
619
+ stdin = kvs.delete 'stdin'
620
+ data = kvs.delete 'data'
621
+ #
622
+ # validate/munge state value iff present
623
+ #
624
+ if((state = kvs['state']))
625
+ case state
626
+ when %r/^p/io
627
+ kvs['state'] = 'pending'
628
+ when %r/^h/io
629
+ kvs['state'] = 'holding'
630
+ else
631
+ raise "update of <state> = <#{ state }> not allowed (try pending or holding)"
632
+ end
633
+ end
634
+ #
635
+ # validate kvs pairs
636
+ #
637
+ allowed = %w( priority command tag runner restartable )
638
+ kvs.each do |key, val|
639
+ raise "update of <#{ key }> = <#{ val }> not allowed" unless
640
+ (allowed.include?(key)) or (key == 'state' and %w( pending holding ).include?(val))
641
+ end
642
+ #
643
+ # ensure there are acutally some jobs to update
644
+ #
645
+ raise "no jobs to update" if jids.empty?
646
+ #
647
+ # generates sql to update jids with kvs and sql to show updated tuples
648
+ #
649
+ build_sql =
650
+ lambda do |kvs, jids|
651
+ if(jids.delete('pending'))
652
+ execute("select jid from jobs where state='pending'") do |tuple|
653
+ jids << tuple['jid']
654
+ end
655
+ end
656
+
657
+ if(jids.delete('holding'))
658
+ execute("select jid from jobs where state='holding'") do |tuple|
659
+ jids << tuple['jid']
660
+ end
661
+ end
662
+
663
+ rollback_transaction "no jobs to update" if jids.empty?
664
+
665
+ update_clause = kvs.map{|k,v| v ? "#{ k }='#{ v }'" : "#{ k }=NULL" }.join(",\n")
666
+ where_clause = jids.map{|jid| "jid=#{ jid }"}.join(" or\n")
667
+ update_sql =
668
+ "update jobs\n" <<
669
+ "set\n#{ update_clause }\n" <<
670
+ "where\n(state='pending' or state='holding') and\n(#{ where_clause })"
671
+ select_sql = "select * from jobs where (state='pending' or state='holding') and\n(#{ where_clause })"
672
+
673
+ if kvs.empty?
674
+ [ nil, select_sql ]
675
+ else
676
+ [ update_sql, select_sql ]
677
+ end
678
+ end
679
+ #
680
+ # setup stdin
681
+ #
682
+ tmp_stdin(stdin) do |ts|
683
+ clobber_stdin = lambda do |job|
684
+ FileUtils::cp ts.path, standard_in_4(job['jid']) if ts
685
+ true
686
+ end
687
+
688
+ clobber_data = lambda do |job|
689
+ if data
690
+ FileUtils::rm_rf data_4(job['jid'])
691
+ FileUtils::cp_r data, data_4(job['jid'])
692
+ end
693
+ true
694
+ end
695
+
696
+ tuples = []
697
+
698
+ metablock =
699
+ if block
700
+ lambda{|job| clobber_stdin[job] and clobber_data[job] and block[job]}
701
+ else
702
+ lambda{|job| clobber_stdin[job] and clobber_data[job] and tuples << job}
703
+ end
704
+
705
+ transaction do
706
+ update_sql, select_sql = build_sql[kvs, jids]
707
+ break unless select_sql
708
+ execute(update_sql){} if update_sql
709
+ execute(select_sql, &metablock)
710
+ end
711
+
712
+ block ? nil : tuples
713
+ end
714
+ #--}}}
715
+ end
716
+
717
+ def getjob
718
+ #--{{{
719
+ sql = <<-sql
720
+ select * from jobs
721
+ where
722
+ (state='pending' or (state='dead' and (not restartable isnull))) and
723
+ (runner like '%#{ Util::host }%' or runner isnull)
724
+ order by priority desc, submitted asc, jid asc
725
+ limit 1;
726
+ sql
727
+ tuples = execute sql
728
+ job = tuples.first
729
+ job
730
+ #--}}}
731
+ end
732
+ def jobisrunning job
733
+ #--{{{
734
+ sql = <<-sql
735
+ update jobs
736
+ set
737
+ pid='#{ job['pid'] }',
738
+ state='#{ job['state'] }',
739
+ started='#{ job['started'] }',
740
+ runner='#{ job['runner'] }',
741
+ stdout='#{ job['stdout'] }',
742
+ stderr='#{ job['stderr'] }'
743
+ where jid=#{ job['jid'] };
744
+ sql
745
+ execute sql
746
+ #--}}}
747
+ end
748
+ def jobisdone job
749
+ #--{{{
750
+ sql = <<-sql
751
+ update jobs
752
+ set
753
+ state = '#{ job['state'] }',
754
+ exit_status = '#{ job['exit_status'] }',
755
+ finished = '#{ job['finished'] }',
756
+ elapsed = '#{ job['elapsed'] }'
757
+ where jid = #{ job['jid'] };
758
+ sql
759
+ execute sql
760
+ #--}}}
761
+ end
762
+ def getdeadjobs(started, &block)
763
+ #--{{{
764
+ ret = nil
765
+ sql = <<-sql
766
+ select * from jobs
767
+ where
768
+ state = 'running' and
769
+ runner='#{ Util::hostname }' and
770
+ started<='#{ started }'
771
+ sql
772
+ if block
773
+ execute(sql, &block)
774
+ else
775
+ ret = execute(sql)
776
+ end
777
+ ret
778
+ #--}}}
779
+ end
780
+ def jobisdead job
781
+ #--{{{
782
+ jid = job['jid']
783
+ if jid
784
+ sql = "update jobs set state='dead' where jid='#{ jid }'"
785
+ execute(sql){}
786
+ end
787
+ job
788
+ #--}}}
789
+ end
790
+
791
+ def transaction(*args)
792
+ #--{{{
793
+ raise "cannot upgrade ro_transaction" if @in_ro_transaction
794
+ ret = nil
795
+ if @in_transaction
796
+ ret = yield
797
+ else
798
+ begin
799
+ @in_transaction = true
800
+ @qdb.transaction(*args){ ret = yield }
801
+ ensure
802
+ @in_transaction = false
803
+ end
804
+ end
805
+ ret
806
+ #--}}}
807
+ end
808
+ def ro_transaction(*args)
809
+ #--{{{
810
+ ret = nil
811
+ if @in_ro_transaction || @in_transaction
812
+ ret = yield
813
+ else
814
+ begin
815
+ @in_ro_transaction = true
816
+ @qdb.ro_transaction(*args){ ret = yield }
817
+ ensure
818
+ @in_ro_transaction = false
819
+ end
820
+ end
821
+ ret
822
+ #--}}}
823
+ end
824
+ def execute(*args, &block)
825
+ #--{{{
826
+ @qdb.execute(*args, &block)
827
+ #--}}}
828
+ end
829
+ def integrity_check(*args, &block)
830
+ #--{{{
831
+ @qdb.integrity_check(*args, &block)
832
+ #--}}}
833
+ end
834
+ def recover!(*args, &block)
835
+ #--{{{
836
+ @qdb.recover!(*args, &block)
837
+ #--}}}
838
+ end
839
+ def lock(*args, &block)
840
+ #--{{{
841
+ @qdb.lock(*args, &block)
842
+ #--}}}
843
+ end
844
+ def abort_transaction(*a,&b)
845
+ #--{{{
846
+ @qdb.abort_transaction(*a,&b)
847
+ #--}}}
848
+ end
849
+ def rollback_transaction(*a,&b)
850
+ #--{{{
851
+ @qdb.rollback_transaction(*a,&b)
852
+ #--}}}
853
+ end
854
+
855
+ def snapshot qtmp = "#{ @basename }.snapshot", retries = nil
856
+ #--{{{
857
+ qtmp ||= "#{ @basename }.snapshot"
858
+ debug{ "snapshot <#{ @path }> -> <#{ qtmp }>" }
859
+ retries = Integer(retries || 16)
860
+ debug{ "retries <#{ retries }>" }
861
+
862
+ qss = nil
863
+ loopno = 0
864
+
865
+ take_snapshot = lambda do
866
+ FileUtils::rm_rf qtmp
867
+ FileUtils::mkdir_p qtmp
868
+ %w(db db.schema lock).each do |base|
869
+ src, dest = File::join(@path, base), File::join(qtmp, base)
870
+ debug{ "cp <#{ src }> -> <#{ dest }>" }
871
+ FileUtils::cp(src, dest)
872
+ end
873
+ ss = klass::new qtmp, @opts
874
+ if ss.integrity_check
875
+ ss
876
+ else
877
+ begin; recover! unless integrity_check; rescue; nil; end
878
+ ss.recover!
879
+ end
880
+ end
881
+
882
+ loop do
883
+ break if loopno >= retries
884
+ if((ss = take_snapshot.call))
885
+ debug{ "snapshot <#{ qtmp }> created" }
886
+ qss = ss
887
+ break
888
+ else
889
+ debug{ "failure <#{ loopno + 1}> of <#{ retries }> attempts to create snapshot <#{ qtmp }> - retrying..." }
890
+ end
891
+ loopno += 1
892
+ end
893
+
894
+ unless qss
895
+ debug{ "locking <#{ @path }> as last resort" }
896
+ @qdb.write_lock do
897
+ if((ss = take_snapshot.call))
898
+ debug{ "snapshot <#{ qtmp }> created" }
899
+ qss = ss
900
+ else
901
+ raise "failed <#{ loopno }> times to create snapshot <#{ qtmp }>"
902
+ end
903
+ end
904
+ end
905
+
906
+ qss
907
+ #--}}}
908
+ end
909
+
910
+ # TODO - use mtime to optimize checks by feeder??
911
+ def mtime
912
+ #--{{{
913
+ File::stat(@path).mtime
914
+ #--}}}
915
+ end
916
+ def []= key, value
917
+ #--{{{
918
+ sql = "select count(*) from attributes where key='#{ key }';"
919
+ tuples = @qdb.execute sql
920
+ tuple = tuples.first
921
+ count = Integer tuple['count(*)']
922
+ case count
923
+ when 0
924
+ sql = "insert into attributes values('#{ key }','#{ value }');"
925
+ @qdb.execute sql
926
+ when 1
927
+ sql = "update attributes set key='#{ key }', value='#{ value }' where key='#{ key }';"
928
+ @qdb.execute sql
929
+ else
930
+ raise "key <#{ key }> has become corrupt!"
931
+ end
932
+ #--}}}
933
+ end
934
+ def attributes
935
+ #--{{{
936
+ h = {}
937
+ tuples = @qdb.execute "select * from attributes;"
938
+ tuples.map!{|t| h[t['key']] = t['value']}
939
+ h
940
+ #--}}}
941
+ end
942
+ #--}}}
943
+ end # class JobQueue
944
+ #--}}}
945
+ end # module RQ
946
+ $__rq_jobqueue__ = __FILE__
947
+ end