resqued 0.9.0 → 0.11.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.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGES.md +23 -0
  3. data/docs/signals.md +4 -0
  4. data/exe/resqued +41 -22
  5. data/lib/resqued.rb +5 -5
  6. data/lib/resqued/config.rb +7 -7
  7. data/lib/resqued/config/after_fork.rb +1 -1
  8. data/lib/resqued/config/base.rb +1 -1
  9. data/lib/resqued/config/before_fork.rb +1 -1
  10. data/lib/resqued/config/worker.rb +13 -13
  11. data/lib/resqued/daemon.rb +1 -0
  12. data/lib/resqued/exec_on_hup.rb +43 -0
  13. data/lib/resqued/listener.rb +51 -48
  14. data/lib/resqued/listener_pool.rb +97 -0
  15. data/lib/resqued/listener_proxy.rb +40 -31
  16. data/lib/resqued/listener_state.rb +8 -0
  17. data/lib/resqued/logging.rb +15 -8
  18. data/lib/resqued/master.rb +94 -98
  19. data/lib/resqued/master_state.rb +73 -0
  20. data/lib/resqued/procline_version.rb +2 -2
  21. data/lib/resqued/sleepy.rb +6 -4
  22. data/lib/resqued/test_case.rb +3 -3
  23. data/lib/resqued/version.rb +1 -1
  24. data/lib/resqued/worker.rb +21 -14
  25. data/spec/fixtures/test_case_after_fork_raises.rb +5 -2
  26. data/spec/fixtures/test_case_before_fork_raises.rb +4 -1
  27. data/spec/fixtures/test_case_environment.rb +3 -1
  28. data/spec/integration/listener_still_starting_spec.rb +24 -0
  29. data/spec/integration/master_inherits_child_spec.rb +85 -0
  30. data/spec/integration/restart_spec.rb +21 -0
  31. data/spec/resqued/backoff_spec.rb +27 -27
  32. data/spec/resqued/config/fork_event_spec.rb +8 -8
  33. data/spec/resqued/config/worker_spec.rb +68 -55
  34. data/spec/resqued/config_spec.rb +6 -6
  35. data/spec/resqued/sleepy_spec.rb +10 -11
  36. data/spec/resqued/test_case_spec.rb +7 -7
  37. data/spec/spec_helper.rb +6 -1
  38. data/spec/support/custom_matchers.rb +10 -2
  39. data/spec/support/extra-child-shim +6 -0
  40. data/spec/support/resqued_integration_helpers.rb +50 -0
  41. data/spec/support/resqued_path.rb +11 -0
  42. metadata +57 -36
  43. data/exe/resqued-listener +0 -6
@@ -1,12 +1,12 @@
1
- require 'spec_helper'
2
- require 'resqued/config/after_fork'
3
- require 'resqued/config/before_fork'
4
- require 'resqued/runtime_info'
1
+ require "spec_helper"
2
+ require "resqued/config/after_fork"
3
+ require "resqued/config/before_fork"
4
+ require "resqued/runtime_info"
5
5
 
6
6
  describe do
7
7
  before { evaluator.apply(config) }
8
8
 
9
- context 'after_fork' do
9
+ context "after_fork" do
10
10
  # Run the after_fork block.
11
11
  #
12
12
  # after_fork do |resque_worker|
@@ -26,13 +26,13 @@ describe do
26
26
  end
27
27
  END_CONFIG
28
28
 
29
- let(:evaluator) { Resqued::Config::AfterFork.new(:worker => worker) }
29
+ let(:evaluator) { Resqued::Config::AfterFork.new(worker: worker) }
30
30
  let(:worker) { FakeResqueWorker.new }
31
31
 
32
32
  it { expect(worker.token).to eq(:called) }
33
33
  end
34
34
 
35
- context 'before_fork' do
35
+ context "before_fork" do
36
36
  # Run the before_fork block.
37
37
  #
38
38
  # before_fork do
@@ -56,7 +56,7 @@ describe do
56
56
  end
57
57
  END_CONFIG
58
58
 
