resque-pool 0.6.0 → 0.7.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.
@@ -12,6 +12,7 @@ module Resque
12
12
 
13
13
  def run
14
14
  opts = parse_options
15
+ obtain_shared_lock opts[:lock_file]
15
16
  daemonize if opts[:daemon]
16
17
  manage_pidfile opts[:pidfile]
17
18
  redirect opts
@@ -20,9 +21,9 @@ module Resque
20
21
  start_pool
21
22
  end
22
23
 
23
- def parse_options
24
+ def parse_options(argv=nil)
24
25
  opts = {}
25
- OptionParser.new do |opt|
26
+ parser = OptionParser.new do |opt|
26
27
  opt.banner = <<-EOS.gsub(/^ /, '')
27
28
  resque-pool is the best way to manage a group (pool) of resque workers
28
29
 
@@ -36,11 +37,31 @@ module Resque
36
37
  EOS
37
38
  opt.on('-c', '--config PATH', "Alternate path to config file") { |c| opts[:config] = c }
38
39
  opt.on('-a', '--appname NAME', "Alternate appname") { |c| opts[:appname] = c }
39
- opt.on("-d", '--daemon', "Run as a background daemon") { opts[:daemon] = true }
40
+ opt.on("-d", '--daemon', "Run as a background daemon") {
41
+ opts[:daemon] = true
42
+ opts[:stdout] ||= "log/resque-pool.stdout.log"
43
+ opts[:stderr] ||= "log/resque-pool.stderr.log"
44
+ opts[:pidfile] ||= "tmp/pids/resque-pool.pid" unless opts[:no_pidfile]
45
+ }
46
+ opt.on("-k", '--kill-others', "Shutdown any other Resque Pools on startup") { opts[:killothers] = true }
40
47
  opt.on('-o', '--stdout FILE', "Redirect stdout to logfile") { |c| opts[:stdout] = c }
41
48
  opt.on('-e', '--stderr FILE', "Redirect stderr to logfile") { |c| opts[:stderr] = c }
42
49
  opt.on('--nosync', "Don't sync logfiles on every write") { opts[:nosync] = true }
43
- opt.on("-p", '--pidfile FILE', "PID file location") { |c| opts[:pidfile] = c }
50
+ opt.on("-p", '--pidfile FILE', "PID file location") { |c|
51
+ opts[:pidfile] = c
52
+ opts[:no_pidfile] = false
53
+ }
54
+ opt.on('--no-pidfile', "Force no pidfile, even if daemonized") {
55
+ opts[:pidfile] = nil
56
+ opts[:no_pidfile] = true
57
+ }
58
+ opt.on('-l', '--lock FILE' "Open a shared lock on a file") { |c| opts[:lock_file] = c }
59
+ opt.on("-H", "--hot-swap", "Set appropriate defaults to hot-swap a new pool for a running pool") {|c|
60
+ opts[:pidfile] = nil
61
+ opts[:no_pidfile] = true
62
+ opts[:lock_file] ||= "tmp/resque-pool.lock"
63
+ opts[:killothers] = true
64
+ }
44
65
  opt.on("-E", '--environment ENVIRONMENT', "Set RAILS_ENV/RACK_ENV/RESQUE_ENV") { |c| opts[:environment] = c }
45
66
  opt.on("-s", '--spawn-delay MS', Integer, "Delay in milliseconds between spawning missing workers") { |c| opts[:spawn_delay] = c }
46
67
  opt.on('--term-graceful-wait', "On TERM signal, wait for workers to shut down gracefully") { opts[:term_graceful_wait] = true }
@@ -49,12 +70,9 @@ module Resque
49
70
  opt.on('--single-process-group', "Workers remain in the same process group as the master") { opts[:single_process_group] = true }
50
71
  opt.on("-h", "--help", "Show this.") { puts opt; exit }
51
72
  opt.on("-v", "--version", "Show Version"){ puts "resque-pool #{VERSION} (c) nicholas a. evans"; exit}
52
- end.parse!
53
- if opts[:daemon]
54
- opts[:stdout] ||= "log/resque-pool.stdout.log"
55
- opts[:stderr] ||= "log/resque-pool.stderr.log"
56
- opts[:pidfile] ||= "tmp/pids/resque-pool.pid"
57
73
  end
