unicorn 0.7.1 → 0.8.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/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}
|