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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.utilsrc +25 -0
- data/Gemfile +5 -0
- data/README.md +0 -0
- data/Rakefile +27 -0
- data/VERSION +1 -0
- data/bin/unicorn_relay +9 -0
- data/bin/unocorn +49 -0
- data/lib/unicorn_relay.rb +9 -0
- data/lib/unicorn_relay/forker.rb +192 -0
- data/lib/unicorn_relay/teardown.rb +38 -0
- data/lib/unicorn_relay/version.rb +8 -0
- data/spec/forker_spec.rb +274 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/teardown_spec.rb +73 -0
- data/unicorn_relay.gemspec +50 -0
- metadata +157 -0
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
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
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
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,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
|
data/spec/forker_spec.rb
ADDED
|
@@ -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
|
data/spec/spec_helper.rb
ADDED
|
@@ -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
|