resqued 0.12.1 → 0.12.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13f45787a92226688e4b30d34f1f6a5390277c34423b94786511d6bf6a4b5d71
4
- data.tar.gz: 0d6af78fd2ced8e9fff8703456052333ddb20e75c32d7c1ffab0466dbab6b9cb
3
+ metadata.gz: a05ff83ea7efb498899c8e366977f9b59e854c5c999ae9b8edcdb66e36ec8551
4
+ data.tar.gz: af4beac8bbd7b030527dc23b60587672126227a022560962f4d31f62efef5d5d
5
5
  SHA512:
6
- metadata.gz: 782cb7f9c4d7ceeb3e7de4d8612eb2df0363105f447fe110404dc779e73cffa9a433e3d08f6021cd6fd6f7f17b34c305c4ac623c28536b140c72ca8a451f3030
7
- data.tar.gz: 766db53a09ffab45db9288b9ed165bdb0c679ff394edcb2f40dd1e626c12e1735cb32a4dc39e2fbbb248d8d596826ff1bb01ff568bd944ad9ff0483c836947d4
6
+ metadata.gz: 55b640e571730cdaa8155c8296c21923eb54d9cb4fe93ec5245ee739e78201ff45736653544c070c0c6d012a0d83678d1158fe0243b1cf665d7292c29af10de6
7
+ data.tar.gz: b086650c78f7710ec143ac6e787a2cfd7809bae17c10fc9583259a37ae258ef65df3252052d6c5d35309e5c2ae15a90180fa856803bae40f356c277f6628d3fb
data/CHANGES.md CHANGED
@@ -1,5 +1,13 @@
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.12.3
4
+ -------
5
+ * Add `after_exit` that provides a `WorkerSummary` about each exited worker.
6
+
7
+ v0.12.2
8
+ -------
9
+ * Added lifecycle hook for container runtimes. (#67)
10
+
3
11
  v0.12.1
4
12
  -------
5
13
  * Fixed Resqued::TestCase. v0.12.0 introduced a regression that stopped
data/README.md CHANGED
@@ -152,6 +152,11 @@ You can configure the Resque worker in the `after_fork` block
152
152
  worker.term_timeout = 1.minute
153
153
  end
154
154
 
155
+ after_exit do |worker_summary|
156
+ puts "Worker was alive for #{worker_summary.alive_time_sec}"
157
+ puts "Process::Status of exited worker: #{worker_summary.process_status.inspect}"
158
+ end
159
+
155
160
  In this example, a Rails application is being set up with 7 workers:
156
161
  * high
157
162
  * low (interval = 30)
data/exe/resqued CHANGED
@@ -1,9 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- if ARGV[0] == "listener"
3
+ case ARGV[0]
4
+ when "listener"
4
5
  require "resqued/listener"
5
6
  Resqued::Listener.exec!
6
7
  exit 0
8
+ when "quit-and-wait"
9
+ require "resqued/quit-and-wait"
10
+ Resqued::QuitAndWait.exec!(ARGV.drop(1))
11
+ exit 0
7
12
  end
8
13
 
9
14
  require "optparse"
@@ -0,0 +1,22 @@
1
+ require "resqued/config/base"
2
+
3
+ module Resqued
4
+ module Config
5
+ # A config handler that executes the `after_exit` block.
6
+ #
7
+ # after_exit do |worker_summary|
8
+ # # Runs in each listener.
9
+ # end
10
+ class AfterExit < Base
11
+ # Public.
12
+ def initialize(options = {})
13
+ @worker_summary = options.fetch(:worker_summary)
14
+ end
15
+
16
+ # DSL: execute the `after_exit` block.
17
+ def after_exit
18
+ yield @worker_summary
19
+ end
20
+ end
21
+ end
22
+ end
@@ -12,6 +12,10 @@ module Resqued
12
12
  def after_fork(&block)
13
13
  end
14
14
 
15
+ # Public: Define a block to be run once after each worker exits.
16
+ def after_exit(&block)
17
+ end
18
+
15
19
  # Public: Define a worker that will work on a queue.
16
20
  def worker(*queues)
17
21
  end
@@ -1,5 +1,6 @@
1
1
  require "resqued/config/after_fork"
2
2
  require "resqued/config/before_fork"
3
+ require "resqued/config/after_exit"
3
4
  require "resqued/config/worker"
4
5
 
5
6
  module Resqued
@@ -27,6 +28,11 @@ module Resqued
27
28
  Resqued::Config::AfterFork.new(worker: worker).apply_all(@config_data)
28
29
  end
29
30
 
31
+ # Public: Perform the `after_exit` action from the config.
32
+ def after_exit(worker_summary)
33
+ Resqued::Config::AfterExit.new(worker_summary: worker_summary).apply_all(@config_data)
34
+ end
35
+
30
36
  # Public: Builds the workers specified in the config.
31
37
  def build_workers
32
38
  Resqued::Config::Worker.new(config: self).apply_all(@config_data)
@@ -0,0 +1,84 @@
1
+ require "optparse"
2
+
3
+ module Resqued
4
+ class QuitAndWait
5
+ def self.exec!(argv)
6
+ options = { grace_seconds: 30 }
7
+
8
+ opts = OptionParser.new do |opts| # rubocop: disable Lint/ShadowingOuterLocalVariable
9
+ opts.banner = <<~USAGE
10
+ Usage: resqued quit-and-wait PIDFILE [--grace-period SECONDS]
11
+
12
+ Use this as a preStop script in kubernetes. This script will send a SIGQUIT to
13
+ resqued immediately, and then sleep until either resqued exits or until the
14
+ grace period is nearing expiration. This script exits 0 if resqued exited and
15
+ 99 otherwise.
16
+ USAGE
17
+
18
+ opts.on "-h", "--help", "Show this message" do
19
+ puts opts
20
+ exit
21
+ end
22
+
23
+ opts.on "-v", "--version", "Show the version" do
24
+ require "resqued/version"
25
+ puts Resqued::VERSION
26
+ exit
27
+ end
28
+
29
+ opts.on "--grace-period SECONDS", Numeric, "Grace period provided to container runtime (default 30)" do |v|
30
+ options[:grace_seconds] = v
31
+ end
32
+
33
+ opts.on "--quiet" do
34
+ options[:quiet] = true
35
+ end
36
+ end
37
+
38
+ argv = opts.parse(argv)
39
+ if argv.size != 1
40
+ puts opts
41
+ exit 1
42
+ end
43
+ options[:pidfile] = argv.shift
44
+
45
+ new(**options).exec!
46
+ end
47
+
48
+ def initialize(grace_seconds:, pidfile:, quiet: false)
49
+ @grace_seconds = grace_seconds
50
+ @pidfile = pidfile
51
+ @quiet = quiet
52
+ end
53
+
54
+ attr_reader :grace_seconds, :pidfile, :quiet
55
+
56
+ def exec!
57
+ start = Time.now
58
+ stop_at = start + grace_seconds - 5
59
+ pid = File.read(pidfile).to_i
60
+
61
+ log "kill -QUIT #{pid} (resqued-master)"
62
+ Process.kill(:QUIT, pid)
63
+
64
+ while Time.now < stop_at
65
+ begin
66
+ # check if pid is still alive.
67
+ Process.kill(0, pid)
68
+ rescue Errno::ESRCH # no such process, it exited!
69
+ log "ok: resqued-master with pid #{pid} exited"
70
+ exit 0
71
+ end
72
+ end
73
+
74
+ log "giving up, resqued-master with pid #{pid} is still running."
75
+ exit 99
76
+ end
77
+
78
+ def log(message)
79
+ return if quiet
80
+
81
+ puts "#{Time.now.strftime('%H:%M:%S.%L')} #{message}"
82
+ end
83
+ end
84
+ end
@@ -18,6 +18,7 @@ module Resqued
18
18
  config.before_fork(RuntimeInfo.new)
19
19
  config.build_workers
20
20
  config.after_fork(FakeWorker.new)
21
+ config.after_exit(Resqued::WorkerSummary.new(alive_time_sec: 1.0, process_status: Process::Status.allocate))
21
22
  end
22
23
  end
23
24
 
@@ -1,3 +1,3 @@
1
1
  module Resqued
2
- VERSION = "0.12.1".freeze
2
+ VERSION = "0.12.3".freeze
3
3
  end
@@ -64,6 +64,9 @@ module Resqued
64
64
  @pid = nil
65
65
  @backoff.died unless @killed
66
66
  elsif !process_status.nil? && @self_started
67
+ alive_time_sec = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_time
68
+ @config.after_exit(WorkerSummary.new(alive_time_sec: alive_time_sec, process_status: process_status))
69
+
67
70
  log :debug, "#{summary} I exited: #{process_status}"
68
71
  @pid = nil
69
72
  @backoff.died unless @killed
@@ -84,6 +87,8 @@ module Resqued
84
87
  @backoff.started
85
88
  @self_started = true
86
89
  @killed = false
90
+ @start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
91
+
87
92
  if @pid = fork
88
93
  @pids << @pid
89
94
  # still in the listener
@@ -112,4 +117,14 @@ module Resqued
112
117
  log "Can't kill #{pid}: #{e}"
113
118
  end
114
119
  end
120
+
121
+ # Metadata for an exited listener worker.
122
+ class WorkerSummary
123
+ attr_reader :alive_time_sec, :process_status
124
+
125
+ def initialize(alive_time_sec:, process_status:)
126
+ @alive_time_sec = alive_time_sec
127
+ @process_status = process_status
128
+ end
129
+ end
115
130
  end
@@ -0,0 +1,5 @@
1
+ after_exit do
2
+ raise "boom"
3
+ end
4
+
5
+ worker "test"
@@ -0,0 +1,37 @@
1
+ require "spec_helper"
2
+ require "timeout"
3
+
4
+ describe "quit-and-wait gracefully stops resqued" do
5
+ include ResquedIntegrationHelpers
6
+
7
+ it do
8
+ pidfile = File.join(SPEC_TEMPDIR, "graceful.pid")
9
+
10
+ start_resqued(pidfile: pidfile)
11
+ expect_running listener: "listener #1"
12
+
13
+ # Make sure the process gets cleaned up.
14
+ Thread.new do
15
+ begin
16
+ loop do
17
+ Process.wait(@pid)
18
+ end
19
+ rescue Errno::ECHILD
20
+ # ok!
21
+ end
22
+ end
23
+
24
+ Timeout.timeout(15) do
25
+ expect(system(resqued_path, "quit-and-wait", pidfile, "--grace-period", "10")).to be true
26
+ end
27
+
28
+ expect_not_running
29
+ end
30
+
31
+ after do
32
+ begin
33
+ Process.kill(:QUIT, @pid) if @pid
34
+ rescue Errno::ESRCH
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ require "spec_helper"
2
+ require "resqued/worker"
3
+ require "resqued/config/after_exit"
4
+
5
+ describe do
6
+ before { evaluator.apply(config) }
7
+
8
+ context "after_exit" do
9
+ # Run the after_exit block.
10
+ #
11
+ # after_exit do |worker_summary|
12
+ # puts "#{worker_summary.alive_time_sec}"
13
+ # end
14
+ #
15
+ # ignore calls to any other top-level method.
16
+
17
+ let(:config) { <<-END_CONFIG }
18
+ worker('one')
19
+ worker_pool(1)
20
+ queue('*')
21
+
22
+ after_exit do |worker_summary|
23
+ $after_exit_called = true
24
+ $worker_alive_time_sec = worker_summary.alive_time_sec
25
+ $exit_status = worker_summary.process_status.exitstatus
26
+ end
27
+ END_CONFIG
28
+
29
+ let(:evaluator) {
30
+ # In ruby versions < 3, Process::Status evaluates $? regardless of what status is returned by
31
+ # exitcode for a status created with Process::Status.allocate
32
+ fork do
33
+ exit!
34
+ end
35
+ _, status = Process.waitpid2
36
+ Resqued::Config::AfterExit.new(worker_summary: Resqued::WorkerSummary.new(alive_time_sec: 1, process_status: status))
37
+ }
38
+
39
+ it { expect($after_exit_called).to eq(true) }
40
+ it { expect($worker_alive_time_sec > 0).to eq(true) }
41
+ it { expect($exit_status).to eq(0) }
42
+ end
43
+ end
44
+
45
+ class FakeResqueWorker
46
+ attr_accessor :token
47
+ end
@@ -9,6 +9,7 @@ describe Resqued::TestCase do
9
9
  it { expect { test_case.assert_resqued "spec/fixtures/test_case_environment.rb", "spec/fixtures/test_case_clean.rb" }.not_to raise_error }
10
10
  it { expect { test_case.assert_resqued "spec/fixtures/test_case_environment.rb", "spec/fixtures/test_case_before_fork_raises.rb" }.to raise_error(RuntimeError) }
11
11
  it { expect { test_case.assert_resqued "spec/fixtures/test_case_environment.rb", "spec/fixtures/test_case_after_fork_raises.rb" }.to raise_error(RuntimeError) }
12
+ it { expect { test_case.assert_resqued "spec/fixtures/test_case_environment.rb", "spec/fixtures/test_case_after_exit_raises.rb" }.to raise_error(RuntimeError) }
12
13
  it { expect { test_case.assert_resqued "spec/fixtures/test_case_environment.rb", "spec/fixtures/test_case_no_workers.rb" }.not_to raise_error }
13
14
  end
14
15
  end
@@ -1,20 +1,25 @@
1
1
  module ResquedIntegrationHelpers
2
2
  include ResquedPath
3
3
 
4
- def start_resqued(config: "", debug: false)
5
- # Don't configure any workers. That way, we don't need to have redis running.
4
+ def start_resqued(config: "", debug: false, pidfile: nil)
5
+ # Don't configure any workers by default. That way, we don't need to have
6
+ # redis running.
6
7
  config_path = File.join(SPEC_TEMPDIR, "config.rb")
7
8
  File.write(config_path, config)
8
9
 
9
- @pid =
10
- if debug
11
- spawn resqued_path, config_path
12
- else
13
- logfile = File.join(SPEC_TEMPDIR, "resqued.log")
14
- File.write(logfile, "") # truncate it
10
+ cmd = [resqued_path]
11
+ if pidfile
12
+ cmd += ["--pidfile", pidfile]
13
+ end
14
+ unless debug
15
+ logfile = File.join(SPEC_TEMPDIR, "resqued.log")
16
+ File.write(logfile, "") # truncate it
17
+ cmd += ["--logfile", logfile]
18
+ end
19
+ cmd += [config_path]
20
+
21
+ @pid = spawn(*cmd)
15
22
 
16
- spawn resqued_path, "--logfile", logfile, config_path
17
- end
18
23
  sleep 1.0
19
24
  end
20
25
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resqued
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.1
4
+ version: 0.12.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Burke
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-23 00:00:00.000000000 Z
11
+ date: 2022-11-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: kgio
@@ -111,6 +111,7 @@ files:
111
111
  - lib/resqued.rb
112
112
  - lib/resqued/backoff.rb
113
113
  - lib/resqued/config.rb
114
+ - lib/resqued/config/after_exit.rb
114
115
  - lib/resqued/config/after_fork.rb
115
116
  - lib/resqued/config/base.rb
116
117
  - lib/resqued/config/before_fork.rb
@@ -127,11 +128,13 @@ files:
127
128
  - lib/resqued/master_state.rb
128
129
  - lib/resqued/pidfile.rb
129
130
  - lib/resqued/procline_version.rb
131
+ - lib/resqued/quit-and-wait.rb
130
132
  - lib/resqued/runtime_info.rb
131
133
  - lib/resqued/sleepy.rb
132
134
  - lib/resqued/test_case.rb
133
135
  - lib/resqued/version.rb
134
136
  - lib/resqued/worker.rb
137
+ - spec/fixtures/test_case_after_exit_raises.rb
135
138
  - spec/fixtures/test_case_after_fork_raises.rb
136
139
  - spec/fixtures/test_case_before_fork_raises.rb
137
140
  - spec/fixtures/test_case_clean.rb
@@ -139,8 +142,10 @@ files:
139
142
  - spec/fixtures/test_case_no_workers.rb
140
143
  - spec/integration/listener_still_starting_spec.rb
141
144
  - spec/integration/master_inherits_child_spec.rb
145
+ - spec/integration/quit_and_wait_spec.rb
142
146
  - spec/integration/restart_spec.rb
143
147
  - spec/resqued/backoff_spec.rb
148
+ - spec/resqued/config/exit_event_spec.rb
144
149
  - spec/resqued/config/fork_event_spec.rb
145
150
  - spec/resqued/config/worker_spec.rb
146
151
  - spec/resqued/config_spec.rb
@@ -156,7 +161,7 @@ homepage: https://github.com/spraints/resqued
156
161
  licenses:
157
162
  - MIT
158
163
  metadata: {}
159
- post_install_message:
164
+ post_install_message:
160
165
  rdoc_options: []
161
166
  require_paths:
162
167
  - lib
@@ -171,28 +176,31 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
176
  - !ruby/object:Gem::Version
172
177
  version: '0'
173
178
  requirements: []
174
- rubygems_version: 3.2.15
175
- signing_key:
179
+ rubygems_version: 3.1.6
180
+ signing_key:
176
181
  specification_version: 4
177
182
  summary: Daemon of resque workers
178
183
  test_files:
179
- - spec/fixtures/test_case_after_fork_raises.rb
180
- - spec/fixtures/test_case_before_fork_raises.rb
184
+ - spec/spec_helper.rb
185
+ - spec/integration/restart_spec.rb
186
+ - spec/integration/quit_and_wait_spec.rb
187
+ - spec/integration/listener_still_starting_spec.rb
188
+ - spec/integration/master_inherits_child_spec.rb
189
+ - spec/support/resqued_integration_helpers.rb
190
+ - spec/support/custom_matchers.rb
191
+ - spec/support/resqued_path.rb
192
+ - spec/support/extra-child-shim
181
193
  - spec/fixtures/test_case_clean.rb
194
+ - spec/fixtures/test_case_before_fork_raises.rb
182
195
  - spec/fixtures/test_case_environment.rb
183
196
  - spec/fixtures/test_case_no_workers.rb
184
- - spec/integration/listener_still_starting_spec.rb
185
- - spec/integration/master_inherits_child_spec.rb
186
- - spec/integration/restart_spec.rb
187
- - spec/resqued/backoff_spec.rb
197
+ - spec/fixtures/test_case_after_fork_raises.rb
198
+ - spec/fixtures/test_case_after_exit_raises.rb
199
+ - spec/resqued/config_spec.rb
188
200
  - spec/resqued/config/fork_event_spec.rb
189
201
  - spec/resqued/config/worker_spec.rb
190
- - spec/resqued/config_spec.rb
202
+ - spec/resqued/config/exit_event_spec.rb
191
203
  - spec/resqued/sleepy_spec.rb
192
- - spec/resqued/start_ctx_spec.rb
193
204
  - spec/resqued/test_case_spec.rb
194
- - spec/spec_helper.rb
195
- - spec/support/custom_matchers.rb
196
- - spec/support/extra-child-shim
197
- - spec/support/resqued_integration_helpers.rb
198
- - spec/support/resqued_path.rb
205
+ - spec/resqued/start_ctx_spec.rb
206
+ - spec/resqued/backoff_spec.rb