unicorn 0.9.1 → 0.9.2
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/.gitignore +1 -1
- data/CHANGELOG +1 -0
- data/GNUmakefile +15 -15
- data/Manifest +6 -6
- data/README +11 -4
- data/Rakefile +5 -5
- data/ext/{unicorn/http11 → unicorn_http}/ext_help.h +0 -0
- data/ext/unicorn_http/extconf.rb +5 -0
- data/ext/{unicorn/http11/http11.c → unicorn_http/unicorn_http.c} +2 -2
- data/ext/{unicorn/http11/http11_parser.h → unicorn_http/unicorn_http.h} +87 -87
- data/ext/{unicorn/http11/http11_parser.rl → unicorn_http/unicorn_http.rl} +4 -4
- data/ext/{unicorn/http11/http11_parser_common.rl → unicorn_http/unicorn_http_common.rl} +2 -2
- data/lib/unicorn.rb +10 -13
- data/lib/unicorn/app/exec_cgi.rb +3 -9
- data/lib/unicorn/chunked_reader.rb +1 -18
- data/lib/unicorn/const.rb +1 -1
- data/lib/unicorn/http_request.rb +5 -7
- data/lib/unicorn/tee_input.rb +19 -21
- data/lib/unicorn/trailer_parser.rb +1 -1
- data/lib/unicorn/util.rb +17 -0
- data/test/test_helper.rb +1 -1
- data/test/unit/test_chunked_reader.rb +2 -59
- data/test/unit/test_tee_input.rb +66 -0
- data/test/unit/test_trailer_parser.rb +1 -1
- data/test/unit/test_util.rb +30 -28
- data/unicorn.gemspec +6 -6
- metadata +22 -21
- data/ext/unicorn/http11/extconf.rb +0 -5
@@ -2,8 +2,8 @@
|
|
2
2
|
* Copyright (c) 2005 Zed A. Shaw
|
3
3
|
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
4
|
*/
|
5
|
-
#ifndef
|
6
|
-
#define
|
5
|
+
#ifndef unicorn_http_h
|
6
|
+
#define unicorn_http_h
|
7
7
|
|
8
8
|
#include <sys/types.h>
|
9
9
|
|
@@ -105,7 +105,7 @@ static void downcase_char(char *c)
|
|
105
105
|
fbreak;
|
106
106
|
}
|
107
107
|
|
108
|
-
include
|
108
|
+
include unicorn_http_common "unicorn_http_common.rl";
|
109
109
|
}%%
|
110
110
|
|
111
111
|
/** Data **/
|
@@ -155,4 +155,4 @@ static int http_parser_has_error(http_parser *parser) {
|
|
155
155
|
static int http_parser_is_finished(http_parser *parser) {
|
156
156
|
return parser->cs == http_parser_first_final;
|
157
157
|
}
|
158
|
-
#endif /*
|
158
|
+
#endif /* unicorn_http_h */
|
data/lib/unicorn.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'fcntl'
|
2
|
-
require 'tempfile'
|
3
2
|
require 'unicorn/socket_helper'
|
4
3
|
autoload :Rack, 'rack'
|
5
4
|
|
@@ -66,7 +65,7 @@ module Unicorn
|
|
66
65
|
0 => $0.dup,
|
67
66
|
}
|
68
67
|
|
69
|
-
class Worker < Struct.new(:nr, :
|
68
|
+
class Worker < Struct.new(:nr, :tmp)
|
70
69
|
# worker objects may be compared to just plain numbers
|
71
70
|
def ==(other_nr)
|
72
71
|
self.nr == other_nr
|
@@ -332,7 +331,7 @@ module Unicorn
|
|
332
331
|
self.pid = pid.chomp('.oldbin') if pid
|
333
332
|
proc_name 'master'
|
334
333
|
else
|
335
|
-
worker = WORKERS.delete(wpid) and worker.
|
334
|
+
worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
|
336
335
|
logger.info "reaped #{status.inspect} " \
|
337
336
|
"worker=#{worker.nr rescue 'unknown'}"
|
338
337
|
end
|
@@ -392,16 +391,16 @@ module Unicorn
|
|
392
391
|
end
|
393
392
|
|
394
393
|
# forcibly terminate all workers that haven't checked in in timeout
|
395
|
-
# seconds. The timeout is implemented using an unlinked
|
394
|
+
# seconds. The timeout is implemented using an unlinked File
|
396
395
|
# shared between the parent process and each worker. The worker
|
397
|
-
# runs File#chmod to modify the ctime of the
|
396
|
+
# runs File#chmod to modify the ctime of the File. If the ctime
|
398
397
|
# is stale for >timeout seconds, then we'll kill the corresponding
|
399
398
|
# worker.
|
400
399
|
def murder_lazy_workers
|
401
400
|
diff = stat = nil
|
402
401
|
WORKERS.dup.each_pair do |wpid, worker|
|
403
402
|
stat = begin
|
404
|
-
worker.
|
403
|
+
worker.tmp.stat
|
405
404
|
rescue => e
|
406
405
|
logger.warn "worker=#{worker.nr} PID:#{wpid} stat error: #{e.inspect}"
|
407
406
|
kill_worker(:QUIT, wpid)
|
@@ -425,9 +424,7 @@ module Unicorn
|
|
425
424
|
SIG_QUEUE << :QUIT # forcibly emulate SIGQUIT
|
426
425
|
return
|
427
426
|
end
|
428
|
-
|
429
|
-
tempfile.unlink # don't allow other processes to find or see it
|
430
|
-
worker = Worker.new(worker_nr, tempfile)
|
427
|
+
worker = Worker.new(worker_nr, Unicorn::Util.tmpio)
|
431
428
|
before_fork.call(self, worker)
|
432
429
|
WORKERS[fork { worker_loop(worker) }] = worker
|
433
430
|
end
|
@@ -482,10 +479,10 @@ module Unicorn
|
|
482
479
|
proc_name "worker[#{worker.nr}]"
|
483
480
|
START_CTX.clear
|
484
481
|
init_self_pipe!
|
485
|
-
WORKERS.values.each { |other| other.
|
482
|
+
WORKERS.values.each { |other| other.tmp.close rescue nil }
|
486
483
|
WORKERS.clear
|
487
484
|
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
488
|
-
worker.
|
485
|
+
worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
489
486
|
after_fork.call(self, worker) # can drop perms
|
490
487
|
self.timeout /= 2.0 # halve it for select()
|
491
488
|
build_app! unless preload_app
|
@@ -505,7 +502,7 @@ module Unicorn
|
|
505
502
|
ppid = master_pid
|
506
503
|
init_worker_process(worker)
|
507
504
|
nr = 0 # this becomes negative if we need to reopen logs
|
508
|
-
alive = worker.
|
505
|
+
alive = worker.tmp # tmp is our lifeline to the master process
|
509
506
|
ready = LISTENERS
|
510
507
|
t = ti = 0
|
511
508
|
|
@@ -570,7 +567,7 @@ module Unicorn
|
|
570
567
|
begin
|
571
568
|
Process.kill(signal, wpid)
|
572
569
|
rescue Errno::ESRCH
|
573
|
-
worker = WORKERS.delete(wpid) and worker.
|
570
|
+
worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
|
574
571
|
end
|
575
572
|
end
|
576
573
|
|
data/lib/unicorn/app/exec_cgi.rb
CHANGED
@@ -42,11 +42,8 @@ module Unicorn::App
|
|
42
42
|
|
43
43
|
# Calls the app
|
44
44
|
def call(env)
|
45
|
-
out, err =
|
46
|
-
out.unlink
|
47
|
-
err.unlink
|
45
|
+
out, err = Unicorn::Util.tmpio, Unicorn::Util.tmpio
|
48
46
|
inp = force_file_input(env)
|
49
|
-
out.sync = err.sync = true
|
50
47
|
pid = fork { run_child(inp, out, err, env) }
|
51
48
|
inp.close
|
52
49
|
pid, status = Process.waitpid2(pid)
|
@@ -125,12 +122,9 @@ module Unicorn::App
|
|
125
122
|
if inp.size == 0 # inp could be a StringIO or StringIO-like object
|
126
123
|
::File.open('/dev/null', 'rb')
|
127
124
|
else
|
128
|
-
tmp =
|
129
|
-
tmp.unlink
|
130
|
-
tmp.binmode
|
131
|
-
tmp.sync = true
|
125
|
+
tmp = Unicorn::Util.tmpio
|
132
126
|
|
133
|
-
buf = Z.dup
|
127
|
+
buf = Unicorn::Z.dup
|
134
128
|
while inp.read(CHUNK_SIZE, buf)
|
135
129
|
tmp.syswrite(buf)
|
136
130
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
3
|
|
4
4
|
require 'unicorn'
|
5
|
-
require '
|
5
|
+
require 'unicorn_http'
|
6
6
|
|
7
7
|
module Unicorn
|
8
8
|
class ChunkedReader
|
@@ -32,23 +32,6 @@ module Unicorn
|
|
32
32
|
buf
|
33
33
|
end
|
34
34
|
|
35
|
-
def gets
|
36
|
-
line = nil
|
37
|
-
begin
|
38
|
-
line = readpartial(Const::CHUNK_SIZE)
|
39
|
-
begin
|
40
|
-
if line.sub!(%r{\A(.*?#{$/})}, Z)
|
41
|
-
@chunk_left += line.size
|
42
|
-
@buf = @buf ? (line << @buf) : line
|
43
|
-
return $1.dup
|
44
|
-
end
|
45
|
-
line << readpartial(Const::CHUNK_SIZE)
|
46
|
-
end while true
|
47
|
-
rescue EOFError
|
48
|
-
return line
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
35
|
private
|
53
36
|
|
54
37
|
def last_block(max = nil)
|
data/lib/unicorn/const.rb
CHANGED
@@ -5,7 +5,7 @@ module Unicorn
|
|
5
5
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
6
6
|
# Symbols did not really improve things much compared to constants.
|
7
7
|
module Const
|
8
|
-
UNICORN_VERSION="0.9.
|
8
|
+
UNICORN_VERSION="0.9.2".freeze
|
9
9
|
|
10
10
|
DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
|
11
11
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
|
3
3
|
# compiled extension
|
4
|
-
require '
|
4
|
+
require 'unicorn_http'
|
5
5
|
|
6
6
|
module Unicorn
|
7
7
|
class HttpRequest
|
@@ -81,16 +81,14 @@ module Unicorn
|
|
81
81
|
PARAMS[Const::RACK_INPUT] = if (body = PARAMS.delete(:http_body))
|
82
82
|
length = PARAMS[Const::CONTENT_LENGTH].to_i
|
83
83
|
|
84
|
-
if
|
85
|
-
|
86
|
-
|
87
|
-
length = body = nil
|
88
|
-
end
|
84
|
+
if /\Achunked\z/i =~ PARAMS[Const::HTTP_TRANSFER_ENCODING]
|
85
|
+
socket = ChunkedReader.new(PARAMS, socket, body)
|
86
|
+
length = body = nil
|
89
87
|
end
|
90
88
|
|
91
89
|
TeeInput.new(socket, length, body)
|
92
90
|
else
|
93
|
-
NULL_IO
|
91
|
+
NULL_IO
|
94
92
|
end
|
95
93
|
|
96
94
|
PARAMS.update(DEFAULTS)
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# Copyright (c) 2009 Eric Wong
|
2
2
|
# You can redistribute it and/or modify it under the same terms as Ruby.
|
3
3
|
|
4
|
-
require 'tempfile'
|
5
|
-
|
6
4
|
# acts like tee(1) on an input input to provide a input-like stream
|
7
|
-
# while providing rewindable semantics through a
|
5
|
+
# while providing rewindable semantics through a File/StringIO
|
8
6
|
# backing store. On the first pass, the input is only read on demand
|
9
7
|
# so your Rack application can use input notification (upload progress
|
10
8
|
# and like). This should fully conform to the Rack::InputWrapper
|
@@ -16,10 +14,7 @@ module Unicorn
|
|
16
14
|
class TeeInput
|
17
15
|
|
18
16
|
def initialize(input, size, body)
|
19
|
-
@tmp =
|
20
|
-
@tmp.unlink
|
21
|
-
@tmp.binmode
|
22
|
-
@tmp.sync = true
|
17
|
+
@tmp = Unicorn::Util.tmpio
|
23
18
|
|
24
19
|
if body
|
25
20
|
@tmp.write(body)
|
@@ -73,22 +68,25 @@ module Unicorn
|
|
73
68
|
@input or return @tmp.gets
|
74
69
|
nil == $/ and return read
|
75
70
|
|
76
|
-
|
77
|
-
if @tmp.pos
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
# half the line was already read, and the rest of has not been read
|
82
|
-
if buf = @input.gets
|
83
|
-
@tmp.write(buf)
|
84
|
-
line << buf
|
85
|
-
else
|
86
|
-
@input = nil
|
87
|
-
end
|
88
|
-
elsif line = @input.gets
|
89
|
-
@tmp.write(line)
|
71
|
+
orig_size = @tmp.stat.size
|
72
|
+
if @tmp.pos == orig_size
|
73
|
+
tee(Const::CHUNK_SIZE, Z.dup) or return nil
|
74
|
+
@tmp.seek(orig_size)
|
90
75
|
end
|
91
76
|
|
77
|
+
line = @tmp.gets # cannot be nil here since size > pos
|
78
|
+
$/ == line[-$/.size, $/.size] and return line
|
79
|
+
|
80
|
+
# unlikely, if we got here, then @tmp is at EOF
|
81
|
+
begin
|
82
|
+
orig_size = @tmp.stat.size
|
83
|
+
tee(Const::CHUNK_SIZE, Z.dup) or break
|
84
|
+
@tmp.seek(orig_size)
|
85
|
+
line << @tmp.gets
|
86
|
+
$/ == line[-$/.size, $/.size] and return line
|
87
|
+
# @tmp is at EOF again here, retry the loop
|
88
|
+
end while true
|
89
|
+
|
92
90
|
line
|
93
91
|
end
|
94
92
|
|
data/lib/unicorn/util.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fcntl'
|
2
|
+
require 'tmpdir'
|
2
3
|
|
3
4
|
module Unicorn
|
4
5
|
class Util
|
@@ -39,6 +40,22 @@ module Unicorn
|
|
39
40
|
nr
|
40
41
|
end
|
41
42
|
|
43
|
+
# creates and returns a new File object. The File is unlinked
|
44
|
+
# immediately, switched to binary mode, and userspace output
|
45
|
+
# buffering is disabled
|
46
|
+
def tmpio
|
47
|
+
fp = begin
|
48
|
+
File.open("#{Dir::tmpdir}/#{rand}",
|
49
|
+
File::RDWR|File::CREAT|File::EXCL, 0600)
|
50
|
+
rescue Errno::EEXIST
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
File.unlink(fp.path)
|
54
|
+
fp.binmode
|
55
|
+
fp.sync = true
|
56
|
+
fp
|
57
|
+
end
|
58
|
+
|
42
59
|
end
|
43
60
|
|
44
61
|
end
|
data/test/test_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'unicorn'
|
3
|
-
require '
|
3
|
+
require 'unicorn_http'
|
4
4
|
require 'tempfile'
|
5
5
|
require 'io/nonblock'
|
6
6
|
require 'digest/sha1'
|
@@ -53,63 +53,6 @@ class TestChunkedReader < Test::Unit::TestCase
|
|
53
53
|
assert_raises(EOFError) { cr.readpartial(8192) }
|
54
54
|
end
|
55
55
|
|
56
|
-
def test_gets1
|
57
|
-
cr = bin_reader("4\r\nasdf\r\n0\r\n")
|
58
|
-
STDOUT.sync = true
|
59
|
-
assert_equal 'asdf', cr.gets
|
60
|
-
assert_raises(EOFError) { cr.readpartial(8192) }
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_gets2
|
64
|
-
cr = bin_reader("4\r\nasd\n\r\n0\r\n\r\n")
|
65
|
-
assert_equal "asd\n", cr.gets
|
66
|
-
assert_nil cr.gets
|
67
|
-
end
|
68
|
-
|
69
|
-
def test_gets3
|
70
|
-
max = Unicorn::Const::CHUNK_SIZE * 2
|
71
|
-
str = ('a' * max).freeze
|
72
|
-
first = 5
|
73
|
-
last = str.size - first
|
74
|
-
cr = bin_reader(
|
75
|
-
"#{'%x' % first}\r\n#{str[0, first]}\r\n" \
|
76
|
-
"#{'%x' % last}\r\n#{str[-last, last]}\r\n" \
|
77
|
-
"0\r\n")
|
78
|
-
assert_equal str, cr.gets
|
79
|
-
assert_nil cr.gets
|
80
|
-
end
|
81
|
-
|
82
|
-
def test_readpartial_gets_mixed1
|
83
|
-
max = Unicorn::Const::CHUNK_SIZE * 2
|
84
|
-
str = ('a' * max).freeze
|
85
|
-
first = 5
|
86
|
-
last = str.size - first
|
87
|
-
cr = bin_reader(
|
88
|
-
"#{'%x' % first}\r\n#{str[0, first]}\r\n" \
|
89
|
-
"#{'%x' % last}\r\n#{str[-last, last]}\r\n" \
|
90
|
-
"0\r\n")
|
91
|
-
partial = cr.readpartial(16384)
|
92
|
-
assert String === partial
|
93
|
-
|
94
|
-
len = max - partial.size
|
95
|
-
assert_equal(str[-len, len], cr.gets)
|
96
|
-
assert_raises(EOFError) { cr.readpartial(1) }
|
97
|
-
assert_nil cr.gets
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_gets_mixed_readpartial
|
101
|
-
max = 10
|
102
|
-
str = ("z\n" * max).freeze
|
103
|
-
first = 5
|
104
|
-
last = str.size - first
|
105
|
-
cr = bin_reader(
|
106
|
-
"#{'%x' % first}\r\n#{str[0, first]}\r\n" \
|
107
|
-
"#{'%x' % last}\r\n#{str[-last, last]}\r\n" \
|
108
|
-
"0\r\n")
|
109
|
-
assert_equal("z\n", cr.gets)
|
110
|
-
assert_equal("z\n", cr.gets)
|
111
|
-
end
|
112
|
-
|
113
56
|
def test_dd
|
114
57
|
cr = bin_reader("6\r\nhello\n\r\n")
|
115
58
|
tmp = Tempfile.new('test_dd')
|
@@ -138,7 +81,7 @@ class TestChunkedReader < Test::Unit::TestCase
|
|
138
81
|
exit 0
|
139
82
|
end while true
|
140
83
|
}
|
141
|
-
assert_equal "hello\n", cr.
|
84
|
+
assert_equal "hello\n", cr.readpartial(6)
|
142
85
|
sha1 = Digest::SHA1.new
|
143
86
|
buf = Unicorn::Z.dup
|
144
87
|
begin
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
require 'test/unit'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'unicorn'
|
5
|
+
|
6
|
+
class TestTeeInput < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@rs = $/
|
10
|
+
@env = {}
|
11
|
+
@rd, @wr = IO.pipe
|
12
|
+
@rd.sync = @wr.sync = true
|
13
|
+
@start_pid = $$
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
return if $$ != @start_pid
|
18
|
+
$/ = @rs
|
19
|
+
@rd.close rescue nil
|
20
|
+
@wr.close rescue nil
|
21
|
+
begin
|
22
|
+
Process.wait
|
23
|
+
rescue Errno::ECHILD
|
24
|
+
break
|
25
|
+
end while true
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_gets_long
|
29
|
+
ti = Unicorn::TeeInput.new(@rd, nil, "hello")
|
30
|
+
status = line = nil
|
31
|
+
pid = fork {
|
32
|
+
@rd.close
|
33
|
+
3.times { @wr.write("ffff" * 4096) }
|
34
|
+
@wr.write "#$/foo#$/"
|
35
|
+
@wr.close
|
36
|
+
}
|
37
|
+
@wr.close
|
38
|
+
assert_nothing_raised { line = ti.gets }
|
39
|
+
assert_equal(4096 * 4 * 3 + 5 + $/.size, line.size)
|
40
|
+
assert_equal("hello" << ("ffff" * 4096 * 3) << "#$/", line)
|
41
|
+
assert_nothing_raised { line = ti.gets }
|
42
|
+
assert_equal "foo#$/", line
|
43
|
+
assert_nil ti.gets
|
44
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
45
|
+
assert status.success?
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_gets_short
|
49
|
+
ti = Unicorn::TeeInput.new(@rd, nil, "hello")
|
50
|
+
status = line = nil
|
51
|
+
pid = fork {
|
52
|
+
@rd.close
|
53
|
+
@wr.write "#$/foo"
|
54
|
+
@wr.close
|
55
|
+
}
|
56
|
+
@wr.close
|
57
|
+
assert_nothing_raised { line = ti.gets }
|
58
|
+
assert_equal("hello#$/", line)
|
59
|
+
assert_nothing_raised { line = ti.gets }
|
60
|
+
assert_equal "foo", line
|
61
|
+
assert_nil ti.gets
|
62
|
+
assert_nothing_raised { pid, status = Process.waitpid2(pid) }
|
63
|
+
assert status.success?
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|