resqued 0.9.0 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: acc537c28c318ee1628ea4c14911a4716c0d8330
4
- data.tar.gz: 7b625fcb530e90ca12459f75e7529641dd660758
3
+ metadata.gz: d56c813a2d3adf7d10fe76dafefff24c263f9766
4
+ data.tar.gz: e3c9cd5fc92aca0b70d4d8ed31de6f15c418c94c
5
5
  SHA512:
6
- metadata.gz: 68d8f563141474b5b4e8be2c300763cd34052bfd2da431f2455f01cdbc36abfd9c0cf0d73b87bc599620a6adc0461d246717845a64d4cfe6707d49d2c725cb5b
7
- data.tar.gz: 23faeaefbffacca556681d465bb9de03d07ff73bd2ab135788e8263998e483db49dbc4d6b75b5b6bfc1d5922634a2427590a34e2134e3d5c28470585ff514d9f
6
+ metadata.gz: a28be514424a281b554224c78ba0d4fa6ff328f1d9fff622c76653c09ceefc6bc6c70cdb389d41dbd566fa202741d073ad6817103d2231237cbfdd726dd32473
7
+ data.tar.gz: 14375d125a0c9e89ecc1918fa6d46f062206ddc0ac08a3ac8f504e7aeab563f90ea3d62cb54b955b1408f3308692b9cc98479c19d2d420b0136d73c10182687d
data/CHANGES.md CHANGED
@@ -1,5 +1,11 @@
1
1
  Starting with version 0.6.1, resqued uses semantic versioning to indicate incompatibilities between the master process, listener process, and configuration.
2
2
 