59
- let(:evaluator) { $before_fork_called = false ; Resqued::Config::BeforeFork.new(:resqued => resqued) }
59
+ let(:evaluator) { $before_fork_called = false; Resqued::Config::BeforeFork.new(resqued: resqued) }
60
60
  let(:resqued) { Resqued::RuntimeInfo.new }
61
61
 
62
62
  it { expect($before_fork_called).to eq(true) }
@@ -1,5 +1,5 @@
1
- require 'spec_helper'
2
- require 'resqued/config/worker'
1
+ require "spec_helper"
2
+ require "resqued/config/worker"
3
3
 
4
4
  describe Resqued::Config::Worker do
5
5
  # Create a bunch of Resqued::Worker objects from
@@ -15,7 +15,7 @@ describe Resqued::Config::Worker do
15
15
  #
16
16
  # ignore calls to any other top-level method.
17
17
 
18
- let(:evaluator) { described_class.new(:worker_class => FakeWorker) }
18
+ let(:evaluator) { described_class.new(worker_class: FakeWorker) }
19
19
  let(:result) { evaluator.apply(config) }
20
20
  module FakeWorker
21
21
  def self.new(options)
@@ -23,7 +23,7 @@ describe Resqued::Config::Worker do
23
23
  end
24
24
  end
25
25
 
26
- context 'individual' do
26
+ context "individual" do
27
27
  let(:config) { <<-END_CONFIG }
28
28
  before_fork { }
29
29
  after_fork { }
@@ -35,25 +35,38 @@ describe Resqued::Config::Worker do
35
35
  after_fork { } # So that we don't rely on `workers`'s result falling through.
36
36
  END_CONFIG
37
37
  it { expect(result.size).to eq(6) }
38
- it { expect(result[0]).to eq(:queues => ['a']) }
39
- it { expect(result[1]).to eq(:queues => ['a']) }
40
- it { expect(result[2]).to eq(:queues => ['b']) }
41
- it { expect(result[3]).to eq(:queues => ['c', 'd']) }
42
- it { expect(result[4]).to eq(:queues => ['d', 'c'], :interval => 3) }
43
- it { expect(result[5]).to eq(:queues => ['*']) }
38
+ it { expect(result[0]).to eq(queues: ["a"]) }
39
+ it { expect(result[1]).to eq(queues: ["a"]) }
40
+ it { expect(result[2]).to eq(queues: ["b"]) }
41
+ it { expect(result[3]).to eq(queues: ["c", "d"]) }
42
+ it { expect(result[4]).to eq(queues: ["d", "c"], interval: 3) }
43
+ it { expect(result[5]).to eq(queues: ["*"]) }
44
44
  end
45
45
 
46
- context 'concise pool' do
46
+ context "concise pool" do
47
47
  let(:config) { <<-END_CONFIG }
48
48
  worker_pool 2, 'a', 'b', 'c', :interval => 1
49
49
  END_CONFIG
50
- it { expect(result).to eq([
51
- { :queues => ['a', 'b', 'c'], :interval => 1 },
52
- { :queues => ['a', 'b', 'c'], :interval => 1 },
53
- ]) }
50
+ it do
51
+ expect(result).to eq([
52
+ { queues: ["a", "b", "c"], interval: 1 },
53
+ { queues: ["a", "b", "c"], interval: 1 },
54
+ ])
55
+ end
54
56
  end
55
57
 
56
- context 'pool (hash for concurrency)' do
58
+ context "small pool with percent" do
59
+ let(:config) { <<-END_CONFIG }
60
+ worker_pool 2
61
+ queue "a"
62
+ queue "b", :percent => 45
63
+ END_CONFIG
64
+ it { expect(result.size).to eq(2) }
65
+ it { expect(result[0]).to eq(queues: ["a", "b"]) }
66
+ it { expect(result[1]).to eq(queues: ["a"]) }
67
+ end
68
+
69
+ context "pool (hash for concurrency)" do
57
70
  let(:config) { <<-END_CONFIG }
58
71
  before_fork { }
59
72
  after_fork { }
