unicorn_relay 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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