rainbows 0.1.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.
- data/.document +11 -0
- data/.gitignore +18 -0
- data/COPYING +339 -0
- data/DEPLOY +29 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/rainbows.1.txt +159 -0
- data/FAQ +50 -0
- data/GIT-VERSION-GEN +41 -0
- data/GNUmakefile +156 -0
- data/LICENSE +55 -0
- data/README +122 -0
- data/Rakefile +103 -0
- data/SIGNALS +94 -0
- data/TODO +20 -0
- data/TUNING +31 -0
- data/bin/rainbows +166 -0
- data/lib/rainbows.rb +53 -0
- data/lib/rainbows/base.rb +69 -0
- data/lib/rainbows/const.rb +24 -0
- data/lib/rainbows/http_response.rb +35 -0
- data/lib/rainbows/http_server.rb +47 -0
- data/lib/rainbows/revactor.rb +158 -0
- data/lib/rainbows/revactor/tee_input.rb +44 -0
- data/lib/rainbows/thread_pool.rb +96 -0
- data/lib/rainbows/thread_spawn.rb +79 -0
- data/local.mk.sample +54 -0
- data/rainbows.gemspec +47 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +64 -0
- data/t/bin/unused_listen +39 -0
- data/t/sha1.ru +17 -0
- data/t/t0000-basic.sh +18 -0
- data/t/t1000-thread-pool-basic.sh +53 -0
- data/t/t2000-thread-spawn-basic.sh +50 -0
- data/t/t3000-revactor-basic.sh +52 -0
- data/t/t3100-revactor-tee-input.sh +49 -0
- data/t/test-lib.sh +41 -0
- data/vs_Unicorn +48 -0
- metadata +135 -0
data/t/.gitignore
ADDED
data/t/GNUmakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# we can run tests in parallel with GNU make
|
2
|
+
|
3
|
+
all::
|
4
|
+
|
5
|
+
ruby = ruby
|
6
|
+
rainbows_lib := $(shell cd ../lib && pwd)
|
7
|
+
-include ../local.mk
|
8
|
+
ifeq ($(RUBY_VERSION),)
|
9
|
+
RUBY_VERSION := $(shell $(ruby) -e 'puts RUBY_VERSION')
|
10
|
+
endif
|
11
|
+
|
12
|
+
ifeq ($(RUBYLIB),)
|
13
|
+
RUBYLIB := $(rainbows_lib)
|
14
|
+
else
|
15
|
+
RUBYLIB := $(rainbows_lib):$(RUBYLIB)
|
16
|
+
endif
|
17
|
+
export RUBYLIB
|
18
|
+
|
19
|
+
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
20
|
+
|
21
|
+
all:: $(T)
|
22
|
+
|
23
|
+
# can't rely on "set -o pipefail" since we don't require bash or ksh93 :<
|
24
|
+
t_code = $@-$(RUBY_VERSION).code
|
25
|
+
t_log = $@-$(RUBY_VERSION).log
|
26
|
+
t_run = $(TRACER) $(SHELL) $(TEST_OPTS) $@
|
27
|
+
|
28
|
+
# prefix stdout messages with ':', and stderr messages with '!'
|
29
|
+
t_wrap = ( ( ( $(RM) $(t_code); \
|
30
|
+
$(t_run); \
|
31
|
+
echo $$? > $(t_code) ) \
|
32
|
+
| sed 's/^/$(pfx):/' 1>&3 ) 2>&1 \
|
33
|
+
| sed 's/^/$(pfx)!/' 1>&2 ) 3>&1
|
34
|
+
|
35
|
+
ifndef V
|
36
|
+
quiet_pre = @echo '* $@';
|
37
|
+
quiet_post = > $(t_log) 2>&1; exit $$(cat $(t_code))
|
38
|
+
pfx =
|
39
|
+
else
|
40
|
+
quiet_pre = @echo '* $@';
|
41
|
+
quiet_post = 2>&1 | tee $(t_log); exit $$(cat $(t_code))
|
42
|
+
pfx = $@
|
43
|
+
endif
|
44
|
+
|
45
|
+
# TRACER='strace -f -o $@.strace -s 100000'
|
46
|
+
run_test = $(quiet_pre) ( $(t_wrap) ) $(quiet_post)
|
47
|
+
|
48
|
+
test-bin-$(RUBY_VERSION)/rainbows: ruby_bin = $(shell which $(ruby))
|
49
|
+
test-bin-$(RUBY_VERSION)/rainbows: ../bin/rainbows
|
50
|
+
mkdir -p $(@D)
|
51
|
+
install -m 755 $^ $@+
|
52
|
+
$(ruby) -i -p -e '$$_.gsub!(%r{^#!.*$$},"#!$(ruby_bin)")' $@+
|
53
|
+
cmp $@+ $@ || mv $@+ $@
|
54
|
+
$(RM) $@+
|
55
|
+
|
56
|
+
$(T): export ruby := $(ruby)
|
57
|
+
$(T): export PATH := $(CURDIR)/test-bin-$(RUBY_VERSION):$(PATH)
|
58
|
+
$(T): test-bin-$(RUBY_VERSION)/rainbows
|
59
|
+
$(run_test)
|
60
|
+
|
61
|
+
clean:
|
62
|
+
$(RM) -r *.log *.code test-bin-$(RUBY_VERSION)
|
63
|
+
|
64
|
+
.PHONY: $(T) clean
|
data/t/bin/unused_listen
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# this is to remain compatible with the unused_port function in the
|
3
|
+
# Unicorn test/test_helper.rb file
|
4
|
+
require 'socket'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
default_port = 8080
|
8
|
+
addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
9
|
+
retries = 100
|
10
|
+
base = 5000
|
11
|
+
port = sock = lock_path = nil
|
12
|
+
|
13
|
+
begin
|
14
|
+
begin
|
15
|
+
port = base + rand(32768 - base)
|
16
|
+
while port == default_port
|
17
|
+
port = base + rand(32768 - base)
|
18
|
+
end
|
19
|
+
|
20
|
+
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
21
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
22
|
+
sock.listen(5)
|
23
|
+
rescue Errno::EADDRINUSE, Errno::EACCES
|
24
|
+
sock.close rescue nil
|
25
|
+
retry if (retries -= 1) >= 0
|
26
|
+
end
|
27
|
+
|
28
|
+
# since we'll end up closing the random port we just got, there's a race
|
29
|
+
# condition could allow the random port we just chose to reselect itself
|
30
|
+
# when running tests in parallel with gmake. Create a lock file while
|
31
|
+
# we have the port here to ensure that does not happen.
|
32
|
+
lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
|
33
|
+
lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
|
34
|
+
rescue Errno::EEXIST
|
35
|
+
sock.close rescue nil
|
36
|
+
retry
|
37
|
+
end
|
38
|
+
sock.close rescue nil
|
39
|
+
puts "listen=#{addr}:#{port} lock_path=#{lock_path}"
|
data/t/sha1.ru
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# SHA1 checksum generator
|
2
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
3
|
+
require 'digest/sha1'
|
4
|
+
use Rack::ContentLength
|
5
|
+
app = lambda do |env|
|
6
|
+
/\A100-continue\z/i =~ env['HTTP_EXPECT'] and
|
7
|
+
return [ 100, {}, [] ]
|
8
|
+
digest = Digest::SHA1.new
|
9
|
+
input = env['rack.input']
|
10
|
+
buf = input.read(bs)
|
11
|
+
begin
|
12
|
+
digest.update(buf)
|
13
|
+
end while input.read(bs, buf)
|
14
|
+
|
15
|
+
[ 200, {'Content-Type' => 'text/plain'}, [ digest.hexdigest << "\n" ] ]
|
16
|
+
end
|
17
|
+
run app
|
data/t/t0000-basic.sh
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
eval $(unused_listen)
|
5
|
+
config_ru=$(mktemp -t rainbows.$$.XXXXXXXX.config.ru)
|
6
|
+
pid=$(mktemp -t rainbows.$$.XXXXXXXX.pid)
|
7
|
+
TEST_RM_LIST="$TEST_RM_LIST $config_ru $lock_path"
|
8
|
+
|
9
|
+
cat > $config_ru <<\EOF
|
10
|
+
use Rack::ContentLength
|
11
|
+
use Rack::ContentType
|
12
|
+
run lambda { |env| [ 200, {}, [ env.inspect << "\n" ] ] }
|
13
|
+
EOF
|
14
|
+
|
15
|
+
rainbows $config_ru -l $listen --pid $pid &
|
16
|
+
wait_for_pid $pid
|
17
|
+
curl -sSfv http://$listen/
|
18
|
+
kill $(cat $pid)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
eval $(unused_listen)
|
5
|
+
config_ru=$(mktemp -t rainbows.$$.XXXXXXXX.config.ru)
|
6
|
+
unicorn_config=$(mktemp -t rainbows.$$.XXXXXXXX.unicorn.rb)
|
7
|
+
curl_out=$(mktemp -t rainbows.$$.XXXXXXXX.curl.out)
|
8
|
+
curl_err=$(mktemp -t rainbows.$$.XXXXXXXX.curl.err)
|
9
|
+
pid=$(mktemp -t rainbows.$$.XXXXXXXX.pid)
|
10
|
+
TEST_RM_LIST="$TEST_RM_LIST $config_ru $unicorn_config $lock_path"
|
11
|
+
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err"
|
12
|
+
|
13
|
+
cat > $config_ru <<\EOF
|
14
|
+
use Rack::ContentLength
|
15
|
+
use Rack::ContentType
|
16
|
+
run lambda { |env|
|
17
|
+
sleep 1
|
18
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
19
|
+
}
|
20
|
+
EOF
|
21
|
+
|
22
|
+
nr_client=30
|
23
|
+
nr_thread=10
|
24
|
+
|
25
|
+
cat > $unicorn_config <<EOF
|
26
|
+
listen "$listen"
|
27
|
+
pid "$pid"
|
28
|
+
Rainbows! do
|
29
|
+
use :ThreadPool
|
30
|
+
worker_connections $nr_thread
|
31
|
+
end
|
32
|
+
EOF
|
33
|
+
|
34
|
+
rainbows -D $config_ru -c $unicorn_config
|
35
|
+
wait_for_pid $pid
|
36
|
+
|
37
|
+
start=$(date +%s)
|
38
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
39
|
+
do
|
40
|
+
( curl -sSf http://$listen/$i >> $curl_out 2>> $curl_err ) &
|
41
|
+
done
|
42
|
+
wait
|
43
|
+
echo elapsed=$(( $(date +%s) - $start ))
|
44
|
+
|
45
|
+
kill $(cat $pid)
|
46
|
+
|
47
|
+
! test -s $curl_err
|
48
|
+
test x"$(wc -l < $curl_out)" = x$nr_client
|
49
|
+
|
50
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
51
|
+
|
52
|
+
test "$nr" -le $nr_thread
|
53
|
+
test "$nr" -gt 1
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
eval $(unused_listen)
|
5
|
+
config_ru=$(mktemp -t rainbows.$$.XXXXXXXX.config.ru)
|
6
|
+
unicorn_config=$(mktemp -t rainbows.$$.XXXXXXXX.unicorn.rb)
|
7
|
+
curl_out=$(mktemp -t rainbows.$$.XXXXXXXX.curl.out)
|
8
|
+
curl_err=$(mktemp -t rainbows.$$.XXXXXXXX.curl.err)
|
9
|
+
pid=$(mktemp -t rainbows.$$.XXXXXXXX.pid)
|
10
|
+
TEST_RM_LIST="$TEST_RM_LIST $config_ru $unicorn_config $lock_path"
|
11
|
+
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err"
|
12
|
+
|
13
|
+
cat > $config_ru <<\EOF
|
14
|
+
use Rack::ContentLength
|
15
|
+
use Rack::ContentType
|
16
|
+
run lambda { |env|
|
17
|
+
sleep 1
|
18
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
19
|
+
}
|
20
|
+
EOF
|
21
|
+
|
22
|
+
nr_client=30
|
23
|
+
nr_thread=10
|
24
|
+
|
25
|
+
cat > $unicorn_config <<EOF
|
26
|
+
listen "$listen"
|
27
|
+
pid "$pid"
|
28
|
+
Rainbows! do
|
29
|
+
use :ThreadSpawn
|
30
|
+
worker_connections $nr_thread
|
31
|
+
end
|
32
|
+
EOF
|
33
|
+
|
34
|
+
rainbows -D $config_ru -c $unicorn_config
|
35
|
+
wait_for_pid $pid
|
36
|
+
|
37
|
+
start=$(date +%s)
|
38
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
39
|
+
do
|
40
|
+
( curl -sSf http://$listen/$i >> $curl_out 2>> $curl_err ) &
|
41
|
+
done
|
42
|
+
wait
|
43
|
+
echo elapsed=$(( $(date +%s) - $start ))
|
44
|
+
|
45
|
+
kill $(cat $pid)
|
46
|
+
|
47
|
+
! test -s $curl_err
|
48
|
+
test x"$(wc -l < $curl_out)" = x$nr_client
|
49
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
50
|
+
test "$nr" -eq $nr_client
|
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
require_revactor
|
4
|
+
|
5
|
+
eval $(unused_listen)
|
6
|
+
config_ru=$(mktemp -t rainbows.$$.XXXXXXXX.config.ru)
|
7
|
+
unicorn_config=$(mktemp -t rainbows.$$.XXXXXXXX.unicorn.rb)
|
8
|
+
curl_out=$(mktemp -t rainbows.$$.XXXXXXXX.curl.out)
|
9
|
+
curl_err=$(mktemp -t rainbows.$$.XXXXXXXX.curl.err)
|
10
|
+
pid=$(mktemp -t rainbows.$$.XXXXXXXX.pid)
|
11
|
+
TEST_RM_LIST="$TEST_RM_LIST $config_ru $unicorn_config $lock_path"
|
12
|
+
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err"
|
13
|
+
|
14
|
+
cat > $config_ru <<\EOF
|
15
|
+
use Rack::ContentLength
|
16
|
+
use Rack::ContentType
|
17
|
+
run lambda { |env|
|
18
|
+
Actor.sleep 1
|
19
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
20
|
+
}
|
21
|
+
EOF
|
22
|
+
|
23
|
+
nr_client=30
|
24
|
+
nr_actor=10
|
25
|
+
|
26
|
+
cat > $unicorn_config <<EOF
|
27
|
+
listen "$listen"
|
28
|
+
pid "$pid"
|
29
|
+
Rainbows! do
|
30
|
+
use :Revactor
|
31
|
+
worker_connections $nr_actor
|
32
|
+
end
|
33
|
+
EOF
|
34
|
+
|
35
|
+
rainbows -D $config_ru -c $unicorn_config
|
36
|
+
wait_for_pid $pid
|
37
|
+
|
38
|
+
start=$(date +%s)
|
39
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
40
|
+
do
|
41
|
+
( curl -sSf http://$listen/$i >> $curl_out 2>> $curl_err ) &
|
42
|
+
done
|
43
|
+
wait
|
44
|
+
echo elapsed=$(( $(date +%s) - $start ))
|
45
|
+
|
46
|
+
kill $(cat $pid)
|
47
|
+
|
48
|
+
! test -s $curl_err
|
49
|
+
test x"$(wc -l < $curl_out)" = x$nr_client
|
50
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
51
|
+
|
52
|
+
test "$nr" -eq 1
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
nr_client=${nr_client-25}
|
3
|
+
nr_actor=${nr_actor-50}
|
4
|
+
|
5
|
+
. ./test-lib.sh
|
6
|
+
require_revactor
|
7
|
+
|
8
|
+
eval $(unused_listen)
|
9
|
+
unicorn_config=$(mktemp -t rainbows.$$.unicorn.rb.XXXXXXXX)
|
10
|
+
curl_out=$(mktemp -t rainbows.$$.curl.out.XXXXXXXX)
|
11
|
+
curl_err=$(mktemp -t rainbows.$$.curl.err.XXXXXXXX)
|
12
|
+
r_err=$(mktemp -t rainbows.$$.r.err.XXXXXXXX)
|
13
|
+
r_out=$(mktemp -t rainbows.$$.r.out.XXXXXXXX)
|
14
|
+
pid=$(mktemp -t rainbows.$$.pid.XXXXXXXX)
|
15
|
+
blob=$(mktemp -t rainbows.$$.blob.XXXXXXXX)
|
16
|
+
TEST_RM_LIST="$TEST_RM_LIST $unicorn_config $lock_path $r_err $r_out"
|
17
|
+
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err $blob"
|
18
|
+
|
19
|
+
cat > $unicorn_config <<EOF
|
20
|
+
listen "$listen"
|
21
|
+
pid "$pid"
|
22
|
+
stderr_path "$r_err"
|
23
|
+
stdout_path "$r_out"
|
24
|
+
Rainbows! do
|
25
|
+
use :Revactor
|
26
|
+
worker_connections $nr_actor
|
27
|
+
end
|
28
|
+
EOF
|
29
|
+
|
30
|
+
echo pid=$pid
|
31
|
+
rainbows -D sha1.ru -c $unicorn_config
|
32
|
+
wait_for_pid $pid
|
33
|
+
|
34
|
+
dd if=/dev/urandom bs=1M count=10 of=$blob 2>/dev/null
|
35
|
+
|
36
|
+
start=$(date +%s)
|
37
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
38
|
+
do
|
39
|
+
( curl -sSf -T- < $blob http://$listen/$i >> $curl_out 2>> $curl_err ) &
|
40
|
+
done
|
41
|
+
wait
|
42
|
+
echo elapsed=$(( $(date +%s) - $start ))
|
43
|
+
|
44
|
+
kill $(cat $pid)
|
45
|
+
test $nr_client -eq $(wc -l < $curl_out)
|
46
|
+
test 1 -eq $(sort < $curl_out | uniq | wc -l)
|
47
|
+
blob_sha1=$( expr "$(sha1sum < $blob)" : '\([a-f0-9]\+\)')
|
48
|
+
echo blob_sha1=$blob_sha1
|
49
|
+
test x"$blob_sha1" = x"$(sort < $curl_out | uniq)"
|
data/t/test-lib.sh
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Copyright (c) 2009 Eric Wong
|
3
|
+
set -e
|
4
|
+
set -u
|
5
|
+
T=$(basename $0)
|
6
|
+
ruby="${ruby-ruby}"
|
7
|
+
|
8
|
+
# ensure a sane environment
|
9
|
+
TZ=UTC LC_ALL=C LANG=C
|
10
|
+
export LANG LC_ALL TZ
|
11
|
+
unset CDPATH
|
12
|
+
|
13
|
+
die () {
|
14
|
+
echo >&2 "$@"
|
15
|
+
exit 1
|
16
|
+
}
|
17
|
+
|
18
|
+
TEST_RM_LIST=""
|
19
|
+
trap 'rm -f $TEST_RM_LIST' 0
|
20
|
+
PATH=$PWD/bin:$PATH
|
21
|
+
export PATH
|
22
|
+
|
23
|
+
test -x $PWD/bin/unused_listen || die "must be run in 't' directory"
|
24
|
+
|
25
|
+
wait_for_pid () {
|
26
|
+
path="$1"
|
27
|
+
nr=30
|
28
|
+
while ! test -s "$path" && test $nr -gt 0
|
29
|
+
do
|
30
|
+
nr=$(($nr - 1))
|
31
|
+
sleep 1
|
32
|
+
done
|
33
|
+
}
|
34
|
+
|
35
|
+
require_revactor () {
|
36
|
+
if ! $ruby -rrevactor -e "puts Revactor::VERSION" >/dev/null 2>&1
|
37
|
+
then
|
38
|
+
echo >&2 "skipping $T since we don't have Revactor"
|
39
|
+
exit 0
|
40
|
+
fi
|
41
|
+
}
|
data/vs_Unicorn
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
= \Rainbows! is like Unicorn, but Different...
|
2
|
+
|
3
|
+
While \Rainbows! depends on Unicorn for its process/socket management,
|
4
|
+
HTTP parser and configuration language; \Rainbows! is more ambitious.
|
5
|
+
|
6
|
+
== Differences from Unicorn
|
7
|
+
|
8
|
+
* log rotation is handled immediately in \Rainbows! whereas Unicorn has
|
9
|
+
the luxury of delaying it until the current request is finished
|
10
|
+
processing to prevent log entries for one request to be split across
|
11
|
+
files.
|
12
|
+
|
13
|
+
* load balancing between workers is imperfect, certain worker processes
|
14
|
+
may be servicing more requests than others so it is important to not
|
15
|
+
set +worker_connections+ too high. Unicorn worker processes can never
|
16
|
+
be servicing more than one request at once.
|
17
|
+
|
18
|
+
* speculative, non-blocking accept() is not used, this is to help
|
19
|
+
load balance between multiple worker processes.
|
20
|
+
|
21
|
+
* HTTP pipelining and keepalive may be used for GET and HEAD requests.
|
22
|
+
|
23
|
+
* Less heavily-tested and inherently more complex.
|
24
|
+
|
25
|
+
|
26
|
+
== Similarities with Unicorn
|
27
|
+
|
28
|
+
While some similarities are obvious (we depend on and subclass of
|
29
|
+
Unicorn code), some things are not:
|
30
|
+
|
31
|
+
* Does not attempt to accept() connections when pre-configured limits
|
32
|
+
are hit (+worker_connections+). This will first help balance load
|
33
|
+
to different worker processes, and if your listen() +:backlog+ is
|
34
|
+
overflowing: to other machines in your cluster.
|
35
|
+
|
36
|
+
* Accepts the same {signals}[http://unicorn.bogomips.org/SIGNALS.html]
|
37
|
+
for process management, so you can share scripts to manage them (and
|
38
|
+
nginx, too).
|
39
|
+
|
40
|
+
* supports per-process listeners, allowing an external load balancer
|
41
|
+
like haproxy or nginx to be used to balance between multiple
|
42
|
+
worker processes.
|
43
|
+
|
44
|
+
* Exposes a streaming "rack.input" to the Rack application that reads
|
45
|
+
data off the socket as the application reads it (while retaining
|
46
|
+
rewindable semantics as required by Rack). This allows Rack-compliant
|
47
|
+
apps/middleware to implement things such as real-time upload progress
|
48
|
+
monitoring.
|