resque_starter 0.1.0
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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +2 -0
- data/bin/resque_starter +1 -0
- data/bin/resque_starter.rb +32 -0
- data/bin/start_resque +1 -0
- data/bin/start_resque.rb +1 -0
- data/example/resque.conf.rb +43 -0
- data/lib/resque_starter/config.rb +60 -0
- data/lib/resque_starter/logger.rb +33 -0
- data/lib/resque_starter/version.rb +3 -0
- data/lib/resque_starter.rb +180 -0
- data/resque_starter.gemspec +25 -0
- data/tmp/log/.gitkeep +0 -0
- data/tmp/pids/.gitkeep +0 -0
- metadata +107 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0ab87ab83e696f9a2082b4cb7162dbbdd8a77efd
|
4
|
+
data.tar.gz: ab0e725521c9bf8e8d759224b69867bd7820feaa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4ea956678caef390ea95e715362b343052b0c61c95614960ccb6780b7ae6a59600131ddb1b4a7e2039671129ea2f594bbebfbaad3aeb3309306a79d5add38a99
|
7
|
+
data.tar.gz: 6b9894349095bc5774f9b1feab545f495aba696a668faa12d9f6be6387c67eb7c44db49b3e2caceae427160d361c26a161c78036c7dfeb598b63d170b25c4bbc
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 sonots
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# ResqueStarter
|
2
|
+
|
3
|
+
[Resque](https://github.com/resque/resque) is widely used Redis-backed Ruby library for creating background jobs. [ResqueStarter](https://github.com/sonots/resque_starter) is a tool to start and manage multiple resque workers supporing graceful shutdown and restart, leveraging the [Copy-on-write (COW)](https://en.wikipedia.org/wiki/Copy-on-write).
|
4
|
+
|
5
|
+
```
|
6
|
+
PID COMMAND
|
7
|
+
14814 resque_starter # preload app
|
8
|
+
14815 \_ resque:work[1]
|
9
|
+
14816 \_ resque:work[2]
|
10
|
+
14817 \_ resque:work[3]
|
11
|
+
```
|
12
|
+
|
13
|
+
# Background
|
14
|
+
|
15
|
+
[Resque](https://github.com/resque/resque) provides [resque:workers](https://github.com/resque/resque#running-multiple-workers) task to run multiple resque workers, but it is only for development purpose as [code comments](https://github.com/resque/resque/blob/c295da9de0034b20ce79600e9f54fb279695f522/lib/resque/tasks.rb#L23-L38) says.
|
16
|
+
It also provides an example configuration of [god](http://godrb.com/) as [resque.god](https://github.com/resque/resque/blob/c295da9de0034b20ce79600e9f54fb279695f522/examples/god/resque.god), but it does not allow us to leverage Copy-on-write (CoW) to save memory of preloaded application.
|
17
|
+
|
18
|
+
# Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'resque_starter'
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
```
|
29
|
+
$ bundle
|
30
|
+
```
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
```
|
35
|
+
$ gem install resque_starter
|
36
|
+
```
|
37
|
+
|
38
|
+
# Configuration
|
39
|
+
|
40
|
+
Please see [example/resque.conf.rb](./example/resque.conf.rb)
|
41
|
+
|
42
|
+
You can configure logger, pid file, number of concurrency, dequeue interval, queue lists.
|
43
|
+
|
44
|
+
# Usage
|
45
|
+
|
46
|
+
```
|
47
|
+
bundle exec resque_starter -c /path/to/resque.conf.rb
|
48
|
+
```
|
49
|
+
|
50
|
+
# Signals
|
51
|
+
|
52
|
+
Resque starter responds to a few different signals:
|
53
|
+
|
54
|
+
* TERM / INT - Quick shutdown, kills all workers immediately then exit
|
55
|
+
* QUIT - Graceful shutdown, waits for workers to finish processing then exit
|
56
|
+
* USR1 - Send USR1 to all workers, which immediately kill worker's child but don't exit
|
57
|
+
* USR2 - Send USR2 to all workers, which don't start to process any new jobs
|
58
|
+
* CONT - Send CONT to all workers, which start to process new jobs again after a USR2
|
59
|
+
* TTIN - Increment the number of worker processes by one
|
60
|
+
* TTOU - Decrement the number of worker processes by one with QUIT
|
61
|
+
|
62
|
+
# Graceful restart
|
63
|
+
|
64
|
+
Resque starter itself does not support graceful restart, yet. But, graceful restart can be done with [server-starter](https://github.com/sonots/ruby-server-starter).
|
65
|
+
|
66
|
+
Example configuration is available at [server-starter/example/resque](https://github.com/sonots/ruby-server-starter/blob/master/example/resque). See `start_server` and `config/resque.conf.rb` files.
|
67
|
+
|
68
|
+
**HOW IT WORKS**
|
69
|
+
|
70
|
+
On receiving HUP signal, server starter creates a new `resque_starter` (master) process.
|
71
|
+
The new `resque_starter` (master) process forks a new resque:work.
|
72
|
+
On `after_fork`, send `TTOU` to old `resque_starter` (master) process to gracefully shutdown one old resque:work.
|
73
|
+
By repeating this procedure, new `resque_starter` process can be gracefully restarted.
|
74
|
+
The number of working resque workers will be suppressed up to `concurrency + 1` in this way.
|
75
|
+
|
76
|
+
**ILLUSTRATION**
|
77
|
+
|
78
|
+
On bootup:
|
79
|
+
|
80
|
+
```
|
81
|
+
PID COMMAND
|
82
|
+
14813 server_starter
|
83
|
+
14814 \_ resque_starter
|
84
|
+
14815 \_ resque:work[1]
|
85
|
+
14816 \_ resque:work[2]
|
86
|
+
14817 \_ resque:work[3]
|
87
|
+
```
|
88
|
+
|
89
|
+
Send HUP:
|
90
|
+
|
91
|
+
```
|
92
|
+
PID COMMAND
|
93
|
+
14813 server_starter
|
94
|
+
14814 \_ resque_starter (old)
|
95
|
+
14815 \_ resque:work[1] (dies)
|
96
|
+
14816 \_ resque:work[2]
|
97
|
+
14817 \_ resque:work[3]
|
98
|
+
14818 \_ resque_starter (new)
|
99
|
+
14819 \_ resque:work[1]
|
100
|
+
```
|
101
|
+
|
102
|
+
Finally:
|
103
|
+
|
104
|
+
```
|
105
|
+
PID COMMAND
|
106
|
+
14813 server_starter
|
107
|
+
14818 \_ resque_starter (new)
|
108
|
+
14819 \_ resque:work[1]
|
109
|
+
14820 \_ resque:work[2]
|
110
|
+
14821 \_ resque:work[3]
|
111
|
+
```
|
112
|
+
|
113
|
+
# Contributing
|
114
|
+
|
115
|
+
1. Fork it ( https://github.com/sonots/resque_starter/fork )
|
116
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
117
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
118
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
119
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/resque_starter
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
resque_starter.rb
|
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require_relative '../lib/resque_starter'
|
5
|
+
|
6
|
+
opts = {
|
7
|
+
}
|
8
|
+
OptionParser.new do |opt|
|
9
|
+
opt.on(
|
10
|
+
'--config=file',
|
11
|
+
'path to config file',
|
12
|
+
) {|v| opts[:config_file] = v }
|
13
|
+
opt.on(
|
14
|
+
'--version',
|
15
|
+
'print version',
|
16
|
+
) {|v| puts ResqueStarter::VERSION; exit 0 }
|
17
|
+
|
18
|
+
opt.parse!(ARGV)
|
19
|
+
|
20
|
+
define_method(:usage) do |msg = nil|
|
21
|
+
puts opt.to_s
|
22
|
+
puts "error: #{msg}" if msg
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if opts[:config_file].nil?
|
28
|
+
usage 'Option --config is required'
|
29
|
+
end
|
30
|
+
|
31
|
+
starter = ResqueStarter.new(opts[:config_file])
|
32
|
+
starter.start_resque
|
data/bin/start_resque
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
resque_starter.rb
|
data/bin/start_resque.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
resque_starter.rb
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Number of concurrency (resque workers)
|
2
|
+
concurrency 2
|
3
|
+
|
4
|
+
# By default, resque_starter logs to stdout
|
5
|
+
# logger ::Logger.new(::File.expand_path('../../tmp/log/resque_starter.log', __FILE__))
|
6
|
+
# logger.level = ::Logger::INFO
|
7
|
+
# logger.formatter = ResqueStarter::Logger::Formatter.new
|
8
|
+
|
9
|
+
# Stores pid for resque starter (master) itself
|
10
|
+
pid_file ::File.expand_path('../../tmp/pids/resque_starter.pid', __FILE__)
|
11
|
+
|
12
|
+
# Status file stores pids of resque workers, etc
|
13
|
+
status_file ::File.expand_path('../../tmp/pids/resque_starter.stat', __FILE__)
|
14
|
+
|
15
|
+
# Watching queues of resque workers in priority orders
|
16
|
+
# Same with QUEUES environment variables for rake resque:work
|
17
|
+
# See https://github.com/resque/resque#priorities-and-queue-lists
|
18
|
+
#
|
19
|
+
# Default obtains from @queue class instance variables of worker class
|
20
|
+
# See https://github.com/resque/resque#overview
|
21
|
+
queues ['high', 'low']
|
22
|
+
|
23
|
+
# Polling frequency (default: 5)
|
24
|
+
# Same with INTERVAL environment variables for rake resque:work
|
25
|
+
# See https://github.com/resque/resque#polling-frequency
|
26
|
+
dequeue_interval 0.1
|
27
|
+
|
28
|
+
# Preload rails application codes before forking to save memory by CoW
|
29
|
+
# require ::File.expand_path('../../config/environment', __FILE__)
|
30
|
+
# Rails.application.eager_load!
|
31
|
+
|
32
|
+
before_fork do |starter, worker, worker_nr|
|
33
|
+
# the following is highly recomended for Rails + "preload_app true"
|
34
|
+
# as there's no need for the master process to hold a connection
|
35
|
+
defined?(ActiveRecord::Base) and
|
36
|
+
ActiveRecord::Base.connection.disconnect!
|
37
|
+
end
|
38
|
+
|
39
|
+
after_fork do |starter, worker, worker_nr|
|
40
|
+
# the following is *required* for Rails + "preload_app true",
|
41
|
+
defined?(ActiveRecord::Base) and
|
42
|
+
ActiveRecord::Base.establish_connection
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'resque_starter/version'
|
2
|
+
|
3
|
+
class ResqueStarter::Config
|
4
|
+
def initialize(config_file)
|
5
|
+
@config_file = config_file
|
6
|
+
# TIPS: resque worker ENV configuration
|
7
|
+
# self.verbose = ENV['LOGGING'] || ENV['VERBOSE']
|
8
|
+
# self.very_verbose = ENV['VVERBOSE']
|
9
|
+
# self.term_timeout = ENV['RESQUE_TERM_TIMEOUT'] || 4.0
|
10
|
+
# self.term_child = ENV['TERM_CHILD']
|
11
|
+
# self.graceful_term = ENV['GRACEFUL_TERM']
|
12
|
+
# self.run_at_exit_hooks = ENV['RUN_AT_EXIT_HOOKS']
|
13
|
+
reload
|
14
|
+
end
|
15
|
+
|
16
|
+
def reload
|
17
|
+
@set = Hash.new
|
18
|
+
@set[:concurrency] = 1
|
19
|
+
@set[:dequeue_interval] = 5.0
|
20
|
+
@set[:logger] = ResqueStarter::Logger.new(STDOUT)
|
21
|
+
instance_eval(File.read(@config_file), @config_file)
|
22
|
+
end
|
23
|
+
|
24
|
+
%i[
|
25
|
+
concurrency
|
26
|
+
logger
|
27
|
+
pid_file
|
28
|
+
status_file
|
29
|
+
queues
|
30
|
+
dequeue_interval
|
31
|
+
].each do |name|
|
32
|
+
define_method(name) do |val = nil|
|
33
|
+
if val
|
34
|
+
@set[name] = val
|
35
|
+
else
|
36
|
+
@set[name]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def [](key)
|
42
|
+
@set[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def before_fork(&block)
|
46
|
+
if block_given?
|
47
|
+
@set[:before_fork] = block
|
48
|
+
else
|
49
|
+
@set[:before_fork]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def after_fork(&block)
|
54
|
+
if block_given?
|
55
|
+
@set[:after_fork] = block
|
56
|
+
else
|
57
|
+
@set[:after_fork]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'resque_starter/version'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
class ResqueStarter::Logger < ::Logger
|
5
|
+
class Formatter
|
6
|
+
def call(severity, time, progname, msg)
|
7
|
+
"#{time.iso8601} [#{severity}] #{format_message(msg)}\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def format_message(message)
|
11
|
+
case message
|
12
|
+
when ::Exception
|
13
|
+
e = message
|
14
|
+
"#{e.class} (#{e.message})\\n #{e.backtrace.join("\\n ")}"
|
15
|
+
else
|
16
|
+
message.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(logdev, shift_age = 0, shift_size = 1048576)
|
22
|
+
logdev = STDOUT if logdev == 'STDOUT'
|
23
|
+
logdev = STDERR if logdev == 'STDERR'
|
24
|
+
super(logdev, shift_age, shift_size)
|
25
|
+
@formatter = Formatter.new
|
26
|
+
self.level = 'INFO'
|
27
|
+
end
|
28
|
+
|
29
|
+
def level=(level)
|
30
|
+
level = eval("::Logger::#{level.upcase}") if level.is_a?(String)
|
31
|
+
super(level)
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'resque'
|
3
|
+
require 'resque_starter/version'
|
4
|
+
require 'resque_starter/config'
|
5
|
+
require 'resque_starter/logger'
|
6
|
+
|
7
|
+
class ResqueStarter
|
8
|
+
attr_reader :config, :logger, :old_workers, :num_workers
|
9
|
+
|
10
|
+
def initialize(config_file)
|
11
|
+
@config = ResqueStarter::Config.new(config_file)
|
12
|
+
|
13
|
+
@old_workers = {}
|
14
|
+
@num_workers = config[:concurrency]
|
15
|
+
@self_read, @self_write = IO.pipe
|
16
|
+
|
17
|
+
# open pid file
|
18
|
+
if config[:pid_file]
|
19
|
+
begin
|
20
|
+
File.open(config[:pid_file], "w") do |fh|
|
21
|
+
fh.puts $$
|
22
|
+
end
|
23
|
+
rescue => e
|
24
|
+
@logger.error "failed to open file:#{config[:pid_file]}"
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
at_exit { File.unlink config[:pid_file] rescue nil }
|
28
|
+
end
|
29
|
+
|
30
|
+
# logger
|
31
|
+
@logger = @config[:logger]
|
32
|
+
|
33
|
+
# create guard that removes the status file
|
34
|
+
if config[:status_file]
|
35
|
+
at_exit { File.unlink config[:status_file] rescue nil }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_resque
|
40
|
+
@logger.info "starting resque starter (master) #{Process.pid}"
|
41
|
+
maintain_worker_count
|
42
|
+
|
43
|
+
install_signal_handler
|
44
|
+
@handle_thr = start_handle_thread
|
45
|
+
while true
|
46
|
+
break unless @handle_thr.alive? # dies if @shutdown and @old_workers.empty?
|
47
|
+
begin
|
48
|
+
pid, status = Process.waitpid2(-1)
|
49
|
+
@logger.info "resque worker #{pid} died, status:#{status.exitstatus}"
|
50
|
+
@self_write.puts(pid)
|
51
|
+
rescue Errno::ECHILD
|
52
|
+
@handle_thr.kill if @shutdown # @old_workers should be empty
|
53
|
+
sleep 0.1 # avoid busy loop for no child by TTOU
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@logger.info "shutting down resque starter (master) #{Process.pid}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def install_signal_handler
|
61
|
+
%w(TERM INT QUIT USR1 USR2 CONT TTIN TTOU).each do |sig|
|
62
|
+
trap(sig) { @self_write.puts(sig) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def start_handle_thread
|
67
|
+
Thread.new {
|
68
|
+
while readable_io = IO.select([@self_read])
|
69
|
+
s = readable_io.first[0].gets.strip
|
70
|
+
if pid = (Integer(s) rescue nil)
|
71
|
+
handle_waitpid2(pid)
|
72
|
+
else
|
73
|
+
handle_signal(s)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def update_status_file
|
80
|
+
return unless @config[:status_file]
|
81
|
+
# pid => worker_nr
|
82
|
+
status = {'old_workers' => @old_workers}.to_yaml
|
83
|
+
File.write(@config[:status_file], status)
|
84
|
+
end
|
85
|
+
|
86
|
+
def handle_waitpid2(pid)
|
87
|
+
@old_workers.delete(pid)
|
88
|
+
update_status_file
|
89
|
+
if @shutdown
|
90
|
+
@handle_thr.kill if @old_workers.empty?
|
91
|
+
else
|
92
|
+
maintain_worker_count
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Resque starter (master process) responds to a few different signals:
|
97
|
+
# * TERM / INT - Quick shutdown, kills all workers immediately then exit
|
98
|
+
# * QUIT - Graceful shutdown, waits for workers to finish processing then exit
|
99
|
+
# * USR1 - Send USR1 to all workers, which immediately kill worker's child but don't exit
|
100
|
+
# * USR2 - Send USR2 to all workers, which don't start to process any new jobs
|
101
|
+
# * CONT - Send CONT to all workers, which start to process new jobs again after a USR2
|
102
|
+
# * TTIN - Increment the number of worker processes by one
|
103
|
+
# * TTOU - Decrement the number of worker processes by one with QUIT
|
104
|
+
def handle_signal(sig)
|
105
|
+
pids = @old_workers.keys.sort
|
106
|
+
msg = "received #{sig}, "
|
107
|
+
|
108
|
+
# Resque workers respond to a few different signals:
|
109
|
+
# * TERM / INT - Immediately kill child then exit
|
110
|
+
# * QUIT - Wait for child to finish processing then exit
|
111
|
+
# * USR1 - Immediately kill child but don't exit
|
112
|
+
# * USR2 - Don't start to process any new jobs
|
113
|
+
# * CONT - Start to process new jobs again after a USR2
|
114
|
+
case sig
|
115
|
+
when 'TERM', 'INT'
|
116
|
+
@logger.info msg << "immediately kill all resque workers then exit:#{pids}"
|
117
|
+
@shutdown = true
|
118
|
+
Process.kill(sig, *pids)
|
119
|
+
when 'QUIT'
|
120
|
+
@logger.info msg << "wait for all resque workers to finish processing then exit:#{pids}"
|
121
|
+
@shutdown = true
|
122
|
+
Process.kill(sig, *pids)
|
123
|
+
when 'USR1'
|
124
|
+
@logger.info msg << "immediately kill the child of all resque workers:#{pids}"
|
125
|
+
Process.kill(sig, *pids)
|
126
|
+
when 'USR2'
|
127
|
+
@logger.info msg << "don't start to process any new jobs:#{pids}"
|
128
|
+
Process.kill(sig, *pids)
|
129
|
+
when 'CONT'
|
130
|
+
@logger.info msg << "start to process new jobs again after USR2:#{pids}"
|
131
|
+
Process.kill(sig, *pids)
|
132
|
+
when 'TTIN'
|
133
|
+
@num_workers += 1
|
134
|
+
@logger.info msg << "increment the number of resque workers:#{@num_workers}"
|
135
|
+
maintain_worker_count # ToDo: forking from a thread would be unsafe
|
136
|
+
when 'TTOU'
|
137
|
+
@num_workers -= 1 if @num_workers > 0
|
138
|
+
@logger.info msg << "decrement the number of resque workers:#{@num_workers}"
|
139
|
+
maintain_worker_count
|
140
|
+
end
|
141
|
+
rescue Errno::ESRCH
|
142
|
+
rescue => e
|
143
|
+
@logger.error(e)
|
144
|
+
exit(1)
|
145
|
+
end
|
146
|
+
|
147
|
+
def maintain_worker_count
|
148
|
+
return if (off = @old_workers.size - @num_workers) == 0
|
149
|
+
return spawn_missing_workers if off < 0
|
150
|
+
@old_workers.dup.each_pair {|pid, nr|
|
151
|
+
if nr >= @num_workers
|
152
|
+
Process.kill(:QUIT, pid) rescue nil
|
153
|
+
end
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
def spawn_missing_workers
|
158
|
+
worker_nr = -1
|
159
|
+
until (worker_nr += 1) == @num_workers
|
160
|
+
next if @old_workers.value?(worker_nr)
|
161
|
+
worker = Resque::Worker.new(*(config[:queues]))
|
162
|
+
config[:before_fork].call(self, worker, worker_nr)
|
163
|
+
GC.start
|
164
|
+
if pid = fork
|
165
|
+
@old_workers[pid] = worker_nr
|
166
|
+
update_status_file
|
167
|
+
@logger.info "starting new resque worker #{pid}"
|
168
|
+
else # child
|
169
|
+
@self_read.close rescue nil
|
170
|
+
@self_write.close rescue nil
|
171
|
+
config[:after_fork].call(self, worker, worker_nr)
|
172
|
+
worker.work(config[:dequeue_interval])
|
173
|
+
exit
|
174
|
+
end
|
175
|
+
end
|
176
|
+
rescue => e
|
177
|
+
@logger.error(e)
|
178
|
+
exit(1)
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'resque_starter/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "resque_starter"
|
8
|
+
spec.version = ResqueStarter::VERSION
|
9
|
+
spec.authors = ["Naotoshi Seo"]
|
10
|
+
spec.email = ["sonots@gmail.com"]
|
11
|
+
spec.summary = %q{Start and manage multiple resque workers}
|
12
|
+
spec.description = %q{Start and manage multiple resque workers.}
|
13
|
+
spec.homepage = "https://github.com/sonots/resque_starter"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "resque"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
end
|
data/tmp/log/.gitkeep
ADDED
File without changes
|
data/tmp/pids/.gitkeep
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resque_starter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Naotoshi Seo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: resque
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description: Start and manage multiple resque workers.
|
56
|
+
email:
|
57
|
+
- sonots@gmail.com
|
58
|
+
executables:
|
59
|
+
- resque_starter
|
60
|
+
- resque_starter.rb
|
61
|
+
- start_resque
|
62
|
+
- start_resque.rb
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- bin/resque_starter
|
72
|
+
- bin/resque_starter.rb
|
73
|
+
- bin/start_resque
|
74
|
+
- bin/start_resque.rb
|
75
|
+
- example/resque.conf.rb
|
76
|
+
- lib/resque_starter.rb
|
77
|
+
- lib/resque_starter/config.rb
|
78
|
+
- lib/resque_starter/logger.rb
|
79
|
+
- lib/resque_starter/version.rb
|
80
|
+
- resque_starter.gemspec
|
81
|
+
- tmp/log/.gitkeep
|
82
|
+
- tmp/pids/.gitkeep
|
83
|
+
homepage: https://github.com/sonots/resque_starter
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.5.1
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Start and manage multiple resque workers
|
107
|
+
test_files: []
|