rq 0.1.7

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