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,733 @@
1
+ unless defined? $__rq_qdb__
2
+ module RQ
3
+ #--{{{
4
+ LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
5
+ defined? LIBDIR
6
+
7
+ require 'arrayfields'
8
+
9
+ require LIBDIR + 'util'
10
+ require LIBDIR + 'logging'
11
+ require LIBDIR + 'sleepcycle'
12
+ require LIBDIR + 'refresher'
13
+
14
+ #
15
+ # the QDB class is the low level access point to the actual sqlite database.
16
+ # the primary function if performs is to serialize access to the queue db
17
+ # via the locking protocol
18
+ #
19
+ class QDB
20
+ #--{{{
21
+ include Util
22
+ include Logging
23
+
24
+ class RollbackTransactionError < StandardError; end
25
+ class AbortedTransactionError < StandardError; end
26
+
27
+ FIELDS =
28
+ #--{{{
29
+ %w(
30
+ jid priority state
31
+ submitted started finished elapsed
32
+ submitter runner
33
+ stdin stdout stderr data
34
+ pid exit_status
35
+ tag restartable command
36
+ )
37
+ #--}}}
38
+
39
+ PRAGMAS =
40
+ #--{{{
41
+ <<-sql
42
+ PRAGMA default_synchronous = FULL;
43
+ sql
44
+ #--}}}
45
+
46
+ SCHEMA =
47
+ #--{{{
48
+ <<-sql
49
+ create table jobs
50
+ (
51
+ jid integer primary key,
52
+ #{ FIELDS[1..-1].join ",\n " }
53
+ );
54
+ create table attributes
55
+ (
56
+ key,
57
+ value,
58
+ primary key (key)
59
+ );
60
+ sql
61
+ #--}}}
62
+
63
+ DEFAULT_LOGGER = Logger::new(STDERR)
64
+ DEFAULT_SQL_DEBUG = false
65
+ DEFAULT_TRANSACTION_RETRIES = 4
66
+ DEFAULT_AQUIRE_LOCK_SC = SleepCycle::new(2, 16, 2)
67
+ DEFAULT_TRANSACTION_RETRIES_SC = SleepCycle::new(8, 24, 8)
68
+ DEFAULT_ATTEMPT_LOCKD_RECOVERY = true
69
+ DEFAULT_LOCKD_RECOVER_WAIT = 3600 # 1 hr
70
+ DEFAULT_AQUIRE_LOCK_LOCKFILE_STALE_AGE = 21600 # 6 hrs
71
+ DEFAULT_AQUIRE_LOCK_REFRESH_RATE = 30
72
+
73
+ class << self
74
+ #--{{{
75
+ attr :sql_debug, true
76
+ attr :transaction_retries, true
77
+ attr :aquire_lock_sc, true
78
+ attr :transaction_retries_sc, true
79
+ attr :attempt_lockd_recovery, true
80
+ attr :lockd_recover_wait, true
81
+ attr :aquire_lock_lockfile_stale_age, true
82
+ attr :aquire_lock_refresh_rate, true
83
+
84
+ def fields
85
+ #--{{{
86
+ FIELDS
87
+ #--}}}
88
+ end
89
+ def integrity_check dbpath
90
+ #--{{{
91
+ ret = false
92
+ tuple = nil
93
+ begin
94
+ db =
95
+ begin
96
+ SQLite::Database::new dbpath, 0
97
+ rescue
98
+ SQLite::Database::new dbpath
99
+ end
100
+ opened = true
101
+ db.use_array = true rescue nil
102
+ tuple = db.execute 'PRAGMA integrity_check;'
103
+ ret = (tuple and tuple.first and (tuple.first["integrity_check"] =~ /^\s*ok\s*$/io))
104
+ ensure
105
+ db.close if opened
106
+ db = nil
107
+ end
108
+ ret
109
+ #--}}}
110
+ end
111
+ def t2h tuple
112
+ #--{{{
113
+ h = {}
114
+ FIELDS.each_with_index{|f,i| h[f] = tuple[i]}
115
+ h
116
+ #--}}}
117
+ end
118
+ def h2t h
119
+ #--{{{
120
+ t = tuple
121
+ FIELDS.each{|f| t[f] = h[f]}
122
+ t
123
+ #--}}}
124
+ end
125
+ def tuple
126
+ #--{{{
127
+ t = Array::new FIELDS.size
128
+ t.fields = FIELDS
129
+ t
130
+ #--}}}
131
+ end
132
+ def q tuple
133
+ #--{{{
134
+ [ tuple ].flatten.map do |f|
135
+ if f and not f.to_s.empty?
136
+ "'" << Util.escape(f,"'","'") << "'"
137
+ else
138
+ 'NULL'
139
+ end
140
+ end
141
+ #--}}}
142
+ end
143
+ def create path, opts = {}
144
+ #--{{{
145
+ qdb = new path, opts
146
+ FileUtils::touch qdb.lockfile
147
+ create_schema qdb.schema
148
+ qdb.transaction do
149
+ qdb.execute PRAGMAS
150
+ qdb.execute SCHEMA
151
+ end
152
+ qdb
153
+ #--}}}
154
+ end
155
+ def create_schema path
156
+ #--{{{
157
+ tmp = "#{ path }.tmp"
158
+ open(tmp,'w') do |f|
159
+ f.puts PRAGMAS
160
+ f.puts SCHEMA
161
+ end
162
+ FileUtils::mv tmp, path
163
+ #--}}}
164
+ end
165
+ #--}}}
166
+ end
167
+
168
+ attr :path
169
+ attr :opts
170
+ attr :dirname
171
+ attr :schema
172
+ attr :fields
173
+ attr :mutex
174
+ attr :lockfile
175
+ attr :sql_debug, true
176
+ attr :transaction_retries, true
177
+ attr :aquire_lock_sc, true
178
+ attr :transaction_retries_sc, true
179
+ attr :attempt_lockd_recovery, true
180
+ attr :lockd_recover_wait, true
181
+ attr :aquire_lock_lockfile_stale_age, true
182
+ attr :aquire_lock_refresh_rate, true
183
+
184
+
185
+ def initialize path, opts = {}
186
+ #--{{{
187
+ @path = path
188
+ @opts = opts
189
+
190
+ @logger =
191
+ Util::getopt('logger', @opts) ||
192
+ klass.logger ||
193
+ DEFAULT_LOGGER
194
+
195
+ @sql_debug =
196
+ Util::getopt('sql_debug', @opts) ||
197
+ klass.sql_debug ||
198
+ ENV['RQ_SQL_DEBUG'] ||
199
+ DEFAULT_SQL_DEBUG
200
+
201
+ @transaction_retries =
202
+ Util::getopt('transaction_retries', @opts) ||
203
+ klass.transaction_retries ||
204
+ DEFAULT_TRANSACTION_RETRIES
205
+
206
+ @aquire_lock_sc =
207
+ Util::getopt('aquire_lock_sc', @opts) ||
208
+ klass.aquire_lock_sc ||
209
+ DEFAULT_AQUIRE_LOCK_SC
210
+
211
+ @transaction_retries_sc =
212
+ Util::getopt('transaction_retries_sc', @opts) ||
213
+ klass.transaction_retries_sc ||
214
+ DEFAULT_TRANSACTION_RETRIES_SC
215
+
216
+ @attempt_lockd_recovery =
217
+ Util::getopt('attempt_lockd_recovery', @opts) ||
218
+ klass.attempt_lockd_recovery ||
219
+ DEFAULT_ATTEMPT_LOCKD_RECOVERY
220
+
221
+ @lockd_recover_wait =
222
+ Util::getopt('lockd_recover_wait', @opts) ||
223
+ klass.lockd_recover_wait ||
224
+ DEFAULT_LOCKD_RECOVER_WAIT
225
+
226
+ @aquire_lock_lockfile_stale_age =
227
+ Util::getopt('aquire_lock_lockfile_stale_age', @opts) ||
228
+ klass.aquire_lock_lockfile_stale_age ||
229
+ DEFAULT_AQUIRE_LOCK_LOCKFILE_STALE_AGE
230
+
231
+ @aquire_lock_refresh_rate =
232
+ Util::getopt('aquire_lock_refresh_rate', @opts) ||
233
+ klass.aquire_lock_refresh_rate ||
234
+ DEFAULT_AQUIRE_LOCK_REFRESH_RATE
235
+
236
+
237
+ @schema = "#{ @path }.schema"
238
+ @dirname = File::dirname(path).gsub(%r|/+\s*$|,'')
239
+ @basename = File::basename(path)
240
+ @waiting_w = File::join(@dirname, "#{ Util::hostname }.#{ $$ }.waiting.w")
241
+ @waiting_r = File::join(@dirname, "#{ Util::hostname }.#{ $$ }.waiting.r")
242
+ @lock_w = File::join(@dirname, "#{ Util::hostname }.#{ $$ }.lock.w")
243
+ @lock_r = File::join(@dirname, "#{ Util::hostname }.#{ $$ }.lock.r")
244
+ @lockfile = File::join(@dirname, 'lock')
245
+ @lockf = Lockfile::new("#{ @path }.lock")
246
+ @fields = FIELDS
247
+ @in_transaction = false
248
+ @in_ro_transaction = false
249
+ @db = nil
250
+
251
+ @lockd_recover = "#{ @dirname }.lockd_recover"
252
+ @lockd_recover_lockf = Lockfile::new "#{ @lockd_recover }.lock"
253
+ @lockd_recovered = false
254
+ #--}}}
255
+ end
256
+ def ro_transaction(opts = {}, &block)
257
+ #--{{{
258
+ opts['read_only'] = true
259
+ transaction(opts, &block)
260
+ #--}}}
261
+ end
262
+ def transaction opts = {}
263
+ #--{{{
264
+ raise 'nested transaction' if @in_transaction
265
+ ro = Util::getopt 'read_only', opts
266
+ ret = nil
267
+ begin
268
+ @in_transaction = true
269
+ lockd_recover_wrap(opts) do
270
+ transaction_wrap(opts) do
271
+ aquire_lock(opts) do
272
+ #sillyclean(opts) do
273
+ connect do
274
+ execute 'begin' unless ro
275
+ ret = yield
276
+ execute 'commit' unless ro
277
+ end
278
+ #end
279
+ end
280
+ end
281
+ end
282
+ ensure
283
+ @in_transaction = false
284
+ end
285
+ ret
286
+ #--}}}
287
+ end
288
+ if false
289
+ def ro_transaction(opts = {}, &block)
290
+ #--{{{
291
+ opts['read_only'] = true
292
+ transaction(opts, &block)
293
+ #--}}}
294
+ end
295
+ def transaction opts = {}
296
+ #--{{{
297
+ ro = Util::getopt 'read_only', opts
298
+ ret = nil
299
+ if @in_transaction
300
+ STDERR.puts 'continuing transaction...'
301
+ ret = yield
302
+ else
303
+ begin
304
+ STDERR.puts 'starting transaction...'
305
+ @in_transaction = true
306
+ lockd_recover_wrap(opts) do
307
+ transaction_wrap(opts) do
308
+ aquire_lock(opts) do
309
+ #sillyclean(opts) do
310
+ connect do
311
+ execute 'begin' unless ro
312
+ ret = yield
313
+ execute 'commit' unless ro
314
+ end
315
+ #end
316
+ end
317
+ end
318
+ end
319
+ ensure
320
+ @in_transaction = false
321
+ end
322
+ end
323
+ ret
324
+ #--}}}
325
+ end
326
+ end
327
+ def lockd_recover_wrap opts = {}
328
+ #--{{{
329
+ ret = nil
330
+ try_again = false
331
+ begin
332
+ begin
333
+ @lockd_recovered = false
334
+ old_mtime =
335
+ begin
336
+ Util::uncache @lockd_recover rescue nil
337
+ File::stat(@lockd_recover).mtime
338
+ rescue
339
+ Time::now
340
+ end
341
+ ret = yield
342
+ ensure
343
+ new_mtime =
344
+ begin
345
+ Util::uncache @lockd_recover rescue nil
346
+ File::stat(@lockd_recover).mtime
347
+ rescue
348
+ old_mtime
349
+ end
350
+
351
+ if new_mtime and old_mtime and new_mtime > old_mtime and not @lockd_recovered
352
+ try_again = true
353
+ end
354
+ end
355
+ rescue
356
+ if try_again
357
+ warn{ "a remote lockd recovery has invalidated this transaction!" }
358
+ warn{ "retrying..."}
359
+ sleep 120
360
+ retry
361
+ else
362
+ raise
363
+ end
364
+ end
365
+ ret
366
+ #--}}}
367
+ end
368
+ #
369
+ # TODO - perhaps should not retry on SQLException?? yet errors seem to map to
370
+ # this exception even when the sql is fine... safest (and most anoying) is to
371
+ # simply retry.
372
+ #
373
+ def transaction_wrap opts = {}
374
+ #--{{{
375
+ ro = Util::getopt 'read_only', opts
376
+ ret = nil
377
+ if ro
378
+ ret = yield
379
+ else
380
+ errors = []
381
+ @transaction_retries_sc.reset
382
+ begin
383
+ ret = yield
384
+ rescue => e
385
+ #rescue SQLite::DatabaseException, SQLite::SQLException, SystemCallError => e
386
+ case e
387
+ when AbortedTransactionError
388
+ raise
389
+ when RollbackTransactionError
390
+ raise
391
+ else
392
+ if @transaction_retries == 0
393
+ raise
394
+ elsif errors.size >= @transaction_retries
395
+ error{ "MAXIMUM TRANSACTION RETRIES SURPASSED" }
396
+ raise
397
+ else
398
+ warn{ e } if(errors.empty? or not Util::erreq(errors[-1], e))
399
+ errors << e
400
+ warn{ "retry <#{ errors.size }>..." }
401
+ end
402
+ sleep @transaction_retries_sc.next
403
+ retry
404
+ end
405
+ end
406
+ end
407
+ ret
408
+ #--}}}
409
+ end
410
+ def abort_transaction(*a)
411
+ #--{{{
412
+ raise AbortedTransactionError, *a
413
+ #--}}}
414
+ end
415
+ def rollback_transaction(*a)
416
+ #--{{{
417
+ raise RollbackTransactionError, *a
418
+ #--}}}
419
+ end
420
+ def sillyclean opts = {}
421
+ #--{{{
422
+ ro = Util::getopt 'read_only', opts
423
+ ret = nil
424
+ if ro
425
+ ret = yield
426
+ else
427
+ glob = File::join @dirname,'.nfs*'
428
+ orgsilly = Dir[glob]
429
+ ret = yield
430
+ newsilly = Dir[glob]
431
+ silly = newsilly - orgsilly
432
+ silly.each{|path| FileUtils::rm_rf path}
433
+ end
434
+ ret
435
+ #--}}}
436
+ end
437
+ def aquire_lock opts = {}
438
+ #--{{{
439
+ ro = Util::getopt 'read_only', opts
440
+ ret = nil
441
+
442
+ @aquire_lock_sc.reset
443
+
444
+ waiting, ltype, lfile =
445
+ if ro
446
+ [@waiting_r, File::LOCK_SH | File::LOCK_NB, @lock_r]
447
+ else
448
+ [@waiting_w, File::LOCK_EX | File::LOCK_NB, @lock_w]
449
+ end
450
+
451
+ ltype_s = (ltype == File::LOCK_EX ? 'write' : 'read')
452
+ ltype ||= File::LOCK_NB
453
+
454
+ aquired = false
455
+
456
+ until aquired
457
+ begin
458
+ debug{ "aquiring lock" }
459
+ #@lockf.lock unless ro
460
+
461
+ open(@lockfile, 'a+') do |lf|
462
+
463
+ locked = false
464
+ refresher = nil
465
+ sc = nil
466
+
467
+ begin
468
+ FileUtils::touch waiting
469
+ # poll
470
+ 42.times do
471
+ locked = lf.posixlock(ltype | File::LOCK_NB)
472
+ break if locked
473
+ sleep rand
474
+ end
475
+
476
+ if locked
477
+ aquired = true
478
+ refresher = Refresher::new @lockfile, @aquire_lock_refresh_rate
479
+ debug{ "refresher pid <#{ refresher.pid }> refresh_rate <#{ @aquire_lock_refresh_rate }>" }
480
+ FileUtils::rm_f waiting rescue nil
481
+ FileUtils::touch lfile rescue nil
482
+ debug{ "aquired lock" }
483
+ ret = yield
484
+ debug{ "released lock" }
485
+ else
486
+ aquired = false
487
+ stat = File::stat @lockfile
488
+ mtime = stat.mtime
489
+ stale = mtime < (Time::now - @aquire_lock_lockfile_stale_age)
490
+ if stale
491
+ Util::uncache @lockfile rescue nil
492
+ stat = File::stat @lockfile
493
+ mtime = stat.mtime
494
+ stale = mtime < (Time::now - @aquire_lock_lockfile_stale_age)
495
+ if stale
496
+ warn{ "detected stale lockfile of mtime <#{ mtime }>" }
497
+ lockd_recover if @attempt_lockd_recovery
498
+ end
499
+ end
500
+ sc = @aquire_lock_sc.next
501
+ debug{ "failed to aquire lock - sleep(#{ sc })" }
502
+ sleep sc
503
+ end
504
+
505
+ ensure
506
+ if locked
507
+ unlocked = false
508
+ begin
509
+ 42.times do
510
+ unlocked = lf.posixlock(File::LOCK_UN | File::LOCK_NB)
511
+ break if unlocked
512
+ sleep rand
513
+ end
514
+ ensure
515
+ lf.posixlock File::LOCK_UN unless unlocked
516
+ end
517
+ end
518
+ refresher.kill if refresher
519
+ FileUtils::rm_f waiting rescue nil
520
+ FileUtils::rm_f lfile rescue nil
521
+ end
522
+ end
523
+ ensure
524
+ #@lockf.unlock rescue nil unless read_only
525
+ end
526
+ end
527
+ ret
528
+ #--}}}
529
+ end
530
+ def connect
531
+ #--{{{
532
+ ret = nil
533
+ opened = nil
534
+ begin
535
+ raise 'db has no schema' unless test ?e, @schema
536
+ debug{"connecting to db <#{ @path }>..."}
537
+ $db = @db =
538
+ begin
539
+ SQLite::Database::new(@path, 0)
540
+ rescue
541
+ SQLite::Database::new(@path)
542
+ end
543
+ debug{"connected."}
544
+ opened = true
545
+ @db.use_array = true rescue nil
546
+ ret = yield @db
547
+ ensure
548
+ @db.close if opened
549
+ $db = @db = nil
550
+ debug{"disconnected from db <#{ @path }>"}
551
+ end
552
+ ret
553
+ #--}}}
554
+ end
555
+ def execute sql, &block
556
+ #--{{{
557
+ raise 'not in transaction' unless @in_transaction
558
+ if @sql_debug
559
+ logger << "SQL:\n#{ sql }\n"
560
+ end
561
+ #ret = retry_if_locked{ @db.execute sql, &block }
562
+ ret = @db.execute sql, &block
563
+ if @sql_debug and ret and Array === ret and ret.first
564
+ logger << "RESULT:\n#{ ret.first.inspect }\n...\n"
565
+ end
566
+ ret
567
+ #--}}}
568
+ end
569
+ #
570
+ # TODO - add sleep cycle if this ends up getting used
571
+ #
572
+ def retry_if_locked
573
+ #--{{{
574
+ ret = nil
575
+ begin
576
+ ret = yield
577
+ rescue SQLite::BusyException
578
+ warn{ "database locked - waiting(1.0) and retrying" }
579
+ sleep 1.0
580
+ retry
581
+ end
582
+ ret
583
+ #--}}}
584
+ end
585
+ def vacuum
586
+ #--{{{
587
+ raise 'nested transaction' if @in_transaction
588
+ begin
589
+ @in_transaction = true
590
+ connect{ execute 'vacuum' }
591
+ ensure
592
+ @in_transaction = false
593
+ end
594
+ self
595
+ #--}}}
596
+ end
597
+ def recover!
598
+ #--{{{
599
+ raise 'nested transaction' if @in_transaction
600
+ begin
601
+ @in_transaction = true
602
+ connect{ execute 'vacuum' }
603
+ require 'timeout'
604
+ Timeout::timeout(60){ system "sqlite #{ @path } .tables >/dev/null 2>&1" }
605
+ ensure
606
+ @in_transaction = false
607
+ end
608
+ integrity_check
609
+ #--}}}
610
+ end
611
+ def lockd_recover
612
+ #--{{{
613
+ return nil unless @attempt_lockd_recovery
614
+ warn{ "attempting lockd recovery" }
615
+ time = Time::now
616
+ ret = nil
617
+
618
+ @lockd_recover_lockf.lock do
619
+ Util::uncache @dirname rescue nil
620
+ Util::uncache @path rescue nil
621
+ Util::uncache @lockfile rescue nil
622
+ Util::uncache @lockd_recover rescue nil
623
+ mtime = File::stat(@lockd_recover).mtime rescue time
624
+
625
+ if mtime > time
626
+ warn{ "skipping lockd recovery (another node has already recovered)" }
627
+ ret = true
628
+ else
629
+ moved = false
630
+ begin
631
+ FileUtils::touch @lockd_recover
632
+ @lockd_recovered = false
633
+
634
+ begin
635
+ report = <<-msg
636
+ hostname : #{ Util::hostname }
637
+ pid : #{ Process.pid }
638
+ time : #{ Time::now }
639
+ q :
640
+ path : #{ @dirname }
641
+ stat : #{ File::stat(@dirname).inspect }
642
+ db :
643
+ path : #{ @path }
644
+ stat : #{ File::stat(@path).inspect }
645
+ lockfile :
646
+ path : #{ @lockfile }
647
+ stat : #{ File::stat(@lockfile).inspect }
648
+ msg
649
+ info{ "LOCKD RECOVERY REPORT" }
650
+ logger << report
651
+ cmd = "mail -s LOCKD_RECOVERY ara.t.howard@noaa.gov <<eof\n#{ report }\neof"
652
+ Util::system cmd
653
+ rescue
654
+ nil
655
+ end
656
+
657
+ warn{ "sleeping #{ @lockd_recover_wait }s before continuing..." }
658
+ sleep @lockd_recover_wait
659
+
660
+ tmp = "#{ @dirname }.tmp"
661
+ FileUtils::rm_rf tmp
662
+ FileUtils::mv @dirname, tmp
663
+ moved = true
664
+
665
+ rfiles = [@path, @lockfile].map{|f| File::join(tmp,File::basename(f))}
666
+ rfiles.each do |f|
667
+ ftmp = "#{ f }.tmp"
668
+ FileUtils::rm_rf ftmp
669
+ FileUtils::cp f, ftmp
670
+ FileUtils::rm f
671
+ FileUtils::mv ftmp, f
672
+ end
673
+
674
+ dbtmp = File::join(tmp,File::basename(@path))
675
+
676
+ if integrity_check(dbtmp)
677
+ FileUtils::mv tmp, @dirname
678
+ FileUtils::cp @lockd_recover_lockf.path, @lockd_recover
679
+ @lockd_recovered = true
680
+ Util::uncache @dirname rescue nil
681
+ Util::uncache @path rescue nil
682
+ Util::uncache @lockfile rescue nil
683
+ Util::uncache @lockd_recover rescue nil
684
+ warn{ "lockd recovery complete" }
685
+ else
686
+ FileUtils::mv tmp, @dirname
687
+ @lockd_recovered = false
688
+ error{ "lockd recovery failed" }
689
+ end
690
+
691
+ ret = @lockd_recovered
692
+ ensure
693
+ if moved and not @lockd_recovered and tmp and test(?d, tmp)
694
+ FileUtils::mv tmp, @dirname
695
+ end
696
+ end
697
+ end
698
+ end
699
+ ret
700
+ #--}}}
701
+ end
702
+ def integrity_check path = @path
703
+ #--{{{
704
+ debug{ "running integrity_check on <#{ path }>" }
705
+ klass.integrity_check(path)
706
+ #--}}}
707
+ end
708
+ def lock opts = {}
709
+ #--{{{
710
+ ret = nil
711
+ lockd_recover_wrap do
712
+ aquire_lock(opts) do
713
+ ret = yield
714
+ end
715
+ end
716
+ ret
717
+ #--}}}
718
+ end
719
+ alias write_lock lock
720
+ alias wlock write_lock
721
+ def read_lock(opts = {}, &block)
722
+ #--{{{
723
+ opts['read_only'] = true
724
+ lock opts, &block
725
+ #--}}}
726
+ end
727
+ alias rlock read_lock
728
+ #--}}}
729
+ end # class QDB
730
+ #--}}}
731
+ end # module RQ
732
+ $__rq_qdb__ = __FILE__
733
+ end