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.
Files changed (3) hide show
  1. data/lib/resque/pool/tasks.rb +17 -0
  2. data/lib/resque/pool.rb +203 -0
  3. 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
@@ -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
+