resque-pool 0.2.0 → 0.3.0.beta.1
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/Changelog.md +14 -0
- data/ExperimentalFeatures.md +48 -0
- data/README.md +7 -2
- data/lib/resque/pool.rb +69 -61
- data/lib/resque/pool/cli.rb +1 -1
- data/lib/resque/pool/manager.rb +11 -0
- data/lib/resque/pool/memory_manager.rb +118 -0
- data/lib/resque/pool/orphan_watcher.rb +49 -0
- data/lib/resque/pool/version.rb +1 -1
- data/lib/resque/pool/worker_type_manager.rb +102 -0
- metadata +17 -8
data/Changelog.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## unreleased
|
|
2
2
|
|
|
3
|
+
* enhancement: new callbacks for configuration
|
|
4
|
+
* `Resque::Pool.configure do |pool| ... end`
|
|
5
|
+
* `pool.after_manager_wakeup`
|
|
6
|
+
* `pool.to_calculate_worker_offset`
|
|
7
|
+
* `pool.after_prefork` (instance callback prefered over class callback)
|
|
8
|
+
* experimental: memory management
|
|
9
|
+
* experimental: check orphaned workers
|
|
10
|
+
* development: a good bit of code cleanup and rearrangement
|
|
11
|
+
|
|
12
|
+
See ExperimentalFeatures.md for more info. Thanks to Jason Haruska for these
|
|
13
|
+
features!
|
|
14
|
+
|
|
15
|
+
## 0.2.0 (2011-03-15)
|
|
16
|
+
|
|
3
17
|
* new feature: sending `HUP` to pool manager will reload the logfiles and
|
|
4
18
|
gracefully restart all workers.
|
|
5
19
|
* enhancement: logging now includes timestamp, process "name" (worker or
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Experimental Features
|
|
2
|
+
---------------------
|
|
3
|
+
|
|
4
|
+
Features listed here should not cause you problems if you don't use them... and
|
|
5
|
+
probably won't cause you problems if you *do* use them. Maybe. We hope. ;-)
|
|
6
|
+
Once these features have stood the test of time, can be trusted to work cross
|
|
7
|
+
platform, are well documented and tested, and have settled on a stable API,
|
|
8
|
+
then we'll stop calling them experimental and probably give them command line
|
|
9
|
+
options. Until then, be forewarned that it might not work for you and the API
|
|
10
|
+
might change between minor releases.
|
|
11
|
+
|
|
12
|
+
### Memory management
|
|
13
|
+
|
|
14
|
+
A memory manager is provided which can check once a minute to see if any of
|
|
15
|
+
your workers are using too much memory. If a worker is over the soft limit it
|
|
16
|
+
will be sent a QUIT signal. If a worker is over the hard signal it will be
|
|
17
|
+
sent a TERM signal, and if it is still running a minute later it will be sent a
|
|
18
|
+
KILL signal. To use the memory manager, add something like the following to
|
|
19
|
+
your Rakefile config:
|
|
20
|
+
|
|
21
|
+
task "resque:pool:setup" do
|
|
22
|
+
Resque::Pool.configure do |pool|
|
|
23
|
+
# memory limits are in MB
|
|
24
|
+
hard_limit = 250
|
|
25
|
+
soft_limit = 200
|
|
26
|
+
mm = Resque::Pool::MemoryManager.new(pool, hard_limit, soft_limit)
|
|
27
|
+
pool.after_manager_wakeup { mm.monitor_memory_usage }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
### Wait for orphaned workers to quit.
|
|
32
|
+
|
|
33
|
+
When restarting resque-pool, some orphaned workers may be left over from the
|
|
34
|
+
previous manager, even after the new manager has started up and forked its
|
|
35
|
+
workers. If you set the `RESQUE_WAIT_FOR_ORPHANS` environment variable, then
|
|
36
|
+
the new manager will not start up all of its configured workers until all of
|
|
37
|
+
the orphaned workers have finished. This might be useful for long running jobs
|
|
38
|
+
on memory constrained servers.
|
|
39
|
+
|
|
40
|
+
To use this, add something like the following to your Rakefile config:
|
|
41
|
+
|
|
42
|
+
task "resque:pool:setup" do
|
|
43
|
+
Resque::Pool.configure do |pool|
|
|
44
|
+
orphan_watcher = Resque::Pool::OrphanWatcher.new(pool)
|
|
45
|
+
pool.to_calculate_worker_offset { orphan_watcher.worker_offset }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
data/README.md
CHANGED
|
@@ -130,6 +130,11 @@ their current job) if the manager process disappears before them.
|
|
|
130
130
|
You can specify an alternate config file by setting the `RESQUE_POOL_CONFIG` or
|
|
131
131
|
with the `--config` command line option.
|
|
132
132
|
|
|
133
|
+
Experimental Features
|
|
134
|
+
---------------------
|
|
135
|
+
|
|
136
|
+
See `ExperimentalFeatures.md` for some potentially useful works in progress.
|
|
137
|
+
|
|
133
138
|
TODO
|
|
134
139
|
-----
|
|
135
140
|
|
|
@@ -138,8 +143,8 @@ See [the TODO list](https://github.com/nevans/resque-pool/issues) at github issu
|
|
|
138
143
|
Contributors
|
|
139
144
|
-------------
|
|
140
145
|
|
|
146
|
+
* Jason Haruska from Backupify (pidfile management, memory management, wait for orphaned workers)
|
|
141
147
|
* John Schult (config file can be split by environment)
|
|
142
148
|
* Stephen Celis (increased gemspec sanity)
|
|
143
149
|
* Vincent Agnello, Robert Kamunyori, Paul Kauders; for pairing with me at
|
|
144
|
-
B'more on Rails Open Source Hack Nights. :)
|
|
145
|
-
|
|
150
|
+
[B'more on Rails](http://twitter.com/bmoreonrails) Open Source Hack Nights. :)
|
data/lib/resque/pool.rb
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
require 'resque'
|
|
3
3
|
require 'resque/pool/version'
|
|
4
4
|
require 'resque/pool/logging'
|
|
5
|
-
require 'resque/pool/
|
|
5
|
+
require 'resque/pool/manager'
|
|
6
|
+
require 'resque/pool/worker_type_manager'
|
|
7
|
+
# experimental features!
|
|
8
|
+
require 'resque/pool/memory_manager'
|
|
9
|
+
require 'resque/pool/orphan_watcher'
|
|
10
|
+
|
|
6
11
|
require 'fcntl'
|
|
7
12
|
require 'yaml'
|
|
8
13
|
|
|
@@ -23,26 +28,70 @@ module Resque
|
|
|
23
28
|
procline "(initialized)"
|
|
24
29
|
end
|
|
25
30
|
|
|
26
|
-
# Config:
|
|
31
|
+
# Config: hooks {{{
|
|
32
|
+
|
|
33
|
+
# The +configure+ block will be run once during pool startup, after the
|
|
34
|
+
# internal initialization is complete, but before any workers are started
|
|
35
|
+
# up. This is for configuring any runtime callbacks that you want to
|
|
36
|
+
# customize (e.g. +after_wakup+, +worker_offset_handler+)
|
|
37
|
+
#
|
|
38
|
+
# Call with a block to set.
|
|
39
|
+
# Call with no arguments to return the hook.
|
|
40
|
+
def self.configure(&block)
|
|
41
|
+
block ? (@configure = block) : @configure
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def call_configure!
|
|
45
|
+
self.class.configure && self.class.configure.call(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The +after_manager_wakeup+ hook will be run in the pool manager every
|
|
49
|
+
# time the pool manager wakes up from IO.select (normally once a second).
|
|
50
|
+
# It will run immediately before the pool manager performs its normal
|
|
51
|
+
# worker maintenance (starting and stopping workers as necessary). If you
|
|
52
|
+
# have some special logic for killing workers that are taking too long or
|
|
53
|
+
# using too much memory, this is the place to put it.
|
|
54
|
+
#
|
|
55
|
+
# Call with a block to set the hook.
|
|
56
|
+
# Call with no arguments to return the hook.
|
|
57
|
+
def after_manager_wakeup(&block)
|
|
58
|
+
block ? (@after_manager_wakeup = block) : @after_manager_wakeup
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def call_after_manager_wakeup!
|
|
62
|
+
after_manager_wakeup && after_manager_wakeup.call(self)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_calculate_worker_offset(&block)
|
|
66
|
+
block ? (@to_calculate_worker_offset = block) : @to_calculate_worker_offset
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# deprecated by instance level +after_prefork+ hook
|
|
70
|
+
def self.after_prefork(&block)
|
|
71
|
+
#log '"Resque::Pool.after_prefork(&blk)" is deprecated.'
|
|
72
|
+
#log 'Please use "Resque::Pool.configure {|p| p.after_prefork(&blk) }" instead.'
|
|
73
|
+
block ? (@after_prefork = block) : @after_prefork
|
|
74
|
+
end
|
|
27
75
|
|
|
28
|
-
# The
|
|
76
|
+
# The +after_prefork+ hook will be run in workers if you are using the
|
|
29
77
|
# preforking master worker to save memory. Use this hook to reload
|
|
30
78
|
# database connections and so forth to ensure that they're not shared
|
|
31
79
|
# among workers.
|
|
32
80
|
#
|
|
33
81
|
# Call with a block to set the hook.
|
|
34
82
|
# Call with no arguments to return the hook.
|
|
35
|
-
def
|
|
83
|
+
def after_prefork(&block)
|
|
36
84
|
block ? (@after_prefork = block) : @after_prefork
|
|
37
85
|
end
|
|
38
86
|
|
|
39
87
|
# Set the after_prefork proc.
|
|
40
|
-
def
|
|
88
|
+
def after_prefork=(after_prefork)
|
|
41
89
|
@after_prefork = after_prefork
|
|
42
90
|
end
|
|
43
91
|
|
|
44
92
|
def call_after_prefork!
|
|
45
|
-
|
|
93
|
+
(after_prefork && after_prefork.call) ||
|
|
94
|
+
(self.class.after_prefork && self.class.after_prefork.call)
|
|
46
95
|
end
|
|
47
96
|
|
|
48
97
|
# }}}
|
|
@@ -93,7 +142,9 @@ module Resque
|
|
|
93
142
|
end
|
|
94
143
|
|
|
95
144
|
def environment
|
|
96
|
-
if defined?
|
|
145
|
+
if defined? Rails
|
|
146
|
+
Rails.env
|
|
147
|
+
elsif defined? RAILS_ENV # keep compatibility with older versions of rails
|
|
97
148
|
RAILS_ENV
|
|
98
149
|
else
|
|
99
150
|
ENV['RACK_ENV'] || ENV['RAILS_ENV'] || ENV['RESQUE_ENV']
|
|
@@ -144,10 +195,6 @@ module Resque
|
|
|
144
195
|
end
|
|
145
196
|
end
|
|
146
197
|
|
|
147
|
-
def reset_sig_handlers!
|
|
148
|
-
QUEUE_SIGS.each {|sig| trap(sig, "DEFAULT") }
|
|
149
|
-
end
|
|
150
|
-
|
|
151
198
|
def handle_sig_queue!
|
|
152
199
|
case signal = sig_queue.shift
|
|
153
200
|
when :USR1, :USR2, :CONT
|
|
@@ -185,6 +232,8 @@ module Resque
|
|
|
185
232
|
# start, join, and master sleep {{{
|
|
186
233
|
|
|
187
234
|
def start
|
|
235
|
+
procline("(configuring)")
|
|
236
|
+
call_configure!
|
|
188
237
|
procline("(starting)")
|
|
189
238
|
init_self_pipe!
|
|
190
239
|
init_sig_handlers!
|
|
@@ -209,6 +258,7 @@ module Resque
|
|
|
209
258
|
break if handle_sig_queue! == :break
|
|
210
259
|
if sig_queue.empty?
|
|
211
260
|
master_sleep
|
|
261
|
+
call_after_manager_wakeup!
|
|
212
262
|
maintain_worker_count
|
|
213
263
|
end
|
|
214
264
|
procline("managing #{all_pids.inspect}")
|
|
@@ -242,6 +292,8 @@ module Resque
|
|
|
242
292
|
# TODO: close any file descriptors connected to worker, if any
|
|
243
293
|
log "Reaped resque worker[#{status.pid}] (status: #{status.exitstatus}) queues: #{worker.queues.join(",")}"
|
|
244
294
|
end
|
|
295
|
+
rescue Errno::EINTR
|
|
296
|
+
retry
|
|
245
297
|
rescue Errno::ECHILD, QuitNowException
|
|
246
298
|
end
|
|
247
299
|
end
|
|
@@ -265,64 +317,20 @@ module Resque
|
|
|
265
317
|
end
|
|
266
318
|
|
|
267
319
|
# }}}
|
|
268
|
-
#
|
|
320
|
+
# maintain_worker_count, all_known_worker_types, worker_offset {{{
|
|
269
321
|
|
|
270
322
|
def maintain_worker_count
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
spawn_missing_workers_for(queues) if delta > 0
|
|
274
|
-
quit_excess_workers_for(queues) if delta < 0
|
|
323
|
+
all_known_worker_types.each do |queues|
|
|
324
|
+
WorkerTypeManager.new(self, queues).maintain_worker_count(worker_offset)
|
|
275
325
|
end
|
|
276
326
|
end
|
|
277
327
|
|
|
278
|
-
def
|
|
328
|
+
def all_known_worker_types
|
|
279
329
|
config.keys | workers.keys
|
|
280
330
|
end
|
|
281
331
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
# perhaps this means a class is waiting to be extracted
|
|
285
|
-
|
|
286
|
-
def spawn_missing_workers_for(queues)
|
|
287
|
-
worker_delta_for(queues).times do |nr|
|
|
288
|
-
spawn_worker!(queues)
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
def quit_excess_workers_for(queues)
|
|
293
|
-
delta = -worker_delta_for(queues)
|
|
294
|
-
pids_for(queues)[0...delta].each do |pid|
|
|
295
|
-
Process.kill("QUIT", pid)
|
|
296
|
-
end
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def worker_delta_for(queues)
|
|
300
|
-
config.fetch(queues, 0) - workers.fetch(queues, []).size
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def pids_for(queues)
|
|
304
|
-
workers[queues].keys
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
def spawn_worker!(queues)
|
|
308
|
-
worker = create_worker(queues)
|
|
309
|
-
pid = fork do
|
|
310
|
-
log_worker "Starting worker #{worker}"
|
|
311
|
-
call_after_prefork!
|
|
312
|
-
reset_sig_handlers!
|
|
313
|
-
#self_pipe.each {|io| io.close }
|
|
314
|
-
worker.work(ENV['INTERVAL'] || DEFAULT_WORKER_INTERVAL) # interval, will block
|
|
315
|
-
end
|
|
316
|
-
workers[queues] ||= {}
|
|
317
|
-
workers[queues][pid] = worker
|
|
318
|
-
end
|
|
319
|
-
|
|
320
|
-
def create_worker(queues)
|
|
321
|
-
queues = queues.to_s.split(',')
|
|
322
|
-
worker = PooledWorker.new(*queues)
|
|
323
|
-
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
|
324
|
-
worker.very_verbose = ENV['VVERBOSE']
|
|
325
|
-
worker
|
|
332
|
+
def worker_offset
|
|
333
|
+
to_calculate_worker_offset && to_calculate_worker_offset.call || 0
|
|
326
334
|
end
|
|
327
335
|
|
|
328
336
|
# }}}
|
data/lib/resque/pool/cli.rb
CHANGED
|
@@ -99,7 +99,7 @@ where [options] are:
|
|
|
99
99
|
def setup_environment(opts)
|
|
100
100
|
ENV["RACK_ENV"] = ENV["RAILS_ENV"] = ENV["RESQUE_ENV"] = opts[:environment] if opts[:environment]
|
|
101
101
|
log "Resque Pool running in #{ENV["RAILS_ENV"] || "development"} environment"
|
|
102
|
-
ENV["RESQUE_POOL_CONFIG"] = opts[:config] if opts[:config]
|
|
102
|
+
ENV["RESQUE_POOL_CONFIG"] = opts[:config].to_s if opts[:config]
|
|
103
103
|
end
|
|
104
104
|
|
|
105
105
|
def start_pool
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
require 'resque/pool/logging'
|
|
2
|
+
|
|
3
|
+
module Resque
|
|
4
|
+
class Pool
|
|
5
|
+
class MemoryManager
|
|
6
|
+
include Logging
|
|
7
|
+
|
|
8
|
+
attr_reader :hard_limit, :soft_limit
|
|
9
|
+
|
|
10
|
+
def initialize(hard_limit=250, soft_limit=200)
|
|
11
|
+
@hard_limit = hard_limit
|
|
12
|
+
@soft_limit = soft_limit
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def monitor_memory_usage(pool_manager)
|
|
16
|
+
#only check every minute
|
|
17
|
+
if @last_mem_check.nil? || @last_mem_check < Time.now - 60
|
|
18
|
+
hard_kill_workers
|
|
19
|
+
pool_manager.all_pids.each do |pid|
|
|
20
|
+
total_usage = memory_usage(pid)
|
|
21
|
+
child_pid = find_child_pid(pid)
|
|
22
|
+
total_usage += memory_usage(child_pid) if child_pid
|
|
23
|
+
|
|
24
|
+
if total_usage > hard_limit
|
|
25
|
+
log "Terminating worker #{pid} for using #{total_usage}MB memory"
|
|
26
|
+
stop_worker(pid)
|
|
27
|
+
elsif total_usage > soft_limit
|
|
28
|
+
log "Gracefully shutting down worker #{pid} for using #{total_usage}MB memory"
|
|
29
|
+
stop_worker(pid, :QUIT)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
@last_mem_check = Time.now
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def add_killed_worker(pid)
|
|
39
|
+
@term_workers ||= []
|
|
40
|
+
@term_workers << pid if pid
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def find_child_pid(parent_pid)
|
|
44
|
+
begin
|
|
45
|
+
p = `ps --ppid #{parent_pid} -o pid --no-header`.to_i
|
|
46
|
+
p == 0 ? nil : p
|
|
47
|
+
rescue Errno::EINTR
|
|
48
|
+
retry
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def hard_kill_workers
|
|
53
|
+
@term_workers ||= []
|
|
54
|
+
#look for workers that didn't terminate
|
|
55
|
+
@term_workers.delete_if {|pid| !process_exists?(pid)}
|
|
56
|
+
#send the rest a -9
|
|
57
|
+
@term_workers.each {|pid| `kill -9 #{pid}`}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def hostname
|
|
61
|
+
begin
|
|
62
|
+
@hostname ||= `hostname`.strip
|
|
63
|
+
rescue Errno::EINTR
|
|
64
|
+
retry
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def memory_usage(pid)
|
|
69
|
+
smaps_filename = "/proc/#{pid}/smaps"
|
|
70
|
+
#Grab actual memory usage from proc in MB
|
|
71
|
+
begin
|
|
72
|
+
mem_usage = `
|
|
73
|
+
if [ -f #{smaps_filename} ];
|
|
74
|
+
then
|
|
75
|
+
grep Private_Dirty #{smaps_filename} | awk '{s+=$2} END {printf("%d", s/1000)}'
|
|
76
|
+
else echo "0"
|
|
77
|
+
fi
|
|
78
|
+
`.to_i
|
|
79
|
+
rescue Errno::EINTR
|
|
80
|
+
retry
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# TODO: DRY up this and the pidfile checker in Resque::Pool::CLI
|
|
85
|
+
def process_exists?(pid)
|
|
86
|
+
begin
|
|
87
|
+
ps_line = `ps -p #{pid} --no-header`
|
|
88
|
+
rescue Errno::EINTR
|
|
89
|
+
retry
|
|
90
|
+
end
|
|
91
|
+
!ps_line.nil? && ps_line.strip != ''
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def stop_worker(pid, signal=:TERM)
|
|
95
|
+
begin
|
|
96
|
+
worker = Resque.working.find do |w|
|
|
97
|
+
host, worker_pid, queues = w.id.split(':')
|
|
98
|
+
w if worker_pid.to_i == pid.to_i && host == hostname
|
|
99
|
+
end
|
|
100
|
+
if worker
|
|
101
|
+
encoded_job = worker.job
|
|
102
|
+
verb = signal == :QUIT ? 'Graceful' : 'Forcing'
|
|
103
|
+
total_time = Time.now - Time.parse(encoded_job['run_at']) rescue 0
|
|
104
|
+
log "#{verb} shutdown while processing: #{encoded_job} -- ran for #{'%.2f' % total_time}s"
|
|
105
|
+
end
|
|
106
|
+
Process.kill signal, pid
|
|
107
|
+
if signal == :TERM
|
|
108
|
+
add_killed_worker(pid)
|
|
109
|
+
add_killed_worker(find_child_pid(pid))
|
|
110
|
+
end
|
|
111
|
+
rescue Errno::EINTR
|
|
112
|
+
retry
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
require 'resque/pool/logging'
|
|
2
|
+
|
|
3
|
+
module Resque
|
|
4
|
+
class Pool
|
|
5
|
+
class OrphanWatcher
|
|
6
|
+
include Logging
|
|
7
|
+
|
|
8
|
+
attr_reader :pool_manager
|
|
9
|
+
|
|
10
|
+
def initialize(pool_manager)
|
|
11
|
+
@pool_manager = pool_manager
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def worker_offset
|
|
15
|
+
orphaned_worker_count / pool_manager.all_known_worker_types.size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def orphaned_worker_count
|
|
19
|
+
if @last_orphaned_check.nil? || @last_orphaned_check < Time.now - 60
|
|
20
|
+
if @orphaned_pids.nil?
|
|
21
|
+
begin
|
|
22
|
+
pids_with_parents = `ps -Af | grep resque | grep -v grep | grep -v resque-web | grep -v master | awk '{printf("%d %d\\n", $2, $3)}'`.split("\n")
|
|
23
|
+
rescue Errno::EINTR
|
|
24
|
+
retry
|
|
25
|
+
end
|
|
26
|
+
pids = pids_with_parents.collect {|x| x.split[0].to_i}
|
|
27
|
+
parents = pids_with_parents.collect {|x| x.split[1].to_i}
|
|
28
|
+
pids.delete_if {|x| parents.include?(x)}
|
|
29
|
+
pids.delete_if {|x| all_pids.include?(x)}
|
|
30
|
+
@orphaned_pids = pids
|
|
31
|
+
elsif @orphaned_pids.size > 0
|
|
32
|
+
@orphaned_pids.delete_if do |pid|
|
|
33
|
+
begin
|
|
34
|
+
ps_out = `ps --no-heading p #{pid}`
|
|
35
|
+
ps_out.nil? || ps_out.strip == ''
|
|
36
|
+
rescue Errno::EINTR
|
|
37
|
+
retry
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
@last_orphaned_check = Time.now
|
|
42
|
+
log "Current orphaned pids: #{@orphaned_pids}" if @orphaned_pids.size > 0
|
|
43
|
+
end
|
|
44
|
+
@orphaned_pids.size
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/resque/pool/version.rb
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'resque/pool/logging'
|
|
2
|
+
require 'resque/pool/pooled_worker'
|
|
3
|
+
|
|
4
|
+
module Resque
|
|
5
|
+
class Pool
|
|
6
|
+
class WorkerTypeManager
|
|
7
|
+
include Logging
|
|
8
|
+
|
|
9
|
+
attr_reader :pool_manager, :queues, :queue_array
|
|
10
|
+
|
|
11
|
+
def initialize(pool_manager, queues)
|
|
12
|
+
@pool_manager = pool_manager
|
|
13
|
+
@queues = queues
|
|
14
|
+
@queue_array = queues.to_s.split(',')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# TODO: Pool manager will hold onto WorkerTypeManagers, and will push the
|
|
18
|
+
# configuration directly into the WorkerTypeManagers
|
|
19
|
+
def configuration
|
|
20
|
+
{ :count => pool_manager.config.fetch(queues, 0), }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def maintain_worker_count(offset)
|
|
24
|
+
delta = worker_delta - offset
|
|
25
|
+
spawn_missing_workers_for(delta) if delta > 0
|
|
26
|
+
quit_excess_workers_for(delta) if delta < 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def pids
|
|
30
|
+
running_workers.keys
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# TODO: Pool manager will hold onto WorkerTypeManagers,
|
|
34
|
+
# and WorkerTypeManager will store the workers itself
|
|
35
|
+
def running_workers
|
|
36
|
+
pool_manager.workers.fetch(queues, {})
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def worker_delta
|
|
40
|
+
configuration[:count] - running_workers.size
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def create_worker
|
|
46
|
+
worker = PooledWorker.new(*queue_array)
|
|
47
|
+
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
|
48
|
+
worker.very_verbose = ENV['VVERBOSE']
|
|
49
|
+
worker
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def quit_excess_workers_for(delta)
|
|
53
|
+
if delta < 0
|
|
54
|
+
queue_pids = pids.clone
|
|
55
|
+
if queue_pids.size >= delta.abs
|
|
56
|
+
queue_pids[0...delta.abs].each {|pid| Process.kill("QUIT", pid)}
|
|
57
|
+
else
|
|
58
|
+
queue_pids.each {|pid| Process.kill("QUIT", pid)}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# TODO: Pool manager will hold onto WorkerTypeManagers,
|
|
64
|
+
# and WorkerTypeManager will store the pids itself
|
|
65
|
+
def register_new_worker_with_manager(worker, pid)
|
|
66
|
+
pool_manager.workers[queues] ||= {}
|
|
67
|
+
pool_manager.workers[queues][pid] = worker
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def reset_sig_handlers!
|
|
71
|
+
QUEUE_SIGS.each {|sig| trap(sig, "DEFAULT") }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def spawn_missing_workers_for(delta)
|
|
75
|
+
delta.times { spawn_worker! } if delta > 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def spawn_worker!
|
|
79
|
+
worker = create_worker
|
|
80
|
+
pid = fork do
|
|
81
|
+
start_forked_worker worker
|
|
82
|
+
end
|
|
83
|
+
register_new_worker_with_manager worker, pid
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def start_forked_worker(worker)
|
|
87
|
+
reset_sig_handlers!
|
|
88
|
+
log_worker "Starting worker #{worker}"
|
|
89
|
+
pool_manager.call_after_prefork!
|
|
90
|
+
begin
|
|
91
|
+
worker.work(ENV['INTERVAL'] || DEFAULT_WORKER_INTERVAL) # interval, will block
|
|
92
|
+
rescue Errno::EINTR
|
|
93
|
+
log_worker "Caught interrupted system call Errno::EINTR. Retrying."
|
|
94
|
+
retry
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
metadata
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: resque-pool
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
hash:
|
|
5
|
-
prerelease:
|
|
4
|
+
hash: 62196369
|
|
5
|
+
prerelease: true
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
8
|
+
- 3
|
|
9
9
|
- 0
|
|
10
|
-
|
|
10
|
+
- beta
|
|
11
|
+
- 1
|
|
12
|
+
version: 0.3.0.beta.1
|
|
11
13
|
platform: ruby
|
|
12
14
|
authors:
|
|
13
15
|
- nicholas a. evans
|
|
@@ -179,11 +181,16 @@ files:
|
|
|
179
181
|
- lib/resque/pool/tasks.rb
|
|
180
182
|
- lib/resque/pool/cli.rb
|
|
181
183
|
- lib/resque/pool/version.rb
|
|
184
|
+
- lib/resque/pool/manager.rb
|
|
182
185
|
- lib/resque/pool/pooled_worker.rb
|
|
186
|
+
- lib/resque/pool/memory_manager.rb
|
|
187
|
+
- lib/resque/pool/orphan_watcher.rb
|
|
188
|
+
- lib/resque/pool/worker_type_manager.rb
|
|
183
189
|
- lib/resque/pool/logging.rb
|
|
184
190
|
- config/cucumber.yml
|
|
185
191
|
- config/alternate.yml
|
|
186
192
|
- Gemfile
|
|
193
|
+
- ExperimentalFeatures.md
|
|
187
194
|
has_rdoc: true
|
|
188
195
|
homepage: http://github.com/nevans/resque-pool
|
|
189
196
|
licenses: []
|
|
@@ -205,12 +212,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
205
212
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
213
|
none: false
|
|
207
214
|
requirements:
|
|
208
|
-
- - "
|
|
215
|
+
- - ">"
|
|
209
216
|
- !ruby/object:Gem::Version
|
|
210
|
-
hash:
|
|
217
|
+
hash: 25
|
|
211
218
|
segments:
|
|
212
|
-
-
|
|
213
|
-
|
|
219
|
+
- 1
|
|
220
|
+
- 3
|
|
221
|
+
- 1
|
|
222
|
+
version: 1.3.1
|
|
214
223
|
requirements: []
|
|
215
224
|
|
|
216
225
|
rubyforge_project:
|