unicorn 0.9.1 → 0.9.2

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.
@@ -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