resque-pool 0.0.2
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/lib/resque/pool/tasks.rb +17 -0
- data/lib/resque/pool.rb +203 -0
- metadata +101 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
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
|
+
namespace :resque do
|
8
|
+
task :setup
|
9
|
+
|
10
|
+
desc "Launch a pool of resque workers (set RESQUE_POOL_CONFIG)"
|
11
|
+
task :pool => :setup do
|
12
|
+
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true
|
13
|
+
require 'resque/pool'
|
14
|
+
Resque::Pool.new(RESQUE_POOL_CONFIG).start.join
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/resque/pool.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'resque'
|
3
|
+
require 'fcntl'
|
4
|
+
|
5
|
+
module Resque
|
6
|
+
class Pool
|
7
|
+
attr_reader :pool_config
|
8
|
+
attr_reader :workers
|
9
|
+
|
10
|
+
SELF_PIPE = []
|
11
|
+
SIG_QUEUE = []
|
12
|
+
QUEUE_SIGS = [ :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, ]
|
13
|
+
CHUNK_SIZE=(16 * 1024)
|
14
|
+
|
15
|
+
def initialize(config)
|
16
|
+
@pool_config = config.dup
|
17
|
+
@workers = pool_config.keys.inject({}) {|h,k| h[k] = {}; h}
|
18
|
+
procline "(initialized)"
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# The `after_prefork` hook will be run in workers if you are using the
|
23
|
+
# preforking master worker to save memory. Use this hook to reload
|
24
|
+
# database connections and so forth to ensure that they're not shared
|
25
|
+
# among workers.
|
26
|
+
#
|
27
|
+
# Call with a block to set the hook.
|
28
|
+
# Call with no arguments to return the hook.
|
29
|
+
def self.after_prefork(&block)
|
30
|
+
block ? (@after_prefork = block) : @after_prefork
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set the after_prefork proc.
|
34
|
+
def self.after_prefork=(after_prefork)
|
35
|
+
@after_prefork = after_prefork
|
36
|
+
end
|
37
|
+
|
38
|
+
def call_after_prefork!
|
39
|
+
self.class.after_prefork && self.class.after_prefork.call
|
40
|
+
end
|
41
|
+
|
42
|
+
# Given a string, sets the procline ($0)
|
43
|
+
# Procline is always in the format of:
|
44
|
+
# resque-pool-master: STRING
|
45
|
+
def procline(string)
|
46
|
+
$0 = "resque-pool-master: #{string}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# TODO: make this use an actual logger
|
50
|
+
def log(message)
|
51
|
+
puts message
|
52
|
+
end
|
53
|
+
|
54
|
+
def init_self_pipe!
|
55
|
+
SELF_PIPE.each { |io| io.close rescue nil }
|
56
|
+
SELF_PIPE.replace(IO.pipe)
|
57
|
+
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
58
|
+
end
|
59
|
+
|
60
|
+
def init_sig_handlers!
|
61
|
+
QUEUE_SIGS.each { |sig| trap_deferred(sig) }
|
62
|
+
trap(:CHLD) { |_| awaken_master }
|
63
|
+
end
|
64
|
+
|
65
|
+
# defer a signal for later processing in #join (master process)
|
66
|
+
def trap_deferred(signal)
|
67
|
+
trap(signal) do |sig_nr|
|
68
|
+
if SIG_QUEUE.size < 5
|
69
|
+
SIG_QUEUE << signal
|
70
|
+
awaken_master
|
71
|
+
else
|
72
|
+
log "ignoring SIG#{signal}, queue=#{SIG_QUEUE.inspect}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def start
|
78
|
+
procline("(starting)")
|
79
|
+
init_self_pipe!
|
80
|
+
init_sig_handlers!
|
81
|
+
maintain_worker_count
|
82
|
+
procline("(started)")
|
83
|
+
log "**** done in pool master #initialize"
|
84
|
+
log "**** Pool contains PIDs: #{all_pids.inspect}"
|
85
|
+
self
|
86
|
+
end
|
87
|
+
|
88
|
+
def join
|
89
|
+
loop do
|
90
|
+
reap_all_workers
|
91
|
+
break if handle_sig_queue! == :break
|
92
|
+
maintain_worker_count if SIG_QUEUE.empty?
|
93
|
+
procline("managing #{all_pids.inspect}")
|
94
|
+
end
|
95
|
+
procline("(shutting down)")
|
96
|
+
#stop # gracefully shutdown all workers on our way out
|
97
|
+
#unlink_pid_safe(pid) if pid
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_sig_queue!
|
101
|
+
case SIG_QUEUE.shift
|
102
|
+
when nil
|
103
|
+
master_sleep
|
104
|
+
when :QUIT # graceful shutdown
|
105
|
+
:break
|
106
|
+
when :TERM, :INT # immediate shutdown
|
107
|
+
#stop(false)
|
108
|
+
:break
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def reap_all_workers
|
113
|
+
begin
|
114
|
+
loop do
|
115
|
+
wpid, status = Process.waitpid2(-1, Process::WNOHANG)
|
116
|
+
wpid or break
|
117
|
+
#if reexec_pid == wpid
|
118
|
+
#log "reaped #{status.inspect} exec()-ed"
|
119
|
+
#self.reexec_pid = 0
|
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
|
127
|
+
end
|
128
|
+
rescue Errno::ECHILD
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def delete_worker(pid)
|
133
|
+
workers.each do |queues, pid_to_worker|
|
134
|
+
pid_to_worker.delete(pid)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def master_sleep
|
139
|
+
begin
|
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
|
145
|
+
end
|
146
|
+
|
147
|
+
def awaken_master
|
148
|
+
begin
|
149
|
+
SELF_PIPE.last.write_nonblock('.') # wakeup master process from select
|
150
|
+
rescue Errno::EAGAIN, Errno::EINTR
|
151
|
+
# pipe is full, master should wake up anyways
|
152
|
+
retry
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def maintain_worker_count
|
157
|
+
pool_config.each do |queues, count|
|
158
|
+
next if (delta = worker_delta_for(queues)) == 0
|
159
|
+
spawn_missing_workers_for(queues) if delta > 0
|
160
|
+
#TODO: quit_excess_workers_for(queues)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def all_pids
|
165
|
+
workers.map {|q,workers| workers.keys }.flatten
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# all methods below operate on a single grouping of queues
|
170
|
+
# perhaps this means a class is waiting to be extracted
|
171
|
+
|
172
|
+
def spawn_missing_workers_for(queues)
|
173
|
+
worker_delta_for(queues).times do |nr|
|
174
|
+
spawn_worker!(queues)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def worker_delta_for(queues)
|
179
|
+
pool_config[queues] - workers[queues].size
|
180
|
+
end
|
181
|
+
|
182
|
+
def spawn_worker!(queues)
|
183
|
+
worker = create_worker(queues)
|
184
|
+
pid = fork do
|
185
|
+
log "*** Starting worker #{worker}"
|
186
|
+
call_after_prefork!
|
187
|
+
worker.work(ENV['INTERVAL'] || 5) # interval, will block
|
188
|
+
end
|
189
|
+
workers[queues][pid] = worker
|
190
|
+
end
|
191
|
+
|
192
|
+
def create_worker(queues)
|
193
|
+
queues = queues.to_s.split(',')
|
194
|
+
worker = Resque::Worker.new(*queues)
|
195
|
+
worker.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
196
|
+
worker.very_verbose = ENV['VVERBOSE']
|
197
|
+
worker
|
198
|
+
rescue Resque::NoQueueError
|
199
|
+
abort "set QUEUE env var, e.g. $ QUEUE=critical,high rake resque:work"
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resque-pool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 2
|
10
|
+
version: 0.0.2
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- nicholas a. evans
|
14
|
+
- Unicorn hackers
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2010-06-15 00:00:00 -04:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: rspec
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: resque
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 49
|
45
|
+
segments:
|
46
|
+
- 1
|
47
|
+
- 9
|
48
|
+
- 1
|
49
|
+
version: 1.9.1
|
50
|
+
type: :runtime
|
51
|
+
version_requirements: *id002
|
52
|
+
description: " quickly and easily fork a pool of resque workers,\n saving memory (w/REE) and monitoring their uptime\n"
|
53
|
+
email:
|
54
|
+
- nick@ekenosen.net
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- lib/resque/pool.rb
|
63
|
+
- lib/resque/pool/tasks.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/nevans/resque-pool
|
66
|
+
licenses: []
|
67
|
+
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
|
71
|
+
require_paths:
|
72
|
+
- lib
|
73
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 23
|
88
|
+
segments:
|
89
|
+
- 1
|
90
|
+
- 3
|
91
|
+
- 6
|
92
|
+
version: 1.3.6
|
93
|
+
requirements: []
|
94
|
+
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 1.3.7
|
97
|
+
signing_key:
|
98
|
+
specification_version: 3
|
99
|
+
summary: quickly and easily fork a pool of resque workers
|
100
|
+
test_files: []
|
101
|
+
|