@@ -64,15 +77,15 @@ describe Resqued::Config::Worker do
64
77
  after_fork { } # So that we don't rely on `worker_pool`'s result falling through.
65
78
  END_CONFIG
66
79
  it { expect(result.size).to eq(20) }
67
- it { expect(result[0]).to eq(:queues => ['a', 'b1', 'b2', 'c'], :interval => 1) }
68
- it { expect(result[3]).to eq(:queues => ['a', 'b1', 'b2', 'c'], :interval => 1) }
69
- it { expect(result[4]).to eq(:queues => ['b1', 'b2', 'c'], :interval => 1) }
70
- it { expect(result[9]).to eq(:queues => ['b1', 'b2', 'c'], :interval => 1) }
71
- it { expect(result[10]).to eq(:queues => ['c'], :interval => 1) }
72
- it { expect(result[19]).to eq(:queues => ['c'], :interval => 1) }
80
+ it { expect(result[0]).to eq(queues: ["a", "b1", "b2", "c"], interval: 1) }
81
+ it { expect(result[3]).to eq(queues: ["a", "b1", "b2", "c"], interval: 1) }
82
+ it { expect(result[4]).to eq(queues: ["b1", "b2", "c"], interval: 1) }
83
+ it { expect(result[9]).to eq(queues: ["b1", "b2", "c"], interval: 1) }
84
+ it { expect(result[10]).to eq(queues: ["c"], interval: 1) }
85
+ it { expect(result[19]).to eq(queues: ["c"], interval: 1) }
73
86
  end
74
87
 
75
- context 'pool, with implied queue' do
88
+ context "pool, with implied queue" do
76
89
  let(:config) { <<-END_CONFIG }
77
90
  before_fork { }
78
91
  after_fork { }
@@ -80,11 +93,11 @@ describe Resqued::Config::Worker do
80
93
  after_fork { } # So that we don't rely on `worker_pool`'s result falling through.
81
94
  END_CONFIG
82
95
  it { expect(result.size).to eq(20) }
83
- it { expect(result[0]).to eq(:queues => ['*']) }
84
- it { expect(result[19]).to eq(:queues => ['*']) }
96
+ it { expect(result[0]).to eq(queues: ["*"]) }
97
+ it { expect(result[19]).to eq(queues: ["*"]) }
85
98
  end
86
99
 
87
- context 'pool, with fewer queues than workers' do
100
+ context "pool, with fewer queues than workers" do
88
101
  let(:config) { <<-END_CONFIG }
89
102
  before_fork { }
90
103
  after_fork { }
@@ -93,13 +106,13 @@ describe Resqued::Config::Worker do
93
106
  after_fork { } # So that we don't rely on `worker_pool`'s result falling through.
94
107
  END_CONFIG
95
108
  it { expect(result.size).to eq(20) }
96
- it { expect(result[0]).to eq(:queues => ['a']) }
97
- it { expect(result[9]).to eq(:queues => ['a']) }
98
- it { expect(result[10]).to eq(:queues => ['*']) }
99
- it { expect(result[19]).to eq(:queues => ['*']) }
109
+ it { expect(result[0]).to eq(queues: ["a"]) }
110
+ it { expect(result[9]).to eq(queues: ["a"]) }
111
+ it { expect(result[10]).to eq(queues: ["*"]) }
112
+ it { expect(result[19]).to eq(queues: ["*"]) }
100
113
  end
101
114
 
102
- context 'pool, with more queues than workers' do
115
+ context "pool, with more queues than workers" do
103
116
  let(:config) { <<-END_CONFIG }
104
117
  before_fork { }
105
118
  after_fork { }
@@ -110,48 +123,48 @@ describe Resqued::Config::Worker do
110
123
  it { expect(result.size).to eq(20) }
111
124
  end
112
125
 
113
- context 'pool, with shuffled queues' do
126
+ context "pool, with shuffled queues" do
114
127
  let(:config) { <<-END_CONFIG }
115
- worker_pool 20, :shuffle_queues => true
116
- queue 'a', :count => 10
117
- queue 'b', :count => 15
128
+ worker_pool 200, :shuffle_queues => true
129
+ queue 'a', :count => 100
130
+ queue 'b', :count => 150
118
131
  END_CONFIG
