perfectqueue 0.8.44.1 → 0.8.45
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 +4 -4
- data/ChangeLog +5 -2
- data/lib/perfectqueue/application/decider.rb +2 -2
- data/lib/perfectqueue/application/router.rb +1 -1
- data/lib/perfectqueue/backend/rdb_compat.rb +40 -58
- data/lib/perfectqueue/engine.rb +2 -5
- data/lib/perfectqueue/multiprocess/thread_processor.rb +1 -1
- data/lib/perfectqueue/task.rb +2 -1
- data/lib/perfectqueue/task_metadata.rb +2 -4
- data/lib/perfectqueue/version.rb +1 -1
- data/perfectqueue.gemspec +1 -2
- data/spec/application/base_spec.rb +81 -0
- data/spec/application/decider_spec.rb +56 -0
- data/spec/application/dispatch_spec.rb +22 -0
- data/spec/application/router_spec.rb +48 -0
- data/spec/backend_spec.rb +9 -0
- data/spec/blocking_flag_spec.rb +103 -0
- data/spec/client_spec.rb +28 -0
- data/spec/daemons_logger_spec.rb +35 -0
- data/spec/engine_spec.rb +159 -0
- data/spec/multiprocess/child_process_monitor_spec.rb +300 -0
- data/spec/multiprocess/child_process_spec.rb +160 -0
- data/spec/multiprocess/fork_processor_spec.rb +170 -0
- data/spec/multiprocess/thread_processor_spec.rb +52 -0
- data/spec/queue_spec.rb +73 -68
- data/spec/rdb_compat_backend_spec.rb +481 -19
- data/spec/runner_spec.rb +32 -0
- data/spec/signal_thread_spec.rb +43 -0
- data/spec/stress.rb +1 -1
- data/spec/supervisor_spec.rb +188 -33
- data/spec/task_metadata_spec.rb +69 -0
- data/spec/task_monitor_spec.rb +42 -0
- data/spec/task_spec.rb +70 -0
- metadata +40 -19
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PerfectQueue::Application::Dispatch do
|
4
|
+
describe '.new' do
|
5
|
+
before do
|
6
|
+
router = Application::Dispatch.router
|
7
|
+
handler = double('handler')
|
8
|
+
allow(handler).to receive(:new).and_return(nil)
|
9
|
+
router.add(/\Afoo\z/, handler, nil)
|
10
|
+
end
|
11
|
+
it 'returns a PerfectQueue::Application::Dispatch' do
|
12
|
+
task = double('task', type: 'foo')
|
13
|
+
dispatch = Application::Dispatch.new(task)
|
14
|
+
expect(dispatch).to be_an_instance_of(Application::Dispatch)
|
15
|
+
end
|
16
|
+
it 'raises RuntimeError if the task type doesn\'t match' do
|
17
|
+
task = double('task', type: 'bar')
|
18
|
+
expect(task).to receive(:retry!).exactly(:once)
|
19
|
+
expect{Application::Dispatch.new(task)}.to raise_error(RuntimeError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PerfectQueue::Application::Router do
|
4
|
+
describe '.new' do
|
5
|
+
it 'returns a PerfectQueue::Application::Router' do
|
6
|
+
router = Application::Router.new
|
7
|
+
expect(router).to be_an_instance_of(Application::Router)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#add' do
|
12
|
+
let (:router){ Application::Router.new }
|
13
|
+
let (:sym){ double('sym') }
|
14
|
+
it 'accepts Regexp' do
|
15
|
+
router.add(/\Afoo\z/, sym, double)
|
16
|
+
expect(router.patterns[0]).to eq([/\Afoo\z/, sym])
|
17
|
+
end
|
18
|
+
it 'accepts String' do
|
19
|
+
router.add('foo', sym, double)
|
20
|
+
expect(router.patterns[0]).to eq([/\Afoo\z/, sym])
|
21
|
+
end
|
22
|
+
it 'accepts Symbol' do
|
23
|
+
router.add(:foo, sym, double)
|
24
|
+
expect(router.patterns[0]).to eq([/\Afoo\z/, sym])
|
25
|
+
end
|
26
|
+
it 'raises for others' do
|
27
|
+
expect{router.add(nil, nil, nil)}.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#route' do
|
32
|
+
let (:router) do
|
33
|
+
rt = Application::Router.new
|
34
|
+
rt.add(/\Afoo\z/, :TestHandler, double)
|
35
|
+
rt
|
36
|
+
end
|
37
|
+
let (:handler){ double('handler') }
|
38
|
+
before do
|
39
|
+
Application::Router::TestHandler = handler
|
40
|
+
end
|
41
|
+
after do
|
42
|
+
Application::Router.__send__(:remove_const, :TestHandler)
|
43
|
+
end
|
44
|
+
it 'return related handler' do
|
45
|
+
expect(router.route('foo')).to eq(handler)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PerfectQueue::BlockingFlag do
|
4
|
+
describe '.new' do
|
5
|
+
it 'returns a BlockingFlag' do
|
6
|
+
flag = BlockingFlag.new
|
7
|
+
expect(flag).to be_an_instance_of(BlockingFlag)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#set!' do
|
12
|
+
let (:flag){ BlockingFlag.new }
|
13
|
+
it 'returns true if it was false' do
|
14
|
+
expect(flag.set?).to eq false
|
15
|
+
expect(flag.set!).to eq true
|
16
|
+
expect(flag.set?).to eq true
|
17
|
+
end
|
18
|
+
it 'returns false if it was already true' do
|
19
|
+
flag.set!
|
20
|
+
expect(flag.set?).to eq true
|
21
|
+
expect(flag.set!).to eq false
|
22
|
+
expect(flag.set?).to eq true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#reset!' do
|
27
|
+
let (:flag){ BlockingFlag.new }
|
28
|
+
it 'returns false if it was already false' do
|
29
|
+
expect(flag.set?).to eq false
|
30
|
+
expect(flag.reset!).to eq false
|
31
|
+
expect(flag.set?).to eq false
|
32
|
+
end
|
33
|
+
it 'returns false if it was true' do
|
34
|
+
flag.set!
|
35
|
+
expect(flag.set?).to eq true
|
36
|
+
expect(flag.reset!).to eq true
|
37
|
+
expect(flag.set?).to eq false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#set_region' do
|
42
|
+
let (:flag){ BlockingFlag.new }
|
43
|
+
it 'set in the block and reset it was set' do
|
44
|
+
flag.set!
|
45
|
+
flag.set_region do
|
46
|
+
expect(flag.set?).to eq true
|
47
|
+
end
|
48
|
+
expect(flag.set?).to eq false
|
49
|
+
end
|
50
|
+
it 'set in the block and reset if it was reset' do
|
51
|
+
flag.reset!
|
52
|
+
flag.set_region do
|
53
|
+
expect(flag.set?).to eq true
|
54
|
+
end
|
55
|
+
expect(flag.set?).to eq false
|
56
|
+
end
|
57
|
+
it 'set in the block and reset even if it raiess error' do
|
58
|
+
flag.set_region do
|
59
|
+
expect(flag.set?).to eq true
|
60
|
+
raise
|
61
|
+
end rescue nil
|
62
|
+
expect(flag.set?).to eq false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#reset_region' do
|
67
|
+
let (:flag){ BlockingFlag.new }
|
68
|
+
it 'reset in the block and set it was set' do
|
69
|
+
flag.set!
|
70
|
+
flag.reset_region do
|
71
|
+
expect(flag.set?).to eq false
|
72
|
+
end
|
73
|
+
expect(flag.set?).to eq true
|
74
|
+
end
|
75
|
+
it 'reset in the block and set if it was reset' do
|
76
|
+
flag.reset!
|
77
|
+
flag.reset_region do
|
78
|
+
expect(flag.set?).to eq false
|
79
|
+
end
|
80
|
+
expect(flag.set?).to eq true
|
81
|
+
end
|
82
|
+
it 'set in the block and reset even if it raiess error' do
|
83
|
+
flag.reset_region do
|
84
|
+
expect(flag.set?).to eq false
|
85
|
+
raise
|
86
|
+
end rescue nil
|
87
|
+
expect(flag.set?).to eq true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#wait' do
|
92
|
+
let (:flag){ BlockingFlag.new }
|
93
|
+
it 'wait until a thread set/reset the flag' do
|
94
|
+
th1 = Thread.start do
|
95
|
+
flag.wait(5)
|
96
|
+
expect(flag.set?).to eq true
|
97
|
+
end
|
98
|
+
Thread.pass until th1.stop?
|
99
|
+
flag.set!
|
100
|
+
th1.join(2)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PerfectQueue::Client do
|
4
|
+
describe '#preempt' do
|
5
|
+
it '(key)' do
|
6
|
+
backend = double('backend')
|
7
|
+
alive_time = double('alive_time')
|
8
|
+
object_double('PerfectQueue::Backend', new_backend: backend).as_stubbed_const
|
9
|
+
client = Client.new({alive_time: alive_time})
|
10
|
+
ret = double('ret')
|
11
|
+
key = double('key')
|
12
|
+
expect(backend).to receive(:preempt).with(key, alive_time, {}).and_return(ret)
|
13
|
+
expect(client.preempt(key)).to eq(ret)
|
14
|
+
end
|
15
|
+
|
16
|
+
it '(key, options)' do
|
17
|
+
backend = double('backend')
|
18
|
+
alive_time = double('alive_time')
|
19
|
+
object_double('PerfectQueue::Backend', new_backend: backend).as_stubbed_const
|
20
|
+
client = Client.new({alive_time: alive_time})
|
21
|
+
ret = double('ret')
|
22
|
+
key = double('key')
|
23
|
+
options = {alive_time: alive_time}
|
24
|
+
expect(backend).to receive(:preempt).with(key, alive_time, options).and_return(ret)
|
25
|
+
expect(client.preempt(key, options)).to eq(ret)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DaemonsLogger do
|
4
|
+
context 'new' do
|
5
|
+
it 'creates logger with path string' do
|
6
|
+
Tempfile.open('daemons_logger') do |io|
|
7
|
+
logger = DaemonsLogger.new(io.path)
|
8
|
+
expect(logger.class).to eq(DaemonsLogger)
|
9
|
+
logger.close
|
10
|
+
logger.close
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'creates logger with IO object' do
|
15
|
+
io = double('dummy io', write: nil, close: nil)
|
16
|
+
expect(DaemonsLogger.new(io).class).to eq(DaemonsLogger)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context 'reopen' do
|
21
|
+
it 'reopens IOs' do
|
22
|
+
Tempfile.open('daemons_logger') do |f|
|
23
|
+
logger = DaemonsLogger.new(f.path)
|
24
|
+
expect(STDOUT).to receive(:reopen).twice
|
25
|
+
logger.hook_stdout!
|
26
|
+
expect(STDERR).to receive(:reopen).twice
|
27
|
+
logger.hook_stderr!
|
28
|
+
logger.reopen
|
29
|
+
io = logger.instance_variable_get(:@log)
|
30
|
+
allow(logger).to receive(:reopen!) { raise }
|
31
|
+
logger.reopen
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/spec/engine_spec.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PerfectQueue::Engine do
|
4
|
+
let (:logger){ double('logger').as_null_object }
|
5
|
+
let (:engine) do
|
6
|
+
config = {logger: logger, processor_type: :thread}
|
7
|
+
Engine.new(double, config)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.new' do
|
11
|
+
it 'returns an Engine with ForkProcessor for processor_type: nil' do
|
12
|
+
config = {logger: double('logger'), processor_type: nil}
|
13
|
+
engine = Engine.new(double, config)
|
14
|
+
expect(engine).to be_an_instance_of(Engine)
|
15
|
+
expect(engine.processors).to be_a(Array)
|
16
|
+
expect(engine.processors.size).to eq(1)
|
17
|
+
expect(engine.processors[0]).to be_an_instance_of(Multiprocess::ForkProcessor)
|
18
|
+
end
|
19
|
+
it 'returns an Engine with ForkProcessor for processor_type: :process' do
|
20
|
+
config = {logger: double('logger'), processor_type: :process}
|
21
|
+
engine = Engine.new(double, config)
|
22
|
+
expect(engine).to be_an_instance_of(Engine)
|
23
|
+
expect(engine.processors).to be_a(Array)
|
24
|
+
expect(engine.processors.size).to eq(1)
|
25
|
+
expect(engine.processors[0]).to be_an_instance_of(Multiprocess::ForkProcessor)
|
26
|
+
end
|
27
|
+
it 'returns an Engine with ThreadProcessor for processor_type: :thread' do
|
28
|
+
config = {logger: double('logger'), processor_type: :thread}
|
29
|
+
engine = Engine.new(double, config)
|
30
|
+
expect(engine).to be_an_instance_of(Engine)
|
31
|
+
expect(engine.processors).to be_a(Array)
|
32
|
+
expect(engine.processors.size).to eq(1)
|
33
|
+
expect(engine.processors[0]).to be_an_instance_of(Multiprocess::ThreadProcessor)
|
34
|
+
end
|
35
|
+
it 'returns an Engine with ForkProcessor for processor_type: :invalid' do
|
36
|
+
config = {logger: double('logger'), processor_type: :invalid}
|
37
|
+
expect{Engine.new(double, config)}.to raise_error(ConfigError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#run' do
|
42
|
+
before do
|
43
|
+
processor_klass = (PerfectQueue::Multiprocess::ThreadProcessor)
|
44
|
+
allow(processor_klass).to receive(:new) do
|
45
|
+
processor = double('processor')
|
46
|
+
expect(processor).to receive(:keepalive).exactly(:twice)
|
47
|
+
expect(processor).to receive(:stop)
|
48
|
+
expect(processor).to receive(:join)
|
49
|
+
processor
|
50
|
+
end
|
51
|
+
expect(engine).to receive(:sleep).with(0...2)
|
52
|
+
end
|
53
|
+
it 'runs until stopped' do
|
54
|
+
Thread.start{sleep 1; engine.stop(true) }
|
55
|
+
engine.run
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#restart' do
|
60
|
+
context 'previous num_processors is small' do
|
61
|
+
it 'increase the number of processors' do
|
62
|
+
config = {logger: logger, processor_type: :thread}
|
63
|
+
engine = Engine.new(double, config)
|
64
|
+
expect(engine.processors.size).to eq(1)
|
65
|
+
config[:processors] = 3
|
66
|
+
expect(engine.restart(true, config)).to eq(engine)
|
67
|
+
expect(engine.processors.size).to eq(3)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
context 'previous num_processors is large' do
|
71
|
+
it 'decrease the number of processors' do
|
72
|
+
config = {logger: logger, processor_type: :thread, processors: 2}
|
73
|
+
engine = Engine.new(double, config)
|
74
|
+
config[:processors] = 1
|
75
|
+
expect(engine.restart(true, config)).to eq(engine)
|
76
|
+
expect(engine.processors.size).to eq(1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
context 'same number of processors' do
|
80
|
+
it 'decrease the number of processors' do
|
81
|
+
config = {logger: logger, processor_type: :thread}
|
82
|
+
engine = Engine.new(double, config)
|
83
|
+
expect(engine.restart(true, config)).to eq(engine)
|
84
|
+
expect(engine.processors.size).to eq(1)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#stop' do
|
90
|
+
let (:immediate){ double('immediate') }
|
91
|
+
before do
|
92
|
+
engine.processors.each do |c|
|
93
|
+
expect(c).to receive(:stop).with(immediate)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
it '@processors.each {|c| c.stop(immediate) }' do
|
97
|
+
expect(engine.stop(immediate)).to eq(engine)
|
98
|
+
expect(engine.instance_variable_get(:@finish_flag).set?).to be true
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#join' do
|
103
|
+
before do
|
104
|
+
engine.processors.each do |c|
|
105
|
+
expect(c).to receive(:join)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
it '@processors.each {|c| c.join }' do
|
109
|
+
expect(engine.join).to eq(engine)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '#shutdown' do
|
114
|
+
it 'calls stop and join' do
|
115
|
+
immediate = double('immediate')
|
116
|
+
expect(engine).to receive(:stop).with(immediate)
|
117
|
+
expect(engine).to receive(:join)
|
118
|
+
engine.shutdown(immediate)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe '#replace' do
|
123
|
+
context 'already replaced' do
|
124
|
+
before do
|
125
|
+
engine.instance_variable_set(:@replaced_pid, double)
|
126
|
+
end
|
127
|
+
it 'returns nil' do
|
128
|
+
expect(engine).not_to receive(:stop)
|
129
|
+
expect(engine.replace(double, double)).to be_nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
context 'not replaced yet' do
|
133
|
+
it 'calls spawn with [$0]+ARGV' do
|
134
|
+
immediate = double('immediate')
|
135
|
+
expect(engine).to receive(:stop).with(immediate)
|
136
|
+
expect(Process).to receive(:spawn).with(*([$0]+ARGV))
|
137
|
+
engine.replace(immediate)
|
138
|
+
end
|
139
|
+
it 'calls spawn with given command' do
|
140
|
+
immediate = double('immediate')
|
141
|
+
command = double('command')
|
142
|
+
expect(engine).to receive(:stop).with(immediate)
|
143
|
+
expect(Process).to receive(:spawn).with(command)
|
144
|
+
engine.replace(immediate, command)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe '#logrotated' do
|
150
|
+
before do
|
151
|
+
engine.processors.each do |c|
|
152
|
+
expect(c).to receive(:logrotated)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
it '@processors.each {|c| c.logrotated }' do
|
156
|
+
engine.logrotated
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PerfectQueue::Multiprocess::ChildProcessMonitor do
|
4
|
+
let (:rpipe){ double('rpipe') }
|
5
|
+
let (:last_heartbeat){ 42 }
|
6
|
+
let (:last_kill_time){ 42 }
|
7
|
+
let (:processor_id){ double('processor_id') }
|
8
|
+
let (:log){ double('log').as_null_object }
|
9
|
+
let (:cpm) {
|
10
|
+
cpm = Multiprocess::ChildProcessMonitor.new(log, processor_id, rpipe)
|
11
|
+
cpm.instance_variable_set(:@last_heartbeat, last_heartbeat)
|
12
|
+
cpm
|
13
|
+
}
|
14
|
+
let (:now){ 72 }
|
15
|
+
describe '.new' do
|
16
|
+
it 'returns a PerfectQueue::Multiprocess::ChildProcessMonitor' do
|
17
|
+
processor = Multiprocess::ChildProcessMonitor.new(log, processor_id, rpipe)
|
18
|
+
expect(processor).to be_an_instance_of(Multiprocess::ChildProcessMonitor)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#check_heartbeat' do
|
23
|
+
before do
|
24
|
+
allow(object_double('Time').as_stubbed_const).to \
|
25
|
+
receive_message_chain(:now, :to_i).and_return(now)
|
26
|
+
end
|
27
|
+
context 'rpipe returns value' do
|
28
|
+
before do
|
29
|
+
expect(rpipe).to receive(:read_nonblock)
|
30
|
+
end
|
31
|
+
it 'returns true' do
|
32
|
+
limit = double('limit')
|
33
|
+
expect(cpm.check_heartbeat(limit)).to be true
|
34
|
+
expect(cpm.instance_variable_get(:@last_heartbeat)).to eq(now)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
context 'rpipe.read_nonblock raises EINTR' do
|
38
|
+
before do
|
39
|
+
expect(rpipe).to receive(:read_nonblock).and_raise(Errno::EINTR)
|
40
|
+
end
|
41
|
+
it 'returns false if last_heartbeat is too old on interupt' do
|
42
|
+
expect(cpm.check_heartbeat(now-last_heartbeat-1)).to be false
|
43
|
+
expect(cpm.instance_variable_get(:@last_heartbeat)).to eq(last_heartbeat)
|
44
|
+
end
|
45
|
+
it 'returns true if last_heartbeat is enough new on interupt' do
|
46
|
+
expect(cpm.check_heartbeat(now-last_heartbeat)).to be true
|
47
|
+
expect(cpm.instance_variable_get(:@last_heartbeat)).to eq(last_heartbeat)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#start_killing' do
|
53
|
+
before do
|
54
|
+
allow(object_double('Time').as_stubbed_const).to \
|
55
|
+
receive_message_chain(:now, :to_i).and_return(now)
|
56
|
+
end
|
57
|
+
context 'initial state' do
|
58
|
+
it 'calls kill_children immediately if immediate: true' do
|
59
|
+
expect(cpm).to receive(:kill_children).with(now, nil).exactly(:once)
|
60
|
+
cpm.start_killing(true)
|
61
|
+
expect(cpm.instance_variable_get(:@kill_immediate)).to eq(true)
|
62
|
+
expect(cpm.instance_variable_get(:@last_kill_time)).to eq(now)
|
63
|
+
expect(cpm.instance_variable_get(:@kill_start_time)).to eq(now)
|
64
|
+
end
|
65
|
+
it 'sets @last_kill_time if immediate: true, delay!=0' do
|
66
|
+
delay = 3
|
67
|
+
expect(cpm).not_to receive(:kill_children)
|
68
|
+
cpm.start_killing(true, delay)
|
69
|
+
expect(cpm.instance_variable_get(:@kill_immediate)).to eq(true)
|
70
|
+
expect(cpm.instance_variable_get(:@last_kill_time)).to eq(now+delay)
|
71
|
+
expect(cpm.instance_variable_get(:@kill_start_time)).to eq(now+delay)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
context 'already killed immediately' do
|
75
|
+
before do
|
76
|
+
cpm.instance_variable_set(:@kill_immediate, true)
|
77
|
+
cpm.instance_variable_set(:@last_kill_time, now)
|
78
|
+
cpm.instance_variable_set(:@kill_start_time, now)
|
79
|
+
end
|
80
|
+
it 'returns without do anything if immediate: true' do
|
81
|
+
expect(cpm).not_to receive(:kill_children)
|
82
|
+
cpm.start_killing(true)
|
83
|
+
end
|
84
|
+
it 'returns without do anything if immediate: false' do
|
85
|
+
expect(cpm).not_to receive(:kill_children)
|
86
|
+
cpm.start_killing(false)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context 'already started killing' do
|
90
|
+
before do
|
91
|
+
cpm.instance_variable_set(:@kill_start_time, double)
|
92
|
+
end
|
93
|
+
it 'return with do nothing if immediate: false' do
|
94
|
+
cpm.start_killing(false, double)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#killing_status' do
|
100
|
+
context '@kill_start_time: nil' do
|
101
|
+
before { cpm.instance_variable_set(:@kill_start_time, nil) }
|
102
|
+
it 'returns nil' do
|
103
|
+
expect(cpm.killing_status).to be_nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
context '@kill_start_time: <time>' do
|
107
|
+
before { cpm.instance_variable_set(:@kill_start_time, double) }
|
108
|
+
context '@kill_immediate: true' do
|
109
|
+
before { cpm.instance_variable_set(:@kill_immediate, true) }
|
110
|
+
it 'returns nil' do
|
111
|
+
expect(cpm.killing_status).to be true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
context '@kill_immediate: false' do
|
115
|
+
before { cpm.instance_variable_set(:@kill_immediate, false) }
|
116
|
+
it 'returns nil' do
|
117
|
+
expect(cpm.killing_status).to be false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#try_join' do
|
124
|
+
context 'not killed yet' do
|
125
|
+
it 'returns nil' do
|
126
|
+
expect(cpm).not_to receive(:kill_children)
|
127
|
+
expect(cpm.try_join(double, double)).to be_nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
context 'killing' do
|
131
|
+
let (:cProcess) do
|
132
|
+
allow(Process).to receive(:waitpid).with(processor_id, Process::WNOHANG)
|
133
|
+
end
|
134
|
+
before do
|
135
|
+
cpm.instance_variable_set(:@kill_start_time, double)
|
136
|
+
end
|
137
|
+
context 'waitpid returns pid' do
|
138
|
+
before do
|
139
|
+
cProcess.and_return(processor_id)
|
140
|
+
expect(cpm).not_to receive(:kill_children)
|
141
|
+
end
|
142
|
+
it 'returns true' do
|
143
|
+
expect(cpm.try_join(double, double)).to be true
|
144
|
+
end
|
145
|
+
end
|
146
|
+
context 'waitpid raises ECHILD' do
|
147
|
+
before do
|
148
|
+
cProcess.and_raise(Errno::ECHILD)
|
149
|
+
expect(cpm).not_to receive(:kill_children)
|
150
|
+
end
|
151
|
+
it 'returns true' do
|
152
|
+
expect(cpm.try_join(double, double)).to be true
|
153
|
+
end
|
154
|
+
end
|
155
|
+
context 'waitpid returns nil' do
|
156
|
+
before do
|
157
|
+
cProcess.and_return(nil)
|
158
|
+
allow(object_double('Time').as_stubbed_const).to \
|
159
|
+
receive_message_chain(:now, :to_i).and_return(now)
|
160
|
+
cpm.instance_variable_set(:@last_kill_time, last_kill_time)
|
161
|
+
end
|
162
|
+
it 'returns true if last_kill_time is new' do
|
163
|
+
graceful_kill_limit = double('graceful_kill_limit')
|
164
|
+
expect(cpm).to receive(:kill_children).with(now, graceful_kill_limit).exactly(:once)
|
165
|
+
expect(cpm.try_join(30, graceful_kill_limit)).to be false
|
166
|
+
expect(cpm.instance_variable_get(:@last_kill_time)).to eq(now)
|
167
|
+
end
|
168
|
+
it 'returns false if last_kill_time is old' do
|
169
|
+
expect(cpm).not_to receive(:kill_children)
|
170
|
+
expect(cpm.try_join(31, double)).to be false
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe '#cleanup' do
|
177
|
+
context 'rpipe is open' do
|
178
|
+
it 'closes rpipe' do
|
179
|
+
allow(rpipe).to receive(:closed?).and_return(false)
|
180
|
+
expect(rpipe).to receive(:close).exactly(:once)
|
181
|
+
cpm.cleanup
|
182
|
+
end
|
183
|
+
end
|
184
|
+
context 'rpipe is closed' do
|
185
|
+
it 'doesn\'t close rpipe' do
|
186
|
+
allow(rpipe).to receive(:closed?).and_return(true)
|
187
|
+
expect(rpipe).not_to receive(:close)
|
188
|
+
cpm.cleanup
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '#send_signal' do
|
194
|
+
let (:sig){ double('sig') }
|
195
|
+
let (:cProcess) do
|
196
|
+
allow(Process).to receive(:kill).with(sig, processor_id)
|
197
|
+
end
|
198
|
+
context 'kill returnes pid' do
|
199
|
+
before do
|
200
|
+
cProcess.and_return(processor_id)
|
201
|
+
end
|
202
|
+
it { cpm.send_signal(sig) }
|
203
|
+
end
|
204
|
+
context 'kill raises ESRCH' do
|
205
|
+
before{ cProcess.and_raise(Errno::ESRCH) }
|
206
|
+
it { cpm.send_signal(sig) }
|
207
|
+
end
|
208
|
+
context 'kill raises EPERM' do
|
209
|
+
before{ cProcess.and_raise(Errno::EPERM) }
|
210
|
+
it { cpm.send_signal(sig) }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe '#kill_children' do
|
215
|
+
context '@kill_start_time: nil' do
|
216
|
+
# don't happen
|
217
|
+
end
|
218
|
+
context '@kill_start_time: <time>' do
|
219
|
+
before do
|
220
|
+
cpm.instance_variable_set(:@kill_start_time, 42)
|
221
|
+
end
|
222
|
+
context '@kill_immediate: true' do
|
223
|
+
before do
|
224
|
+
cpm.instance_variable_set(:@kill_immediate, true)
|
225
|
+
expect(cpm).to receive(:get_ppid_pids_map).with(no_args).and_return({1=>processor_id}).exactly(:once)
|
226
|
+
expect(cpm).to receive(:collect_child_pids).with({1=>processor_id}, [processor_id], processor_id) \
|
227
|
+
.and_return([processor_id]).exactly(:once)
|
228
|
+
expect(cpm).to receive(:kill_process).with(processor_id, true)
|
229
|
+
end
|
230
|
+
it 'calls kill_process immediately' do
|
231
|
+
cpm.__send__(:kill_children, now, double)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
context '@kill_immediate: false' do
|
235
|
+
before do
|
236
|
+
cpm.instance_variable_set(:@kill_immediate, false)
|
237
|
+
end
|
238
|
+
it 'calls kill_process immediately' do
|
239
|
+
expect(cpm).to receive(:get_ppid_pids_map).with(no_args).and_return({1=>processor_id}).exactly(:once)
|
240
|
+
expect(cpm).to receive(:collect_child_pids).with({1=>processor_id}, [processor_id], processor_id) \
|
241
|
+
.and_return([processor_id]).exactly(:once)
|
242
|
+
expect(cpm).to receive(:kill_process).with(processor_id, true)
|
243
|
+
cpm.__send__(:kill_children, now, 29)
|
244
|
+
end
|
245
|
+
it 'calls kill_process' do
|
246
|
+
expect(cpm).not_to receive(:get_ppid_pids_map)
|
247
|
+
expect(cpm).not_to receive(:collect_child_pids)
|
248
|
+
expect(cpm).to receive(:kill_process).with(processor_id, false)
|
249
|
+
cpm.__send__(:kill_children, now, 30)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
describe '#get_ppid_pids_map' do
|
256
|
+
before do
|
257
|
+
expect(cpm).to receive(:`).with('ps axo pid,ppid') \
|
258
|
+
.and_return <<eom
|
259
|
+
PID PPID
|
260
|
+
1 0
|
261
|
+
2 1
|
262
|
+
3 1
|
263
|
+
4 2
|
264
|
+
5 3
|
265
|
+
eom
|
266
|
+
end
|
267
|
+
it 'returns a tree of hash' do
|
268
|
+
expect(cpm.__send__(:get_ppid_pids_map)).to eq({0=>[1], 1=>[2, 3], 2=>[4], 3=>[5]})
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
describe '#collect_child_pids' do
|
273
|
+
it 'returns a flat array of given children' do
|
274
|
+
ppid_pids = {0=>[1], 1=>[2, 3], 2=>[4], 3=>[5]}
|
275
|
+
parent_pid = 1
|
276
|
+
results = cpm.__send__(:collect_child_pids, ppid_pids, [parent_pid], parent_pid)
|
277
|
+
expect(results).to eq([1, 2, 4, 3, 5])
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe '#kill_process' do
|
282
|
+
let (:pid){ double('pid') }
|
283
|
+
it 'kill(:KILL, pid) for immediate:true' do
|
284
|
+
expect(Process).to receive(:kill).with(:KILL, pid).and_return(pid).exactly(:once)
|
285
|
+
expect(cpm.__send__(:kill_process, pid, true)).to eq(pid)
|
286
|
+
end
|
287
|
+
it 'kill(:TERM, pid) for immediate:false' do
|
288
|
+
expect(Process).to receive(:kill).with(:TERM, pid).and_return(pid).exactly(:once)
|
289
|
+
expect(cpm.__send__(:kill_process, pid, false)).to eq(pid)
|
290
|
+
end
|
291
|
+
it 'rescues ESRCH' do
|
292
|
+
expect(Process).to receive(:kill).with(:KILL, pid).and_raise(Errno::ESRCH).exactly(:once)
|
293
|
+
expect(cpm.__send__(:kill_process, pid, true)).to be_nil
|
294
|
+
end
|
295
|
+
it 'rescues EPERM' do
|
296
|
+
expect(Process).to receive(:kill).with(:KILL, pid).and_raise(Errno::EPERM).exactly(:once)
|
297
|
+
expect(cpm.__send__(:kill_process, pid, true)).to be_nil
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|