unicorn 0.7.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +1 -0
- data/Manifest +0 -1
- data/Rakefile +1 -0
- data/lib/unicorn.rb +59 -68
- data/lib/unicorn/app/exec_cgi.rb +9 -3
- data/lib/unicorn/app/old_rails/static.rb +6 -4
- data/lib/unicorn/const.rb +1 -55
- data/lib/unicorn/http_request.rb +35 -43
- data/lib/unicorn/http_response.rb +10 -18
- data/test/benchmark/big_request.rb +10 -1
- data/test/benchmark/request.rb +9 -0
- data/test/benchmark/response.rb +1 -0
- data/test/test_helper.rb +2 -0
- data/test/unit/test_request.rb +3 -2
- data/test/unit/test_signals.rb +83 -0
- data/unicorn.gemspec +6 -3
- metadata +13 -5
- data/test/tools/trickletest.rb +0 -45
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -120,7 +120,6 @@ test/rails/app-2.3.2.1/public/404.html
|
|
120
120
|
test/rails/app-2.3.2.1/public/500.html
|
121
121
|
test/rails/test_rails.rb
|
122
122
|
test/test_helper.rb
|
123
|
-
test/tools/trickletest.rb
|
124
123
|
test/unit/test_configurator.rb
|
125
124
|
test/unit/test_http_parser.rb
|
126
125
|
test/unit/test_request.rb
|
data/Rakefile
CHANGED
data/lib/unicorn.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
require 'fcntl'
|
2
|
-
|
3
2
|
require 'unicorn/socket_helper'
|
4
|
-
|
5
|
-
require 'unicorn/http_request'
|
6
|
-
require 'unicorn/http_response'
|
7
|
-
require 'unicorn/configurator'
|
8
|
-
require 'unicorn/util'
|
3
|
+
autoload :Rack, 'rack'
|
9
4
|
|
10
5
|
# Unicorn module containing all of the classes (include C extensions) for running
|
11
6
|
# a Unicorn web server. It contains a minimalist HTTP server with just enough
|
12
7
|
# functionality to service web application requests fast as possible.
|
13
8
|
module Unicorn
|
9
|
+
autoload :Const, 'unicorn/const'
|
10
|
+
autoload :HttpRequest, 'unicorn/http_request'
|
11
|
+
autoload :HttpResponse, 'unicorn/http_response'
|
12
|
+
autoload :Configurator, 'unicorn/configurator'
|
13
|
+
autoload :Util, 'unicorn/util'
|
14
|
+
|
14
15
|
class << self
|
15
16
|
def run(app, options = {})
|
16
17
|
HttpServer.new(app, options).start.join
|
@@ -377,7 +378,9 @@ module Unicorn
|
|
377
378
|
# worker.
|
378
379
|
def murder_lazy_workers
|
379
380
|
WORKERS.each_pair do |pid, worker|
|
380
|
-
|
381
|
+
stat = worker.tempfile.stat
|
382
|
+
stat.mode == 0100000 and next
|
383
|
+
Time.now - stat.ctime <= @timeout and next
|
381
384
|
logger.error "worker=#{worker.nr} PID:#{pid} is too old, killing"
|
382
385
|
kill_worker(:KILL, pid) # take no prisoners for @timeout violations
|
383
386
|
worker.tempfile.close rescue nil
|
@@ -414,8 +417,6 @@ module Unicorn
|
|
414
417
|
# once a client is accepted, it is processed in its entirety here
|
415
418
|
# in 3 easy steps: read request, call app, write app response
|
416
419
|
def process_client(client)
|
417
|
-
# one syscall less than "client.nonblock = false":
|
418
|
-
client.fcntl(Fcntl::F_SETFL, File::RDWR)
|
419
420
|
HttpResponse.write(client, @app.call(@request.read(client)))
|
420
421
|
# if we get any error, try to write something back to the client
|
421
422
|
# assuming we haven't closed the socket, but don't get hung up
|
@@ -423,20 +424,15 @@ module Unicorn
|
|
423
424
|
# the socket is closed at the end of this function
|
424
425
|
rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
|
425
426
|
client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil
|
427
|
+
client.close rescue nil
|
426
428
|
rescue HttpParserError # try to tell the client they're bad
|
427
429
|
client.write_nonblock(Const::ERROR_400_RESPONSE) rescue nil
|
430
|
+
client.close rescue nil
|
428
431
|
rescue Object => e
|
429
432
|
client.write_nonblock(Const::ERROR_500_RESPONSE) rescue nil
|
433
|
+
client.close rescue nil
|
430
434
|
logger.error "Read error: #{e.inspect}"
|
431
435
|
logger.error e.backtrace.join("\n")
|
432
|
-
ensure
|
433
|
-
begin
|
434
|
-
client.closed? or client.close
|
435
|
-
rescue Object => e
|
436
|
-
logger.error "Client error: #{e.inspect}"
|
437
|
-
logger.error e.backtrace.join("\n")
|
438
|
-
end
|
439
|
-
@request.reset
|
440
436
|
end
|
441
437
|
|
442
438
|
# gets rid of stuff the worker has no business keeping track of
|
@@ -475,16 +471,18 @@ module Unicorn
|
|
475
471
|
nr = 0 # this becomes negative if we need to reopen logs
|
476
472
|
alive = worker.tempfile # tempfile is our lifeline to the master process
|
477
473
|
ready = LISTENERS
|
478
|
-
|
474
|
+
t = ti = 0
|
479
475
|
|
480
476
|
# closing anything we IO.select on will raise EBADF
|
481
477
|
trap(:USR1) { nr = -65536; SELF_PIPE.first.close rescue nil }
|
482
478
|
trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil } }
|
483
|
-
[:TERM, :INT].each { |sig| trap(sig) { exit(0) } } # instant shutdown
|
479
|
+
[:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
|
484
480
|
@logger.info "worker=#{worker.nr} ready"
|
485
481
|
|
486
|
-
|
487
|
-
reopen_worker_logs(worker.nr)
|
482
|
+
begin
|
483
|
+
nr < 0 and reopen_worker_logs(worker.nr)
|
484
|
+
nr = 0
|
485
|
+
|
488
486
|
# we're a goner in @timeout seconds anyways if alive.chmod
|
489
487
|
# breaks, so don't trap the exception. Using fchmod() since
|
490
488
|
# futimes() is not available in base Ruby and I very strongly
|
@@ -493,55 +491,41 @@ module Unicorn
|
|
493
491
|
# changes with chmod doesn't update ctime on all filesystems; so
|
494
492
|
# we change our counter each and every time (after process_client
|
495
493
|
# and before IO.select).
|
496
|
-
alive.chmod(
|
494
|
+
t == (ti = Time.now.to_i) or alive.chmod(t = ti)
|
495
|
+
|
496
|
+
ready.each do |sock|
|
497
|
+
begin
|
498
|
+
process_client(sock.accept_nonblock)
|
499
|
+
nr += 1
|
500
|
+
t == (ti = Time.now.to_i) or alive.chmod(t = ti)
|
501
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
502
|
+
end
|
503
|
+
break if nr < 0
|
504
|
+
end
|
505
|
+
|
506
|
+
# make the following bet: if we accepted clients this round,
|
507
|
+
# we're probably reasonably busy, so avoid calling select()
|
508
|
+
# and do a speculative accept_nonblock on every listener
|
509
|
+
# before we sleep again in select().
|
510
|
+
redo unless nr == 0 # (nr < 0) => reopen logs
|
497
511
|
|
512
|
+
master_pid == Process.ppid or return
|
513
|
+
alive.chmod(t = 0)
|
498
514
|
begin
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
process_client(client)
|
507
|
-
rescue Errno::ECONNABORTED
|
508
|
-
# client closed the socket even before accept
|
509
|
-
client.close rescue nil
|
510
|
-
ensure
|
511
|
-
alive.chmod(nr += 1) if client
|
512
|
-
break if nr < 0
|
513
|
-
end
|
514
|
-
end
|
515
|
-
client = nil
|
516
|
-
|
517
|
-
# make the following bet: if we accepted clients this round,
|
518
|
-
# we're probably reasonably busy, so avoid calling select()
|
519
|
-
# and do a speculative accept_nonblock on every listener
|
520
|
-
# before we sleep again in select().
|
521
|
-
if nr != 0 # (nr < 0) => reopen logs
|
522
|
-
ready = LISTENERS
|
523
|
-
else
|
524
|
-
master_pid == Process.ppid or exit(0)
|
525
|
-
alive.chmod(nr += 1)
|
526
|
-
begin
|
527
|
-
# timeout used so we can detect parent death:
|
528
|
-
ret = IO.select(LISTENERS, nil, SELF_PIPE, @timeout) or next
|
529
|
-
ready = ret.first
|
530
|
-
rescue Errno::EINTR
|
531
|
-
ready = LISTENERS
|
532
|
-
rescue Errno::EBADF => e
|
533
|
-
nr < 0 or exit(alive ? 1 : 0)
|
534
|
-
end
|
535
|
-
end
|
536
|
-
rescue SignalException, SystemExit => e
|
537
|
-
raise e
|
538
|
-
rescue Object => e
|
539
|
-
if alive
|
540
|
-
logger.error "Unhandled listen loop exception #{e.inspect}."
|
541
|
-
logger.error e.backtrace.join("\n")
|
542
|
-
end
|
515
|
+
# timeout used so we can detect parent death:
|
516
|
+
ret = IO.select(LISTENERS, nil, SELF_PIPE, @timeout) or redo
|
517
|
+
ready = ret.first
|
518
|
+
rescue Errno::EINTR
|
519
|
+
ready = LISTENERS
|
520
|
+
rescue Errno::EBADF
|
521
|
+
nr < 0 or return
|
543
522
|
end
|
544
|
-
|
523
|
+
rescue Object => e
|
524
|
+
if alive
|
525
|
+
logger.error "Unhandled listen loop exception #{e.inspect}."
|
526
|
+
logger.error e.backtrace.join("\n")
|
527
|
+
end
|
528
|
+
end while alive
|
545
529
|
end
|
546
530
|
|
547
531
|
# delivers a signal to a worker and fails gracefully if the worker
|
@@ -585,6 +569,7 @@ module Unicorn
|
|
585
569
|
@config.reload
|
586
570
|
@config.commit!(self)
|
587
571
|
kill_each_worker(:QUIT)
|
572
|
+
Unicorn::Util.reopen_logs
|
588
573
|
logger.info "done reloading config_file=#{@config.config_file}"
|
589
574
|
rescue Object => e
|
590
575
|
logger.error "error reloading config_file=#{@config.config_file}: " \
|
@@ -598,7 +583,13 @@ module Unicorn
|
|
598
583
|
end
|
599
584
|
|
600
585
|
def build_app!
|
601
|
-
|
586
|
+
if @app.respond_to?(:arity) && @app.arity == 0
|
587
|
+
if defined?(Gem) && Gem.respond_to?(:refresh)
|
588
|
+
logger.info "Refreshing Gem list"
|
589
|
+
Gem.refresh
|
590
|
+
end
|
591
|
+
@app = @app.call
|
592
|
+
end
|
602
593
|
end
|
603
594
|
|
604
595
|
def proc_name(tag)
|
data/lib/unicorn/app/exec_cgi.rb
CHANGED
@@ -95,10 +95,15 @@ module Unicorn::App
|
|
95
95
|
# Allows +out+ to be used as a Rack body.
|
96
96
|
def out.each
|
97
97
|
sysseek(@unicorn_app_exec_cgi_offset)
|
98
|
+
|
99
|
+
# don't use a preallocated buffer for sysread since we can't
|
100
|
+
# guarantee an actual socket is consuming the yielded string
|
101
|
+
# (or if somebody is pushing to an array for eventual concatenation
|
98
102
|
begin
|
99
|
-
|
103
|
+
yield(sysread(CHUNK_SIZE))
|
100
104
|
rescue EOFError
|
101
|
-
|
105
|
+
return
|
106
|
+
end while true
|
102
107
|
end
|
103
108
|
|
104
109
|
prev = nil
|
@@ -126,7 +131,8 @@ module Unicorn::App
|
|
126
131
|
tmp.binmode
|
127
132
|
|
128
133
|
# Rack::Lint::InputWrapper doesn't allow sysread :(
|
129
|
-
|
134
|
+
buf = ''
|
135
|
+
while inp.read(CHUNK_SIZE, buf)
|
130
136
|
tmp.syswrite(buf)
|
131
137
|
end
|
132
138
|
tmp.sysseek(0)
|
@@ -22,6 +22,8 @@ require 'rack/file'
|
|
22
22
|
class Unicorn::App::OldRails::Static
|
23
23
|
FILE_METHODS = { 'GET' => true, 'HEAD' => true }.freeze
|
24
24
|
REQUEST_METHOD = 'REQUEST_METHOD'.freeze
|
25
|
+
REQUEST_URI = 'REQUEST_URI'.freeze
|
26
|
+
PATH_INFO = 'PATH_INFO'.freeze
|
25
27
|
|
26
28
|
def initialize(app)
|
27
29
|
@app = app
|
@@ -34,10 +36,10 @@ class Unicorn::App::OldRails::Static
|
|
34
36
|
FILE_METHODS.include?(env[REQUEST_METHOD]) or return @app.call(env)
|
35
37
|
|
36
38
|
# first try the path as-is
|
37
|
-
path_info = env[
|
39
|
+
path_info = env[PATH_INFO].chomp("/")
|
38
40
|
if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
|
39
41
|
# File exists as-is so serve it up
|
40
|
-
env[
|
42
|
+
env[PATH_INFO] = path_info
|
41
43
|
return @file_server.call(env)
|
42
44
|
end
|
43
45
|
|
@@ -45,11 +47,11 @@ class Unicorn::App::OldRails::Static
|
|
45
47
|
|
46
48
|
# grab the semi-colon REST operator used by old versions of Rails
|
47
49
|
# this is the reason we didn't just copy the new Rails::Rack::Static
|
48
|
-
env[
|
50
|
+
env[REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
|
49
51
|
path_info << "#$1#{ActionController::Base.page_cache_extension}"
|
50
52
|
|
51
53
|
if File.file?("#@root/#{::Rack::Utils.unescape(path_info)}")
|
52
|
-
env[
|
54
|
+
env[PATH_INFO] = path_info
|
53
55
|
return @file_server.call(env)
|
54
56
|
end
|
55
57
|
|
data/lib/unicorn/const.rb
CHANGED
@@ -1,65 +1,11 @@
|
|
1
|
-
|
2
1
|
module Unicorn
|
3
2
|
|
4
|
-
# Every standard HTTP code mapped to the appropriate message. These are
|
5
|
-
# used so frequently that they are placed directly in Unicorn for easy
|
6
|
-
# access rather than Unicorn::Const itself.
|
7
|
-
HTTP_STATUS_CODES = {
|
8
|
-
100 => 'Continue',
|
9
|
-
101 => 'Switching Protocols',
|
10
|
-
200 => 'OK',
|
11
|
-
201 => 'Created',
|
12
|
-
202 => 'Accepted',
|
13
|
-
203 => 'Non-Authoritative Information',
|
14
|
-
204 => 'No Content',
|
15
|
-
205 => 'Reset Content',
|
16
|
-
206 => 'Partial Content',
|
17
|
-
300 => 'Multiple Choices',
|
18
|
-
301 => 'Moved Permanently',
|
19
|
-
302 => 'Moved Temporarily',
|
20
|
-
303 => 'See Other',
|
21
|
-
304 => 'Not Modified',
|
22
|
-
305 => 'Use Proxy',
|
23
|
-
400 => 'Bad Request',
|
24
|
-
401 => 'Unauthorized',
|
25
|
-
402 => 'Payment Required',
|
26
|
-
403 => 'Forbidden',
|
27
|
-
404 => 'Not Found',
|
28
|
-
405 => 'Method Not Allowed',
|
29
|
-
406 => 'Not Acceptable',
|
30
|
-
407 => 'Proxy Authentication Required',
|
31
|
-
408 => 'Request Time-out',
|
32
|
-
409 => 'Conflict',
|
33
|
-
410 => 'Gone',
|
34
|
-
411 => 'Length Required',
|
35
|
-
412 => 'Precondition Failed',
|
36
|
-
413 => 'Request Entity Too Large',
|
37
|
-
414 => 'Request-URI Too Large',
|
38
|
-
415 => 'Unsupported Media Type',
|
39
|
-
500 => 'Internal Server Error',
|
40
|
-
501 => 'Not Implemented',
|
41
|
-
502 => 'Bad Gateway',
|
42
|
-
503 => 'Service Unavailable',
|
43
|
-
504 => 'Gateway Time-out',
|
44
|
-
505 => 'HTTP Version not supported'
|
45
|
-
}.inject({}) { |hash,(code,msg)|
|
46
|
-
hash[code] = "#{code} #{msg}"
|
47
|
-
hash
|
48
|
-
}
|
49
|
-
|
50
3
|
# Frequently used constants when constructing requests or responses. Many times
|
51
4
|
# the constant just refers to a string with the same contents. Using these constants
|
52
5
|
# gave about a 3% to 10% performance improvement over using the strings directly.
|
53
6
|
# Symbols did not really improve things much compared to constants.
|
54
7
|
module Const
|
55
|
-
|
56
|
-
PATH_INFO="PATH_INFO".freeze
|
57
|
-
|
58
|
-
# The original URI requested by the client.
|
59
|
-
REQUEST_URI='REQUEST_URI'.freeze
|
60
|
-
REQUEST_PATH='REQUEST_PATH'.freeze
|
61
|
-
|
62
|
-
UNICORN_VERSION="0.7.1".freeze
|
8
|
+
UNICORN_VERSION="0.8.0".freeze
|
63
9
|
|
64
10
|
DEFAULT_HOST = "0.0.0.0".freeze # default TCP listen host address
|
65
11
|
DEFAULT_PORT = "8080".freeze # default TCP listen port
|
data/lib/unicorn/http_request.rb
CHANGED
@@ -12,19 +12,22 @@ module Unicorn
|
|
12
12
|
#
|
13
13
|
class HttpRequest
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
15
|
+
# default parameters we merge into the request env for Rack handlers
|
16
|
+
DEFAULTS = {
|
17
|
+
"rack.errors" => $stderr,
|
18
|
+
"rack.multiprocess" => true,
|
19
|
+
"rack.multithread" => false,
|
20
|
+
"rack.run_once" => false,
|
21
|
+
"rack.version" => [1, 0].freeze,
|
22
|
+
"SCRIPT_NAME" => "".freeze,
|
23
|
+
|
24
|
+
# this is not in the Rack spec, but some apps may rely on it
|
25
|
+
"SERVER_SOFTWARE" => "Unicorn #{Const::UNICORN_VERSION}".freeze
|
26
|
+
}
|
27
|
+
|
28
|
+
# Optimize for the common case where there's no request body
|
29
|
+
# (GET/HEAD) requests.
|
30
|
+
NULL_IO = StringIO.new
|
28
31
|
LOCALHOST = '127.0.0.1'.freeze
|
29
32
|
|
30
33
|
# Being explicitly single-threaded, we have certain advantages in
|
@@ -35,14 +38,6 @@ module Unicorn
|
|
35
38
|
|
36
39
|
def initialize(logger)
|
37
40
|
@logger = logger
|
38
|
-
reset
|
39
|
-
end
|
40
|
-
|
41
|
-
def reset
|
42
|
-
PARAMS[Const::RACK_INPUT].close rescue nil
|
43
|
-
PARAMS[Const::RACK_INPUT].close! rescue nil
|
44
|
-
PARSER.reset
|
45
|
-
PARAMS.clear
|
46
41
|
end
|
47
42
|
|
48
43
|
# Does the majority of the IO processing. It has been written in
|
@@ -59,6 +54,14 @@ module Unicorn
|
|
59
54
|
# This does minimal exception trapping and it is up to the caller
|
60
55
|
# to handle any socket errors (e.g. user aborted upload).
|
61
56
|
def read(socket)
|
57
|
+
# reset the parser
|
58
|
+
unless NULL_IO == (input = PARAMS[Const::RACK_INPUT]) # unlikely
|
59
|
+
input.close rescue nil
|
60
|
+
input.close! rescue nil
|
61
|
+
end
|
62
|
+
PARAMS.clear
|
63
|
+
PARSER.reset
|
64
|
+
|
62
65
|
# From http://www.ietf.org/rfc/rfc3875:
|
63
66
|
# "Script authors should be aware that the REMOTE_ADDR and
|
64
67
|
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
@@ -70,15 +73,15 @@ module Unicorn
|
|
70
73
|
TCPSocket === socket ? socket.peeraddr.last : LOCALHOST
|
71
74
|
|
72
75
|
# short circuit the common case with small GET requests first
|
73
|
-
PARSER.execute(PARAMS,
|
76
|
+
PARSER.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and
|
74
77
|
return handle_body(socket)
|
75
78
|
|
76
|
-
data = BUFFER.dup #
|
79
|
+
data = BUFFER.dup # socket.readpartial will clobber BUFFER
|
77
80
|
|
78
81
|
# Parser is not done, queue up more data to read and continue parsing
|
79
82
|
# an Exception thrown from the PARSER will throw us out of the loop
|
80
83
|
begin
|
81
|
-
data <<
|
84
|
+
data << socket.readpartial(Const::CHUNK_SIZE, BUFFER)
|
82
85
|
PARSER.execute(PARAMS, data) and return handle_body(socket)
|
83
86
|
end while true
|
84
87
|
rescue HttpParserError => e
|
@@ -99,8 +102,8 @@ module Unicorn
|
|
99
102
|
content_length = PARAMS[Const::CONTENT_LENGTH].to_i
|
100
103
|
|
101
104
|
if content_length == 0 # short circuit the common case
|
102
|
-
PARAMS[Const::RACK_INPUT] =
|
103
|
-
return PARAMS.update(
|
105
|
+
PARAMS[Const::RACK_INPUT] = NULL_IO.closed? ? NULL_IO.reopen : NULL_IO
|
106
|
+
return PARAMS.update(DEFAULTS)
|
104
107
|
end
|
105
108
|
|
106
109
|
# must read more data to complete body
|
@@ -110,21 +113,19 @@ module Unicorn
|
|
110
113
|
StringIO.new : Tempfile.new('unicorn')
|
111
114
|
|
112
115
|
body.binmode
|
113
|
-
body.
|
114
|
-
body.syswrite(http_body)
|
116
|
+
body.write(http_body)
|
115
117
|
|
116
118
|
# Some clients (like FF1.0) report 0 for body and then send a body.
|
117
119
|
# This will probably truncate them but at least the request goes through
|
118
120
|
# usually.
|
119
121
|
read_body(socket, remain, body) if remain > 0
|
120
122
|
body.rewind
|
121
|
-
body.sysseek(0) if body.respond_to?(:sysseek)
|
122
123
|
|
123
124
|
# in case read_body overread because the client tried to pipeline
|
124
125
|
# another request, we'll truncate it. Again, we don't do pipelining
|
125
126
|
# or keepalive
|
126
127
|
body.truncate(content_length)
|
127
|
-
PARAMS.update(
|
128
|
+
PARAMS.update(DEFAULTS)
|
128
129
|
end
|
129
130
|
|
130
131
|
# Does the heavy lifting of properly reading the larger body
|
@@ -133,10 +134,10 @@ module Unicorn
|
|
133
134
|
# of the body that has been read to be in the PARAMS['rack.input']
|
134
135
|
# already. It will return true if successful and false if not.
|
135
136
|
def read_body(socket, remain, body)
|
136
|
-
|
137
|
-
#
|
138
|
-
remain -= body.
|
139
|
-
end
|
137
|
+
begin
|
138
|
+
# write always writes the requested amount on a POSIX filesystem
|
139
|
+
remain -= body.write(socket.readpartial(Const::CHUNK_SIZE, BUFFER))
|
140
|
+
end while remain > 0
|
140
141
|
rescue Object => e
|
141
142
|
@logger.error "Error reading HTTP body: #{e.inspect}"
|
142
143
|
|
@@ -147,14 +148,5 @@ module Unicorn
|
|
147
148
|
raise e
|
148
149
|
end
|
149
150
|
|
150
|
-
# read(2) on "slow" devices like sockets can be interrupted by signals
|
151
|
-
def read_socket(socket)
|
152
|
-
begin
|
153
|
-
socket.sysread(Const::CHUNK_SIZE, BUFFER)
|
154
|
-
rescue Errno::EINTR
|
155
|
-
retry
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
151
|
end
|
160
152
|
end
|
@@ -21,6 +21,12 @@ module Unicorn
|
|
21
21
|
|
22
22
|
class HttpResponse
|
23
23
|
|
24
|
+
# Every standard HTTP code mapped to the appropriate message.
|
25
|
+
CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
|
26
|
+
hash[code] = "#{code} #{msg}"
|
27
|
+
hash
|
28
|
+
}
|
29
|
+
|
24
30
|
# Rack does not set/require a Date: header. We always override the
|
25
31
|
# Connection: and Date: headers no matter what (if anything) our
|
26
32
|
# Rack application sent us.
|
@@ -31,7 +37,7 @@ module Unicorn
|
|
31
37
|
# writes the rack_response to socket as an HTTP response
|
32
38
|
def self.write(socket, rack_response)
|
33
39
|
status, headers, body = rack_response
|
34
|
-
status =
|
40
|
+
status = CODES[status.to_i]
|
35
41
|
OUT.clear
|
36
42
|
|
37
43
|
# Don't bother enforcing duplicate supression, it's a Hash most of
|
@@ -49,30 +55,16 @@ module Unicorn
|
|
49
55
|
# so don't worry or care about them.
|
50
56
|
# Date is required by HTTP/1.1 as long as our clock can be trusted.
|
51
57
|
# Some broken clients require a "Status" header so we accomodate them
|
52
|
-
|
53
|
-
"HTTP/1.1 #{status}\r\n" \
|
58
|
+
socket.write("HTTP/1.1 #{status}\r\n" \
|
54
59
|
"Date: #{Time.now.httpdate}\r\n" \
|
55
60
|
"Status: #{status}\r\n" \
|
56
61
|
"Connection: close\r\n" \
|
57
62
|
"#{OUT.join(EMPTY)}\r\n")
|
58
|
-
body.each { |chunk|
|
59
|
-
socket.close # uncorks the socket immediately
|
63
|
+
body.each { |chunk| socket.write(chunk) }
|
64
|
+
socket.close # flushes and uncorks the socket immediately
|
60
65
|
ensure
|
61
66
|
body.respond_to?(:close) and body.close rescue nil
|
62
67
|
end
|
63
68
|
|
64
|
-
private
|
65
|
-
|
66
|
-
# write(2) can return short on slow devices like sockets as well
|
67
|
-
# as fail with EINTR if a signal was caught.
|
68
|
-
def self.socket_write(socket, buffer)
|
69
|
-
begin
|
70
|
-
written = socket.syswrite(buffer)
|
71
|
-
return written if written == buffer.length
|
72
|
-
buffer = buffer[written..-1]
|
73
|
-
rescue Errno::EINTR
|
74
|
-
end while true
|
75
|
-
end
|
76
|
-
|
77
69
|
end
|
78
70
|
end
|
@@ -8,7 +8,11 @@ length = bs * count
|
|
8
8
|
slice = (' ' * bs).freeze
|
9
9
|
|
10
10
|
big = Tempfile.new('')
|
11
|
-
|
11
|
+
|
12
|
+
def big.unicorn_peeraddr # old versions of Unicorn used this
|
13
|
+
'127.0.0.1'
|
14
|
+
end
|
15
|
+
|
12
16
|
big.syswrite(
|
13
17
|
"PUT /hello/world/puturl?abcd=efg&hi#anchor HTTP/1.0\r\n" \
|
14
18
|
"Host: localhost\r\n" \
|
@@ -22,6 +26,11 @@ big.fsync
|
|
22
26
|
|
23
27
|
include Unicorn
|
24
28
|
request = HttpRequest.new(Logger.new($stderr))
|
29
|
+
unless request.respond_to?(:reset)
|
30
|
+
def request.reset
|
31
|
+
# no-op
|
32
|
+
end
|
33
|
+
end
|
25
34
|
|
26
35
|
Benchmark.bmbm do |x|
|
27
36
|
x.report("big") do
|
data/test/benchmark/request.rb
CHANGED
@@ -10,6 +10,9 @@ class TestClient
|
|
10
10
|
buf.replace(@response)
|
11
11
|
end
|
12
12
|
|
13
|
+
alias readpartial sysread
|
14
|
+
|
15
|
+
# old versions of Unicorn used this
|
13
16
|
def unicorn_peeraddr
|
14
17
|
'127.0.0.1'
|
15
18
|
end
|
@@ -31,6 +34,12 @@ medium = TestClient.new([
|
|
31
34
|
|
32
35
|
include Unicorn
|
33
36
|
request = HttpRequest.new(Logger.new($stderr))
|
37
|
+
unless request.respond_to?(:reset)
|
38
|
+
def request.reset
|
39
|
+
# no-op
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
34
43
|
Benchmark.bmbm do |x|
|
35
44
|
x.report("small") do
|
36
45
|
for i in 1..nr
|
data/test/benchmark/response.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/test/unit/test_request.rb
CHANGED
@@ -14,7 +14,9 @@ include Unicorn
|
|
14
14
|
|
15
15
|
class RequestTest < Test::Unit::TestCase
|
16
16
|
|
17
|
-
class MockRequest < StringIO
|
17
|
+
class MockRequest < StringIO
|
18
|
+
alias_method :readpartial, :sysread
|
19
|
+
end
|
18
20
|
|
19
21
|
def setup
|
20
22
|
@request = HttpRequest.new(Logger.new($stderr))
|
@@ -75,7 +77,6 @@ class RequestTest < Test::Unit::TestCase
|
|
75
77
|
client = MockRequest.new("GET #{abs_uri} HTTP/1.1\r\n" \
|
76
78
|
"Host: foo\r\n\r\n")
|
77
79
|
assert_raises(HttpParserError) { @request.read(client) }
|
78
|
-
@request.reset
|
79
80
|
end
|
80
81
|
end
|
81
82
|
|
data/test/unit/test_signals.rb
CHANGED
@@ -37,6 +37,87 @@ class SignalsTest < Test::Unit::TestCase
|
|
37
37
|
@server = nil
|
38
38
|
end
|
39
39
|
|
40
|
+
def test_worker_dies_on_dead_master
|
41
|
+
pid = fork {
|
42
|
+
app = lambda { |env| [ 200, {'X-Pid' => "#$$" }, [] ] }
|
43
|
+
opts = @server_opts.merge(:timeout => 3)
|
44
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
45
|
+
}
|
46
|
+
child = sock = buf = t0 = nil
|
47
|
+
assert_nothing_raised do
|
48
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
49
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
50
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
51
|
+
buf = sock.readpartial(4096)
|
52
|
+
sock.close
|
53
|
+
buf =~ /\bX-Pid: (\d+)\b/ or raise Exception
|
54
|
+
child = $1.to_i
|
55
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
56
|
+
Process.kill(:KILL, pid)
|
57
|
+
Process.waitpid(pid)
|
58
|
+
t0 = Time.now
|
59
|
+
end
|
60
|
+
assert child
|
61
|
+
assert t0
|
62
|
+
assert_raises(Errno::ESRCH) { loop { Process.kill(0, child); sleep 0.2 } }
|
63
|
+
assert((Time.now - t0) < 60)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_sleepy_kill
|
67
|
+
rd, wr = IO.pipe
|
68
|
+
pid = fork {
|
69
|
+
rd.close
|
70
|
+
app = lambda { |env| wr.syswrite('.'); sleep; [ 200, {}, [] ] }
|
71
|
+
redirect_test_io { HttpServer.new(app, @server_opts).start.join }
|
72
|
+
}
|
73
|
+
sock = buf = nil
|
74
|
+
wr.close
|
75
|
+
assert_nothing_raised do
|
76
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
77
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
78
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
79
|
+
buf = rd.readpartial(1)
|
80
|
+
wait_master_ready("test_stderr.#{pid}.log")
|
81
|
+
Process.kill(:INT, pid)
|
82
|
+
Process.waitpid(pid)
|
83
|
+
end
|
84
|
+
assert_equal '.', buf
|
85
|
+
buf = nil
|
86
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
87
|
+
Errno::EBADF) do
|
88
|
+
buf = sock.sysread(4096)
|
89
|
+
end
|
90
|
+
assert_nil buf
|
91
|
+
ensure
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_timeout_slow_response
|
95
|
+
pid = fork {
|
96
|
+
app = lambda { |env| sleep }
|
97
|
+
opts = @server_opts.merge(:timeout => 3)
|
98
|
+
redirect_test_io { HttpServer.new(app, opts).start.join }
|
99
|
+
}
|
100
|
+
t0 = Time.now
|
101
|
+
sock = nil
|
102
|
+
assert_nothing_raised do
|
103
|
+
wait_workers_ready("test_stderr.#{pid}.log", 1)
|
104
|
+
sock = TCPSocket.new('127.0.0.1', @port)
|
105
|
+
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
106
|
+
end
|
107
|
+
|
108
|
+
buf = nil
|
109
|
+
assert_raises(EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,
|
110
|
+
Errno::EBADF) do
|
111
|
+
buf = sock.sysread(4096)
|
112
|
+
end
|
113
|
+
diff = Time.now - t0
|
114
|
+
assert_nil buf
|
115
|
+
assert diff > 1.0, "diff was #{diff.inspect}"
|
116
|
+
assert diff < 60.0
|
117
|
+
ensure
|
118
|
+
Process.kill(:QUIT, pid) rescue nil
|
119
|
+
end
|
120
|
+
|
40
121
|
def test_response_write
|
41
122
|
app = lambda { |env|
|
42
123
|
[ 200, { 'Content-Type' => 'text/plain', 'X-Pid' => Process.pid.to_s },
|
@@ -45,6 +126,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
45
126
|
redirect_test_io { @server = HttpServer.new(app, @server_opts).start }
|
46
127
|
sock = nil
|
47
128
|
assert_nothing_raised do
|
129
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
48
130
|
sock = TCPSocket.new('127.0.0.1', @port)
|
49
131
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
50
132
|
end
|
@@ -82,6 +164,7 @@ class SignalsTest < Test::Unit::TestCase
|
|
82
164
|
pid = nil
|
83
165
|
|
84
166
|
assert_nothing_raised do
|
167
|
+
wait_workers_ready("test_stderr.#{$$}.log", 1)
|
85
168
|
sock = TCPSocket.new('127.0.0.1', @port)
|
86
169
|
sock.syswrite("GET / HTTP/1.0\r\n\r\n")
|
87
170
|
pid = sock.sysread(4096)[/\r\nX-Pid: (\d+)\r\n/, 1].to_i
|
data/unicorn.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{unicorn}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.8.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Eric Wong"]
|
9
|
-
s.date = %q{2009-05-
|
9
|
+
s.date = %q{2009-05-26}
|
10
10
|
s.description = %q{A small fast HTTP library and server for Rack applications.}
|
11
11
|
s.email = %q{normalperson@yhbt.net}
|
12
12
|
s.executables = ["unicorn", "unicorn_rails"]
|
13
13
|
s.extensions = ["ext/unicorn/http11/extconf.rb"]
|
14
14
|
s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README", "TODO", "bin/unicorn", "bin/unicorn_rails", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/util.rb"]
|
15
|
-
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "PHILOSOPHY", "README", "Rakefile", "SIGNALS", "TODO", "TUNING", "bin/unicorn", "bin/unicorn_rails", "examples/init.sh", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/util.rb", "local.mk.sample", "setup.rb", "test/aggregate.rb", "test/benchmark/README", "test/benchmark/big_request.rb", "test/benchmark/dd.ru", "test/benchmark/request.rb", "test/benchmark/response.rb", "test/exec/README", "test/exec/test_exec.rb", "test/rails/app-1.2.3/.gitignore", "test/rails/app-1.2.3/Rakefile", "test/rails/app-1.2.3/app/controllers/application.rb", "test/rails/app-1.2.3/app/controllers/foo_controller.rb", "test/rails/app-1.2.3/app/helpers/application_helper.rb", "test/rails/app-1.2.3/config/boot.rb", "test/rails/app-1.2.3/config/database.yml", "test/rails/app-1.2.3/config/environment.rb", "test/rails/app-1.2.3/config/environments/development.rb", "test/rails/app-1.2.3/config/environments/production.rb", "test/rails/app-1.2.3/config/routes.rb", "test/rails/app-1.2.3/db/.gitignore", "test/rails/app-1.2.3/log/.gitignore", "test/rails/app-1.2.3/public/404.html", "test/rails/app-1.2.3/public/500.html", "test/rails/app-2.0.2/.gitignore", "test/rails/app-2.0.2/Rakefile", "test/rails/app-2.0.2/app/controllers/application.rb", "test/rails/app-2.0.2/app/controllers/foo_controller.rb", "test/rails/app-2.0.2/app/helpers/application_helper.rb", "test/rails/app-2.0.2/config/boot.rb", "test/rails/app-2.0.2/config/database.yml", "test/rails/app-2.0.2/config/environment.rb", "test/rails/app-2.0.2/config/environments/development.rb", "test/rails/app-2.0.2/config/environments/production.rb", "test/rails/app-2.0.2/config/routes.rb", "test/rails/app-2.0.2/db/.gitignore", "test/rails/app-2.0.2/log/.gitignore", "test/rails/app-2.0.2/public/404.html", "test/rails/app-2.0.2/public/500.html", "test/rails/app-2.1.2/.gitignore", "test/rails/app-2.1.2/Rakefile", "test/rails/app-2.1.2/app/controllers/application.rb", "test/rails/app-2.1.2/app/controllers/foo_controller.rb", "test/rails/app-2.1.2/app/helpers/application_helper.rb", "test/rails/app-2.1.2/config/boot.rb", "test/rails/app-2.1.2/config/database.yml", "test/rails/app-2.1.2/config/environment.rb", "test/rails/app-2.1.2/config/environments/development.rb", "test/rails/app-2.1.2/config/environments/production.rb", "test/rails/app-2.1.2/config/routes.rb", "test/rails/app-2.1.2/db/.gitignore", "test/rails/app-2.1.2/log/.gitignore", "test/rails/app-2.1.2/public/404.html", "test/rails/app-2.1.2/public/500.html", "test/rails/app-2.2.2/.gitignore", "test/rails/app-2.2.2/Rakefile", "test/rails/app-2.2.2/app/controllers/application.rb", "test/rails/app-2.2.2/app/controllers/foo_controller.rb", "test/rails/app-2.2.2/app/helpers/application_helper.rb", "test/rails/app-2.2.2/config/boot.rb", "test/rails/app-2.2.2/config/database.yml", "test/rails/app-2.2.2/config/environment.rb", "test/rails/app-2.2.2/config/environments/development.rb", "test/rails/app-2.2.2/config/environments/production.rb", "test/rails/app-2.2.2/config/routes.rb", "test/rails/app-2.2.2/db/.gitignore", "test/rails/app-2.2.2/log/.gitignore", "test/rails/app-2.2.2/public/404.html", "test/rails/app-2.2.2/public/500.html", "test/rails/app-2.3.2.1/.gitignore", "test/rails/app-2.3.2.1/Rakefile", "test/rails/app-2.3.2.1/app/controllers/application_controller.rb", "test/rails/app-2.3.2.1/app/controllers/foo_controller.rb", "test/rails/app-2.3.2.1/app/helpers/application_helper.rb", "test/rails/app-2.3.2.1/config/boot.rb", "test/rails/app-2.3.2.1/config/database.yml", "test/rails/app-2.3.2.1/config/environment.rb", "test/rails/app-2.3.2.1/config/environments/development.rb", "test/rails/app-2.3.2.1/config/environments/production.rb", "test/rails/app-2.3.2.1/config/routes.rb", "test/rails/app-2.3.2.1/db/.gitignore", "test/rails/app-2.3.2.1/log/.gitignore", "test/rails/app-2.3.2.1/public/404.html", "test/rails/app-2.3.2.1/public/500.html", "test/rails/test_rails.rb", "test/test_helper.rb", "test/
|
15
|
+
s.files = [".document", ".gitignore", "CHANGELOG", "CONTRIBUTORS", "DESIGN", "GNUmakefile", "LICENSE", "Manifest", "PHILOSOPHY", "README", "Rakefile", "SIGNALS", "TODO", "TUNING", "bin/unicorn", "bin/unicorn_rails", "examples/init.sh", "ext/unicorn/http11/ext_help.h", "ext/unicorn/http11/extconf.rb", "ext/unicorn/http11/http11.c", "ext/unicorn/http11/http11_parser.h", "ext/unicorn/http11/http11_parser.rl", "ext/unicorn/http11/http11_parser_common.rl", "lib/unicorn.rb", "lib/unicorn/app/exec_cgi.rb", "lib/unicorn/app/old_rails.rb", "lib/unicorn/app/old_rails/static.rb", "lib/unicorn/cgi_wrapper.rb", "lib/unicorn/configurator.rb", "lib/unicorn/const.rb", "lib/unicorn/http_request.rb", "lib/unicorn/http_response.rb", "lib/unicorn/launcher.rb", "lib/unicorn/socket_helper.rb", "lib/unicorn/util.rb", "local.mk.sample", "setup.rb", "test/aggregate.rb", "test/benchmark/README", "test/benchmark/big_request.rb", "test/benchmark/dd.ru", "test/benchmark/request.rb", "test/benchmark/response.rb", "test/exec/README", "test/exec/test_exec.rb", "test/rails/app-1.2.3/.gitignore", "test/rails/app-1.2.3/Rakefile", "test/rails/app-1.2.3/app/controllers/application.rb", "test/rails/app-1.2.3/app/controllers/foo_controller.rb", "test/rails/app-1.2.3/app/helpers/application_helper.rb", "test/rails/app-1.2.3/config/boot.rb", "test/rails/app-1.2.3/config/database.yml", "test/rails/app-1.2.3/config/environment.rb", "test/rails/app-1.2.3/config/environments/development.rb", "test/rails/app-1.2.3/config/environments/production.rb", "test/rails/app-1.2.3/config/routes.rb", "test/rails/app-1.2.3/db/.gitignore", "test/rails/app-1.2.3/log/.gitignore", "test/rails/app-1.2.3/public/404.html", "test/rails/app-1.2.3/public/500.html", "test/rails/app-2.0.2/.gitignore", "test/rails/app-2.0.2/Rakefile", "test/rails/app-2.0.2/app/controllers/application.rb", "test/rails/app-2.0.2/app/controllers/foo_controller.rb", "test/rails/app-2.0.2/app/helpers/application_helper.rb", "test/rails/app-2.0.2/config/boot.rb", "test/rails/app-2.0.2/config/database.yml", "test/rails/app-2.0.2/config/environment.rb", "test/rails/app-2.0.2/config/environments/development.rb", "test/rails/app-2.0.2/config/environments/production.rb", "test/rails/app-2.0.2/config/routes.rb", "test/rails/app-2.0.2/db/.gitignore", "test/rails/app-2.0.2/log/.gitignore", "test/rails/app-2.0.2/public/404.html", "test/rails/app-2.0.2/public/500.html", "test/rails/app-2.1.2/.gitignore", "test/rails/app-2.1.2/Rakefile", "test/rails/app-2.1.2/app/controllers/application.rb", "test/rails/app-2.1.2/app/controllers/foo_controller.rb", "test/rails/app-2.1.2/app/helpers/application_helper.rb", "test/rails/app-2.1.2/config/boot.rb", "test/rails/app-2.1.2/config/database.yml", "test/rails/app-2.1.2/config/environment.rb", "test/rails/app-2.1.2/config/environments/development.rb", "test/rails/app-2.1.2/config/environments/production.rb", "test/rails/app-2.1.2/config/routes.rb", "test/rails/app-2.1.2/db/.gitignore", "test/rails/app-2.1.2/log/.gitignore", "test/rails/app-2.1.2/public/404.html", "test/rails/app-2.1.2/public/500.html", "test/rails/app-2.2.2/.gitignore", "test/rails/app-2.2.2/Rakefile", "test/rails/app-2.2.2/app/controllers/application.rb", "test/rails/app-2.2.2/app/controllers/foo_controller.rb", "test/rails/app-2.2.2/app/helpers/application_helper.rb", "test/rails/app-2.2.2/config/boot.rb", "test/rails/app-2.2.2/config/database.yml", "test/rails/app-2.2.2/config/environment.rb", "test/rails/app-2.2.2/config/environments/development.rb", "test/rails/app-2.2.2/config/environments/production.rb", "test/rails/app-2.2.2/config/routes.rb", "test/rails/app-2.2.2/db/.gitignore", "test/rails/app-2.2.2/log/.gitignore", "test/rails/app-2.2.2/public/404.html", "test/rails/app-2.2.2/public/500.html", "test/rails/app-2.3.2.1/.gitignore", "test/rails/app-2.3.2.1/Rakefile", "test/rails/app-2.3.2.1/app/controllers/application_controller.rb", "test/rails/app-2.3.2.1/app/controllers/foo_controller.rb", "test/rails/app-2.3.2.1/app/helpers/application_helper.rb", "test/rails/app-2.3.2.1/config/boot.rb", "test/rails/app-2.3.2.1/config/database.yml", "test/rails/app-2.3.2.1/config/environment.rb", "test/rails/app-2.3.2.1/config/environments/development.rb", "test/rails/app-2.3.2.1/config/environments/production.rb", "test/rails/app-2.3.2.1/config/routes.rb", "test/rails/app-2.3.2.1/db/.gitignore", "test/rails/app-2.3.2.1/log/.gitignore", "test/rails/app-2.3.2.1/public/404.html", "test/rails/app-2.3.2.1/public/500.html", "test/rails/test_rails.rb", "test/test_helper.rb", "test/unit/test_configurator.rb", "test/unit/test_http_parser.rb", "test/unit/test_request.rb", "test/unit/test_response.rb", "test/unit/test_server.rb", "test/unit/test_signals.rb", "test/unit/test_socket_helper.rb", "test/unit/test_upload.rb", "test/unit/test_util.rb", "unicorn.gemspec"]
|
16
16
|
s.has_rdoc = true
|
17
17
|
s.homepage = %q{http://unicorn.bogomips.org}
|
18
18
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Unicorn", "--main", "README"]
|
@@ -27,8 +27,11 @@ Gem::Specification.new do |s|
|
|
27
27
|
s.specification_version = 3
|
28
28
|
|
29
29
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
30
|
+
s.add_runtime_dependency(%q<rack>, [">= 0"])
|
30
31
|
else
|
32
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
31
33
|
end
|
32
34
|
else
|
35
|
+
s.add_dependency(%q<rack>, [">= 0"])
|
33
36
|
end
|
34
37
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: unicorn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eric Wong
|
@@ -9,10 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-26 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
16
25
|
description: A small fast HTTP library and server for Rack applications.
|
17
26
|
email: normalperson@yhbt.net
|
18
27
|
executables:
|
@@ -168,7 +177,6 @@ files:
|
|
168
177
|
- test/rails/app-2.3.2.1/public/500.html
|
169
178
|
- test/rails/test_rails.rb
|
170
179
|
- test/test_helper.rb
|
171
|
-
- test/tools/trickletest.rb
|
172
180
|
- test/unit/test_configurator.rb
|
173
181
|
- test/unit/test_http_parser.rb
|
174
182
|
- test/unit/test_request.rb
|
data/test/tools/trickletest.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
require 'socket'
|
2
|
-
require 'stringio'
|
3
|
-
|
4
|
-
def do_test(st, chunk)
|
5
|
-
s = TCPSocket.new('127.0.0.1',ARGV[0].to_i);
|
6
|
-
req = StringIO.new(st)
|
7
|
-
nout = 0
|
8
|
-
randstop = rand(st.length / 10)
|
9
|
-
STDERR.puts "stopping after: #{randstop}"
|
10
|
-
|
11
|
-
begin
|
12
|
-
while data = req.read(chunk)
|
13
|
-
nout += s.write(data)
|
14
|
-
s.flush
|
15
|
-
sleep 0.1
|
16
|
-
if nout > randstop
|
17
|
-
STDERR.puts "BANG! after #{nout} bytes."
|
18
|
-
break
|
19
|
-
end
|
20
|
-
end
|
21
|
-
rescue Object => e
|
22
|
-
STDERR.puts "ERROR: #{e}"
|
23
|
-
ensure
|
24
|
-
s.close
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
content = "-" * (1024 * 240)
|
29
|
-
st = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\nContent-Length: #{content.length}\r\n\r\n#{content}"
|
30
|
-
|
31
|
-
puts "length: #{content.length}"
|
32
|
-
|
33
|
-
threads = []
|
34
|
-
ARGV[1].to_i.times do
|
35
|
-
t = Thread.new do
|
36
|
-
size = 100
|
37
|
-
puts ">>>> #{size} sized chunks"
|
38
|
-
do_test(st, size)
|
39
|
-
end
|
40
|
-
|
41
|
-
t.abort_on_exception = true
|
42
|
-
threads << t
|
43
|
-
end
|
44
|
-
|
45
|
-
threads.each {|t| t.join}
|