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