rq-ruby1.8 3.4.3

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.
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