74
+ parser.parse!(argv || parser.default_argv)
75
+
58
76
  opts
59
77
  end
60
78
 
@@ -66,6 +84,18 @@ module Resque
66
84
  exit unless pid.nil?
67
85
  end
68
86
 
87
+ # Obtain a lock on a file that will be held for the lifetime of
88
+ # the process. This aids in concurrent daemonized deployment with
89
+ # process managers like upstart since multiple pools can share a
90
+ # lock, but not a pidfile.
91
+ def obtain_shared_lock(lock_path)
92
+ return unless lock_path
93
+ @lock_file = File.open(lock_path, 'w')
94
+ unless @lock_file.flock(File::LOCK_SH)
95
+ fail "unable to obtain shared lock on #{@lock_file}"
96
+ end
97
+ end
98
+
69
99
  def manage_pidfile(pidfile)
70
100
  return unless pidfile
71
101
  pid = Process.pid
@@ -90,8 +120,7 @@ module Resque
90
120
 
91
121
  def process_still_running?(pidfile)
92
122
  old_pid = open(pidfile).read.strip.to_i
93
- Process.kill 0, old_pid
94
- true
123
+ old_pid > 0 && Process.kill(0, old_pid)
95
124
  rescue Errno::ESRCH
96
125
  false
97
126
  rescue Errno::EPERM
@@ -130,6 +159,7 @@ module Resque
130
159
  if opts[:spawn_delay]
131
160
  Resque::Pool.spawn_delay = opts[:spawn_delay] * 0.001
132
161
  end
162
+ Resque::Pool.kill_other_pools = !!opts[:killothers]
133
163
  end
134
164
 
135
165
  def setup_environment(opts)
@@ -142,7 +172,14 @@ module Resque
142
172
 
143
173
  def start_pool
144
174
  require 'rake'
175
+ self.const_set :RakeApp, Class.new(Rake::Application) {
176
+ def default_task_name # :nodoc:
177
+ "resque:pool"
178
+ end
179
+ }
180
+ Rake.application = RakeApp.new
145
181
  require 'resque/pool/tasks'
182
+
146
183
  Rake.application.init
147
184
  Rake.application.load_rakefile
148
185
  Rake.application["resque:pool"].invoke
