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.
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,98 @@
1
+ unless defined? $__rq_queryier__
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 Querier simply takes an sql where clause (such as 'jid = 42') from the
11
+ # command line, queries the queue, and dumps a valid yaml representation of
12
+ # the tuples returned. the output of a Querier can be used as the input to
13
+ # a Deleter or Submitter, etc.
14
+ #
15
+ #
16
+ class Querier < MainHelper
17
+ #--{{{
18
+ def query
19
+ #--{{{
20
+ set_q
21
+
22
+ @q.qdb.transaction_retries = 1
23
+
24
+ where_clause = @argv.join ' '
25
+
26
+ if where_clause.empty? and stdin?
27
+ debug{ "reading where_clause from stdin" }
28
+ while((buf = stdin.gets))
29
+ buf.strip!
30
+ buf.gsub! %r/#.*$/o, ''
31
+ next if buf.empty?
32
+ where_clause << "#{ buf } "
33
+ end
34
+ end
35
+
36
+ @q.query(where_clause, &dumping_yaml_tuples)
37
+ #--}}}
38
+ end
39
+ def query
40
+ #--{{{
41
+ set_q
42
+
43
+ @q.qdb.transaction_retries = 1
44
+
45
+ simple_pat = %r/^\s*([^=\s!~]+)(=~|!~|!=|==|=)([^=\s]+)\s*$/ox
46
+ simple_query = @argv.select{|arg| arg !~ simple_pat }.empty?
47
+
48
+ where_clause =
49
+ if simple_query
50
+ wc = []
51
+
52
+ @argv.each do |arg|
53
+ m = simple_pat.match(arg).to_a[1..-1]
54
+ field, op, value = m[0], m[1], m[2..-1].join
55
+ op =
56
+ case op
57
+ when '=', '=='
58
+ '='
59
+ when '!='
60
+ '!='
61
+ when '=~'
62
+ 'like'
63
+ when '!~'
64
+ 'not like'
65
+ end
66
+
67
+ quoted = (value =~ %r/^\s*'.*'\s*$/o)
68
+ numeric = begin; Float(value); true; rescue; false; end
69
+
70
+ value = "'#{ value }'" unless quoted or numeric
71
+
72
+ wc << "(#{ field } #{ op } #{ value })"
73
+ end
74
+
75
+ wc.join ' and '
76
+ else
77
+ @argv.join ' '
78
+ end
79
+
80
+ if where_clause.strip.empty? and stdin?
81
+ debug{ "reading where_clause from stdin" }
82
+ while((buf = stdin.gets))
83
+ buf.strip!
84
+ buf.gsub! %r/#.*$/o, ''
85
+ next if buf.empty?
86
+ where_clause << "#{ buf } "
87
+ end
88
+ end
89
+
90
+ @q.query(where_clause, &dumping_yaml_tuples)
91
+ #--}}}
92
+ end
93
+ #--}}}
94
+ end # class Queryier
95
+ #--}}}
96
+ end # module RQ
97
+ $__rq_queryier__ = __FILE__
98
+ end
@@ -0,0 +1,80 @@
1
+ if defined?(Rails)
2
+ require 'fileutils'
3
+
4
+ module Rails
5
+ class << self
6
+ def qpath *arg
7
+ @qpath = arg.shift unless arg.empty?
8
+ @qpath ||= File.join(RAILS_ROOT, 'q')
9
+ @qpath
10
+ end
11
+
12
+ def q
13
+ defined?(@q) ? @q : qinit
14
+ end
15
+
16
+ def qinit
17
+ unless test ?e, qpath
18
+ cmd = "rq #{ qpath } create"
19
+ system cmd or abort "cmd <#{ cmd }> failed with <#{ $?.inspect }>"
20
+ cmd = "cp `which rqmailer` #{ qpath }/bin"
21
+ system cmd or abort "cmd <#{ cmd }> failed with <#{ $?.inspect }>"
22
+ end
23
+ (( logger = Logger.new STDERR )).level = Logger::FATAL
24
+ @q = RQ::JobQueue.new qpath, 'logger' => logger
25
+ @q.extend qextension
26
+ @q
27
+ end
28
+
29
+ def qextension
30
+ Module.new do
31
+ def rqmailer config, template = nil, submission = {}
32
+ config = YAML.load config if String === config
33
+
34
+ ### clean up keys
35
+ config = config.inject({}){|h,kv| k, v = kv; h.update k.to_s => v}
36
+ submission = submission.inject({}){|h,kv| k, v = kv; h.update k.to_s => v}
37
+
38
+ command = (config["command"] || config["cmd"]) or raise "no command in <#{ config.inspect }>!"
39
+
40
+ mconfig = config["mail"] || config
41
+ attachements = [ mconfig["attach"] ].flatten.compact
42
+
43
+ tag = "rqmailer"
44
+ command = "rqmailer ### #{ command }"
45
+
46
+ submission["tag"] ||= tag
47
+ submission["command"] = command
48
+
49
+ tmpdir = File.join RAILS_ROOT, 'tmp', Process.pid.to_s, '.rqmailer.d'
50
+ FileUtils.mkdir_p tmpdir
51
+ begin
52
+ open(File.join(tmpdir, 'config'), 'w') do |fd|
53
+ fd.write config.to_yaml
54
+ end
55
+ if template
56
+ open(File.join(tmpdir, 'template'), 'w') do |fd|
57
+ fd.write template
58
+ end
59
+ end
60
+ d = File.join(tmpdir, "attachements")
61
+ FileUtils.mkdir_p d
62
+ attachements.each do |attachment|
63
+ FileUtils.cp attachment, d
64
+ end
65
+
66
+ submission["data"] = tmpdir
67
+ job = nil
68
+ submit(submission){|job|}
69
+ job
70
+ ensure
71
+ FileUtils.rm_rf tmpdir
72
+ end
73
+ end
74
+ alias_method "mailrun", "rqmailer"
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,28 @@
1
+ unless defined? $__rq_recoverer__
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
+ class Recoverer < MainHelper
10
+ #--{{{
11
+ def recover
12
+ #--{{{
13
+ set_q
14
+
15
+ bool = @q.recover! ? true : false
16
+ puts "---"
17
+ puts "recovered : #{ bool }"
18
+
19
+ EXIT_SUCCESS
20
+ #--}}}
21
+ end
22
+ alias recover! recover
23
+ #--}}}
24
+ end # class Recoverer
25
+ #--}}}
26
+ end # module RQ
27
+ $__rq_recoverer__ = __FILE__
28
+ end
@@ -0,0 +1,80 @@
1
+ unless defined? $__rq_refresher__
2
+ module RQ
3
+ #--{{{
4
+ LIBDIR = File::dirname(File::expand_path(__FILE__)) + File::SEPARATOR unless
5
+ defined? LIBDIR
6
+
7
+ #
8
+ # the job of the Refresher is to maintain a _lease_ on a file that has been
9
+ # locked. the method is simply to touch the file at a certain interval
10
+ # thereby keeping it 'fresh'. a separate process, vs. a thread, is used for
11
+ # this task to eliminate any chance that the ruby interpreter might put all
12
+ # threads to sleep for some blocking tasks, like fcntl based locks which are
13
+ # used heavily in RQ, resulting in a a prematurely stale lockfile
14
+ #
15
+ class Refresher
16
+ #--{{{
17
+ SIGNALS = %w(SIGTERM SIGINT SIGKILL)
18
+ attr :path
19
+ attr :pid
20
+ attr :refresh_rate
21
+ def initialize path, refresh_rate = 8
22
+ #--{{{
23
+ @path = path
24
+ File::stat path
25
+ @refresh_rate = Float refresh_rate
26
+ @pipe = IO::pipe
27
+ if((@pid = Util::fork))
28
+ @pipe.last.close
29
+ @pipe = @pipe.first
30
+ @thread = Thread::new{loop{@pipe.gets}}
31
+ Process::detach @pid
32
+ else
33
+ begin
34
+ pid = Process::pid
35
+ ppid = Process::ppid
36
+ $0 = "#{ path }.refresher.#{ pid }"
37
+ SIGNALS.each{|sig| trap(sig){ raise }}
38
+ @pipe.first.close
39
+ @pipe = @pipe.last
40
+ loop do
41
+ FileUtils::touch @path
42
+ sleep @refresh_rate
43
+ Process::kill 0, ppid
44
+ @pipe.puts pid
45
+ end
46
+ rescue Exception => e
47
+ exit!
48
+ end
49
+ end
50
+ #--}}}
51
+ end
52
+ def kill
53
+ #--{{{
54
+ begin
55
+ @thread.kill rescue nil
56
+ @pipe.close rescue nil
57
+ SIGNALS.each{|sig| Process::kill sig, @pid rescue nil}
58
+ ensure
59
+ =begin
60
+ n = 42
61
+ dead = false
62
+ begin
63
+ n.times do |i|
64
+ Process::kill 0, @pid
65
+ sleep 1
66
+ end
67
+ rescue Errno::ESRCH
68
+ dead = true
69
+ end
70
+ raise "runaway refresher <#{ @pid }> must be killed!" unless dead
71
+ =end
72
+ end
73
+ #--}}}
74
+ end
75
+ #--}}}
76
+ end # class Refresher
77
+ #--}}}
78
+ end # module RQ
79
+ $__rq_refresher__ = __FILE__
80
+ end
@@ -0,0 +1,283 @@
1
+ unless defined? $__rq_relayer__
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 Relayer < MainHelper
14
+ #--{{{
15
+ DEFAULT_MIN_SLEEP = 42
16
+ DEFAULT_MAX_SLEEP = 240
17
+ DEFAULT_RELAY = 16
18
+
19
+ class << self
20
+ #--{{{
21
+ attr :min_sleep, true
22
+ attr :max_sleep, true
23
+ attr :relay, true
24
+ #--}}}
25
+ end
26
+
27
+ def relay
28
+ #--{{{
29
+ daemon do
30
+ gen_pidfile
31
+ @main.init_logging
32
+ @logger = @main.logger
33
+
34
+ set_q
35
+
36
+ #
37
+ # munge @q/@qpath to set there
38
+ #
39
+ @here = @q
40
+ @qpath = realpath @main.argv.shift
41
+ set_q
42
+ @there = @q
43
+ @q = @here
44
+ @hdb = @here.qdb
45
+ @tdb = @there.qdb
46
+
47
+ @pid = Process::pid
48
+ @cmd = @main.cmd
49
+ @started = Util::timestamp
50
+ @min_sleep = Integer(@options['min_sleep'] || defval('min_sleep'))
51
+ @max_sleep = Integer(@options['max_sleep'] || defval('max_sleep'))
52
+ @relay = Integer(@options['number'] || defval('relay'))
53
+
54
+ @transactions = {}
55
+
56
+
57
+ install_signal_handlers
58
+
59
+ info{ "** STARTED **" }
60
+ info{ "version <#{ RQ::VERSION }>" }
61
+ info{ "cmd <#{ @cmd }>" }
62
+ info{ "pid <#{ @pid }>" }
63
+ info{ "pidfile <#{ @pidfile.path }>" }
64
+ info{ "here <#{ @here.path }>" }
65
+ info{ "there <#{ @there.path }>" }
66
+
67
+ debug{ "mode <#{ @mode }>" }
68
+ debug{ "min_sleep <#{ @min_sleep }>" }
69
+ debug{ "max_sleep <#{ @max_sleep }>" }
70
+ debug{ "relay <#{ @relay }>" }
71
+
72
+ exit
73
+
74
+ loop do
75
+ handle_signal if $rq_signaled
76
+ throttle(@min_sleep) do
77
+ reap_and_sow
78
+ relax
79
+ end
80
+ end
81
+ =begin
82
+ loop do
83
+ handle_signal if $rq_signaled
84
+ throttle(@min_sleep) do
85
+ start_jobs unless busy?
86
+ if nothing_running?
87
+ relax
88
+ else
89
+ reap_jobs
90
+ end
91
+ end
92
+ end
93
+ =end
94
+ end
95
+ #--}}}
96
+ end
97
+
98
+ def daemon
99
+ #--{{{
100
+ if @options['daemon']
101
+ fork do
102
+ Process::setsid
103
+ fork do
104
+ Dir::chdir(Util.realpath('~'))
105
+ File::umask 0
106
+ open('/dev/null','r+') do |f|
107
+ STDIN.reopen f
108
+ STDOUT.reopen f
109
+ STDERR.reopen f
110
+ end
111
+ @daemon = true
112
+ yield
113
+ exit EXIT_SUCCESS
114
+ end
115
+ exit!
116
+ end
117
+ exit!
118
+ else
119
+ @daemon = false
120
+ yield
121
+ exit EXIT_SUCCESS
122
+ end
123
+ #--}}}
124
+ end
125
+ def gen_pidfile name = nil
126
+ #--{{{
127
+ name ||= gen_relayer_name(@options['name'] || @qpath)
128
+ @pidfile =
129
+ begin
130
+ open name, File::CREAT | File::EXCL | File::RDWR
131
+ rescue
132
+ open name, File::RDWR
133
+ end
134
+ unless @pidfile and @pidfile.posixlock(File::LOCK_EX | File::LOCK_NB)
135
+ pid = IO::read(name) rescue nil
136
+ pid ||= 'unknown'
137
+ if @options['quiet']
138
+ exit EXIT_FAILURE
139
+ else
140
+ raise "process <#{ pid }> is already relaying from this queue"
141
+ end
142
+ else
143
+ @pidfile.rewind
144
+ @pidfile.sync = true
145
+ @pidfile.print Process::pid
146
+ @pidfile.truncate @pidfile.pos
147
+ at_exit{ FileUtils::rm_f name rescue nil }
148
+ end
149
+ #--}}}
150
+ end
151
+ def gen_relayer_name path
152
+ #--{{{
153
+ path = Util::realpath(path).gsub(%r|/|o, '_')
154
+ File::join(Util::realpath('~'), ".#{ path }.relayer")
155
+ #--}}}
156
+ end
157
+ def install_signal_handlers
158
+ #--{{{
159
+ if @daemon
160
+ $rq_signaled = false
161
+ $rq_sighup = false
162
+ $rq_sigterm = false
163
+ $rq_sigint = false
164
+ trap('SIGHUP') do
165
+ $rq_signaled = $rq_sighup = 'SIGHUP'
166
+ warn{ "signal <SIGHUP>" }
167
+ warn{ "finishing running jobs before handling signal" }
168
+ end
169
+ trap('SIGTERM') do
170
+ $rq_signaled = $rq_sigterm = 'SIGTERM'
171
+ warn{ "signal <SIGTERM>" }
172
+ warn{ "finishing running jobs before handling signal" }
173
+ end
174
+ trap('SIGINT') do
175
+ $rq_signaled = $rq_sigint = 'SIGINT'
176
+ warn{ "signal <SIGINT>" }
177
+ warn{ "finishing running jobs before handling signal" }
178
+ end
179
+ @jrd.install_signal_handlers
180
+ end
181
+ #--}}}
182
+ end
183
+ def handle_signal
184
+ #--{{{
185
+ if $rq_sigterm or $rq_sigint
186
+ reap_jobs(reap_only = true) until nothing_running?
187
+ info{ "** STOPPING **" }
188
+ @jrd.shutdown rescue nil
189
+ @pidfile.posixlock File::LOCK_UN
190
+ exit EXIT_SUCCESS
191
+ end
192
+
193
+ if $rq_sighup
194
+ reap_jobs(reap_only = true) until nothing_running?
195
+ info{ "** RESTARTING **" }
196
+ @jrd.shutdown rescue nil
197
+ Util::uncache __FILE__
198
+ @pidfile.posixlock File::LOCK_UN
199
+ Util::exec @cmd
200
+ end
201
+ #--}}}
202
+ end
203
+ def throttle rate = @min_sleep
204
+ #--{{{
205
+ if Numeric === rate and rate > 0
206
+ if defined? @last_throttle_time and @last_throttle_time
207
+ elapsed = Time.now - @last_throttle_time
208
+ timeout = rate - elapsed
209
+ if timeout > 0
210
+ timeout = timeout + rand(rate * 0.10)
211
+ debug{ "throttle rate of <#{ rate }> exceeded - sleeping <#{ timeout }>" }
212
+ sleep timeout
213
+ end
214
+ end
215
+ @last_throttle_time = Time.now
216
+ end
217
+ yield
218
+ #--}}}
219
+ end
220
+ def transaction
221
+ #--{{{
222
+ ret = nil
223
+ if @in_transaction
224
+ ret = yield
225
+ else
226
+ begin
227
+ @in_transaction = true
228
+ @here.transaction do
229
+ @there.transaction do
230
+ ret = yield
231
+ end
232
+ end
233
+ ensure
234
+ @in_transaction = false
235
+ end
236
+ end
237
+ ret
238
+ #--}}}
239
+ end
240
+ def relax
241
+ #--{{{
242
+ seconds = rand(@max_sleep - @min_sleep + 1) + @min_sleep
243
+ debug{ "relaxing <#{ seconds }>" }
244
+ sleep seconds
245
+ #--}}}
246
+ end
247
+
248
+ #
249
+ # TODO - this will need to map jids here to jids there
250
+ #
251
+ def reap_and_sow
252
+ #--{{{
253
+ transaction{ reap and sow }
254
+ #--}}}
255
+ end
256
+ def reap
257
+ #--{{{
258
+ debug{ "reaping finished/dead jobs" }
259
+
260
+ sql = <<-sql
261
+ select jid from jobs where or state='running'
262
+ sql
263
+ tuples = hdb.execute sql
264
+ hjids = tuples.map{|t| t['jid']}
265
+
266
+ unless jids.empty
267
+ where_clauses = hjids.map{|hjid| "jid=#{ hjid }" }
268
+ where_clause = where_clauses.join ' or '
269
+ sql = <<-sql
270
+ select jid from jobs where state='finished' or state='dead' and (#{ where_clause })
271
+ sql
272
+ end
273
+
274
+ debug{ "reaped finished/dead jobs" }
275
+ self
276
+ #--}}}
277
+ end
278
+ #--}}}
279
+ end # class Relayer
280
+ #--}}}
281
+ end # module RQ
282
+ $__rq_relayer__ = __FILE__
283
+ end