resque-pool 0.0.2 → 0.0.4
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/README.md +132 -0
- data/lib/resque/pool.rb +169 -71
- data/lib/resque/pool/logging.rb +19 -0
- data/lib/resque/pool/pooled_worker.rb +65 -0
- data/lib/resque/pool/tasks.rb +2 -7
- metadata +11 -8
data/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Resque Pool
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
Resque pool is a simple library for managing a pool of resque workers. Given a
|
|
5
|
+
configuration hash or a config file (resque-pool.yml or
|
|
6
|
+
config/resque-pool.yml), it will manage your workers for you, starting up the
|
|
7
|
+
appropriate number of workers for each.
|
|
8
|
+
|
|
9
|
+
Benefits
|
|
10
|
+
---------
|
|
11
|
+
|
|
12
|
+
* Less memory consumption - If you are using Ruby Enterprise Edition, or any
|
|
13
|
+
ruby with copy-on-write safe garbage collection, this will save you a lot of
|
|
14
|
+
memory when you managing many workers.
|
|
15
|
+
* Simpler (less) config - If you are using monit or god or an init script to
|
|
16
|
+
start up your workers, you can simply start up one pool, and it will manage
|
|
17
|
+
your workers for you.
|
|
18
|
+
* Faster startup - if you are starting many workers at once, you would normally
|
|
19
|
+
have them competing for CPU as they load their environments. Resque-pool can
|
|
20
|
+
load the environment once, and almost immediately fork all of your workers.
|
|
21
|
+
|
|
22
|
+
How to use
|
|
23
|
+
-----------
|
|
24
|
+
|
|
25
|
+
To configure resque-pool, you can either set `Resque::Pool.config` to a hash in
|
|
26
|
+
your `resque:setup` or you can set the same config in either `resque-pool.yml`
|
|
27
|
+
or `config/resque-pool.yml`. To use resque-pool, require its rake tasks in
|
|
28
|
+
your rake file, and call the resque:pool task.
|
|
29
|
+
|
|
30
|
+
For example, to use resque-pool with rails, in `config/resque-pool.yml`:
|
|
31
|
+
|
|
32
|
+
foo: 1
|
|
33
|
+
bar: 2
|
|
34
|
+
"foo,bar,baz": 4
|
|
35
|
+
|
|
36
|
+
and in `lib/tasks/resque.rake`:
|
|
37
|
+
|
|
38
|
+
require 'resque/pool/tasks'
|
|
39
|
+
namespace :resque do
|
|
40
|
+
|
|
41
|
+
# preload the rails environment in the pool master
|
|
42
|
+
task :setup => :environment
|
|
43
|
+
# it's better to use a config file, but you can also config here:
|
|
44
|
+
# Resque::Pool.config = {"foo" => 1, "bar" => 1}
|
|
45
|
+
|
|
46
|
+
# close any sockets or files in pool master
|
|
47
|
+
ActiveRecord::Base.connection.disconnect!
|
|
48
|
+
|
|
49
|
+
# and re-open them in the resque worker parent
|
|
50
|
+
Resque::Pool.after_prefork do |job|
|
|
51
|
+
ActiveRecord::Base.establish_connection
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# you could also re-open them in the resque worker child, using
|
|
55
|
+
# Resque.after_fork, but that probably isn't necessary, and
|
|
56
|
+
# Resque::Pool.after_prefork should be faster, since it won't run
|
|
57
|
+
# for every single job.
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Then you can start the queues via:
|
|
62
|
+
|
|
63
|
+
rake resque:pool RAILS_ENV=production
|
|
64
|
+
|
|
65
|
+
This will start up seven worker processes, one each looking exclusively at each
|
|
66
|
+
of the foo, bar, and baz queues, and four workers looking at all queues in
|
|
67
|
+
priority. This is similar to if you ran the following:
|
|
68
|
+
|
|
69
|
+
rake resque:worker RAILS_ENV=production QUEUES=foo
|
|
70
|
+
rake resque:worker RAILS_ENV=production QUEUES=bar
|
|
71
|
+
rake resque:worker RAILS_ENV=production QUEUES=bar
|
|
72
|
+
rake resque:worker RAILS_ENV=production QUEUES=foo,bar,baz
|
|
73
|
+
rake resque:worker RAILS_ENV=production QUEUES=foo,bar,baz
|
|
74
|
+
rake resque:worker RAILS_ENV=production QUEUES=foo,bar,baz
|
|
75
|
+
rake resque:worker RAILS_ENV=production QUEUES=foo,bar,baz
|
|
76
|
+
|
|
77
|
+
Resque already forks for its own child processes, giving two levels. The pool
|
|
78
|
+
master will stay around monitoring the resque worker parents, giving three
|
|
79
|
+
levels:
|
|
80
|
+
|
|
81
|
+
* a single pool master
|
|
82
|
+
* many worker parents
|
|
83
|
+
* a worker child per worker (when the actual job is being processed)
|
|
84
|
+
|
|
85
|
+
For example, `ps -ef f | grep [r]esque` might return something like the
|
|
86
|
+
following:
|
|
87
|
+
|
|
88
|
+
rails 13858 1 0 13:44 ? S 0:02 resque-pool-master: managing [13867, 13875, 13871, 13872, 13868, 13870, 13876]
|
|
89
|
+
rails 13867 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Waiting for foo
|
|
90
|
+
rails 13868 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Waiting for bar
|
|
91
|
+
rails 13870 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Waiting for bar
|
|
92
|
+
rails 13871 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Waiting for foo,bar,baz
|
|
93
|
+
rails 13872 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Forked 7481 at 1280343254
|
|
94
|
+
rails 7481 13872 0 14:54 ? S 0:00 \_ resque-1.9.9: Processing foo since 1280343254
|
|
95
|
+
rails 13875 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Waiting for foo,bar,baz
|
|
96
|
+
rails 13876 13858 0 13:44 ? S 0:00 \_ resque-1.9.9: Forked 7485 at 1280343255
|
|
97
|
+
rails 7485 13876 0 14:54 ? S 0:00 \_ resque-1.9.9: Processing bar since 1280343254
|
|
98
|
+
|
|
99
|
+
SIGNALS
|
|
100
|
+
-------
|
|
101
|
+
|
|
102
|
+
The pool master responds to the following signals:
|
|
103
|
+
|
|
104
|
+
* `HUP` - reload the config file, e.g. to change the number of workers per queue list
|
|
105
|
+
* `QUIT` - send `QUIT` to each worker parent and shutdown the master after all workers are done.
|
|
106
|
+
* `INT` - send `QUIT` to each worker parent and immediately shutdown master
|
|
107
|
+
* `TERM` - send `TERM` to each worker parent and immediately shutdown master
|
|
108
|
+
* `WINCH` - send `QUIT` to each worker, but keep master running (send `HUP` to reload config and restart workers)
|
|
109
|
+
* `USR1`/`USR2`/`CONT` - send the signal on to all worker parents (see Resque docs).
|
|
110
|
+
|
|
111
|
+
`HUP` will no-op if you use a hash for configuration instead of a config file.
|
|
112
|
+
So you should probably use a config file. After a `HUP`, workers that are no
|
|
113
|
+
longer needed will be gracefully shutdown via `QUIT`.
|
|
114
|
+
|
|
115
|
+
Other Features
|
|
116
|
+
--------------
|
|
117
|
+
|
|
118
|
+
Workers will watch the pool master, and gracefully shutdown if the master
|
|
119
|
+
process dies (for whatever reason) before them.
|
|
120
|
+
|
|
121
|
+
TODO
|
|
122
|
+
-----
|
|
123
|
+
|
|
124
|
+
* do appropriate logging (e.g. all to one logfile, each queue to its own
|
|
125
|
+
logfile, or each worker to its own logfile). Logfile location must be
|
|
126
|
+
configurable.
|
|
127
|
+
* (optionally) daemonize, setting a PID file somewhere
|
|
128
|
+
* recover gracefully from a malformed config file (on startup and HUP)
|
|
129
|
+
* figure out a good way to test this (preferably via cucumber or rspec)
|
|
130
|
+
* clean up the code (I stole most of it from unicorn, and it's still a bit
|
|
131
|
+
bastardized)
|
|
132
|
+
* web interface for adding and removing workers (etc)
|
data/lib/resque/pool.rb
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
require 'resque'
|
|
3
|
+
require 'resque/pool/logging'
|
|
4
|
+
require 'resque/pool/pooled_worker'
|
|
3
5
|
require 'fcntl'
|
|
6
|
+
require 'yaml'
|
|
4
7
|
|
|
5
8
|
module Resque
|
|
6
9
|
class Pool
|
|
7
|
-
|
|
10
|
+
include Logging
|
|
11
|
+
attr_reader :config
|
|
8
12
|
attr_reader :workers
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
# CONSTANTS {{{
|
|
15
|
+
SIG_QUEUE_MAX_SIZE = 5
|
|
16
|
+
DEFAULT_WORKER_INTERVAL = 5
|
|
17
|
+
QUEUE_SIGS = [ :QUIT, :INT, :TERM, :USR1, :USR2, :CONT, :HUP, :WINCH, ]
|
|
13
18
|
CHUNK_SIZE=(16 * 1024)
|
|
19
|
+
# }}}
|
|
14
20
|
|
|
15
21
|
def initialize(config)
|
|
16
|
-
|
|
17
|
-
@workers =
|
|
22
|
+
init_config(config)
|
|
23
|
+
@workers = {}
|
|
18
24
|
procline "(initialized)"
|
|
19
25
|
end
|
|
20
26
|
|
|
27
|
+
# Config: after_prefork {{{
|
|
21
28
|
|
|
22
29
|
# The `after_prefork` hook will be run in workers if you are using the
|
|
23
30
|
# preforking master worker to save memory. Use this hook to reload
|
|
@@ -39,48 +46,131 @@ module Resque
|
|
|
39
46
|
self.class.after_prefork && self.class.after_prefork.call
|
|
40
47
|
end
|
|
41
48
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
# }}}
|
|
50
|
+
# Config: class methods to start up the pool using the default config {{{
|
|
51
|
+
|
|
52
|
+
@config_files = ["resque-pool.yml", "config/resque-pool.yml"]
|
|
53
|
+
class << self; attr_accessor :config, :config_files; end
|
|
54
|
+
def self.load_default_config
|
|
55
|
+
if @config
|
|
56
|
+
@config
|
|
57
|
+
else
|
|
58
|
+
@config_files.detect { |f| File.exist?(f) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.run
|
|
63
|
+
if GC.respond_to?(:copy_on_write_friendly=)
|
|
64
|
+
GC.copy_on_write_friendly = true
|
|
65
|
+
end
|
|
66
|
+
Resque::Pool.new(load_default_config).start.join
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# }}}
|
|
70
|
+
# Config: load config and config file {{{
|
|
71
|
+
|
|
72
|
+
def load_config_from_file
|
|
73
|
+
return unless @pool_config_file
|
|
74
|
+
log "**** loading config from #{@pool_config_file}"
|
|
75
|
+
@config = YAML.load_file(@pool_config_file)
|
|
47
76
|
end
|
|
48
77
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
def init_config(config)
|
|
79
|
+
unless config
|
|
80
|
+
raise ArgumentError,
|
|
81
|
+
"No configuration found. Please setup config/resque-pool.yml"
|
|
82
|
+
end
|
|
83
|
+
if config.kind_of? String
|
|
84
|
+
@pool_config_file = config.to_s
|
|
85
|
+
load_config_from_file
|
|
86
|
+
else
|
|
87
|
+
@config = config.dup
|
|
88
|
+
end
|
|
89
|
+
log "**** config: #{@config.inspect}"
|
|
52
90
|
end
|
|
53
91
|
|
|
92
|
+
# }}}
|
|
93
|
+
|
|
94
|
+
# Sig handlers and self pipe management {{{
|
|
95
|
+
|
|
96
|
+
def self_pipe; @self_pipe ||= [] end
|
|
97
|
+
def sig_queue; @sig_queue ||= [] end
|
|
98
|
+
|
|
54
99
|
def init_self_pipe!
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
100
|
+
self_pipe.each { |io| io.close rescue nil }
|
|
101
|
+
self_pipe.replace(IO.pipe)
|
|
102
|
+
self_pipe.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
|
58
103
|
end
|
|
59
104
|
|
|
60
105
|
def init_sig_handlers!
|
|
61
106
|
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
|
62
|
-
trap(:CHLD)
|
|
107
|
+
trap(:CHLD) { |_| awaken_master }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def awaken_master
|
|
111
|
+
begin
|
|
112
|
+
self_pipe.last.write_nonblock('.') # wakeup master process from select
|
|
113
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
|
114
|
+
# pipe is full, master should wake up anyways
|
|
115
|
+
retry
|
|
116
|
+
end
|
|
63
117
|
end
|
|
64
118
|
|
|
65
119
|
# defer a signal for later processing in #join (master process)
|
|
66
120
|
def trap_deferred(signal)
|
|
67
121
|
trap(signal) do |sig_nr|
|
|
68
|
-
if
|
|
69
|
-
|
|
122
|
+
if sig_queue.size < SIG_QUEUE_MAX_SIZE
|
|
123
|
+
sig_queue << signal
|
|
70
124
|
awaken_master
|
|
71
125
|
else
|
|
72
|
-
log "ignoring SIG#{signal}, queue=#{
|
|
126
|
+
log "ignoring SIG#{signal}, queue=#{sig_queue.inspect}"
|
|
73
127
|
end
|
|
74
128
|
end
|
|
75
129
|
end
|
|
76
130
|
|
|
131
|
+
def reset_sig_handlers!
|
|
132
|
+
QUEUE_SIGS.each {|sig| trap(sig, "DEFAULT") }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def handle_sig_queue!
|
|
136
|
+
case signal = sig_queue.shift
|
|
137
|
+
when :USR1, :USR2, :CONT
|
|
138
|
+
log "#{signal}: sending to all workers"
|
|
139
|
+
signal_all_workers(signal)
|
|
140
|
+
when :HUP
|
|
141
|
+
log "HUP: reload config file"
|
|
142
|
+
load_config_from_file
|
|
143
|
+
maintain_worker_count
|
|
144
|
+
when :WINCH
|
|
145
|
+
log "WINCH: gracefully stopping all workers"
|
|
146
|
+
@config = {}
|
|
147
|
+
maintain_worker_count
|
|
148
|
+
when :QUIT
|
|
149
|
+
log "QUIT: graceful shutdown, waiting for children"
|
|
150
|
+
signal_all_workers(:QUIT)
|
|
151
|
+
reap_all_workers(0) # will hang until all workers are shutdown
|
|
152
|
+
:break
|
|
153
|
+
when :INT
|
|
154
|
+
log "INT: immediate shutdown (graceful worker shutdown)"
|
|
155
|
+
signal_all_workers(:QUIT)
|
|
156
|
+
:break
|
|
157
|
+
when :TERM
|
|
158
|
+
log "TERM: immediate shutdown (and immediate worker shutdown)"
|
|
159
|
+
signal_all_workers(:TERM)
|
|
160
|
+
:break
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# }}}
|
|
165
|
+
# start, join, and master sleep {{{
|
|
166
|
+
|
|
77
167
|
def start
|
|
78
168
|
procline("(starting)")
|
|
79
169
|
init_self_pipe!
|
|
80
170
|
init_sig_handlers!
|
|
81
171
|
maintain_worker_count
|
|
82
172
|
procline("(started)")
|
|
83
|
-
log "****
|
|
173
|
+
log "**** started master at PID: #{Process.pid}"
|
|
84
174
|
log "**** Pool contains PIDs: #{all_pids.inspect}"
|
|
85
175
|
self
|
|
86
176
|
end
|
|
@@ -89,84 +179,78 @@ module Resque
|
|
|
89
179
|
loop do
|
|
90
180
|
reap_all_workers
|
|
91
181
|
break if handle_sig_queue! == :break
|
|
92
|
-
|
|
182
|
+
if sig_queue.empty?
|
|
183
|
+
master_sleep
|
|
184
|
+
maintain_worker_count
|
|
185
|
+
end
|
|
93
186
|
procline("managing #{all_pids.inspect}")
|
|
94
187
|
end
|
|
95
188
|
procline("(shutting down)")
|
|
96
189
|
#stop # gracefully shutdown all workers on our way out
|
|
190
|
+
log "**** master complete"
|
|
97
191
|
#unlink_pid_safe(pid) if pid
|
|
98
192
|
end
|
|
99
193
|
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
when :TERM, :INT # immediate shutdown
|
|
107
|
-
#stop(false)
|
|
108
|
-
:break
|
|
194
|
+
def master_sleep
|
|
195
|
+
begin
|
|
196
|
+
ready = IO.select([self_pipe.first], nil, nil, 1) or return
|
|
197
|
+
ready.first && ready.first.first or return
|
|
198
|
+
loop { self_pipe.first.read_nonblock(CHUNK_SIZE) }
|
|
199
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
|
109
200
|
end
|
|
110
201
|
end
|
|
111
202
|
|
|
112
|
-
|
|
203
|
+
# }}}
|
|
204
|
+
# worker process management {{{
|
|
205
|
+
|
|
206
|
+
def reap_all_workers(waitpid_flags=Process::WNOHANG)
|
|
113
207
|
begin
|
|
114
208
|
loop do
|
|
115
|
-
wpid, status = Process.waitpid2(-1,
|
|
209
|
+
wpid, status = Process.waitpid2(-1, waitpid_flags)
|
|
116
210
|
wpid or break
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
#self.pid = pid.chomp('.oldbin') if pid
|
|
121
|
-
#proc_name 'master'
|
|
122
|
-
#else
|
|
123
|
-
worker = delete_worker(wpid) #and worker.tmp.close rescue nil
|
|
124
|
-
log "reaped #{status.inspect} " \
|
|
125
|
-
"worker=#{worker.nr rescue 'unknown'}"
|
|
126
|
-
#end
|
|
211
|
+
worker = delete_worker(wpid)
|
|
212
|
+
# TODO: close any file descriptors connected to worker, if any
|
|
213
|
+
log "** reaped #{status.inspect}, worker=#{worker.queues.join(",")}"
|
|
127
214
|
end
|
|
128
215
|
rescue Errno::ECHILD
|
|
129
216
|
end
|
|
130
217
|
end
|
|
131
218
|
|
|
132
219
|
def delete_worker(pid)
|
|
133
|
-
|
|
134
|
-
|
|
220
|
+
worker = nil
|
|
221
|
+
workers.detect do |queues, pid_to_worker|
|
|
222
|
+
worker = pid_to_worker.delete(pid)
|
|
135
223
|
end
|
|
224
|
+
worker
|
|
136
225
|
end
|
|
137
226
|
|
|
138
|
-
def
|
|
139
|
-
|
|
140
|
-
ready = IO.select([SELF_PIPE.first], nil, nil, 1) or return
|
|
141
|
-
ready.first && ready.first.first or return
|
|
142
|
-
loop { SELF_PIPE.first.read_nonblock(CHUNK_SIZE) }
|
|
143
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
|
144
|
-
end
|
|
227
|
+
def all_pids
|
|
228
|
+
workers.map {|q,workers| workers.keys }.flatten
|
|
145
229
|
end
|
|
146
230
|
|
|
147
|
-
def
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
rescue Errno::EAGAIN, Errno::EINTR
|
|
151
|
-
# pipe is full, master should wake up anyways
|
|
152
|
-
retry
|
|
231
|
+
def signal_all_workers(signal)
|
|
232
|
+
all_pids.each do |pid|
|
|
233
|
+
Process.kill signal, pid
|
|
153
234
|
end
|
|
154
235
|
end
|
|
155
236
|
|
|
237
|
+
# }}}
|
|
238
|
+
# ???: maintain_worker_count, all_known_queues {{{
|
|
239
|
+
|
|
156
240
|
def maintain_worker_count
|
|
157
|
-
|
|
158
|
-
|
|
241
|
+
all_known_queues.each do |queues|
|
|
242
|
+
delta = worker_delta_for(queues)
|
|
159
243
|
spawn_missing_workers_for(queues) if delta > 0
|
|
160
|
-
|
|
244
|
+
quit_excess_workers_for(queues) if delta < 0
|
|
161
245
|
end
|
|
162
246
|
end
|
|
163
247
|
|
|
164
|
-
def
|
|
165
|
-
|
|
248
|
+
def all_known_queues
|
|
249
|
+
config.keys | workers.keys
|
|
166
250
|
end
|
|
167
251
|
|
|
168
|
-
|
|
169
|
-
#
|
|
252
|
+
# }}}
|
|
253
|
+
# methods that operate on a single grouping of queues {{{
|
|
170
254
|
# perhaps this means a class is waiting to be extracted
|
|
171
255
|
|
|
172
256
|
def spawn_missing_workers_for(queues)
|
|
@@ -175,8 +259,19 @@ module Resque
|
|
|
175
259
|
end
|
|
176
260
|
end
|
|
177
261
|
|
|
262
|
+
def quit_excess_workers_for(queues)
|
|
263
|
+
delta = -worker_delta_for(queues)
|
|
264
|
+
pids_for(queues)[0...delta].each do |pid|
|
|
265
|
+
Process.kill("QUIT", pid)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
178
269
|
def worker_delta_for(queues)
|
|
179
|
-
|
|
270
|
+
config.fetch(queues, 0) - workers.fetch(queues, []).size
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def pids_for(queues)
|
|
274
|
+
workers[queues].keys
|
|
180
275
|
end
|
|
181
276
|
|
|
182
277
|
def spawn_worker!(queues)
|
|
@@ -184,20 +279,23 @@ module Resque
|
|
|
184
279
|
pid = fork do
|
|
185
280
|
log "*** Starting worker #{worker}"
|
|
186
281
|
call_after_prefork!
|
|
187
|
-
|
|
282
|
+
reset_sig_handlers!
|
|
283
|
+
#self_pipe.each {|io| io.close }
|
|
284
|
+
worker.work(ENV['INTERVAL'] || DEFAULT_WORKER_INTERVAL) # interval, will block
|
|
188
285
|
end
|
|
286
|
+
workers[queues] ||= {}
|
|
189
287
|
workers[queues][pid] = worker
|
|
190
288
|
end
|
|
191
289
|
|
|
192
290
|
def create_worker(queues)
|
|
193
291
|
queues = queues.to_s.split(',')
|
|
194
|
-
worker =
|
|
292
|
+
worker = PooledWorker.new(*queues)
|
|
195
293
|
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
|
196
294
|
worker.very_verbose = ENV['VVERBOSE']
|
|
197
295
|
worker
|
|
198
|
-
rescue Resque::NoQueueError
|
|
199
|
-
abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
|
|
200
296
|
end
|
|
201
297
|
|
|
298
|
+
# }}}
|
|
299
|
+
|
|
202
300
|
end
|
|
203
301
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Resque
|
|
2
|
+
class Pool
|
|
3
|
+
module Logging
|
|
4
|
+
|
|
5
|
+
# Given a string, sets the procline ($0)
|
|
6
|
+
# Procline is always in the format of:
|
|
7
|
+
# resque-pool-master: STRING
|
|
8
|
+
def procline(string)
|
|
9
|
+
$0 = "resque-pool-master: #{string}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# TODO: make this use an actual logger
|
|
13
|
+
def log(message)
|
|
14
|
+
puts message
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
class Resque::Pool
|
|
2
|
+
|
|
3
|
+
class PooledWorker < ::Resque::Worker
|
|
4
|
+
|
|
5
|
+
def initialize(*args)
|
|
6
|
+
@pool_master_pid = Process.pid
|
|
7
|
+
super
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def pool_master_has_gone_away?
|
|
11
|
+
@pool_master_pid && @pool_master_pid != Process.ppid
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# this allows us to shutdown
|
|
15
|
+
def shutdown
|
|
16
|
+
@shutdown || pool_master_has_gone_away?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# this entire method (except for one line) is copied and pasted from
|
|
20
|
+
# resque-1.9.9. If shutdown were used as a method (attr_reader) rather
|
|
21
|
+
# than an instance variable, I wouldn't need to reduplicate this. :-(
|
|
22
|
+
#
|
|
23
|
+
# hopefully I can get defunkt to accept my patch for this.
|
|
24
|
+
# Until it is, the resque-pool gem will depend on an exact version of
|
|
25
|
+
# resque.
|
|
26
|
+
def work(interval = 5, &block)
|
|
27
|
+
$0 = "resque: Starting"
|
|
28
|
+
startup
|
|
29
|
+
|
|
30
|
+
loop do
|
|
31
|
+
#### THIS IS THE ONLY LINE THAT IS CHANGED
|
|
32
|
+
break if shutdown
|
|
33
|
+
#### THAT WAS THE ONLY LINE THAT WAS CHANGED
|
|
34
|
+
|
|
35
|
+
if not @paused and job = reserve
|
|
36
|
+
log "got: #{job.inspect}"
|
|
37
|
+
run_hook :before_fork
|
|
38
|
+
working_on job
|
|
39
|
+
|
|
40
|
+
if @child = fork
|
|
41
|
+
rand # Reseeding
|
|
42
|
+
procline "Forked #{@child} at #{Time.now.to_i}"
|
|
43
|
+
Process.wait
|
|
44
|
+
else
|
|
45
|
+
procline "Processing #{job.queue} since #{Time.now.to_i}"
|
|
46
|
+
perform(job, &block)
|
|
47
|
+
exit! unless @cant_fork
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
done_working
|
|
51
|
+
@child = nil
|
|
52
|
+
else
|
|
53
|
+
break if interval.to_i == 0
|
|
54
|
+
log! "Sleeping for #{interval.to_i}"
|
|
55
|
+
procline @paused ? "Paused" : "Waiting for #{@queues.join(',')}"
|
|
56
|
+
sleep interval.to_i
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ensure
|
|
61
|
+
unregister_worker
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
data/lib/resque/pool/tasks.rb
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
2
|
|
|
3
|
-
# require 'resque/pool/tasks'
|
|
4
|
-
# and configure "resque:setup" task to start up the environment, initialize
|
|
5
|
-
# RESQUE_POOL_CONFIG, and setup other resque hooks
|
|
6
|
-
|
|
7
3
|
namespace :resque do
|
|
8
4
|
task :setup
|
|
9
5
|
|
|
10
|
-
desc "Launch a pool of resque workers
|
|
6
|
+
desc "Launch a pool of resque workers"
|
|
11
7
|
task :pool => :setup do
|
|
12
|
-
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true
|
|
13
8
|
require 'resque/pool'
|
|
14
|
-
Resque::Pool.
|
|
9
|
+
Resque::Pool.run
|
|
15
10
|
end
|
|
16
11
|
|
|
17
12
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-pool
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
4
|
+
hash: 23
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 0
|
|
9
|
-
-
|
|
10
|
-
version: 0.0.
|
|
9
|
+
- 4
|
|
10
|
+
version: 0.0.4
|
|
11
11
|
platform: ruby
|
|
12
12
|
authors:
|
|
13
13
|
- nicholas a. evans
|
|
@@ -16,7 +16,7 @@ autorequire:
|
|
|
16
16
|
bindir: bin
|
|
17
17
|
cert_chain: []
|
|
18
18
|
|
|
19
|
-
date: 2010-
|
|
19
|
+
date: 2010-07-29 00:00:00 -04:00
|
|
20
20
|
default_executable:
|
|
21
21
|
dependencies:
|
|
22
22
|
- !ruby/object:Gem::Dependency
|
|
@@ -39,14 +39,14 @@ dependencies:
|
|
|
39
39
|
requirement: &id002 !ruby/object:Gem::Requirement
|
|
40
40
|
none: false
|
|
41
41
|
requirements:
|
|
42
|
-
- -
|
|
42
|
+
- - "="
|
|
43
43
|
- !ruby/object:Gem::Version
|
|
44
|
-
hash:
|
|
44
|
+
hash: 33
|
|
45
45
|
segments:
|
|
46
46
|
- 1
|
|
47
47
|
- 9
|
|
48
|
-
-
|
|
49
|
-
version: 1.9.
|
|
48
|
+
- 9
|
|
49
|
+
version: 1.9.9
|
|
50
50
|
type: :runtime
|
|
51
51
|
version_requirements: *id002
|
|
52
52
|
description: " quickly and easily fork a pool of resque workers,\n saving memory (w/REE) and monitoring their uptime\n"
|
|
@@ -61,6 +61,9 @@ extra_rdoc_files: []
|
|
|
61
61
|
files:
|
|
62
62
|
- lib/resque/pool.rb
|
|
63
63
|
- lib/resque/pool/tasks.rb
|
|
64
|
+
- lib/resque/pool/pooled_worker.rb
|
|
65
|
+
- lib/resque/pool/logging.rb
|
|
66
|
+
- README.md
|
|
64
67
|
has_rdoc: true
|
|
65
68
|
homepage: http://github.com/nevans/resque-pool
|
|
66
69
|
licenses: []
|