@@ -0,0 +1,63 @@
1
+ module Resque
2
+ class Pool
3
+ module ConfigLoaders
4
+
5
+ class FileOrHashLoader
6
+ def initialize(filename_or_hash=nil)
7
+ case filename_or_hash
8
+ when String, nil
9
+ @filename = filename_or_hash
10
+ when Hash
11
+ @static_config = filename_or_hash.dup
12
+ else
13
+ raise "#{self.class} cannot be initialized with #{filename_or_hash.inspect}"
14
+ end
15
+ end
16
+
17
+ def call(environment)
18
+ @config ||= load_config_from_file(environment)
19
+ end
20
+
21
+ def reset!
22
+ @config = nil
23
+ end
24
+
25
+ private
26
+
27
+ def load_config_from_file(environment)
28
+ if @static_config
29
+ new_config = @static_config
30
+ else
31
+ filename = config_filename
32
+ new_config = load_config filename
33
+ end
34
+ apply_environment new_config, environment
35
+ end
36
+
37
+ def apply_environment(config, environment)
38
+ environment and config[environment] and config.merge!(config[environment])
39
+ config.delete_if {|key, value| value.is_a? Hash }
40
+ end
41
+
42
+ def config_filename
43
+ @filename || choose_config_file
44
+ end
45
+
46
+ def load_config(filename)
47
+ return {} unless filename
48
+ YAML.load(ERB.new(IO.read(filename)).result)
49
+ end
50
+
51
+ CONFIG_FILES = ["resque-pool.yml", "config/resque-pool.yml"]
52
+ def choose_config_file
53
+ if ENV["RESQUE_POOL_CONFIG"]
54
+ ENV["RESQUE_POOL_CONFIG"]
55
+ else
56
+ CONFIG_FILES.detect { |f| File.exist?(f) }
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,44 @@
1
+ require "delegate"
2
+
3
+ module Resque
4
+ class Pool
5
+
6
+ module ConfigLoaders
7
+
8
+ # Throttle the frequency of loading pool configuration
9
+ # Defaults to call only once per 10 seconds.
10
+ class Throttled < SimpleDelegator
11
+
12
+ def initialize(config_loader, period: 10, time_source: Time)
13
+ super(config_loader)
14
+ @period = period
15
+ @resettable = config_loader.respond_to?(:reset!)
16
+ @last_check = 0
17
+ @time_source = time_source
18
+ end
19
+
20
+ def call(env)
21
+ # We do not need to cache per `env`, since the value of `env` will not
22
+ # change during the life of the process.
23
+ if (now > @last_check + @period)
24
+ @cache = super
25
+ @last_check = now
26
+ end
27
+ @cache
28
+ end
29
+
30
+ def reset!
31
+ @last_check = 0
32
+ super if @resettable
33
+ end
34
+
35
+ private
36
+
37
+ def now
38
+ @time_source.now.to_f
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,40 @@
1
+ module Resque
2
+ class Pool
3
+ class Killer
4
+ include Logging
5
+
6
+ GRACEFUL_SHUTDOWN_SIGNAL=:INT
7
+
8
+ def self.run
9
+ new.run
10
+ end
11
+
12
+ def run
13
+ my_pid = Process.pid
14
+ pool_pids = all_resque_pool_processes
15
+ pids_to_kill = pool_pids.reject{|pid| pid == my_pid}
16
+ pids_to_kill.each do |pid|
17
+ log "Pool (#{my_pid}) in kill-others mode: killing pool with pid (#{pid})"
18
+ Process.kill(GRACEFUL_SHUTDOWN_SIGNAL, pid)
19
+ end
20
+ end
21
+
22
+
23
+ def all_resque_pool_processes
24
+ out = `ps -e -o pid= -o command= 2>&1`
25
+ raise "Unable to identify other pools: #{out}" unless $?.success?
26
+ parse_pids_from_output out
27
+ end
28
+
29
+ RESQUE_POOL_PIDS = /
30
+ ^\s*(\d+) # PID digits, optional leading spaces
31
+ \s+ # column divider
32
+ #{Regexp.escape(PROCLINE_PREFIX)} # exact match at start of command
33
+ /x
34
+
35
+ def parse_pids_from_output(output)
36
+ output.scan(RESQUE_POOL_PIDS).flatten.map(&:to_i)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -75,11 +75,13 @@ module Resque
75
75
  reopened_count
76
76
  end
77
77
 
78
+ PROCLINE_PREFIX="resque-pool-master"
79
+
78
80
  # Given a string, sets the procline ($0)
79
81
  # Procline is always in the format of:
80
82
  # resque-pool-master: STRING
81
83
  def procline(string)
82
- $0 = "resque-pool-master#{app}: #{string}"
84
+ $0 = "#{PROCLINE_PREFIX}#{app}: #{string}"
83
85
  end
84
86
 
85
87
  # TODO: make this use an actual logger
@@ -1,5 +1,5 @@
1
1
  module Resque
2
2
  class Pool
3
- VERSION = "0.6.0"
3
+ VERSION = "0.7.0"
4
4
  end
5
5
  end
