resque-pool 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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