rq-ruby1.8 3.4.3

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