@@ -0,0 +1,42 @@
1
+ require_relative "lib/resque/pool/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "resque-pool"
5
+ spec.version = Resque::Pool::VERSION
6
+ spec.authors = ["nicholas a. evans",]
7
+ spec.email = ["nick@ekenosen.net"]
8
+
9
+ spec.summary = "quickly and easily fork a pool of resque workers"
10
+ spec.description = <<-EOF
11
+ quickly and easily fork a pool of resque workers,
12
+ saving memory (w/REE) and monitoring their uptime
13
+ EOF
14
+ spec.homepage = "http://github.com/nevans/resque-pool"
15
+ spec.license = 'MIT'
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "http://github.com/nevans/resque-pool"
19
+ spec.metadata["changelog_uri"] = "https://github.com/resque/resque/blob/master/HISTORY.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.required_ruby_version = '>= 2.0'
31
+
32
+ spec.add_dependency "resque", ">= 1.22", "< 3"
33
+ spec.add_dependency "rake", ">= 10.0", "< 13.0"
34
+
35
+ spec.add_development_dependency "bundler", "~> 2.0"
36
+ spec.add_development_dependency "rspec", "~> 3.8"
37
+ spec.add_development_dependency "cucumber", "~> 3.0"
38
+ spec.add_development_dependency "aruba", "~> 0.14.0"
39
+ spec.add_development_dependency "ronn"
40
+ spec.add_development_dependency "mustache"
41
+
42
+ end
metadata CHANGED
@@ -1,111 +1,137 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - nicholas a. evans
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-14 00:00:00.000000000 Z
11
+ date: 2019-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: resque
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.22'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - ~>
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '1.22'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: rake
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - '>='
37
+ - - ">="
32
38
  - !ruby/object:Gem::Version
33
- version: '0'
39
+ version: '10.0'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '13.0'
34
43
  type: :runtime
35
44
  prerelease: false
36
45
  version_requirements: !ruby/object:Gem::Requirement
37
46
  requirements:
38
- - - '>='
47
+ - - ">="
39
48
  - !ruby/object:Gem::Version
40
- version: '0'
49
+ version: '10.0'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '13.0'
53
+ - !ruby/object:Gem::Dependency
54
+ name: bundler
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '2.0'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '2.0'
41
67
  - !ruby/object:Gem::Dependency
42
68
  name: rspec
43
69
  requirement: !ruby/object:Gem::Requirement
44
70
  requirements:
45
- - - ~>
71
+ - - "~>"
46
72
  - !ruby/object:Gem::Version
47
- version: '2.10'
73
+ version: '3.8'
48
74
  type: :development
49
75
  prerelease: false
50
76
  version_requirements: !ruby/object:Gem::Requirement
51
77
  requirements:
52
- - - ~>
78
+ - - "~>"
53
79
  - !ruby/object:Gem::Version
54
- version: '2.10'
80
+ version: '3.8'
55
81
  - !ruby/object:Gem::Dependency
56
82
  name: cucumber
57
83
  requirement: !ruby/object:Gem::Requirement
58
84
  requirements:
59
- - - ~>
85
+ - - "~>"
60
86
  - !ruby/object:Gem::Version
61
- version: '1.2'
87
+ version: '3.0'
62
88
  type: :development
63
89
  prerelease: false
64
90
  version_requirements: !ruby/object:Gem::Requirement
65
91
  requirements:
66
- - - ~>
92
+ - - "~>"
67
93
  - !ruby/object:Gem::Version
68
- version: '1.2'
94
+ version: '3.0'
69
95
  - !ruby/object:Gem::Dependency
70
96
  name: aruba
71
97
  requirement: !ruby/object:Gem::Requirement
72
98
  requirements:
73
- - - ~>
99
+ - - "~>"
74
100
  - !ruby/object:Gem::Version
75
- version: 0.4.11
101
+ version: 0.14.0
76
102
  type: :development
77
103
  prerelease: false
78
104
  version_requirements: !ruby/object:Gem::Requirement
79
105
  requirements:
80
- - - ~>
106
+ - - "~>"
81
107
  - !ruby/object:Gem::Version
82
- version: 0.4.11
108
+ version: 0.14.0
83
109
  - !ruby/object:Gem::Dependency
84
- name: bundler
110
+ name: ronn
85
111
  requirement: !ruby/object:Gem::Requirement
86
112
  requirements:
87
- - - ~>
113
+ - - ">="
88
114
  - !ruby/object:Gem::Version
89
- version: '1.0'
115
+ version: '0'
90
116
  type: :development
91
117
  prerelease: false
92
118
  version_requirements: !ruby/object:Gem::Requirement
93
119
  requirements:
94
- - - ~>
120
+ - - ">="
95
121
  - !ruby/object:Gem::Version
96
- version: '1.0'
122
+ version: '0'
97
123
  - !ruby/object:Gem::Dependency