119
- it { expect(result.size).to eq(20) }
120
- it { (0..9).each { |i| expect(result[i][:queues].sort).to eq(['a', 'b']) } }
121
- it { (10..14).each { |i| expect(result[i][:queues]).to eq(['b']) } }
122
- it { (15..19).each { |i| expect(result[i][:queues]).to eq(['*']) } }
132
+ it { expect(result.size).to eq(200) }
133
+ it { (0..99).each { |i| expect(result[i][:queues].sort).to eq(["a", "b"]) } }
134
+ it { (100..149).each { |i| expect(result[i][:queues]).to eq(["b"]) } }
135
+ it { (150..199).each { |i| expect(result[i][:queues]).to eq(["*"]) } }
123
136
  it { result.each { |x| expect(x).not_to have_key(:shuffle_queues) } }
124
137
  it do
125
- shuffled_queues = result.take(10).map { |x| x[:queues] }
126
- expect(shuffled_queues.sort.uniq).to eq([ ['a','b'], ['b','a'] ]) # Some of the queues should be shuffled
138
+ shuffled_queues = result.take(100).map { |x| x[:queues] }
139
+ expect(shuffled_queues.sort.uniq).to eq([["a", "b"], ["b", "a"]]) # Some of the queues should be shuffled
127
140
  end
128
141
  end
129
142
 
130
- context 'multiple worker configs' do
143
+ context "multiple worker configs" do
131
144
  let(:config) { <<-END_CONFIG }
132
145
  worker 'one'
133
146
  worker 'two'
134
147
  worker_pool 2
135
148
  END_CONFIG
136
149
  it { expect(result.size).to eq(4) }
137
- it { expect(result[0]).to eq(:queues => ['one']) }
138
- it { expect(result[1]).to eq(:queues => ['two']) }
139
- it { expect(result[2]).to eq(:queues => ['*']) }
140
- it { expect(result[3]).to eq(:queues => ['*']) }
150
+ it { expect(result[0]).to eq(queues: ["one"]) }
151
+ it { expect(result[1]).to eq(queues: ["two"]) }
152
+ it { expect(result[2]).to eq(queues: ["*"]) }
153
+ it { expect(result[3]).to eq(queues: ["*"]) }
141
154
  end
142
155
 
143
- context 'worker factory' do
156
+ context "worker factory" do
144
157
  let(:config) { <<-END_CONFIG }
145
158
  worker_factory { |queues| queues }
146
159
  worker 'a'
147
160
  END_CONFIG
148
161
 
149
162
  it { expect(result.size).to eq(1) }
150
- it { expect(result[0].reject { |k, _| k == :worker_factory}).to eq(:queues => ['a']) }
151
- it { expect(result[0][:worker_factory].call(result[0][:queues])).to eq(['a']) }
163
+ it { expect(result[0].reject { |k, _| k == :worker_factory }).to eq(queues: ["a"]) }
164
+ it { expect(result[0][:worker_factory].call(result[0][:queues])).to eq(["a"]) }
152
165
  end
153
166
 
154
- context 'worker factory with pool' do
167
+ context "worker factory with pool" do
155
168
  let(:config) { <<-END_CONFIG }
156
169
  worker_factory { |queues| queues }
157
170
  worker_pool 1
@@ -159,15 +172,15 @@ describe Resqued::Config::Worker do
159
172
  END_CONFIG
160
173
 
161
174
  it { expect(result.size).to eq(1) }
162
- it { expect(result[0].reject { |k, _| k == :worker_factory}).to eq(:queues => ['a']) }
163
- it { expect(result[0][:worker_factory].call(result[0][:queues])).to eq(['a']) }
175
+ it { expect(result[0].reject { |k, _| k == :worker_factory }).to eq(queues: ["a"]) }
176
+ it { expect(result[0][:worker_factory].call(result[0][:queues])).to eq(["a"]) }
164
177
  end
165
178
 
