unicorn-fork 6.1.1
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/.CHANGELOG.old +25 -0
- data/.document +28 -0
- data/.gitattributes +5 -0
- data/.gitignore +25 -0
- data/.mailmap +26 -0
- data/.manifest +144 -0
- data/.olddoc.yml +25 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +39 -0
- data/COPYING +674 -0
- data/DESIGN +99 -0
- data/Documentation/.gitignore +3 -0
- data/Documentation/unicorn.1 +222 -0
- data/Documentation/unicorn_rails.1 +207 -0
- data/FAQ +70 -0
- data/GIT-VERSION-FILE +1 -0
- data/GIT-VERSION-GEN +39 -0
- data/GNUmakefile +318 -0
- data/HACKING +117 -0
- data/ISSUES +102 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +67 -0
- data/Links +58 -0
- data/PHILOSOPHY +139 -0
- data/README +165 -0
- data/Rakefile +17 -0
- data/SIGNALS +123 -0
- data/Sandbox +104 -0
- data/TODO +1 -0
- data/TUNING +119 -0
- data/archive/.gitignore +3 -0
- data/archive/slrnpull.conf +4 -0
- data/bin/unicorn +129 -0
- data/bin/unicorn_rails +210 -0
- data/examples/big_app_gc.rb +3 -0
- data/examples/echo.ru +27 -0
- data/examples/init.sh +102 -0
- data/examples/logger_mp_safe.rb +26 -0
- data/examples/logrotate.conf +44 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +14 -0
- data/examples/unicorn.conf.rb +111 -0
- data/examples/unicorn.socket +11 -0
- data/examples/unicorn@.service +40 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +115 -0
- data/ext/unicorn_http/common_field_optimization.h +128 -0
- data/ext/unicorn_http/epollexclusive.h +128 -0
- data/ext/unicorn_http/ext_help.h +38 -0
- data/ext/unicorn_http/extconf.rb +40 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +91 -0
- data/ext/unicorn_http/unicorn_http.c +4348 -0
- data/ext/unicorn_http/unicorn_http.rl +1054 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn/app/old_rails/static.rb +60 -0
- data/lib/unicorn/app/old_rails.rb +36 -0
- data/lib/unicorn/cgi_wrapper.rb +148 -0
- data/lib/unicorn/configurator.rb +749 -0
- data/lib/unicorn/const.rb +22 -0
- data/lib/unicorn/http_request.rb +180 -0
- data/lib/unicorn/http_response.rb +95 -0
- data/lib/unicorn/http_server.rb +860 -0
- data/lib/unicorn/launcher.rb +63 -0
- data/lib/unicorn/oob_gc.rb +82 -0
- data/lib/unicorn/preread_input.rb +34 -0
- data/lib/unicorn/select_waiter.rb +7 -0
- data/lib/unicorn/socket_helper.rb +186 -0
- data/lib/unicorn/stream_input.rb +152 -0
- data/lib/unicorn/tee_input.rb +132 -0
- data/lib/unicorn/tmpio.rb +34 -0
- data/lib/unicorn/util.rb +91 -0
- data/lib/unicorn/version.rb +1 -0
- data/lib/unicorn/worker.rb +166 -0
- data/lib/unicorn.rb +137 -0
- data/man/man1/unicorn.1 +222 -0
- data/man/man1/unicorn_rails.1 +207 -0
- data/setup.rb +1587 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +5 -0
- data/t/README +49 -0
- data/t/active-unix-socket.t +110 -0
- data/t/back-out-of-upgrade.t +44 -0
- data/t/bin/unused_listen +40 -0
- data/t/client_body_buffer_size.ru +15 -0
- data/t/client_body_buffer_size.t +79 -0
- data/t/detach.ru +12 -0
- data/t/env.ru +4 -0
- data/t/fails-rack-lint.ru +6 -0
- data/t/heartbeat-timeout.ru +13 -0
- data/t/heartbeat-timeout.t +60 -0
- data/t/integration.ru +129 -0
- data/t/integration.t +509 -0
- data/t/lib.perl +309 -0
- data/t/listener_names.ru +5 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +18 -0
- data/t/oob_gc_path.ru +18 -0
- data/t/pid.ru +4 -0
- data/t/preread_input.ru +23 -0
- data/t/reload-bad-config.t +49 -0
- data/t/reopen-logs.ru +14 -0
- data/t/reopen-logs.t +36 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0012-reload-empty-config.sh +86 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +13 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +13 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0300-no-default-middleware.sh +20 -0
- data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
- data/t/t0301.ru +14 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +125 -0
- data/t/winch_ttin.t +64 -0
- data/t/working_directory.t +86 -0
- data/test/aggregate.rb +16 -0
- data/test/benchmark/README +60 -0
- data/test/benchmark/dd.ru +19 -0
- data/test/benchmark/ddstream.ru +51 -0
- data/test/benchmark/readinput.ru +41 -0
- data/test/benchmark/stack.ru +9 -0
- data/test/benchmark/uconnect.perl +66 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1030 -0
- data/test/test_helper.rb +307 -0
- data/test/unit/test_configurator.rb +176 -0
- data/test/unit/test_droplet.rb +29 -0
- data/test/unit/test_http_parser.rb +885 -0
- data/test/unit/test_http_parser_ng.rb +715 -0
- data/test/unit/test_server.rb +245 -0
- data/test/unit/test_signals.rb +189 -0
- data/test/unit/test_socket_helper.rb +160 -0
- data/test/unit/test_stream_input.rb +211 -0
- data/test/unit/test_tee_input.rb +304 -0
- data/test/unit/test_util.rb +132 -0
- data/test/unit/test_waiter.rb +35 -0
- data/unicorn.gemspec +49 -0
- metadata +266 -0
data/t/lib.perl
ADDED
@@ -0,0 +1,309 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@80x24.org>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
package UnicornTest;
|
5
|
+
use v5.14;
|
6
|
+
use parent qw(Exporter);
|
7
|
+
use autodie;
|
8
|
+
use Test::More;
|
9
|
+
use Socket qw(SOMAXCONN);
|
10
|
+
use Time::HiRes qw(sleep time);
|
11
|
+
use IO::Socket::INET;
|
12
|
+
use IO::Socket::UNIX;
|
13
|
+
use Carp qw(croak);
|
14
|
+
use POSIX qw(dup2 _exit setpgid :signal_h SEEK_SET F_SETFD);
|
15
|
+
use File::Temp 0.19 (); # 0.19 for ->newdir
|
16
|
+
our ($tmpdir, $errfh, $err_log, $u_sock, $u_conf, $daemon_pid,
|
17
|
+
$pid_file, $wtest_sock, $fifo);
|
18
|
+
our @EXPORT = qw(unicorn slurp tcp_server tcp_start unicorn
|
19
|
+
$tmpdir $errfh $err_log $u_sock $u_conf $daemon_pid $pid_file
|
20
|
+
$wtest_sock $fifo
|
21
|
+
SEEK_SET tcp_host_port which spawn check_stderr unix_start slurp_hdr
|
22
|
+
do_req stop_daemon sleep time mkfifo_die kill_until_dead write_file);
|
23
|
+
|
24
|
+
my ($base) = ($0 =~ m!\b([^/]+)\.[^\.]+\z!);
|
25
|
+
$tmpdir = File::Temp->newdir("unicorn-$base-XXXX", TMPDIR => 1);
|
26
|
+
|
27
|
+
$wtest_sock = "$tmpdir/wtest.sock";
|
28
|
+
$err_log = "$tmpdir/err.log";
|
29
|
+
$pid_file = "$tmpdir/pid";
|
30
|
+
$fifo = "$tmpdir/fifo";
|
31
|
+
$u_sock = "$tmpdir/u.sock";
|
32
|
+
$u_conf = "$tmpdir/u.conf.rb";
|
33
|
+
open($errfh, '+>>', $err_log);
|
34
|
+
|
35
|
+
if (my $t = $ENV{TAIL}) {
|
36
|
+
my @tail = $t =~ /tail/ ? split(/\s+/, $t) : (qw(tail -F));
|
37
|
+
push @tail, $err_log;
|
38
|
+
my $pid = fork;
|
39
|
+
if ($pid == 0) {
|
40
|
+
open STDOUT, '>&', \*STDERR;
|
41
|
+
exec @tail;
|
42
|
+
die "exec(@tail): $!";
|
43
|
+
}
|
44
|
+
say "# @tail";
|
45
|
+
sleep 0.2;
|
46
|
+
UnicornTest::AutoReap->new($pid);
|
47
|
+
}
|
48
|
+
|
49
|
+
sub kill_until_dead ($;%) {
|
50
|
+
my ($pid, %opt) = @_;
|
51
|
+
my $tries = $opt{tries} // 1000;
|
52
|
+
my $sig = $opt{sig} // 0;
|
53
|
+
while (CORE::kill($sig, $pid) && --$tries) { sleep(0.01) }
|
54
|
+
$tries or croak "PID: $pid died after signal ($sig)";
|
55
|
+
}
|
56
|
+
|
57
|
+
sub stop_daemon (;$) {
|
58
|
+
my ($is_END) = @_;
|
59
|
+
kill('TERM', $daemon_pid);
|
60
|
+
kill_until_dead $daemon_pid;
|
61
|
+
if ($is_END && CORE::kill(0, $daemon_pid)) { # after done_testing
|
62
|
+
CORE::kill('KILL', $daemon_pid);
|
63
|
+
die "daemon_pid=$daemon_pid did not die";
|
64
|
+
} else {
|
65
|
+
ok(!CORE::kill(0, $daemon_pid), 'daemonized unicorn gone');
|
66
|
+
undef $daemon_pid;
|
67
|
+
}
|
68
|
+
};
|
69
|
+
|
70
|
+
END {
|
71
|
+
diag slurp($err_log) if $tmpdir;
|
72
|
+
stop_daemon(1) if defined $daemon_pid;
|
73
|
+
};
|
74
|
+
|
75
|
+
sub check_stderr (@) {
|
76
|
+
my @log = @_;
|
77
|
+
slurp($err_log) if !@log;
|
78
|
+
diag("@log") if $ENV{V};
|
79
|
+
my @err = grep(!/NameError.*Unicorn::Waiter/, grep(/error/i, @log));
|
80
|
+
@err = grep(!/failed to set accept_filter=/, @err);
|
81
|
+
@err = grep(!/perhaps accf_.*? needs to be loaded/, @err);
|
82
|
+
is_deeply(\@err, [], 'no unexpected errors in stderr');
|
83
|
+
is_deeply([grep(/SIGKILL/, @log)], [], 'no SIGKILL in stderr');
|
84
|
+
}
|
85
|
+
|
86
|
+
sub slurp_hdr {
|
87
|
+
my ($c) = @_;
|
88
|
+
local $/ = "\r\n\r\n"; # affects both readline+chomp
|
89
|
+
chomp(my $hdr = readline($c));
|
90
|
+
my ($status, @hdr) = split(/\r\n/, $hdr);
|
91
|
+
diag explain([ $status, \@hdr ]) if $ENV{V};
|
92
|
+
($status, \@hdr);
|
93
|
+
}
|
94
|
+
|
95
|
+
sub unix_server (;$@) {
|
96
|
+
my $l = shift // $u_sock;
|
97
|
+
IO::Socket::UNIX->new(Listen => SOMAXCONN, Local => $l, Blocking => 0,
|
98
|
+
Type => SOCK_STREAM, @_);
|
99
|
+
}
|
100
|
+
|
101
|
+
sub unix_connect ($) {
|
102
|
+
IO::Socket::UNIX->new(Peer => $_[0], Type => SOCK_STREAM);
|
103
|
+
}
|
104
|
+
|
105
|
+
sub tcp_server {
|
106
|
+
my %opt = (
|
107
|
+
ReuseAddr => 1,
|
108
|
+
Proto => 'tcp',
|
109
|
+
Type => SOCK_STREAM,
|
110
|
+
Listen => SOMAXCONN,
|
111
|
+
Blocking => 0,
|
112
|
+
@_,
|
113
|
+
);
|
114
|
+
eval {
|
115
|
+
die 'IPv4-only' if $ENV{TEST_IPV4_ONLY};
|
116
|
+
require IO::Socket::INET6;
|
117
|
+
IO::Socket::INET6->new(%opt, LocalAddr => '[::1]')
|
118
|
+
} || eval {
|
119
|
+
die 'IPv6-only' if $ENV{TEST_IPV6_ONLY};
|
120
|
+
IO::Socket::INET->new(%opt, LocalAddr => '127.0.0.1')
|
121
|
+
} || BAIL_OUT "failed to create TCP server: $! ($@)";
|
122
|
+
}
|
123
|
+
|
124
|
+
sub tcp_host_port {
|
125
|
+
my ($s) = @_;
|
126
|
+
my ($h, $p) = ($s->sockhost, $s->sockport);
|
127
|
+
my $ipv4 = $s->sockdomain == AF_INET;
|
128
|
+
if (wantarray) {
|
129
|
+
$ipv4 ? ($h, $p) : ("[$h]", $p);
|
130
|
+
} else {
|
131
|
+
$ipv4 ? "$h:$p" : "[$h]:$p";
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
sub unix_start ($@) {
|
136
|
+
my ($dst, @req) = @_;
|
137
|
+
my $s = unix_connect($dst) or BAIL_OUT "unix connect $dst: $!";
|
138
|
+
$s->autoflush(1);
|
139
|
+
print $s @req, "\r\n\r\n" if @req;
|
140
|
+
$s;
|
141
|
+
}
|
142
|
+
|
143
|
+
sub tcp_start ($@) {
|
144
|
+
my ($dst, @req) = @_;
|
145
|
+
my $addr = tcp_host_port($dst);
|
146
|
+
my $s = ref($dst)->new(
|
147
|
+
Proto => 'tcp',
|
148
|
+
Type => SOCK_STREAM,
|
149
|
+
PeerAddr => $addr,
|
150
|
+
) or BAIL_OUT "failed to connect to $addr: $!";
|
151
|
+
$s->autoflush(1);
|
152
|
+
print $s @req, "\r\n\r\n" if @req;
|
153
|
+
$s;
|
154
|
+
}
|
155
|
+
|
156
|
+
sub slurp {
|
157
|
+
open my $fh, '<', $_[0];
|
158
|
+
local $/ if !wantarray;
|
159
|
+
readline($fh);
|
160
|
+
}
|
161
|
+
|
162
|
+
sub spawn {
|
163
|
+
my $env = ref($_[0]) eq 'HASH' ? shift : undef;
|
164
|
+
my $opt = ref($_[-1]) eq 'HASH' ? pop : {};
|
165
|
+
my @cmd = @_;
|
166
|
+
my $old = POSIX::SigSet->new;
|
167
|
+
my $set = POSIX::SigSet->new;
|
168
|
+
$set->fillset or die "sigfillset: $!";
|
169
|
+
sigprocmask(SIG_SETMASK, $set, $old) or die "SIG_SETMASK: $!";
|
170
|
+
pipe(my $r, my $w);
|
171
|
+
my $pid = fork;
|
172
|
+
if ($pid == 0) {
|
173
|
+
close $r;
|
174
|
+
$SIG{__DIE__} = sub {
|
175
|
+
warn(@_);
|
176
|
+
syswrite($w, my $num = $! + 0);
|
177
|
+
_exit(1);
|
178
|
+
};
|
179
|
+
|
180
|
+
# pretend to be systemd (cf. sd_listen_fds(3))
|
181
|
+
my $cfd;
|
182
|
+
for ($cfd = 0; ($cfd < 3) || defined($opt->{$cfd}); $cfd++) {
|
183
|
+
my $io = $opt->{$cfd} // next;
|
184
|
+
my $pfd = fileno($io);
|
185
|
+
if ($pfd == $cfd) {
|
186
|
+
fcntl($io, F_SETFD, 0);
|
187
|
+
} else {
|
188
|
+
dup2($pfd, $cfd) // die "dup2($pfd, $cfd): $!";
|
189
|
+
}
|
190
|
+
}
|
191
|
+
if (($cfd - 3) > 0) {
|
192
|
+
$env->{LISTEN_PID} = $$;
|
193
|
+
$env->{LISTEN_FDS} = $cfd - 3;
|
194
|
+
}
|
195
|
+
|
196
|
+
if (defined(my $pgid = $opt->{pgid})) {
|
197
|
+
setpgid(0, $pgid) // die "setpgid(0, $pgid): $!";
|
198
|
+
}
|
199
|
+
$SIG{$_} = 'DEFAULT' for grep(!/^__/, keys %SIG);
|
200
|
+
if (defined(my $cd = $opt->{-C})) { chdir $cd }
|
201
|
+
$old->delset(POSIX::SIGCHLD) or die "sigdelset CHLD: $!";
|
202
|
+
sigprocmask(SIG_SETMASK, $old) or die "SIG_SETMASK: ~CHLD: $!";
|
203
|
+
@ENV{keys %$env} = values(%$env) if $env;
|
204
|
+
exec { $cmd[0] } @cmd;
|
205
|
+
die "exec @cmd: $!";
|
206
|
+
}
|
207
|
+
close $w;
|
208
|
+
sigprocmask(SIG_SETMASK, $old) or die "SIG_SETMASK(old): $!";
|
209
|
+
if (my $cerrnum = do { local $/, <$r> }) {
|
210
|
+
$! = $cerrnum;
|
211
|
+
die "@cmd PID=$pid died: $!";
|
212
|
+
}
|
213
|
+
$pid;
|
214
|
+
}
|
215
|
+
|
216
|
+
sub which {
|
217
|
+
my ($file) = @_;
|
218
|
+
return $file if index($file, '/') >= 0;
|
219
|
+
for my $p (split(/:/, $ENV{PATH})) {
|
220
|
+
$p .= "/$file";
|
221
|
+
return $p if -x $p;
|
222
|
+
}
|
223
|
+
undef;
|
224
|
+
}
|
225
|
+
|
226
|
+
# returns an AutoReap object
|
227
|
+
sub unicorn {
|
228
|
+
my %env;
|
229
|
+
if (ref($_[0]) eq 'HASH') {
|
230
|
+
my $e = shift;
|
231
|
+
%env = %$e;
|
232
|
+
}
|
233
|
+
my @args = @_;
|
234
|
+
push(@args, {}) if ref($args[-1]) ne 'HASH';
|
235
|
+
$args[-1]->{2} //= $errfh; # stderr default
|
236
|
+
|
237
|
+
state $ruby = which($ENV{RUBY} // 'ruby');
|
238
|
+
state $lib = File::Spec->rel2abs('lib');
|
239
|
+
state $ver = $ENV{TEST_RUBY_VERSION} // `$ruby -e 'print RUBY_VERSION'`;
|
240
|
+
state $eng = $ENV{TEST_RUBY_ENGINE} // `$ruby -e 'print RUBY_ENGINE'`;
|
241
|
+
state $ext = File::Spec->rel2abs("test/$eng-$ver/ext/unicorn_http");
|
242
|
+
state $exe = File::Spec->rel2abs("test/$eng-$ver/bin/unicorn");
|
243
|
+
state $rl = $ENV{RUBYLIB} ? "$lib:$ext:$ENV{RUBYLIB}" : "$lib:$ext";
|
244
|
+
$env{RUBYLIB} = $rl;
|
245
|
+
my $pid = spawn(\%env, $ruby, $exe, @args);
|
246
|
+
UnicornTest::AutoReap->new($pid);
|
247
|
+
}
|
248
|
+
|
249
|
+
sub do_req ($@) {
|
250
|
+
my ($dst, @req) = @_;
|
251
|
+
my $c = ref($dst) ? tcp_start($dst, @req) : unix_start($dst, @req);
|
252
|
+
return $c if !wantarray;
|
253
|
+
my ($status, $hdr);
|
254
|
+
# read headers iff HTTP/1.x request, HTTP/0.9 remains supported
|
255
|
+
my ($first) = (join('', @req) =~ m!\A([^\r\n]+)!);
|
256
|
+
($status, $hdr) = slurp_hdr($c) if $first =~ m{\s*HTTP/\S+$};
|
257
|
+
my $bdy = do { local $/; <$c> };
|
258
|
+
close $c;
|
259
|
+
($status, $hdr, $bdy);
|
260
|
+
}
|
261
|
+
|
262
|
+
sub mkfifo_die ($;$) {
|
263
|
+
POSIX::mkfifo($_[0], $_[1] // 0600) or croak "mkfifo: $!";
|
264
|
+
}
|
265
|
+
|
266
|
+
sub write_file ($$@) { # mode, filename, LIST (for print)
|
267
|
+
open(my $fh, shift, shift);
|
268
|
+
print $fh @_;
|
269
|
+
# return $fh for futher writes if user wants it:
|
270
|
+
defined(wantarray) && !wantarray ? $fh : close $fh;
|
271
|
+
}
|
272
|
+
|
273
|
+
# automatically kill + reap children when this goes out-of-scope
|
274
|
+
package UnicornTest::AutoReap;
|
275
|
+
use v5.14;
|
276
|
+
use autodie;
|
277
|
+
|
278
|
+
sub new {
|
279
|
+
my (undef, $pid) = @_;
|
280
|
+
bless { pid => $pid, owner => $$ }, __PACKAGE__
|
281
|
+
}
|
282
|
+
|
283
|
+
sub do_kill {
|
284
|
+
my ($self, $sig) = @_;
|
285
|
+
kill($sig // 'TERM', $self->{pid});
|
286
|
+
}
|
287
|
+
|
288
|
+
sub join {
|
289
|
+
my ($self, $sig) = @_;
|
290
|
+
my $pid = delete $self->{pid} or return;
|
291
|
+
kill($sig, $pid) if defined $sig;
|
292
|
+
my $ret = waitpid($pid, 0);
|
293
|
+
$ret == $pid or die "BUG: waitpid($pid) != $ret";
|
294
|
+
}
|
295
|
+
|
296
|
+
sub DESTROY {
|
297
|
+
my ($self) = @_;
|
298
|
+
return if $self->{owner} != $$;
|
299
|
+
$self->join('TERM');
|
300
|
+
}
|
301
|
+
|
302
|
+
package main; # inject ourselves into the t/*.t script
|
303
|
+
UnicornTest->import;
|
304
|
+
Test::More->import;
|
305
|
+
# try to ensure ->DESTROY fires:
|
306
|
+
$SIG{TERM} = sub { exit(15 + 128) };
|
307
|
+
$SIG{INT} = sub { exit(2 + 128) };
|
308
|
+
$SIG{PIPE} = sub { exit(13 + 128) };
|
309
|
+
1;
|
data/t/listener_names.ru
ADDED
data/t/my-tap-lib.sh
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Copyright (c) 2009, 2010 Eric Wong <normalperson@yhbt.net>
|
3
|
+
#
|
4
|
+
# TAP-producing shell library for POSIX-compliant Bourne shells We do
|
5
|
+
# not _rely_ on Bourne Again features, though we will use "set -o
|
6
|
+
# pipefail" from ksh93 or bash 3 if available
|
7
|
+
#
|
8
|
+
# Only generic, non-project/non-language-specific stuff goes here. We
|
9
|
+
# only have POSIX dependencies for the core tests (without --verbose),
|
10
|
+
# though we'll enable useful non-POSIX things if they're available.
|
11
|
+
#
|
12
|
+
# This test library is intentionally unforgiving, it does not support
|
13
|
+
# skipping tests nor continuing after any failure. Any failures
|
14
|
+
# immediately halt execution as do any references to undefined
|
15
|
+
# variables.
|
16
|
+
#
|
17
|
+
# When --verbose is specified, we always prefix stdout/stderr
|
18
|
+
# output with "#" to avoid confusing TAP consumers. Otherwise
|
19
|
+
# the normal stdout/stderr streams are redirected to /dev/null
|
20
|
+
|
21
|
+
# dup normal stdout(fd=1) and stderr (fd=2) to fd=3 and fd=4 respectively
|
22
|
+
# normal TAP output goes to fd=3, nothing should go to fd=4
|
23
|
+
exec 3>&1 4>&2
|
24
|
+
|
25
|
+
# ensure a sane environment
|
26
|
+
TZ=UTC LC_ALL=C LANG=C
|
27
|
+
export LANG LC_ALL TZ
|
28
|
+
unset CDPATH
|
29
|
+
|
30
|
+
# pipefail is non-POSIX, but very useful in ksh93/bash
|
31
|
+
( set -o pipefail 2>/dev/null ) && set -o pipefail
|
32
|
+
|
33
|
+
SED=${SED-sed}
|
34
|
+
|
35
|
+
# Unlike other test frameworks, we are unforgiving and bail immediately
|
36
|
+
# on any failures. We do this because we're lazy about error handling
|
37
|
+
# and also because we believe anything broken should not be allowed to
|
38
|
+
# propagate throughout the rest of the test
|
39
|
+
set -e
|
40
|
+
set -u
|
41
|
+
|
42
|
+
# name of our test
|
43
|
+
T=${0##*/}
|
44
|
+
|
45
|
+
t_expect_nr=-1
|
46
|
+
t_nr=0
|
47
|
+
t_current=
|
48
|
+
t_complete=false
|
49
|
+
|
50
|
+
# list of files to remove unconditionally on exit
|
51
|
+
T_RM_LIST=
|
52
|
+
|
53
|
+
# list of files to remove only on successful exit
|
54
|
+
T_OK_RM_LIST=
|
55
|
+
|
56
|
+
# emit output to stdout, it'll be parsed by the TAP consumer
|
57
|
+
# so it must be TAP-compliant output
|
58
|
+
t_echo () {
|
59
|
+
echo >&3 "$@"
|
60
|
+
}
|
61
|
+
|
62
|
+
# emits non-parsed information to stdout, it will be prefixed with a '#'
|
63
|
+
# to not throw off TAP consumers
|
64
|
+
t_info () {
|
65
|
+
t_echo '#' "$@"
|
66
|
+
}
|
67
|
+
|
68
|
+
# exit with an error and print a diagnostic
|
69
|
+
die () {
|
70
|
+
echo >&2 "$@"
|
71
|
+
exit 1
|
72
|
+
}
|
73
|
+
|
74
|
+
# our at_exit handler, it'll fire for all exits except SIGKILL (unavoidable)
|
75
|
+
t_at_exit () {
|
76
|
+
code=$?
|
77
|
+
set +e
|
78
|
+
if test $code -eq 0
|
79
|
+
then
|
80
|
+
$t_complete || {
|
81
|
+
t_info "t_done not called"
|
82
|
+
code=1
|
83
|
+
}
|
84
|
+
elif test -n "$t_current"
|
85
|
+
then
|
86
|
+
t_echo "not ok $t_nr - $t_current"
|
87
|
+
fi
|
88
|
+
if test $t_expect_nr -ne -1
|
89
|
+
then
|
90
|
+
test $t_expect_nr -eq $t_nr || {
|
91
|
+
t_info "planned $t_expect_nr tests but ran $t_nr"
|
92
|
+
test $code -ne 0 || code=1
|
93
|
+
}
|
94
|
+
fi
|
95
|
+
$t_complete || {
|
96
|
+
t_info "unexpected test failure"
|
97
|
+
test $code -ne 0 || code=1
|
98
|
+
}
|
99
|
+
rm -f $T_RM_LIST
|
100
|
+
test $code -eq 0 && rm -f $T_OK_RM_LIST
|
101
|
+
set +x
|
102
|
+
exec >&3 2>&4
|
103
|
+
t_close_fds
|
104
|
+
exit $code
|
105
|
+
}
|
106
|
+
|
107
|
+
# close test-specific extra file descriptors
|
108
|
+
t_close_fds () {
|
109
|
+
exec 3>&- 4>&-
|
110
|
+
}
|
111
|
+
|
112
|
+
# call this at the start of your test to specify the number of tests
|
113
|
+
# you plan to run
|
114
|
+
t_plan () {
|
115
|
+
test "$1" -ge 1 || die "must plan at least one test"
|
116
|
+
test $t_expect_nr -eq -1 || die "tried to plan twice in one test"
|
117
|
+
t_expect_nr=$1
|
118
|
+
shift
|
119
|
+
t_echo 1..$t_expect_nr "#" "$@"
|
120
|
+
trap t_at_exit EXIT
|
121
|
+
}
|
122
|
+
|
123
|
+
_t_checkup () {
|
124
|
+
test $t_expect_nr -le 0 && die "no tests planned"
|
125
|
+
test -n "$t_current" && t_echo "ok $t_nr - $t_current"
|
126
|
+
true
|
127
|
+
}
|
128
|
+
|
129
|
+
# finalizes any previously test and starts a new one
|
130
|
+
t_begin () {
|
131
|
+
_t_checkup
|
132
|
+
t_nr=$(( $t_nr + 1 ))
|
133
|
+
t_current="$1"
|
134
|
+
|
135
|
+
# just in case somebody wanted to cheat us:
|
136
|
+
set -e
|
137
|
+
}
|
138
|
+
|
139
|
+
# finalizes the current test without starting a new one
|
140
|
+
t_end () {
|
141
|
+
_t_checkup
|
142
|
+
t_current=
|
143
|
+
}
|
144
|
+
|
145
|
+
# run this to signify the end of your test
|
146
|
+
t_done () {
|
147
|
+
_t_checkup
|
148
|
+
t_current=
|
149
|
+
t_complete=true
|
150
|
+
test $t_expect_nr -eq $t_nr || exit 1
|
151
|
+
exit 0
|
152
|
+
}
|
153
|
+
|
154
|
+
# create and assign named-pipes to variable _names_ passed to this function
|
155
|
+
t_fifos () {
|
156
|
+
for _id in "$@"
|
157
|
+
do
|
158
|
+
_name=$_id
|
159
|
+
_tmp=$(mktemp -t $T.$$.$_id.XXXXXXXX)
|
160
|
+
eval "$_id=$_tmp"
|
161
|
+
rm -f $_tmp
|
162
|
+
mkfifo $_tmp
|
163
|
+
T_RM_LIST="$T_RM_LIST $_tmp"
|
164
|
+
done
|
165
|
+
}
|
166
|
+
|
167
|
+
t_verbose=false t_trace=false
|
168
|
+
|
169
|
+
while test "$#" -ne 0
|
170
|
+
do
|
171
|
+
arg="$1"
|
172
|
+
shift
|
173
|
+
case $arg in
|
174
|
+
-v|--verbose) t_verbose=true ;;
|
175
|
+
--trace) t_trace=true t_verbose=true ;;
|
176
|
+
*) die "Unknown option: $arg" ;;
|
177
|
+
esac
|
178
|
+
done
|
179
|
+
|
180
|
+
# we always only setup stdout, nothing should end up in the "real" stderr
|
181
|
+
if $t_verbose
|
182
|
+
then
|
183
|
+
if test x"$(which mktemp 2>/dev/null)" = x
|
184
|
+
then
|
185
|
+
die "mktemp(1) not available for --verbose"
|
186
|
+
fi
|
187
|
+
t_fifos t_stdout t_stderr
|
188
|
+
|
189
|
+
(
|
190
|
+
# use a subshell so seds are not waitable
|
191
|
+
$SED -e 's/^/#: /' < $t_stdout &
|
192
|
+
$SED -e 's/^/#! /' < $t_stderr &
|
193
|
+
) &
|
194
|
+
wait
|
195
|
+
exec > $t_stdout 2> $t_stderr
|
196
|
+
else
|
197
|
+
exec > /dev/null 2> /dev/null
|
198
|
+
fi
|
199
|
+
|
200
|
+
$t_trace && set -x
|
201
|
+
true
|
data/t/oob_gc.ru
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#\-E none
|
2
|
+
# frozen_string_literal: false
|
3
|
+
require 'unicorn/oob_gc'
|
4
|
+
use Rack::ContentLength
|
5
|
+
use Rack::ContentType, "text/plain"
|
6
|
+
use Unicorn::OobGC
|
7
|
+
$gc_started = false
|
8
|
+
|
9
|
+
# Mock GC.start
|
10
|
+
def GC.start
|
11
|
+
$gc_started = true
|
12
|
+
end
|
13
|
+
run lambda { |env|
|
14
|
+
if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"]
|
15
|
+
$gc_started = false
|
16
|
+
end
|
17
|
+
[ 200, {}, [ "#$gc_started\n" ] ]
|
18
|
+
}
|
data/t/oob_gc_path.ru
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#\-E none
|
2
|
+
# frozen_string_literal: false
|
3
|
+
require 'unicorn/oob_gc'
|
4
|
+
use Rack::ContentLength
|
5
|
+
use Rack::ContentType, "text/plain"
|
6
|
+
use Unicorn::OobGC, 5, /BAD/
|
7
|
+
$gc_started = false
|
8
|
+
|
9
|
+
# Mock GC.start
|
10
|
+
def GC.start
|
11
|
+
$gc_started = true
|
12
|
+
end
|
13
|
+
run lambda { |env|
|
14
|
+
if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"]
|
15
|
+
$gc_started = false
|
16
|
+
end
|
17
|
+
[ 200, {}, [ "#$gc_started\n" ] ]
|
18
|
+
}
|
data/t/pid.ru
ADDED
data/t/preread_input.ru
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#\-E none
|
2
|
+
# frozen_string_literal: false
|
3
|
+
require 'digest/md5'
|
4
|
+
require 'unicorn/preread_input'
|
5
|
+
use Unicorn::PrereadInput
|
6
|
+
nr = 0
|
7
|
+
run lambda { |env|
|
8
|
+
$stderr.write "app dispatch: #{nr += 1}\n"
|
9
|
+
input = env["rack.input"]
|
10
|
+
dig = Digest::MD5.new
|
11
|
+
if buf = input.read(16384)
|
12
|
+
begin
|
13
|
+
dig.update(buf)
|
14
|
+
end while input.read(16384, buf)
|
15
|
+
buf.clear # remove this call if Ruby ever gets escape analysis
|
16
|
+
end
|
17
|
+
if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i
|
18
|
+
cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']]
|
19
|
+
cmd5_bin = cmd5_b64.unpack('m')[0]
|
20
|
+
return [500, {}, [ cmd5_b64 ] ] if cmd5_bin != dig.digest
|
21
|
+
end
|
22
|
+
[ 200, {}, [ dig.hexdigest ] ]
|
23
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
5
|
+
use autodie;
|
6
|
+
my $srv = tcp_server();
|
7
|
+
my $host_port = tcp_host_port($srv);
|
8
|
+
my $ru = "$tmpdir/config.ru";
|
9
|
+
|
10
|
+
write_file '>', $ru, <<'EOM';
|
11
|
+
use Rack::ContentLength
|
12
|
+
use Rack::ContentType, 'text/plain'
|
13
|
+
config = ru = "hello world\n" # check for config variable conflicts, too
|
14
|
+
run lambda { |env| [ 200, {}, [ ru.to_s ] ] }
|
15
|
+
EOM
|
16
|
+
|
17
|
+
write_file '>', $u_conf, <<EOM;
|
18
|
+
preload_app true
|
19
|
+
stderr_path "$err_log"
|
20
|
+
EOM
|
21
|
+
|
22
|
+
my $ar = unicorn(qw(-E none -c), $u_conf, $ru, { 3 => $srv });
|
23
|
+
my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0');
|
24
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid at start');
|
25
|
+
is($bdy, "hello world\n", 'body matches expected');
|
26
|
+
|
27
|
+
write_file '>>', $ru, <<'EOM';
|
28
|
+
....this better be a syntax error in any version of ruby...
|
29
|
+
EOM
|
30
|
+
|
31
|
+
$ar->do_kill('HUP'); # reload
|
32
|
+
my @l;
|
33
|
+
for (1..1000) {
|
34
|
+
@l = grep(/(?:done|error) reloading/, slurp($err_log)) and
|
35
|
+
last;
|
36
|
+
sleep 0.011;
|
37
|
+
}
|
38
|
+
diag slurp($err_log) if $ENV{V};
|
39
|
+
ok(grep(/error reloading/, @l), 'got error reloading');
|
40
|
+
open my $fh, '>', $err_log; # truncate
|
41
|
+
close $fh;
|
42
|
+
|
43
|
+
($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0');
|
44
|
+
like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid afte reload');
|
45
|
+
is($bdy, "hello world\n", 'body matches expected after reload');
|
46
|
+
|
47
|
+
check_stderr;
|
48
|
+
undef $tmpdir; # quiet t/lib.perl END{}
|
49
|
+
done_testing;
|
data/t/reopen-logs.ru
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
use Rack::ContentLength
|
3
|
+
use Rack::ContentType, "text/plain"
|
4
|
+
run lambda { |env|
|
5
|
+
|
6
|
+
# our File objects for stderr/stdout should always have #path
|
7
|
+
# and be sync=true
|
8
|
+
ok = $stderr.sync &&
|
9
|
+
$stdout.sync &&
|
10
|
+
String === $stderr.path &&
|
11
|
+
String === $stdout.path
|
12
|
+
|
13
|
+
[ 200, {}, [ "#{ok}\n" ] ]
|
14
|
+
}
|
data/t/reopen-logs.t
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#!perl -w
|
2
|
+
# Copyright (C) unicorn hackers <unicorn-public@yhbt.net>
|
3
|
+
# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
|
4
|
+
use v5.14; BEGIN { require './t/lib.perl' };
|
5
|
+
use autodie;
|
6
|
+
my $srv = tcp_server();
|
7
|
+
my $out_log = "$tmpdir/out.log";
|
8
|
+
write_file '>', $u_conf, <<EOM;
|
9
|
+
stderr_path "$err_log"
|
10
|
+
stdout_path "$out_log"
|
11
|
+
EOM
|
12
|
+
|
13
|
+
my $auto_reap = unicorn('-c', $u_conf, 't/reopen-logs.ru', { 3 => $srv } );
|
14
|
+
my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0');
|
15
|
+
is($bdy, "true\n", 'logs opened');
|
16
|
+
|
17
|
+
rename($err_log, "$err_log.rot");
|
18
|
+
rename($out_log, "$out_log.rot");
|
19
|
+
|
20
|
+
$auto_reap->do_kill('USR1');
|
21
|
+
|
22
|
+
my $tries = 1000;
|
23
|
+
while (!-f $err_log && --$tries) { sleep 0.01 };
|
24
|
+
while (!-f $out_log && --$tries) { sleep 0.01 };
|
25
|
+
|
26
|
+
ok(-f $out_log, 'stdout_path recreated after USR1');
|
27
|
+
ok(-f $err_log, 'stderr_path recreated after USR1');
|
28
|
+
|
29
|
+
($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0');
|
30
|
+
is($bdy, "true\n", 'logs reopened with sync==true');
|
31
|
+
|
32
|
+
$auto_reap->join('QUIT');
|
33
|
+
is($?, 0, 'no error on exit');
|
34
|
+
check_stderr;
|
35
|
+
undef $tmpdir;
|
36
|
+
done_testing;
|