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/backer.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
unless defined? $__rq_backer__
|
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
|
+
# a Backer object makes an (optionally) timestamped hot backup/snapshot of a
|
11
|
+
# queue using a timestamp of milli second resolution.
|
12
|
+
#
|
13
|
+
class Backer < MainHelper
|
14
|
+
#--{{{
|
15
|
+
def backup
|
16
|
+
#--{{{
|
17
|
+
set_q
|
18
|
+
bak = @argv.shift
|
19
|
+
bak ||= "#{ @qpath }.#{ Util::timestamp.gsub(/[:\s\.-]/,'_') }.bak"
|
20
|
+
raise "<#{ bak }> exists" if bak and test(?e, bak)
|
21
|
+
debug{ "bak <#{ bak }>" }
|
22
|
+
@q.lock{ FileUtils::cp_r @qpath, bak }
|
23
|
+
info{ "created backup <#{ bak }>" }
|
24
|
+
#--}}}
|
25
|
+
end
|
26
|
+
#--}}}
|
27
|
+
end # class Backer
|
28
|
+
#--}}}
|
29
|
+
end # module RQ
|
30
|
+
$__rq_backer__ = __FILE__
|
31
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
unless defined? $__rq_configfile__
|
2
|
+
module RQ
|
3
|
+
#--{{{
|
4
|
+
LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
5
|
+
defined? LIBDIR
|
6
|
+
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
#
|
10
|
+
# the ConfigFile class is a thin class that munges yaml input and populates
|
11
|
+
# itself
|
12
|
+
#
|
13
|
+
class ConfigFile < ::Hash
|
14
|
+
#--{{{
|
15
|
+
DEFAULT_CONFIG = LIBDIR + 'defaultconfig.txt'
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def gen_template(arg = nil)
|
19
|
+
#--{{{
|
20
|
+
@data ||= IO::read(DEFAULT_CONFIG)
|
21
|
+
case arg
|
22
|
+
when IO
|
23
|
+
arg.write @data
|
24
|
+
when String
|
25
|
+
open(arg, 'w'){|f| f.write @data}
|
26
|
+
else
|
27
|
+
STDOUT.write @data
|
28
|
+
end
|
29
|
+
self
|
30
|
+
#--}}}
|
31
|
+
end
|
32
|
+
def load_default
|
33
|
+
#--{{{
|
34
|
+
@data ||= IO::read(DEFAULT_CONFIG)
|
35
|
+
@default ||= YAML::load(munge(@data)) || {}
|
36
|
+
#--}}}
|
37
|
+
end
|
38
|
+
def any(basename, *dirnames)
|
39
|
+
#--{{{
|
40
|
+
config = nil
|
41
|
+
dirnames.each do |dirname|
|
42
|
+
path = File::join dirname, basename
|
43
|
+
if test ?e, path
|
44
|
+
config = self::new(path)
|
45
|
+
break
|
46
|
+
end
|
47
|
+
end
|
48
|
+
config || self::new('default')
|
49
|
+
#--}}}
|
50
|
+
end
|
51
|
+
def munge buf
|
52
|
+
#--{{{
|
53
|
+
buf.gsub(%r/\t/o,' ')
|
54
|
+
#--}}}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
attr :path
|
58
|
+
def initialize path
|
59
|
+
#--{{{
|
60
|
+
@path = nil
|
61
|
+
yaml = nil
|
62
|
+
if path.nil? or path and path =~ /^\s*default/io
|
63
|
+
yaml = self.class.load_default
|
64
|
+
@path = 'DEFAULT'
|
65
|
+
else path
|
66
|
+
yaml = YAML::load(self.class.munge(open(path).read))
|
67
|
+
@path = path
|
68
|
+
end
|
69
|
+
self.update yaml
|
70
|
+
#--}}}
|
71
|
+
end
|
72
|
+
def to_hash
|
73
|
+
#--{{{
|
74
|
+
{}.update self
|
75
|
+
#--}}}
|
76
|
+
end
|
77
|
+
#--}}}
|
78
|
+
end # class ConfigFile
|
79
|
+
#--}}}
|
80
|
+
end # module RQ
|
81
|
+
$__rq_configfile__ = __FILE__
|
82
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
unless defined? $__rq_configurator__
|
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
|
+
# a Configurator adds key/value pairs to a queue's configuration. these
|
11
|
+
# key/value pairs are not currently used, but will be in a future release
|
12
|
+
#
|
13
|
+
class Configurator < MainHelper
|
14
|
+
#--{{{
|
15
|
+
#--}}}
|
16
|
+
def configure
|
17
|
+
#--{{{
|
18
|
+
set_q
|
19
|
+
attributes = {}
|
20
|
+
unless @argv.empty?
|
21
|
+
kv_pat = %r/^\s*([^\s]+)\s*=+\s*([^\s]+)\s*$/o
|
22
|
+
@q.transaction do
|
23
|
+
@argv.each do |arg|
|
24
|
+
match = kv_pat.match arg
|
25
|
+
if match
|
26
|
+
k, v = match[1], match[2]
|
27
|
+
@q[k] = v
|
28
|
+
end
|
29
|
+
end
|
30
|
+
attributes = @q.attributes
|
31
|
+
end
|
32
|
+
end
|
33
|
+
y attributes
|
34
|
+
#--}}}
|
35
|
+
end
|
36
|
+
end # class Configurator
|
37
|
+
#--}}}
|
38
|
+
end # module RQ
|
39
|
+
$__rq_configurator__ = __FILE__
|
40
|
+
end
|
data/lib/rq/creator.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
unless defined? $__rq_creator__
|
2
|
+
module RQ
|
3
|
+
#--{{{
|
4
|
+
LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
|
5
|
+
defined? LIBDIR
|
6
|
+
|
7
|
+
require 'fileutils'
|
8
|
+
require LIBDIR + 'mainhelper'
|
9
|
+
|
10
|
+
#
|
11
|
+
# a queue is a directory
|
12
|
+
#
|
13
|
+
# the Creator class is responsible for initializing the queue directory and
|
14
|
+
# all supporting files. these include:
|
15
|
+
# * the sqlite database (binary)
|
16
|
+
# * the sqlite database schema description file (text)
|
17
|
+
# * the empty sentinel file used for locking (text - empty)
|
18
|
+
#
|
19
|
+
# it is an error to attempt to initialize a queue which already exists
|
20
|
+
#
|
21
|
+
class Creator < MainHelper
|
22
|
+
#--{{{
|
23
|
+
def create
|
24
|
+
#--{{{
|
25
|
+
raise "q <#{ @qpath }> exists!" if test ?e, @qpath
|
26
|
+
@q = JobQueue::create @qpath, 'logger' => @logger
|
27
|
+
|
28
|
+
unless quiet?
|
29
|
+
puts '---'
|
30
|
+
puts "q: #{ @q.path }"
|
31
|
+
puts "db: #{ @q.db.path }"
|
32
|
+
puts "schema: #{ @q.db.schema }"
|
33
|
+
puts "lock: #{ @q.db.lockfile }"
|
34
|
+
puts "bin: #{ @q.bin }"
|
35
|
+
puts "stdin: #{ @q.stdin }"
|
36
|
+
puts "stdout: #{ @q.stdout }"
|
37
|
+
puts "stderr: #{ @q.stderr }"
|
38
|
+
puts "data: #{ @q.data }"
|
39
|
+
end
|
40
|
+
|
41
|
+
rqmailer = File.join(File.dirname(@program), 'rqmailer')
|
42
|
+
if test ?e, rqmailer
|
43
|
+
FileUtils.cp rqmailer, @q.bin
|
44
|
+
end
|
45
|
+
|
46
|
+
self
|
47
|
+
#--}}}
|
48
|
+
end
|
49
|
+
#--}}}
|
50
|
+
end # class Creator
|
51
|
+
#--}}}
|
52
|
+
end # module RQ
|
53
|
+
$__rq_creator__ = __FILE__
|
54
|
+
end
|
data/lib/rq/cron.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
unless defined? $__rq_cron__
|
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
|
+
# a class for managing crontab entries and to start/stop rq
|
11
|
+
#
|
12
|
+
class Cron < MainHelper
|
13
|
+
#--{{{
|
14
|
+
def initialize *a, &b
|
15
|
+
#--{{{
|
16
|
+
super
|
17
|
+
ruby = Util::which_ruby
|
18
|
+
this = Util::realpath( File.expand_path( $0 ) )
|
19
|
+
q = qpath
|
20
|
+
|
21
|
+
@cmd = "#{ ruby } #{ this } #{ q }"
|
22
|
+
@md5 = lambda{|mode| Digest::MD5::hexdigest "#{ @cmd } #{ mode }" }
|
23
|
+
#--}}}
|
24
|
+
end
|
25
|
+
def cron
|
26
|
+
#--{{{
|
27
|
+
which = @argv.shift || 'start'
|
28
|
+
which = which.strip.downcase
|
29
|
+
#abort "arg not add|start|shutdown|stop" unless %w( start shutdown stop ).include? which
|
30
|
+
msg = "cron_#{ which }"
|
31
|
+
begin
|
32
|
+
send msg
|
33
|
+
rescue NoMethodError
|
34
|
+
raise ArgumentError, which
|
35
|
+
end
|
36
|
+
self
|
37
|
+
#--}}}
|
38
|
+
end
|
39
|
+
def cron_add
|
40
|
+
#--{{{
|
41
|
+
lines = `crontab -l`.split "\n"
|
42
|
+
|
43
|
+
found = nil
|
44
|
+
|
45
|
+
re = %r/###\s*md5:#{ @md5[:start] }/
|
46
|
+
|
47
|
+
lines.each do |line|
|
48
|
+
line.strip!
|
49
|
+
next if line[ %r/^\s*#/ ]
|
50
|
+
min, hour, dom, mon, dow, entry = line.split %r/\s+/, 6
|
51
|
+
next unless entry
|
52
|
+
entry.strip!
|
53
|
+
entry.gsub! %r/#[^'"]$/, ''
|
54
|
+
entry.strip!
|
55
|
+
found = re.match entry
|
56
|
+
break if found
|
57
|
+
end
|
58
|
+
|
59
|
+
unless found
|
60
|
+
opts = @options.map{|kv| "'--#{ kv.join('=') }'" }.join(' ')
|
61
|
+
entries = [
|
62
|
+
"*/15 * * * * #{ @cmd } start #{ opts } ###md5:#{ @md5[:start] }\n",
|
63
|
+
"0 0 * * * #{ @cmd } rotate ###md5:#{ @md5[:start] }\n",
|
64
|
+
]
|
65
|
+
tmp = Tempfile::new Process::pid.to_s
|
66
|
+
lines.each{|line| tmp << "#{ line }\n"}
|
67
|
+
entries.each do |entry|
|
68
|
+
tmp << entry
|
69
|
+
end
|
70
|
+
tmp.close
|
71
|
+
system("crontab #{ tmp.path }") or abort("failed to cronify!")
|
72
|
+
tmp.close!
|
73
|
+
entries.each do |entry|
|
74
|
+
puts entry
|
75
|
+
end
|
76
|
+
end
|
77
|
+
#--}}}
|
78
|
+
end
|
79
|
+
def cron_tab
|
80
|
+
#--{{{
|
81
|
+
opts = @options.map{|kv| "'--#{ kv.join('=') }'" }.join(' ')
|
82
|
+
entries = [
|
83
|
+
"*/15 * * * * #{ @cmd } start #{ opts } ###md5:#{ @md5[:start] }\n",
|
84
|
+
"0 0 * * * #{ @cmd } rotate ###md5:#{ @md5[:start] }\n",
|
85
|
+
]
|
86
|
+
puts entries
|
87
|
+
#--}}}
|
88
|
+
end
|
89
|
+
def cron_start
|
90
|
+
#--{{{
|
91
|
+
cron_add
|
92
|
+
#main.start
|
93
|
+
#--}}}
|
94
|
+
end
|
95
|
+
def cron_delete
|
96
|
+
#--{{{
|
97
|
+
lines = `crontab -l`.split "\n"
|
98
|
+
|
99
|
+
re = %r/###\s*md5:(#{ @md5[:start] })/
|
100
|
+
found = []
|
101
|
+
|
102
|
+
lines.each_with_index do |line, idx|
|
103
|
+
line.strip!
|
104
|
+
next if line[ %r/^\s*#/ ]
|
105
|
+
min, hour, dom, mon, dow, entry = line.split %r/\s+/, 6
|
106
|
+
next unless entry
|
107
|
+
entry.strip!
|
108
|
+
entry.gsub! %r/#[^'"]$/, ''
|
109
|
+
entry.strip!
|
110
|
+
found << idx if(re.match entry)
|
111
|
+
end
|
112
|
+
|
113
|
+
p found
|
114
|
+
|
115
|
+
unless found.empty?
|
116
|
+
deleted = []
|
117
|
+
found.each{|idx| deleted << lines[idx]; lines.delete_at(idx)}
|
118
|
+
tmp = Tempfile::new Process::pid.to_s
|
119
|
+
lines.each{|line| tmp << "#{ line }\n"}
|
120
|
+
tmp.close
|
121
|
+
system("crontab #{ tmp.path }") or abort("failed to cronify!")
|
122
|
+
tmp.close!
|
123
|
+
puts deleted
|
124
|
+
end
|
125
|
+
#--}}}
|
126
|
+
end
|
127
|
+
def cron_shutdown
|
128
|
+
#--{{{
|
129
|
+
cron_delete
|
130
|
+
main.shutdown
|
131
|
+
#--}}}
|
132
|
+
end
|
133
|
+
def cron_stop
|
134
|
+
#--{{{
|
135
|
+
cron_delete
|
136
|
+
main.stop
|
137
|
+
#--}}}
|
138
|
+
end
|
139
|
+
#--}}}
|
140
|
+
end # class Cron
|
141
|
+
#--}}}
|
142
|
+
end # module RQ
|
143
|
+
$__rq_cron__ = __FILE__
|
144
|
+
end
|
data/lib/rq/deleter.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
unless defined? $__rq_deleter__
|
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 Deleter class reads the command line, stdin, or an infile to determine
|
11
|
+
# the job ids (jid) of jobs to be deleted from the queue. jids may be
|
12
|
+
# specified on the command line or parsed from stdin or the infile. any
|
13
|
+
# input line matching 'jid : number' or 'number' is taken to be a line
|
14
|
+
# indicating a jid to delete.
|
15
|
+
#
|
16
|
+
class Deleter < MainHelper
|
17
|
+
#--{{{
|
18
|
+
def delete
|
19
|
+
#--{{{
|
20
|
+
set_q
|
21
|
+
|
22
|
+
whats = @argv
|
23
|
+
|
24
|
+
if whats.empty? and stdin?
|
25
|
+
pat = %r/^(?:\s*jid\s*:)?\s*(\d+)\s*$|^\s*(all)\s*$/io
|
26
|
+
while((line = stdin.gets))
|
27
|
+
match = pat.match line
|
28
|
+
next unless match
|
29
|
+
whats << (match[1] || match[2])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#whats.map!{|what| what =~ %r/^\s*\d+\s*$/o ? Integer(what) : what}
|
34
|
+
|
35
|
+
raise "nothing to delete" if whats.empty?
|
36
|
+
|
37
|
+
if @options['quiet']
|
38
|
+
@q.delete(*whats)
|
39
|
+
else
|
40
|
+
@q.delete(*whats, &dumping_yaml_tuples)
|
41
|
+
end
|
42
|
+
|
43
|
+
@q.vacuum
|
44
|
+
#--}}}
|
45
|
+
end
|
46
|
+
#--}}}
|
47
|
+
end # class Deleter
|
48
|
+
#--}}}
|
49
|
+
end # module RQ
|
50
|
+
$__rq_deleter__ = __FILE__
|
51
|
+
end
|
data/lib/rq/executor.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
unless defined? $__rq_executor__
|
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 Executor is for expert use only and executes arbitrary sql on the
|
11
|
+
# queue's db. the reason one should not do this directly with the sqlite
|
12
|
+
# command line program is that it will not respect the locking subsystem
|
13
|
+
# used in RQ - the Executor method will
|
14
|
+
#
|
15
|
+
class Executor < MainHelper
|
16
|
+
#--{{{
|
17
|
+
def execute
|
18
|
+
#--{{{
|
19
|
+
set_q
|
20
|
+
sql = @argv.join ' '
|
21
|
+
if sql.empty? and stdin?
|
22
|
+
debug{ "reading sql from stdin" }
|
23
|
+
while((buf = stdin.gets))
|
24
|
+
buf.strip!
|
25
|
+
buf.gsub! %r/#.*$/o, ''
|
26
|
+
next if buf.empty?
|
27
|
+
sql << "#{ buf } "
|
28
|
+
end
|
29
|
+
end
|
30
|
+
abort "no sql to execute" if sql.empty?
|
31
|
+
@q.qdb.transaction_retries = 0
|
32
|
+
@q.transaction{@q.execute(sql, &dumping_yaml_tuples)}
|
33
|
+
#--}}}
|
34
|
+
end
|
35
|
+
#--}}}
|
36
|
+
end # class Executor
|
37
|
+
#--}}}
|
38
|
+
end # module RQ
|
39
|
+
$__rq_executor__ = __FILE__
|
40
|
+
end
|