166
- context 'with default options' do
167
- let(:evaluator) { described_class.new(:worker_class => FakeWorker, :config => 'something') }
179
+ context "with default options" do
180
+ let(:evaluator) { described_class.new(worker_class: FakeWorker, config: "something") }
168
181
  let(:config) { <<-END_CONFIG }
169
182
  worker 'a', :interval => 1
170
183
  END_CONFIG
171
- it { expect(result[0]).to eq(:queues => ['a'], :interval => 1, :config => 'something') }
184
+ it { expect(result[0]).to eq(queues: ["a"], interval: 1, config: "something") }
172
185
  end
173
186
  end
@@ -1,9 +1,9 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
- require 'fileutils'
4
- require 'tmpdir'
3
+ require "fileutils"
4
+ require "tmpdir"
5
5
 
6
- require 'resqued/config'
6
+ require "resqued/config"
7
7
 
8
8
  describe Resqued::Config do
9
9
  context do
@@ -19,8 +19,8 @@ describe Resqued::Config do
19
19
  end
20
20
  let(:config) { Resqued::Config.new([@config_file]) }
21
21
 
22
- it("can require_relative") { config.build_workers ; expect($test_val).to eq(:ok) }
23
- it("does not override require_relative in required files") { config.build_workers ; expect($other_test_val).to eq(:ok) }
22
+ it("can require_relative") { config.build_workers; expect($test_val).to eq(:ok) }
23
+ it("does not override require_relative in required files") { config.build_workers; expect($other_test_val).to eq(:ok) }
24
24
 
25
25
  def make_file(dir, relative_path, content)
26
26
  File.join(dir, relative_path).tap do |path|
@@ -1,34 +1,33 @@
1
- require 'spec_helper'
2
- require 'resqued/sleepy'
3
- require 'thread'
1
+ require "spec_helper"
2
+ require "resqued/sleepy"
4
3
 
5
4
  describe Resqued::Sleepy do
6
5
  include Resqued::Sleepy
7
6
 
8
- it 'sleeps' do
7
+ it "sleeps" do
9
8
  expect { yawn(0.2) }.to run_for(0.2)
10
9
  end
11
10
 
12
- it 'wakes on `awake`' do
13
- Thread.new { sleep 0.1 ; awake }
11
+ it "wakes on `awake`" do
12
+ Thread.new { sleep 0.1; awake }
14
13
  expect { yawn(2.0) }.to run_for(0.1)
15
14
  end
16
15
 
17
- it 'wakes on IO' do
16
+ it "wakes on IO" do
18
17
  rd, wr = IO.pipe
19
- Thread.new { sleep 0.1 ; wr.write('.') }
18
+ Thread.new { sleep 0.1; wr.write(".") }
20
19
  expect { yawn(2.0, rd) }.to run_for(0.1)
21
20
  end
22
21
 
23
- it 'does not sleep if duration is 0' do
22
+ it "does not sleep if duration is 0" do
24
23
  expect { yawn(-0.000001) }.to run_for(0.0)
25
24
  end
26
25
 
27
- it 'does not sleep if duration is negative' do
26
+ it "does not sleep if duration is negative" do
28
27
  expect { yawn(0) }.to run_for(0.0)
29
28
  end
30
29
 
31
- it 'sleeps if io is nil' do
30
+ it "sleeps if io is nil" do
32
31
  expect { yawn(0.5, nil) }.to run_for(0.5)
33
32
  end
34
33
  end
@@ -1,14 +1,14 @@
1
- require 'spec_helper'
2
- require 'resqued/test_case'
1
+ require "spec_helper"
2
+ require "resqued/test_case"
3
3
 
4
4
  describe Resqued::TestCase do
5
5
  let(:test_case) { Object.new.extend(the_module) }
6
6
 
7
- context 'LoadConfig' do
7
+ context "LoadConfig" do
8
8
  let(:the_module) { described_class::LoadConfig }