98
- name: ronn
124
+ name: mustache
99
125
  requirement: !ruby/object:Gem::Requirement
100
126
  requirements:
101
- - - '>='
127
+ - - ">="
102
128
  - !ruby/object:Gem::Version
103
129
  version: '0'
104
130
  type: :development
105
131
  prerelease: false
106
132
  version_requirements: !ruby/object:Gem::Requirement
107
133
  requirements:
108
- - - '>='
134
+ - - ">="
109
135
  - !ruby/object:Gem::Version
110
136
  version: '0'
111
137
  description: |2
@@ -118,43 +144,70 @@ executables:
118
144
  extensions: []
119
145
  extra_rdoc_files: []
120
146
  files:
147
+ - ".gitignore"
148
+ - ".travis.yml"
149
+ - CODE_OF_CONDUCT.md
150
+ - CONTRIBUTING.md
151
+ - Changelog.md
152
+ - Gemfile
153
+ - Gemfile.lock
154
+ - LICENSE.txt
121
155
  - README.md
122
156
  - Rakefile
123
- - LICENSE.txt
124
- - Changelog.md
157
+ - config/alternate.yml
158
+ - config/cucumber.yml
159
+ - examples/Gemfile
160
+ - examples/Gemfile.lock
161
+ - examples/Rakefile
162
+ - examples/chef_cookbook/recipes/default.rb
163
+ - examples/chef_cookbook/templates/default/initd.erb
164
+ - examples/chef_cookbook/templates/default/monitrc.erb
165
+ - examples/log/.gitignore
166
+ - examples/rails-resque.rake
167
+ - examples/resque-pool.god
168
+ - examples/resque-pool.yml
169
+ - examples/tmp/pids/.gitignore
170
+ - examples/upstart-reload.conf
171
+ - examples/upstart.conf
172
+ - exe/resque-pool
125
173
  - lib/resque/pool.rb
126
- - lib/resque/pool/tasks.rb
127
174
  - lib/resque/pool/cli.rb
175
+ - lib/resque/pool/config_loaders/file_or_hash_loader.rb
176
+ - lib/resque/pool/config_loaders/throttled.rb
177
+ - lib/resque/pool/killer.rb
178
+ - lib/resque/pool/logging.rb
128
179
  - lib/resque/pool/pooled_worker.rb
180
+ - lib/resque/pool/tasks.rb
129
181
  - lib/resque/pool/version.rb
130
- - lib/resque/pool/logging.rb
131
- - lib/resque/pool/file_or_hash_loader.rb
132
- - bin/resque-pool
133
- - man/resque-pool.1.ronn
134
182
  - man/resque-pool.1
135
- - man/resque-pool.yml.5.ronn
183
+ - man/resque-pool.1.ronn
136
184
  - man/resque-pool.yml.5
185
+ - man/resque-pool.yml.5.ronn
186
+ - resque-pool.gemspec
137
187
  homepage: http://github.com/nevans/resque-pool
138
188
  licenses:
139
189
  - MIT
140
- metadata: {}
190
+ metadata:
191
+ homepage_uri: http://github.com/nevans/resque-pool
192
+ source_code_uri: http://github.com/nevans/resque-pool
193
+ changelog_uri: https://github.com/resque/resque/blob/master/HISTORY.md
141
194
  post_install_message:
142
195
  rdoc_options: []
143
196
  require_paths:
144
197
  - lib
145
198
  required_ruby_version: !ruby/object:Gem::Requirement
146
199
  requirements:
147
- - - '>='
200
+ - - ">="
148
201
  - !ruby/object:Gem::Version
149
- version: 1.9.3
202
+ version: '2.0'
150
203
  required_rubygems_version: !ruby/object:Gem::Requirement
151
204
  requirements:
152
- - - '>='
205
+ - - ">="
153
206
  - !ruby/object:Gem::Version
154
207
  version: '0'
155
208
  requirements: []
156
209
  rubyforge_project:
157
- rubygems_version: 2.0.14
210
+ rubygems_version: 2.6.14.3
158
211
  signing_key:
159
212
  specification_version: 4
160
213
  summary: quickly and easily fork a pool of resque workers