rq 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -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