unicorn 0.9.1 → 0.9.2

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