unicorn_relay 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bb391afaecdf7ee75869e6d86ef3bcf590e60471
4
+ data.tar.gz: 76fe237036b5be4b6e2275217b59c8f415a3e270
5
+ SHA512:
6
+ metadata.gz: 283caa1b2bda67e62c2158f9dbdbfab85c4877c71a62648c444ae06b2605fcdd278eb4026fcd753b60ef758f0dc7cb5860ddde996a9b8a64f49e2209929da931
7
+ data.tar.gz: 37dc59b4bd7be89f5f30b15b73e7cb7fdda487fab3b9299a4e1a5afd9b78e1443926ab652823f05d550da273cb07248b74b63c3872d4e5a9c9f0f626d9921aad
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .*.sw[pon]
2
+ .AppleDouble
3
+ .DS_Store
4
+ .byebug_history
5
+ .rvmrc
6
+ Gemfile.lock
7
+ coverage
8
+ errors.lst
9
+ pkg
10
+ tags
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.utilsrc ADDED
@@ -0,0 +1,25 @@
1
+ # vim: set ft=ruby:
2
+
3
+ search do
4
+ prune_dirs /\A(\.svn|\.git|CVS|tmp|tags|coverage|pkg)\z/
5
+ skip_files /(\A\.|\.sw[pon]\z|\.(log|fnm|jpg|jpeg|png|pdf|svg)\z|tags|~\z)/i
6
+ end
7
+
8
+ discover do
9
+ prune_dirs /\A(\.svn|\.git|CVS|tmp|tags|coverage|pkg)\z/
10
+ skip_files /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
11
+ binary false
12
+ end
13
+
14
+ strip_spaces do
15
+ prune_dirs /\A(\..*|CVS|pkg)\z/
16
+ skip_files /(\A\.|\.sw[pon]\z|\.log\z|~\z)/
17
+ end
18
+
19
+ probe do
20
+ test_framework :rspec
21
+ end
22
+
23
+ ssh_tunnel do
24
+ terminal_multiplexer :tmux
25
+ end
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # vim: set filetype=ruby et sw=2 ts=2:
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/README.md ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,27 @@
1
+ # vim: set filetype=ruby et sw=2 ts=2:
2
+
3
+ require 'gem_hadar'
4
+
5
+ GemHadar do
6
+ name 'unicorn_relay'
7
+ author 'Florian Frank'
8
+ email 'flori@ping.de'
9
+ homepage "http://flori.github.com/#{name}"
10
+ summary 'Allow controlling unicorn via supervise'
11
+ description 'Allow controlling unicorn via supervise by relaying signals to it'
12
+ test_dir 'spec'
13
+ ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', 'coverage', '.rvmrc',
14
+ '.AppleDouble', 'tags', '.byebug_history', '.DS_Store', 'errors.lst'
15
+ readme 'README.md'
16
+ title "#{name.camelize} -- More Math in Ruby"
17
+ licenses << 'Apache-2.0'
18
+ executables << 'unicorn_relay'
19
+
20
+ dependency 'tins', '~>1.0'
21
+ dependency 'mize'
22
+ development_dependency 'rake'
23
+ development_dependency 'simplecov'
24
+ development_dependency 'rspec'
25
+ end
26
+
27
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
data/bin/unicorn_relay ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'unicorn_relay'
4
+
5
+ # sv stop ... => service down/quit gracefully
6
+ # sv stop ... ; sv interrupt ... => service down/terminate w/o grace
7
+ UnicornRelay::Forker.new(
8
+ pid_file: ENV['PID_FILE']
9
+ ).start
data/bin/unocorn ADDED
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ STDOUT.sync = true
4
+
5
+ STDOUT.puts "[#$$] #{File.basename($0)} started"
6
+
7
+ signals = %i[ HUP USR1 USR2 TTIN TTOU WINCH INT QUIT TERM ]
8
+
9
+ worker_pid = fork do
10
+
11
+ signals.each do |signal|
12
+ trap signal do
13
+ STDOUT.print "[#$$] signal #{signal.inspect} received by worker"
14
+ case signal
15
+ when :INT, :TERM
16
+ STDOUT.puts ", shutting down quickly."
17
+ exit
18
+ when :QUIT
19
+ STDOUT.puts ", shutting down gracefully."
20
+ exit
21
+ else
22
+ STDOUT.puts
23
+ end
24
+ end
25
+ end
26
+
27
+ sleep
28
+ end
29
+
30
+ signals = %i[ HUP USR1 USR2 TTIN TTOU WINCH INT QUIT TERM ]
31
+
32
+ signals.each do |signal|
33
+ trap signal do
34
+ STDOUT.print "[#$$] signal #{signal.inspect} received by master"
35
+ case signal
36
+ when :INT, :TERM
37
+ STDOUT.puts ", shutting down quickly."
38
+ exit
39
+ when :QUIT
40
+ STDOUT.puts ", shutting down gracefully waiting for worker pid=#{worker_pid}."
41
+ Process.waitpid worker_pid
42
+ exit
43
+ else
44
+ STDOUT.puts
45
+ end
46
+ end
47
+ end
48
+
49
+ sleep
@@ -0,0 +1,9 @@
1
+ require 'tins/xt'
2
+ require 'mize'
3
+
4
+ module UnicornRelay
5
+ end
6
+
7
+ require 'unicorn_relay/version'
8
+ require 'unicorn_relay/forker'
9
+ require 'unicorn_relay/teardown'
@@ -0,0 +1,192 @@
1
+ class UnicornRelay::Forker
2
+ class StopException < StandardError; end
3
+
4
+ def initialize(script_path: $0, pid_file: nil, argv: ARGV, env: ENV)
5
+ @name = File.basename(script_path)
6
+ @pid_file = pid_file
7
+ @argv = argv
8
+ @env = env
9
+ @relay_signals = %i[ HUP USR1 USR2 TTIN TTOU WINCH ]
10
+ @shutdown_signals = {
11
+ :INT => :INT,
12
+ :QUIT => :QUIT,
13
+ :TERM => :QUIT,
14
+ }
15
+ end
16
+
17
+ def start
18
+ handle_old_pid_file
19
+ start_control_loop
20
+ end
21
+
22
+ def stop
23
+ stop_control_loop
24
+ end
25
+
26
+ private
27
+
28
+ def process
29
+ "#@name pid=#$$"
30
+ end
31
+
32
+ def output(message)
33
+ STDOUT.puts "#{process} #{message}"
34
+ STDOUT.flush
35
+ self
36
+ end
37
+
38
+ def error(message)
39
+ STDERR.puts "#{process} #{message}"
40
+ STDERR.flush
41
+ self
42
+ end
43
+
44
+ def read_pid
45
+ File.read(@pid_file).to_i
46
+ end
47
+
48
+ def handle_old_pid_file
49
+ unless @pid_file
50
+ output "no pid file was given"
51
+ return
52
+ end
53
+ if pid = read_pid.nonzero?
54
+ Process.kill :INT, pid
55
+ output "interrupts pid=#{pid} from pid file"
56
+ else
57
+ error "ignoring pid file without a pid"
58
+ end
59
+ rescue Errno::ENOENT, Errno::ENOTDIR
60
+ output "no pid file was found at #{@pid_file.inspect}"
61
+ self
62
+ rescue Errno::EPERM
63
+ error "found a pid file for pid=#{pid}, but no permission to signal"
64
+ rescue Errno::ESRCH
65
+ error "found a stale pid file for pid=#{pid}"
66
+ end
67
+
68
+ def setup_spawn_env(env)
69
+ if gc = env.delete('UNICORN_GC')
70
+ gc.split(/\s+/).each_with_object(env) do |l, h|
71
+ k, v = l.split('=', 2)
72
+ h[k] = v
73
+ end
74
+ end
75
+ env
76
+ end
77
+
78
+ def shutdown_process_group(signal:, shutdown_signal:)
79
+ Signal.trap signal do
80
+ unless @shutdown_signal
81
+ @shutdown_signal = shutdown_signal
82
+ output "received #{signal.inspect}, "\
83
+ "shutting down pgid=#@pgid with #{shutdown_signal.inspect}"
84
+ end
85
+ end
86
+ end
87
+
88
+ def relay_to_process_group(signal:)
89
+ # NOTE relaying USR1 signals this way might cause problems when using
90
+ # user/group switching in unicorn
91
+ Signal.trap signal do
92
+ output "relays signal #{signal.inspect} to process group pgid=#@pgid"
93
+ signal_process_group signal
94
+ end
95
+ end
96
+
97
+ def install_shutdown_signal_handlers
98
+ @shutdown_signals.each do |signal, shutdown_signal|
99
+ shutdown_process_group signal: signal, shutdown_signal: shutdown_signal
100
+ end
101
+ self
102
+ end
103
+
104
+ def install_relay_signal_handlers
105
+ @relay_signals.each do |signal|
106
+ relay_to_process_group signal: signal
107
+ end
108
+ self
109
+ end
110
+
111
+ def create_process_group(pid)
112
+ pgid = Process.getpgid(pid)
113
+ Process.detach pid
114
+ pgid
115
+ end
116
+
117
+ def fork_child_process_in_pgroup
118
+ # We're passing Ruby GC configuration environment variables through the
119
+ # UNICORN_GC variable indirectly to its spawn instead of defining them in
120
+ # the run script. We don't want to increase *this* process' memory
121
+ # footprint over ca. 22 MiB. Only the unicorn processes themselves should
122
+ # allocate a lot of memory for rails (= several hundred MiB) at startup.
123
+ setup_spawn_env(@env)
124
+ pid = Process.spawn(@env, @argv.map(&:inspect) * ' ', pgroup: true)
125
+ pgid = create_process_group(pid)
126
+ output "forks child process with pid=#{pid} pgid=#{pgid}"
127
+ pgid
128
+ end
129
+
130
+ def signal_process_group(signal)
131
+ Process.kill signal, -@pgid
132
+ end
133
+
134
+ def shutdown_signal_pending?
135
+ @shutdown_signal && !@shutdown_signal_sent_at
136
+ end
137
+
138
+ # Send shutdown_signal to process group for the first time.
139
+ def send_shutdown_signal
140
+ @shutdown_signal_sent_at = Time.now
141
+ output "Sending #{@shutdown_signal.inspect} to process group pgid=#@pgid"
142
+ signal_process_group @shutdown_signal
143
+ end
144
+
145
+ def shutdown_signal_sent_before?
146
+ @shutdown_signal_sent_at && @shutdown_signal_sent_at - Time.now > 60
147
+ end
148
+
149
+ # Repeat relaying of all shutdown signals to process group approximately
150
+ # every minute after one was received until every process was shutdown.
151
+ def resend_shutdown_signal
152
+ @shutdown_signal_sent_at = Time.now
153
+ output "Resending #{@shutdown_signal.inspect} to process group pgid=#@pgid"
154
+ signal_process_group @shutdown_signal
155
+ end
156
+
157
+ # Exit loop via Errno::ESRCH expception, as soon as process group is
158
+ # empty.
159
+ def check_if_process_group_empty
160
+ signal_process_group 0
161
+ end
162
+
163
+ def stop_control_loop
164
+ raise StopException
165
+ end
166
+
167
+ def start_control_loop
168
+ @shutdown_signal = nil
169
+ @shutdown_signal_sent_at = nil
170
+
171
+ @pgid = fork_child_process_in_pgroup
172
+
173
+ install_shutdown_signal_handlers
174
+ install_relay_signal_handlers
175
+
176
+ loop do
177
+ if shutdown_signal_pending?
178
+ send_shutdown_signal
179
+ elsif shutdown_signal_sent_before?
180
+ resend_shutdown_signal
181
+ else
182
+ check_if_process_group_empty
183
+ end
184
+ sleep 1
185
+ end
186
+ rescue Errno::ESRCH
187
+ output "process group pgid=#@pgid empty, exiting"
188
+ return
189
+ rescue StopException
190
+ return
191
+ end
192
+ end
@@ -0,0 +1,38 @@
1
+ # Before forking anew, kill the unicorn master process that belongs to the
2
+ # .oldbin PID. This enables 0 downtime deploys.
3
+ class UnicornRelay::Teardown
4
+ def initialize(server:, pid_file:)
5
+ @server = server
6
+ @pid_file = pid_file
7
+ end
8
+
9
+ def perform
10
+ server_has_new_pid_file? && pid and kill_pid
11
+ end
12
+
13
+ private
14
+
15
+ def kill_pid
16
+ Process.kill(:QUIT, pid)
17
+ rescue Errno::ESRCH
18
+ end
19
+
20
+ def server_has_new_pid_file?
21
+ pid_file_exist? && @server.pid != @pid_file
22
+ end
23
+
24
+ def pid_file_exist?
25
+ File.exist?(@pid_file)
26
+ end
27
+
28
+ def pid_file_content
29
+ File.read(@pid_file)
30
+ rescue Errno::ENOENT, Errno::ENOTDIR
31
+ end
32
+
33
+ memoize method:
34
+ def pid
35
+ pid = pid_file_content.to_i
36
+ pid.nonzero?
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+ module UnicornRelay
2
+ # UnicornRelay version
3
+ VERSION = '0.0.0'
4
+ VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
+ VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
+ VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
7
+ VERSION_BUILD = VERSION_ARRAY[2] # :nodoc:
8
+ end
@@ -0,0 +1,274 @@
1
+ require 'spec_helper'
2
+
3
+ describe UnicornRelay::Forker do
4
+ let :server do
5
+ double('Server')
6
+ end
7
+
8
+ let :pid_file do
9
+ 'foo/bar.pid.oldbin'
10
+ end
11
+
12
+ let :forker do
13
+ described_class.new(
14
+ script_path: 'foo/bar/unicorn_relay',
15
+ pid_file: pid_file
16
+ )
17
+ end
18
+
19
+ let :process do
20
+ forker.send(:process)
21
+ end
22
+
23
+ describe '#start' do
24
+ it 'calls two other methods' do
25
+ expect(forker).to receive(:handle_old_pid_file)
26
+ expect(forker).to receive(:start_control_loop)
27
+ forker.start
28
+ end
29
+ end
30
+
31
+ describe '#stop' do
32
+ it 'calls stop_control_loop' do
33
+ expect(forker).to receive(:stop_control_loop)
34
+ forker.stop
35
+ end
36
+ end
37
+
38
+ describe '#process' do
39
+ it 'returns process name and pid' do
40
+ expect(process).to eq "unicorn_relay pid=#$$"
41
+ end
42
+ end
43
+
44
+ describe '#output' do
45
+ it 'outputs message to STDOUT and returns self' do
46
+ expect(STDOUT).to receive(:puts).with("#{process} hello")
47
+ expect(forker.send(:output, "hello")).to eq forker
48
+ end
49
+ end
50
+
51
+ describe '#error' do
52
+ it 'outputs message to STDERR and returns self' do
53
+ expect(STDERR).to receive(:puts).with("#{process} hello")
54
+ expect(forker.send(:error, "hello")).to eq forker
55
+ end
56
+ end
57
+
58
+ describe '#read_pid' do
59
+ it 'returns pid as an Integer' do
60
+ allow(File).to receive(:read).with(pid_file).and_return '666'
61
+ expect(forker.send(:read_pid)).to eq 666
62
+ end
63
+ end
64
+
65
+ describe '#handle_old_pid_file' do
66
+ it 'does not have to handle a pid_file' do
67
+ forker = described_class.new
68
+ expect(forker).to receive(:output).with('no pid file was given')
69
+ forker.send(:handle_old_pid_file)
70
+ end
71
+
72
+ it 'ignores a pid file without a pid' do
73
+ allow(forker).to receive(:read_pid).and_return 0
74
+ expect(forker).to receive(:error).with('ignoring pid file without a pid')
75
+ forker.send(:handle_old_pid_file)
76
+ end
77
+
78
+ it 'ignores a pid file without a pid' do
79
+ allow(forker).to receive(:read_pid).and_return 666
80
+ expect(Process).to receive(:kill).with(:INT, 666)
81
+ expect(forker).to receive(:output).
82
+ with('interrupts pid=666 from pid file')
83
+ forker.send(:handle_old_pid_file)
84
+ end
85
+
86
+ it 'handles a missing pid file' do
87
+ allow(forker).to receive(:read_pid).and_raise Errno::ENOENT
88
+ expect(forker).to receive(:output).
89
+ with('no pid file was found at "foo/bar.pid.oldbin"')
90
+ expect(forker.send(:handle_old_pid_file)).to eq forker
91
+ end
92
+
93
+ it 'handles insufficient permissions' do
94
+ allow(forker).to receive(:read_pid).and_return 666
95
+ allow(Process).to receive(:kill).and_raise Errno::EPERM
96
+ expect(forker).to receive(:error).
97
+ with("found a pid file for pid=666, but no permission to signal")
98
+ forker.send(:handle_old_pid_file)
99
+ end
100
+
101
+ it 'handles stale pid files' do
102
+ allow(forker).to receive(:read_pid).and_return 666
103
+ allow(Process).to receive(:kill).and_raise Errno::ESRCH
104
+ expect(forker).to receive(:error).
105
+ with("found a stale pid file for pid=666")
106
+ forker.send(:handle_old_pid_file)
107
+ end
108
+ end
109
+
110
+ describe '#setup_spawn_env' do
111
+ let :env do
112
+ {
113
+ 'FOO' => 'BAR',
114
+ 'UNICORN_GC' => 'BAZ=QUUX PI=3.141',
115
+ }
116
+ end
117
+
118
+ it 'handles UNICORN_GC env var' do
119
+ expect(forker.send(:setup_spawn_env, env)).to eq(
120
+ 'FOO' => 'BAR',
121
+ 'BAZ' => 'QUUX',
122
+ 'PI' => '3.141',
123
+ )
124
+ end
125
+
126
+ it 'just passes ENV on if UNICORN_GC is not defined' do
127
+ expect(forker.send(:setup_spawn_env, { 'FOO' => 'BAR' })).to eq(
128
+ 'FOO' => 'BAR'
129
+ )
130
+ end
131
+ end
132
+
133
+
134
+ describe '#signal_process_group' do
135
+ it 'sends signal to process group' do
136
+ expect(Process).to receive(:kill).with(:USR1, -666)
137
+ forker.instance_eval do
138
+ @pgid = 666
139
+ signal_process_group(:USR1)
140
+ end
141
+ end
142
+ end
143
+
144
+ describe '#shutdown_process_group' do
145
+ it 'installs signal trap' do
146
+ expect(Signal).to receive(:trap).with(:USR1)
147
+ forker.send(:shutdown_process_group, signal: :USR1, shutdown_signal: :USR2)
148
+ end
149
+ end
150
+
151
+ describe '#relay_to_process_group' do
152
+ it 'installs signal trap' do
153
+ expect(Signal).to receive(:trap).with(:USR1)
154
+ forker.send(:relay_to_process_group, signal: :USR1)
155
+ end
156
+ end
157
+
158
+ describe '#install_shutdown_signal_handlers' do
159
+ it 'installs signal handlers' do
160
+ expect(forker).to receive(:shutdown_process_group).at_least(1)
161
+ forker.send(:install_shutdown_signal_handlers)
162
+ end
163
+ end
164
+
165
+ describe '#install_relay_signal_handlers' do
166
+ it 'installs signal handlers' do
167
+ expect(forker).to receive(:relay_to_process_group).at_least(1)
168
+ forker.send(:install_relay_signal_handlers)
169
+ end
170
+ end
171
+
172
+ describe '#create_process_group' do
173
+ it 'creates a process group for a pid' do
174
+ expect(Process).to receive(:getpgid).with(666).and_return 667
175
+ expect(Process).to receive(:detach).with(666)
176
+ expect(forker.send(:create_process_group, 666)).to eq 667
177
+ end
178
+ end
179
+
180
+ describe '#fork_child_process_in_pgroup' do
181
+ it 'forks and creates a process group' do
182
+ forker = described_class.new(env: {}, argv: [])
183
+ expect(Process).to receive(:spawn).with({}, "", pgroup: true).
184
+ and_return 123
185
+ expect(forker).to receive(:create_process_group).with(123).and_return 456
186
+ expect(forker).to receive(:output).
187
+ with('forks child process with pid=123 pgid=456')
188
+ forker.send(:fork_child_process_in_pgroup)
189
+ end
190
+ end
191
+
192
+ context 'shutdown_signal received' do
193
+ before do
194
+ forker.instance_eval do
195
+ @shutdown_signal = :QUIT
196
+ @pgid = 666
197
+ end
198
+ end
199
+
200
+ describe '#send_shutdown_signal' do
201
+ it 'singals process group' do
202
+ expect(forker).to receive(:output).with('Sending :QUIT to process group pgid=666')
203
+ expect(forker).to receive(:signal_process_group).with(:QUIT)
204
+ expect {
205
+ forker.send(:send_shutdown_signal)
206
+ }.to change {
207
+ forker.instance_eval { @shutdown_signal_sent_at }
208
+ }.from(nil).to(instance_of(Time))
209
+ end
210
+ end
211
+
212
+ describe '#resend_shutdown_signal' do
213
+ it 'singals process group' do
214
+ forker.instance_eval { @shutdown_signal_sent_at = Time.now }
215
+ expect(forker).to receive(:output).with('Resending :QUIT to process group pgid=666')
216
+ expect(forker).to receive(:signal_process_group).with(:QUIT)
217
+ expect {
218
+ forker.send(:resend_shutdown_signal)
219
+ }.to change {
220
+ forker.instance_eval { @shutdown_signal_sent_at }
221
+ }
222
+ end
223
+ end
224
+ end
225
+
226
+ describe '#start_control_loop' do
227
+ it 'runs the control loop' do
228
+ expect(forker).to receive(:fork_child_process_in_pgroup).and_return 666
229
+ expect(forker).to receive(:install_shutdown_signal_handlers)
230
+ expect(forker).to receive(:install_relay_signal_handlers)
231
+ expect(forker).to receive(:loop)
232
+ forker.send(:start_control_loop)
233
+ end
234
+
235
+ context 'running the control loop' do
236
+ before do
237
+ allow(forker).to receive(:fork_child_process_in_pgroup).and_return 666
238
+ allow(forker).to receive(:install_shutdown_signal_handlers)
239
+ allow(forker).to receive(:install_relay_signal_handlers)
240
+ end
241
+
242
+ it 'sends a shutdown signal if pending' do
243
+ allow(forker).to receive(:output)
244
+ allow(forker).to receive(:shutdown_signal_pending?).and_return true
245
+ allow(forker).to receive(:shutdown_signal_sent_before?).and_return false
246
+ expect(forker).to receive(:send_shutdown_signal).and_raise UnicornRelay::Forker::StopException
247
+ forker.send(:start_control_loop)
248
+ end
249
+
250
+ it 'resends the shutdown signal after a while' do
251
+ allow(forker).to receive(:output)
252
+ allow(forker).to receive(:shutdown_signal_pending?).and_return false
253
+ allow(forker).to receive(:shutdown_signal_sent_before?).and_return true
254
+ expect(forker).to receive(:resend_shutdown_signal).and_raise UnicornRelay::Forker::StopException
255
+ forker.send(:start_control_loop)
256
+ end
257
+
258
+ it 'ends the control loop if the process group is empty' do
259
+ allow(forker).to receive(:signal_process_group).and_raise Errno::ESRCH
260
+ expect(forker).to receive(:check_if_process_group_empty).and_call_original
261
+ expect(forker).to receive(:output).with('process group pgid=666 empty, exiting')
262
+ forker.send(:start_control_loop)
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#stop_control_loop' do
268
+ it 'raises UnicornRelay::Forker::StopException' do
269
+ expect {
270
+ forker.send(:stop_control_loop)
271
+ }.to raise_error UnicornRelay::Forker::StopException
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,16 @@
1
+ if ENV['START_SIMPLECOV'].to_i == 1
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ add_filter "#{File.basename(File.dirname(__FILE__))}/"
5
+ end
6
+ end
7
+ if ENV['CODECLIMATE_REPO_TOKEN']
8
+ require "codeclimate-test-reporter"
9
+ CodeClimate::TestReporter.start
10
+ end
11
+ require 'rspec'
12
+ begin
13
+ require 'byebug'
14
+ rescue LoadError
15
+ end
16
+ require 'unicorn_relay'
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ describe UnicornRelay::Teardown do
4
+ let :server do
5
+ double('Server')
6
+ end
7
+
8
+ let :teardown do
9
+ described_class.new(server: server, pid_file: 'foo/bar.pid.oldbin')
10
+ end
11
+
12
+ it 'does not perform without new pid_file' do
13
+ allow(teardown).to receive(:server_has_new_pid_file?).and_return false
14
+ allow(teardown).to receive(:pid).and_return 666
15
+ teardown.perform
16
+ expect(teardown).not_to receive(:kill_pid)
17
+ end
18
+
19
+ it 'does not perform without a pid in pid_file' do
20
+ allow(teardown).to receive(:server_has_new_pid_file?).and_return true
21
+ teardown.perform
22
+ expect(teardown).not_to receive(:kill_pid)
23
+ end
24
+
25
+ it 'performs killing otherwise' do
26
+ allow(teardown).to receive(:server_has_new_pid_file?).and_return true
27
+ allow(teardown).to receive(:pid).and_return 666
28
+ expect(Process).to receive(:kill).with(:QUIT, 666)
29
+ expect(teardown).to receive(:kill_pid).and_call_original
30
+ teardown.perform
31
+ end
32
+
33
+ describe '#kill_pid' do
34
+ it 'returns nil if process for pid is not found' do
35
+ allow(Process).to receive(:kill).and_raise Errno::ESRCH
36
+ expect(teardown.send(:kill_pid)).to be_nil
37
+ end
38
+
39
+ end
40
+
41
+ describe '#pid' do
42
+ it 'returns nil for missing pid file or non-numeric content' do
43
+ allow(teardown).to receive(:pid_file_content).and_return 'nix'
44
+ expect(teardown.send(:pid)).to be_nil
45
+ end
46
+ end
47
+
48
+ describe '#server_has_new_pid_file?' do
49
+ it 'returns true if server.pid is different from pid_file' do
50
+ allow(server).to receive(:pid).and_return 'something else'
51
+ allow(teardown).to receive(:pid_file_exist?).and_return true
52
+ expect(teardown.send(:server_has_new_pid_file?)).to eq true
53
+ end
54
+
55
+ it 'returns false if server.pid is the same as pid_file' do
56
+ allow(server).to receive(:pid).and_return 'foo/bar.pid.oldbin'
57
+ allow(teardown).to receive(:pid_file_exist?).and_return true
58
+ expect(teardown.send(:server_has_new_pid_file?)).to eq false
59
+ end
60
+
61
+ it 'returns false if pid_file does not exist' do
62
+ allow(teardown).to receive(:pid_file_exist?).and_return false
63
+ expect(teardown.send(:server_has_new_pid_file?)).to eq false
64
+ end
65
+ end
66
+
67
+ describe '#pid_file_exist?' do
68
+ it 'checks the existence of pid_file' do
69
+ expect(File).to receive(:exist?).with('foo/bar.pid.oldbin')
70
+ teardown.send(:pid_file_exist?)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ # stub: unicorn_relay 0.0.0 ruby lib
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "unicorn_relay".freeze
6
+ s.version = "0.0.0"
7
+
8
+ s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
+ s.require_paths = ["lib".freeze]
10
+ s.authors = ["Florian Frank".freeze]
11
+ s.date = "2016-10-11"
12
+ s.description = "Allow controlling unicorn via supervise by relaying signals to it".freeze
13
+ s.email = "flori@ping.de".freeze
14
+ s.executables = ["unicorn_relay".freeze]
15
+ s.extra_rdoc_files = ["README.md".freeze, "lib/unicorn_relay.rb".freeze, "lib/unicorn_relay/forker.rb".freeze, "lib/unicorn_relay/teardown.rb".freeze, "lib/unicorn_relay/version.rb".freeze]
16
+ s.files = [".gitignore".freeze, ".rspec".freeze, ".utilsrc".freeze, "Gemfile".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "bin/unicorn_relay".freeze, "bin/unocorn".freeze, "lib/unicorn_relay.rb".freeze, "lib/unicorn_relay/forker.rb".freeze, "lib/unicorn_relay/teardown.rb".freeze, "lib/unicorn_relay/version.rb".freeze, "spec/forker_spec.rb".freeze, "spec/spec_helper.rb".freeze, "spec/teardown_spec.rb".freeze, "unicorn_relay.gemspec".freeze]
17
+ s.homepage = "http://flori.github.com/unicorn_relay".freeze
18
+ s.licenses = ["Apache-2.0".freeze]
19
+ s.rdoc_options = ["--title".freeze, "UnicornRelay -- More Math in Ruby".freeze, "--main".freeze, "README.md".freeze]
20
+ s.rubygems_version = "2.6.7".freeze
21
+ s.summary = "Allow controlling unicorn via supervise".freeze
22
+ s.test_files = ["spec/forker_spec.rb".freeze, "spec/spec_helper.rb".freeze, "spec/teardown_spec.rb".freeze]
23
+
24
+ if s.respond_to? :specification_version then
25
+ s.specification_version = 4
26
+
27
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
28
+ s.add_development_dependency(%q<gem_hadar>.freeze, ["~> 1.8.0"])
29
+ s.add_development_dependency(%q<rake>.freeze, [">= 0"])
30
+ s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
31
+ s.add_development_dependency(%q<rspec>.freeze, [">= 0"])
32
+ s.add_runtime_dependency(%q<tins>.freeze, ["~> 1.0"])
33
+ s.add_runtime_dependency(%q<mize>.freeze, [">= 0"])
34
+ else
35
+ s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.8.0"])
36
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
37
+ s.add_dependency(%q<simplecov>.freeze, [">= 0"])
38
+ s.add_dependency(%q<rspec>.freeze, [">= 0"])
39
+ s.add_dependency(%q<tins>.freeze, ["~> 1.0"])
40
+ s.add_dependency(%q<mize>.freeze, [">= 0"])
41
+ end
42
+ else
43
+ s.add_dependency(%q<gem_hadar>.freeze, ["~> 1.8.0"])
44
+ s.add_dependency(%q<rake>.freeze, [">= 0"])
45
+ s.add_dependency(%q<simplecov>.freeze, [">= 0"])
46
+ s.add_dependency(%q<rspec>.freeze, [">= 0"])
47
+ s.add_dependency(%q<tins>.freeze, ["~> 1.0"])
48
+ s.add_dependency(%q<mize>.freeze, [">= 0"])
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,157 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unicorn_relay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Florian Frank
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gem_hadar
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.8.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.8.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: simplecov
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tins
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mize
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Allow controlling unicorn via supervise by relaying signals to it
98
+ email: flori@ping.de
99
+ executables:
100
+ - unicorn_relay
101
+ extensions: []
102
+ extra_rdoc_files:
103
+ - README.md
104
+ - lib/unicorn_relay.rb
105
+ - lib/unicorn_relay/forker.rb
106
+ - lib/unicorn_relay/teardown.rb
107
+ - lib/unicorn_relay/version.rb
108
+ files:
109
+ - ".gitignore"
110
+ - ".rspec"
111
+ - ".utilsrc"
112
+ - Gemfile
113
+ - README.md
114
+ - Rakefile
115
+ - VERSION
116
+ - bin/unicorn_relay
117
+ - bin/unocorn
118
+ - lib/unicorn_relay.rb
119
+ - lib/unicorn_relay/forker.rb
120
+ - lib/unicorn_relay/teardown.rb
121
+ - lib/unicorn_relay/version.rb
122
+ - spec/forker_spec.rb
123
+ - spec/spec_helper.rb
124
+ - spec/teardown_spec.rb
125
+ - unicorn_relay.gemspec
126
+ homepage: http://flori.github.com/unicorn_relay
127
+ licenses:
128
+ - Apache-2.0
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options:
132
+ - "--title"
133
+ - UnicornRelay -- More Math in Ruby
134
+ - "--main"
135
+ - README.md
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubyforge_project:
150
+ rubygems_version: 2.6.7
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: Allow controlling unicorn via supervise
154
+ test_files:
155
+ - spec/forker_spec.rb
156
+ - spec/spec_helper.rb
157
+ - spec/teardown_spec.rb