9
- it { expect { test_case.assert_resqued 'spec/fixtures/test_case_environment.rb', 'spec/fixtures/test_case_clean.rb' }.not_to raise_error }
10
- it { expect { test_case.assert_resqued 'spec/fixtures/test_case_environment.rb', 'spec/fixtures/test_case_before_fork_raises.rb' }.to raise_error }
11
- it { expect { test_case.assert_resqued 'spec/fixtures/test_case_environment.rb', 'spec/fixtures/test_case_after_fork_raises.rb' }.to raise_error }
12
- it { expect { test_case.assert_resqued 'spec/fixtures/test_case_environment.rb', 'spec/fixtures/test_case_no_workers.rb' }.not_to raise_error }
9
+ it { expect { test_case.assert_resqued "spec/fixtures/test_case_environment.rb", "spec/fixtures/test_case_clean.rb" }.not_to raise_error }
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
+ 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_no_workers.rb" }.not_to raise_error }
13
13
  end
14
14
  end
@@ -1 +1,6 @@
1
- require 'support/custom_matchers'
1
+ require "support/custom_matchers"
2
+ require "support/resqued_path"
3
+ require "support/resqued_integration_helpers"
4
+
5
+ SPEC_TEMPDIR = File.expand_path("../tmp/spec", File.dirname(__FILE__))
6
+ FileUtils.mkpath(SPEC_TEMPDIR)
@@ -8,7 +8,11 @@ module CustomMatchers
8
8
  class RunFor
9
9
  def initialize(expected_duration)
10
10
  @expected_duration = expected_duration
11
- @epsilon = 0.01
11
+ @epsilon = 0.01
12
+ end
13
+
14
+ def supports_block_expectations?
15
+ true
12
16
  end
13
17
 
14
18
  def within(epsilon)
@@ -24,9 +28,13 @@ module CustomMatchers
24
28
  @epsilon >= diff
25
29
  end
26
30
 
27
- def failure_message_for_should
31
+ def failure_message
28
32
  "Expected block to run for #{@expected_duration} +/-#{@epsilon} seconds, but it ran for #{@actual_duration} seconds."
29
33
  end
34
+
35
+ def failure_message_when_negated
36
+ "Expected block not to run for #{@expected_duration} +/-#{@epsilon} seconds, but it ran for #{@actual_duration} seconds."
37
+ end
30
38
  end
31
39
  end
32
40
 
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ extra_child_pid = fork { sleep(100); exit! }
4
+ File.write(ENV["EXTRA_CHILD_PIDFILE"], extra_child_pid.to_s)
5
+
6
+ exec(*ARGV)
@@ -0,0 +1,50 @@
1
+ module ResquedIntegrationHelpers
2
+ include ResquedPath
3
+
4
+ def start_resqued(config: "", debug: false)
5
+ # Don't configure any workers. That way, we don't need to have redis running.
6
+ config_path = File.join(SPEC_TEMPDIR, "config.rb")
7
+ File.write(config_path, config)
8
+
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
15
+
16
+ spawn resqued_path, "--logfile", logfile, config_path
17
+ end
18
+ sleep 1.0
19
+ end
20
+
21
+ def restart_resqued
22
+ Process.kill(:HUP, @pid)
23
+ sleep 1.0
24
+ end
25
+
26
+ def expect_running(listener:)
27
+ processes = list_processes
28
+ expect(processes).to include(is_resqued_master)
29
+ listeners = processes.select { |p| p[:ppid] == @pid }.map { |p| p[:args] }
30
+ expect(listeners).to all(match(/#{listener}/)).and(satisfy { |l| l.size == 1 })
31
+ end
32
+
33
+ def expect_not_running
34
+ processes = list_processes
35
+ expect(processes).not_to include(is_resqued_master)
36
+ end
37
+
38
+ def stop_resqued
39
+ Process.kill(:TERM, @pid)
40
+ sleep 1.0
41
+ end
42
+
43
+ def list_processes
44
+ `ps axo pid,ppid,args`.lines.map { |line| pid, ppid, args = line.strip.split(/\s+/, 3); { pid: pid.to_i, ppid: ppid.to_i, args: args } }
45
+ end
46
+
47
+ def is_resqued_master
48
+ satisfy { |p| p[:pid] == @pid && p[:args] =~ /resqued-/ }
49
+ end
50
+ end