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 CHANGED
@@ -1,3 +1,4 @@
1
+ v0.8.0 - enforce Rack dependency, minor performance improvements and fixes
1
2
  v0.7.1 - minor fixes, cleanups and documentation improvements
2
3
  v0.7.0 - rack.version is 1.0
3
4
  v0.6.0 - cleanups + optimizations, signals to {in,de}crement processes
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
@@ -16,6 +16,7 @@ Echoe.new("unicorn") do |p|
16
16
  p.ignore_pattern = /^(pkg|site|projects|doc|log)|CVS|\.log/
17
17
  p.need_tar_gz = false
18
18
  p.need_tgz = true
19
+ p.dependencies = [ 'rack' ]
19
20
 
20
21
  p.extension_pattern = ["ext/**/extconf.rb"]
21
22
 
data/lib/unicorn.rb CHANGED
@@ -1,16 +1,17 @@
1
1
  require 'fcntl'
2
-
3
2
  require 'unicorn/socket_helper'
4
- require 'unicorn/const'
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
- Time.now - worker.tempfile.ctime <= @timeout and next
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
- client = nil
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
- while alive
487
- reopen_worker_logs(worker.nr) if nr < 0
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(nr = 0)
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
- ready.each do |sock|
500
- begin
501
- client = begin
502
- sock.accept_nonblock
503
- rescue Errno::EAGAIN
504
- next
505
- end
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
- end
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
- @app = @app.call if @app.respond_to?(:arity) && @app.arity == 0
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)
@@ -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
- loop { yield(sysread(CHUNK_SIZE)) }
103
+ yield(sysread(CHUNK_SIZE))
100
104
  rescue EOFError
101
- end
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
- while buf = inp.read(CHUNK_SIZE)
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[Unicorn::Const::PATH_INFO].chomp("/")
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[Unicorn::Const::PATH_INFO] = path_info
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[Unicorn::Const::REQUEST_URI] =~ /^#{Regexp.escape(path_info)}(;[^\?]+)/
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[Unicorn::Const::PATH_INFO] = path_info
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
- # This is the part of the path after the SCRIPT_NAME.
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
@@ -12,19 +12,22 @@ module Unicorn
12
12
  #
13
13
  class HttpRequest
14
14
 
15
- # default parameters we merge into the request env for Rack handlers
16
- DEF_PARAMS = {
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
- }.freeze
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, read_socket(socket)) and
76
+ PARSER.execute(PARAMS, socket.readpartial(Const::CHUNK_SIZE, BUFFER)) and
74
77
  return handle_body(socket)
75
78
 
76
- data = BUFFER.dup # read_socket will clobber BUFFER
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 << read_socket(socket)
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] = StringIO.new
103
- return PARAMS.update(DEF_PARAMS)
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.sync = true
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(DEF_PARAMS)
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
- while remain > 0
137
- # writes always write the requested amount on a POSIX filesystem
138
- remain -= body.syswrite(read_socket(socket))
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 = HTTP_STATUS_CODES[status.to_i]
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
- socket_write(socket,
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| socket_write(socket, 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
- def big.unicorn_peeraddr; '127.0.0.1'; end
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
@@ -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
@@ -3,6 +3,7 @@ require 'unicorn'
3
3
 
4
4
  class NullWriter
5
5
  def syswrite(buf); buf.size; end
6
+ alias write syswrite
6
7
  def close; end
7
8
  end
8
9
 
data/test/test_helper.rb CHANGED
@@ -25,7 +25,9 @@ require 'stringio'
25
25
  require 'pathname'
26
26
  require 'tempfile'
27
27
  require 'fileutils'
28
+ require 'logger'
28
29
  require 'unicorn'
30
+ require 'unicorn/http11'
29
31
 
30
32
  if ENV['DEBUG']
31
33
  require 'ruby-debug'
@@ -14,7 +14,9 @@ include Unicorn
14
14
 
15
15
  class RequestTest < Test::Unit::TestCase
16
16
 
17
- class MockRequest < StringIO; end
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
 
@@ -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.7.1"
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-22}
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/tools/trickletest.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"]
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.7.1
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-22 00:00:00 -07:00
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
@@ -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}