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.
- data/Gemfile +22 -0
- data/Gemfile.lock +22 -0
- data/INSTALL +166 -0
- data/LICENSE +10 -0
- data/Makefile +6 -0
- data/README +1183 -0
- data/Rakefile +37 -0
- data/TODO +24 -0
- data/TUTORIAL +230 -0
- data/VERSION +1 -0
- data/bin/rq +902 -0
- data/bin/rqmailer +865 -0
- data/example/a.rb +7 -0
- data/extconf.rb +198 -0
- data/gemspec.rb +40 -0
- data/install.rb +210 -0
- data/lib/rq.rb +155 -0
- data/lib/rq/arrayfields.rb +371 -0
- data/lib/rq/backer.rb +31 -0
- data/lib/rq/configfile.rb +82 -0
- data/lib/rq/configurator.rb +40 -0
- data/lib/rq/creator.rb +54 -0
- data/lib/rq/cron.rb +144 -0
- data/lib/rq/defaultconfig.txt +5 -0
- data/lib/rq/deleter.rb +51 -0
- data/lib/rq/executor.rb +40 -0
- data/lib/rq/feeder.rb +527 -0
- data/lib/rq/ioviewer.rb +48 -0
- data/lib/rq/job.rb +51 -0
- data/lib/rq/jobqueue.rb +947 -0
- data/lib/rq/jobrunner.rb +110 -0
- data/lib/rq/jobrunnerdaemon.rb +193 -0
- data/lib/rq/lister.rb +47 -0
- data/lib/rq/locker.rb +43 -0
- data/lib/rq/lockfile.rb +564 -0
- data/lib/rq/logging.rb +124 -0
- data/lib/rq/mainhelper.rb +189 -0
- data/lib/rq/orderedautohash.rb +39 -0
- data/lib/rq/orderedhash.rb +240 -0
- data/lib/rq/qdb.rb +733 -0
- data/lib/rq/querier.rb +98 -0
- data/lib/rq/rails.rb +80 -0
- data/lib/rq/recoverer.rb +28 -0
- data/lib/rq/refresher.rb +80 -0
- data/lib/rq/relayer.rb +283 -0
- data/lib/rq/resource.rb +22 -0
- data/lib/rq/resourcemanager.rb +40 -0
- data/lib/rq/resubmitter.rb +100 -0
- data/lib/rq/rotater.rb +98 -0
- data/lib/rq/sleepcycle.rb +46 -0
- data/lib/rq/snapshotter.rb +40 -0
- data/lib/rq/sqlite.rb +286 -0
- data/lib/rq/statuslister.rb +48 -0
- data/lib/rq/submitter.rb +113 -0
- data/lib/rq/toucher.rb +182 -0
- data/lib/rq/updater.rb +94 -0
- data/lib/rq/usage.rb +1222 -0
- data/lib/rq/util.rb +304 -0
- data/rdoc.sh +17 -0
- data/rq-ruby1.8.gemspec +120 -0
- data/test/.gitignore +1 -0
- data/test/test_rq.rb +145 -0
- data/white_box/crontab +2 -0
- data/white_box/joblist +8 -0
- data/white_box/killrq +18 -0
- data/white_box/rq_killer +27 -0
- metadata +208 -0
data/lib/rq/qdb.rb
ADDED
@@ -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
|