resque_starter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ /tmp/log/*
16
+ /tmp/pids/*
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ gem 'pry'
5
+ gem 'pry-nav'
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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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
@@ -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,3 @@
1
+ class ResqueStarter
2
+ VERSION = "0.1.0"
3
+ 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: []