3
+ v0.10.0
4
+ -------
5
+ * Master process restarts itself (#51), so that it doesn't continue running stale code indefinitely. This will help with the rollout process when changes like #50 are introduced, so that the master process will catch up. The risk of this is that the master process might not be able to be restarted, which would lead to it crashing. The mostly likely way for that to happen is if you try to roll back your version of resqued to 0.9.0 or earlier. If you need to do that, ensure that your process monitor (systemd, god, etc.) is able to restart the master process. You can disable the new behavior by passing `--no-exec-on-hup`.
6
+ * Added rubocop. (#52)
7
+ * Changed supported (read: tested in CI) ruby versions from [2.0 .. 2.3] to [2.3 .. 2.6].
8
+
3
9
  v0.9.0
4
10
  ------
5
11
  * Avoid Errno::E2BIG on SIGHUP when there are lots of workers and lots of queues per worker. (#50) This changes the format of an env var that master passes to listener. Old and new versions won't crash, but they won't be able to communicate about currenly running workers.
@@ -1,74 +1,93 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- if ARGV[0] == 'listener'
4
- require 'resqued/listener'
3
+ if ARGV[0] == "listener"
4
+ require "resqued/listener"
5
5
  Resqued::Listener.exec!
6
6
  exit 0
7
7
  end
8
8
 
9
- require 'optparse'
9
+ require "optparse"
10
10
 
11
- options = {}
11
+ options = { exec_on_hup: true }
12
12
  daemonize = false
13
13
  test = false
14
14
 
15
- opts = OptionParser.new do |opts|
15
+ opts = OptionParser.new do |opts| # rubocop: disable Lint/ShadowingOuterLocalVariable
16
16
  opts.banner = "Usage: resqued [options] resqued-config-files..."
17
17
 
18
- opts.on '-h', '--help', 'Show this message' do
18
+ opts.on "-h", "--help", "Show this message" do
19
19
  puts opts
20
20
  exit
21
21
  end
22
22
 
23
- opts.on '-v', '--version', 'Show the version' do
24
- require 'resqued/version'
23
+ opts.on "-v", "--version", "Show the version" do
24
+ require "resqued/version"
25
25
  puts Resqued::VERSION
26
26
  exit
27
27
  end
28
28
 
29
- opts.on '--test', 'Report which worker would start' do
29
+ opts.on "--test", "Report which worker would start" do
30
30
  test = true
31
31
  end
32
32
 
33
- opts.on '--fast-exit', 'Exit quickly on SIGQUIT, SIGTERM' do
33
+ opts.on "--fast-exit", "Exit quickly on SIGQUIT, SIGTERM" do
34
34
  options[:fast_exit] = true
35
35
  end
36
36
 
37
- opts.on '-p', '--pidfile PIDFILE', 'Store the pid of the master process in PIDFILE' do |v|
37
+ opts.on "-p", "--pidfile PIDFILE", "Store the pid of the master process in PIDFILE" do |v|
38
38
  options[:master_pidfile] = v
39
39
  end
40
40
 
41
- opts.on '-l', '--logfile LOGFILE', 'Write output to LOGFILE instead of stdout' do |v|
42
- require 'resqued/logging'
41
+ opts.on "-l", "--logfile LOGFILE", "Write output to LOGFILE instead of stdout" do |v|
42
+ require "resqued/logging"
43
43
  Resqued::Logging.log_file = v
44
44
  end
45
45
 
46
- opts.on '-D', '--daemonize', 'Run daemonized in the background' do
46
+ opts.on "-D", "--daemonize", "Run daemonized in the background" do
47
47
  daemonize = true
48
48
  end
49
+
50
+ opts.on "--no-exec-on-hup", "Do not re-exec the master process on SIGHUP" do
51
+ options[:exec_on_hup] = false
52
+ end
53
+
54
+ opts.on "--replace FILE", "(internal)" do |v|
55
+ options[:master_state] = v
56
+ end
49
57
  end
50
58
 
51
59
  opts.parse!
52
60
  options[:config_paths] = ARGV
53
61
 
54
- if options[:config_paths].size == 0
55
- puts opts
56
- exit 1
62
+ def require_config_paths!(options, opts)
63
+ if options[:config_paths].empty?
64
+ puts opts
65
+ exit 1
66
+ end
57
67
  end
58
68
 
59
69
  if test
60
- require 'resqued/config'
70
+ require_config_paths! options, opts
71
+ require "resqued/config"
61
72
  workers = Resqued::Config.new(options[:config_paths]).build_workers
62
73
  puts "Workers defined in #{options[:config_paths].join(' ')}"
63
74
  workers.each_with_index do |worker, index|
64
75
  puts "#{index + 1}: #{worker.queues.join(',')}"
65
76
  end
66
77
  else
67
- require 'resqued'
68
- Resqued.capture_start_ctx!
69
- resqued = Resqued::Master.new(options)
78
+ require "resqued"
79
+ state = Resqued::MasterState.new
80
+ if options[:master_state]
81
+ Resqued::Logging.logger.info "Resuming master from #{options[:master_state]}"
82
+ Resqued::ExecOnHUP.restore_state(state, options[:master_state])
83
+ else
84
+ require_config_paths! options, opts
85
+ Resqued.capture_start_ctx!
86
+ state.init(options)
87
+ end
88
+ resqued = Resqued::Master.new(state)
70
89
  if daemonize
71
- require 'resqued/daemon'
90
+ require "resqued/daemon"
72
91
  resqued = Resqued::Daemon.new(resqued)
73
92
  end
74
93
  resqued.run
@@ -1,12 +1,12 @@
1
- require 'resqued/master'
2
- require 'resqued/version'
1
+ require "resqued/master"
2
+ require "resqued/version"
3
3
 
4
4
  module Resqued
5
- START_CTX = {}
5
+ START_CTX = {} # rubocop: disable Style/MutableConstant
6
6
 
7
7
  def self.capture_start_ctx!
8
- START_CTX['$0'] = $0.dup
9
- START_CTX['pwd'] =
8
+ START_CTX["$0"] = $0.dup
9
+ START_CTX["pwd"] =
10
10
  begin
11
11
  env_pwd = ENV["PWD"]
12
12
  env_pwd_stat = File.stat env_pwd
@@ -1,6 +1,6 @@
1
- require 'resqued/config/after_fork'
2
- require 'resqued/config/before_fork'
3
- require 'resqued/config/worker'
1
+ require "resqued/config/after_fork"
2
+ require "resqued/config/before_fork"
3
+ require "resqued/config/worker"
4
4
 
5
5
  module Resqued
6
6
  module Config
@@ -14,22 +14,22 @@ module Resqued
14
14
  # Does the things that the config file says to do.
15
15
  class Configuration
16
16
  def initialize(config_paths)
17
- @config_data = config_paths.map { |path| {:content => File.read(path), :path => path} }
17
+ @config_data = config_paths.map { |path| { content: File.read(path), path: path } }
18
18
  end
19
19
 
20
20
  # Public: Performs the `before_fork` action from the config.
21
21
  def before_fork(resqued)
22
- Resqued::Config::BeforeFork.new(:resqued => resqued).apply_all(@config_data)
22
+ Resqued::Config::BeforeFork.new(resqued: resqued).apply_all(@config_data)
23
23
  end
24
24
 
25
25
  # Public: Performs the `after_fork` action from the config.
26
26
  def after_fork(worker)
27
- Resqued::Config::AfterFork.new(:worker => worker).apply_all(@config_data)
27
+ Resqued::Config::AfterFork.new(worker: worker).apply_all(@config_data)
28
28
  end
29
29
 
30
30
  # Public: Builds the workers specified in the config.
31
31
  def build_workers
32
- Resqued::Config::Worker.new(:config => self).apply_all(@config_data)
32
+ Resqued::Config::Worker.new(config: self).apply_all(@config_data)
33
33
  end
34
34
  end
35
35
  end
@@ -1,4 +1,4 @@
1
- require 'resqued/config/base'
1
+ require "resqued/config/base"
2
2
 
3
3
  module Resqued
4
4
  module Config
@@ -1,4 +1,4 @@
1
- require 'resqued/config/dsl'
1
+ require "resqued/config/dsl"
2
2
 
3
3
  module Resqued
4
4
  module Config
@@ -1,4 +1,4 @@
1
- require 'resqued/config/base'
1
+ require "resqued/config/base"
2
2
 
3
3
  module Resqued
4
4
  module Config
@@ -1,5 +1,5 @@
1
- require 'resqued/config/base'
2
- require 'resqued/worker'
1
+ require "resqued/config/base"
2
+ require "resqued/worker"
3
3
 
4
4
  module Resqued
5
5
  module Config
@@ -21,9 +21,9 @@ module Resqued
21
21
  def worker(*queues)
22
22
  options = queues.last.is_a?(Hash) ? queues.pop.dup : {}
23
23
  queues = queues.flatten
24
- queues = ['*'] if queues.empty?
24
+ queues = ["*"] if queues.empty?
25
25
  queues = queues.shuffle if options.delete(:shuffle_queues)
26
- @workers << @worker_class.new(options.merge(@worker_options).merge(:queues => queues))
26
+ @workers << @worker_class.new(options.merge(@worker_options).merge(queues: queues))
27
27
  end
28
28
 
29
29
  # DSL: Set up a pool of workers. Define queues for the members of the pool with `queue`.
@@ -81,15 +81,16 @@ module Resqued
81
81
  # on the concurrency values established and the total number of workers.
82
82
  def build_pool_workers!
83
83
  return unless @pool_size
84
+
84
85
  queues = _fixed_concurrency_queues
85
86
  1.upto(@pool_size) do |worker_num|
86
- queue_names = queues.
87
- select { |name, concurrency| concurrency >= worker_num }.
88
- map { |name, _| name }
87
+ queue_names = queues
88
+ .select { |_name, concurrency| concurrency >= worker_num }
89
+ .map { |name, _concurrency| name }
89
90
  if queue_names.any?
90
91
  worker(queue_names, @pool_options)
91
92
  else
92
- worker('*', @pool_options)
93
+ worker("*", @pool_options)
93
94
  end
94
95
  end
95
96
  end
@@ -106,12 +107,11 @@ module Resqued
106
107
  # values (between 0.0 and 1.0). The value may also be nil, in which case the
107
108
  # maximum worker_processes value is returned.
108
109
  def _translate_concurrency_value(value)
109
- case
110
- when value.nil?
110
+ if value.nil?
111
111
  @pool_size
112
- when value.is_a?(1.class)
112
+ elsif value.is_a?(1.class)
113
113
  value < @pool_size ? value : @pool_size
114
- when value.is_a?(Float) && value >= 0.0 && value <= 1.0
114
+ elsif value.is_a?(Float) && value >= 0.0 && value <= 1.0
115
115
  (@pool_size * value).to_i
116
116
  else
117
117
  raise TypeError, "Unknown concurrency value: #{value.inspect}"
@@ -12,6 +12,7 @@ module Resqued
12
12
  wr.close
13
13
  begin
14
14
  master_pid = rd.readpartial(16).to_i
15
+ puts "Started master: #{master_pid}" if ENV["DEBUG"]
15
16
  exit
16
17
  rescue EOFError
17
18
  puts "Master process failed to start!"
@@ -0,0 +1,43 @@
1
+ require "tempfile"
2
+ require "yaml"
3
+
4
+ module Resqued
5
+ class ExecOnHUP
6
+ # Public: Replace the current master process with a new one, while preserving state.
7
+ def self.exec!(state)
8
+ exec Resqued::START_CTX["$0"], "--replace", store_state(state), exec_opts(state)
9
+ end
10
+
11
+ # Internal: Returns exec options for each open socket in 'state'.
12
+ def self.exec_opts(state)
13
+ exec_opts = {}
14
+ state.sockets.each do |sock|
15
+ exec_opts[sock.to_i] = sock
16
+ end
17
+ if pwd = Resqued::START_CTX["pwd"]
18
+ exec_opts[:chdir] = pwd
19
+ end
20
+ return exec_opts
21
+ end
22
+
23
+ # Internal: Write out current state to a file, so that a new master can pick up from where we left off.
24
+ def self.store_state(state)
25
+ data = { version: Resqued::VERSION }
26
+ data[:start_ctx] = Resqued::START_CTX
27
+ data[:state] = state.to_h
28
+
29
+ f = Tempfile.create "resqued-state"
30
+ f.write(YAML.dump(data))
31
+ f.close
32
+ return f.path
33
+ end
34
+
35
+ # Internal: Restore the master's state, and remove the state file.
36
+ def self.restore_state(state, path)
37
+ data = YAML.safe_load(File.read(path), [Symbol], [], true)
38
+ Resqued::START_CTX.replace(data[:start_ctx] || {})
39
+ state.restore(data[:state])
40
+ File.unlink(path) rescue nil
41
+ end
42
+ end
43
+ end
@@ -1,12 +1,12 @@
1
- require 'socket'
1
+ require "socket"
2
2
 
3
- require 'resqued/config'
4
- require 'resqued/logging'
5
- require 'resqued/procline_version'
6
- require 'resqued/runtime_info'
7
- require 'resqued/sleepy'
8
- require 'resqued/version'
9
- require 'resqued/worker'
3
+ require "resqued/config"
4
+ require "resqued/logging"
5
+ require "resqued/procline_version"
6
+ require "resqued/runtime_info"
7
+ require "resqued/sleepy"
8
+ require "resqued/version"
9
+ require "resqued/worker"
10
10
 
11
11
  module Resqued
12
12
  # A listener process. Watches resque queues and forks workers.
@@ -30,67 +30,67 @@ module Resqued
30
30
  # Runs in the master process.
31
31
  def exec
32
32
  socket_fd = @socket.to_i
33
- ENV['RESQUED_SOCKET'] = socket_fd.to_s
34
- ENV['RESQUED_CONFIG_PATH'] = @config_paths.join(':')
35
- ENV['RESQUED_STATE'] = (@old_workers.map { |r| "#{r[:pid]}|#{r[:queue_key]}" }.join('||'))
36
- ENV['RESQUED_LISTENER_ID'] = @listener_id.to_s
37
- ENV['RESQUED_MASTER_VERSION'] = Resqued::VERSION
33
+ ENV["RESQUED_SOCKET"] = socket_fd.to_s
34
+ ENV["RESQUED_CONFIG_PATH"] = @config_paths.join(":")
35
+ ENV["RESQUED_STATE"] = @old_workers.map { |r| "#{r[:pid]}|#{r[:queue_key]}" }.join("||")
36
+ ENV["RESQUED_LISTENER_ID"] = @listener_id.to_s
37
+ ENV["RESQUED_MASTER_VERSION"] = Resqued::VERSION
38
38
  log "exec: #{Resqued::START_CTX['$0']} listener"
39
- exec_opts = {socket_fd => socket_fd} # Ruby 2.0 needs to be told to keep the file descriptor open during exec.
40
- if start_pwd = Resqued::START_CTX['pwd']
39
+ exec_opts = { socket_fd => socket_fd } # Ruby 2.0 needs to be told to keep the file descriptor open during exec.
40
+ if start_pwd = Resqued::START_CTX["pwd"]
41
41
  exec_opts[:chdir] = start_pwd
42
42
  end
43
- procline_buf = ' ' * 256 # make room for setproctitle
44
- Kernel.exec(Resqued::START_CTX['$0'], 'listener', procline_buf, exec_opts)
43
+ procline_buf = " " * 256 # make room for setproctitle
44
+ Kernel.exec(Resqued::START_CTX["$0"], "listener", procline_buf, exec_opts)
45
45
  end
46
46
 
47
47
  # Public: Given args from #exec, start this listener.
48
48
  def self.exec!
49
49
  options = {}
50
- if socket = ENV['RESQUED_SOCKET']
50
+ if socket = ENV["RESQUED_SOCKET"]
51
51
  options[:socket] = Socket.for_fd(socket.to_i)
52
52
  end
53
- if path = ENV['RESQUED_CONFIG_PATH']
54
- options[:config_paths] = path.split(':')
53
+ if path = ENV["RESQUED_CONFIG_PATH"]
54
+ options[:config_paths] = path.split(":")
55
55
  end
56
- if state = ENV['RESQUED_STATE']
57
- options[:old_workers] = state.split('||').map { |s| Hash[[:pid,:queue_key].zip(s.split('|'))] }
56
+ if state = ENV["RESQUED_STATE"]
57
+ options[:old_workers] = state.split("||").map { |s| Hash[[:pid, :queue_key].zip(s.split("|"))] }
58
58
  end
59
- if listener_id = ENV['RESQUED_LISTENER_ID']
59
+ if listener_id = ENV["RESQUED_LISTENER_ID"]
60
60
  options[:listener_id] = listener_id
61
61
  end
62
62
  new(options).run
63
63
  end
64
64
 
65
- SIGNALS = [ :CONT, :QUIT, :INT, :TERM ]
66
- ALL_SIGNALS = SIGNALS + [ :CHLD ]
65
+ SIGNALS = [:CONT, :QUIT, :INT, :TERM].freeze
66
+ ALL_SIGNALS = SIGNALS + [:CHLD]
67
67
 
68
- SIGNAL_QUEUE = []
68
+ SIGNAL_QUEUE = [] # rubocop: disable Style/MutableConstant
69
69
 
70
70
  # Public: Run the main loop.
71
71
  def run
72
72
  trap(:CHLD) { awake }
73
- SIGNALS.each { |signal| trap(signal) { SIGNAL_QUEUE << signal ; awake } }
73
+ SIGNALS.each { |signal| trap(signal) { SIGNAL_QUEUE << signal; awake } }
74
74
  @socket.close_on_exec = true
75
- write_procline('starting')
75
+ write_procline("starting")
76
76
 
77
77
  config = Resqued::Config.new(@config_paths)
78
78
  set_default_resque_logger
79
79
  config.before_fork(info)
80
80
  report_to_master("RUNNING")
81
81
 
82
- write_procline('running')
82
+ write_procline("running")
83
83
  init_workers(config)
84
84
  exit_signal = run_workers_run
85
85
 
86
- write_procline('shutdown')
86
+ write_procline("shutdown")
87
87
  burn_down_workers(exit_signal || :QUIT)
88
88
  end
89
89
 
90
90
  # Private.
91
91
  def set_default_resque_logger
92
- require 'resque'
93
- if Resque.respond_to?('logger=')
92
+ require "resque"
93
+ if Resque.respond_to?("logger=")
94
94
  Resque.logger = Resqued::Logging.build_logger
95
95
  end
96
96
  end
@@ -101,7 +101,7 @@ module Resqued
101
101
  reap_workers(Process::WNOHANG)
102
102
  check_for_expired_workers
103
103
  start_idle_workers
104
- write_procline('running')
104
+ write_procline("running")
105
105
  case signal = SIGNAL_QUEUE.shift
106
106
  when nil
107
107
  yawn
@@ -119,10 +119,11 @@ module Resqued
119
119
  def burn_down_workers(signal)
120
120
  loop do
121
121
  check_for_expired_workers
122
- write_procline('shutdown')
122
+ write_procline("shutdown")
123
123
  SIGNAL_QUEUE.clear
124
124
 
125
125
  break if :no_child == reap_workers(Process::WNOHANG)
126
+
126
127
  kill_all(signal)
127
128
 
128
129
  sleep 1 # Don't kill any more often than every 1s.
@@ -134,7 +135,7 @@ module Resqued
134
135
 
135
136
  # Private: send a signal to all the workers.
136
137
  def kill_all(signal)
137
- idle, running = partition_workers
138
+ running = running_workers
138
139
  log "kill -#{signal} #{running.map { |r| r.pid }.inspect}"
139
140
  running.each { |worker| worker.kill(signal) }
140
141
  end
@@ -171,13 +172,11 @@ module Resqued
171
172
  def reap_workers(waitpidflags = 0)
172
173
  loop do
173
174
  worker_pid, status = Process.waitpid2(-1, waitpidflags)
174
- if worker_pid.nil?
175
- return :none_ready
176
- else
177
- log "Worker exited #{status}"
178
- finish_worker(worker_pid, status)
179
- report_to_master("-#{worker_pid}")
180
- end
175
+ return :none_ready if worker_pid.nil?
176
+
177
+ log "Worker exited #{status}"
178
+ finish_worker(worker_pid, status)
179
+ report_to_master("-#{worker_pid}")
181
180
  end
182
181
  rescue Errno::ECHILD
183
182
  # All done
@@ -187,6 +186,7 @@ module Resqued
187
186
  # Private: Check if master reports any dead workers.
188
187
  def check_for_expired_workers
189
188
  return unless @socket
189
+
190
190
  loop do
191
191
  IO.select([@socket], nil, nil, 0) or return
192
192
  line = @socket.readline
@@ -210,11 +210,11 @@ module Resqued
210
210
  # Private.
211
211
  def start_idle_workers
212
212
  workers.each do |worker|
213
- if worker.idle?
214
- worker.try_start
215
- if pid = worker.pid
216
- report_to_master("+#{pid},#{worker.queue_key}")
217
- end
213
+ next unless worker.idle?
214
+
215
+ worker.try_start
216
+ if pid = worker.pid
217
+ report_to_master("+#{pid},#{worker.queue_key}")
218
218
  end
219
219
  end
220
220
  end
@@ -236,7 +236,7 @@ module Resqued
236
236
  # report_to_master("+12345,queue") # Worker process PID:12345 started, working on a job from "queue".
237
237
  # report_to_master("-12345") # Worker process PID:12345 exited.
238
238
  def report_to_master(status)
239
- @socket.puts(status) if @socket
239
+ @socket&.puts(status)
240
240
  rescue Errno::EPIPE => e
241
241
  @socket = nil
242
242
  log "#{e.class.name} while writing to master"