unicorn 0.94.0 → 0.95.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document CHANGED
@@ -1,3 +1,4 @@
1
+ FAQ
1
2
  README
2
3
  TUNING
3
4
  PHILOSOPHY
@@ -138,6 +138,10 @@ All unrecognized values for RACK_ENV are assumed to be
138
138
  "none". Production deployments are strongly encouraged to use
139
139
  "deployment" or "none" for maximum performance.
140
140
 
141
+ As of Unicorn 0.94.0, RACK_ENV is exported as a process-wide environment
142
+ variable as well. While not current a part of the Rack specification as
143
+ of Rack 1.0.1, this has become a de facto standard in the Rack world.
144
+
141
145
  Note that the Rack::ContentLength and Rack::Chunked middlewares
142
146
  are never loaded by default. If needed, they should be
143
147
  individually specified in the RACKUP_FILE, some frameworks do
@@ -145,6 +149,7 @@ not require them.
145
149
 
146
150
  # ENVIRONMENT VARIABLES
147
151
 
152
+ The RACK_ENV variable is set by the aforementioned \-E switch.
148
153
  All application or library-specific environment variables (e.g. TMPDIR)
149
154
  may always be set in the Unicorn CONFIG_FILE in addition to the spawning
150
155
  shell. When transparently upgrading Unicorn, all environment variables
data/FAQ ADDED
@@ -0,0 +1,45 @@
1
+ = Frequently Asked Questions about Unicorn
2
+
3
+ === Why are my redirects going to "http" URLs when my site uses https?
4
+
5
+ If your site is entirely behind https, then Rack applications that use
6
+ "rack.url_scheme" can set the following in the Unicorn config file:
7
+
8
+ HttpRequest::DEFAULTS["rack.url_scheme"] = "https"
9
+
10
+ For frameworks that do not use "rack.url_scheme", you can also
11
+ try setting one or both of the following:
12
+
13
+ HttpRequest::DEFAULTS["HTTPS"] = "on"
14
+ HttpRequest::DEFAULTS["HTTP_X_FORWARDED_PROTO"] = "https"
15
+
16
+ Otherwise, you can configure your proxy (nginx) to send the
17
+ "X-Forwarded-Proto: https" header only for parts of the site that use
18
+ https. For nginx, you can do it with the following line in appropriate
19
+ "location" blocks of your nginx config file:
20
+
21
+ proxy_set_header X-Forwarded-Proto https;
22
+
23
+ === Why are log messages from Unicorn are unformatted when using Rails?
24
+
25
+ Current versions of Rails unfortunately overrides the default Logger
26
+ formatter.
27
+
28
+ You can undo this behavior with the default logger in your Unicorn
29
+ config file:
30
+
31
+ Configurator::DEFAULTS[:logger].formatter = Logger::Formatter.new
32
+
33
+ Of course you can specify an entirely different logger as well
34
+ with the "logger" directive described by Unicorn::Configurator.
35
+
36
+ === Why am I getting "connection refused"/502 errors under high load?
37
+
38
+ Short answer: your application cannot keep up.
39
+
40
+ You can increase the size of the :backlog parameter if your kernel
41
+ supports a larger listen() queue, but keep in mind having a large listen
42
+ queue makes failover to a different machine more difficult.
43
+
44
+ See the TUNING and Unicorn::Configurator documents for more information
45
+ on :backlog-related topics.
data/GIT-VERSION-GEN CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  GVF=GIT-VERSION-FILE
4
- DEF_VER=v0.94.0.GIT
4
+ DEF_VER=v0.95.0.GIT
5
5
 
6
6
  LF='
7
7
  '
data/GNUmakefile CHANGED
@@ -156,7 +156,7 @@ NEWS: GIT-VERSION-FILE
156
156
  $(rake) -s news_rdoc > $@+
157
157
  mv $@+ $@
158
158
 
159
- SINCE = 0.93.0
159
+ SINCE = 0.94.0
160
160
  ChangeLog: log_range = $(shell test -n "$(SINCE)" && echo v$(SINCE)..)
161
161
  ChangeLog: GIT-VERSION-FILE
162
162
  @echo "ChangeLog from $(GIT_URL) ($(SINCE)..$(GIT_VERSION))" > $@+
data/HACKING CHANGED
@@ -16,6 +16,9 @@ Tests are good, but slow tests make development slow, so we make tests
16
16
  faster (in parallel) with GNU make (instead of Rake) and avoiding
17
17
  Rubygems.
18
18
 
19
+ Users of GNU-based systems (such as GNU/Linux) usually have GNU make installed
20
+ as "make" instead of "gmake".
21
+
19
22
  Since we don't load RubyGems by default, loading Rack properly requires
20
23
  setting up RUBYLIB to point to where Rack is located. Not loading
21
24
  Rubygems drastically lowers the time to run the full test suite. You
@@ -25,15 +28,15 @@ file is provided for reference.
25
28
 
26
29
  Running the entire test suite with 4 tests in parallel:
27
30
 
28
- make -j4 test
31
+ gmake -j4 test
29
32
 
30
33
  Running just one unit test:
31
34
 
32
- make test/unit/test_http_parser.rb
35
+ gmake test/unit/test_http_parser.rb
33
36
 
34
37
  Running just one test case in a unit test:
35
38
 
36
- make test/unit/test_http_parser.rb--test_parse_simple.n
39
+ gmake test/unit/test_http_parser.rb--test_parse_simple.n
37
40
 
38
41
  === HttpServer
39
42
 
@@ -103,11 +106,11 @@ It is easy to install the contents of your git working directory:
103
106
 
104
107
  Via RubyGems (RubyGems 1.3.5+ recommended for prerelease versions):
105
108
 
106
- make install-gem
109
+ gmake install-gem
107
110
 
108
111
  Without RubyGems (via setup.rb):
109
112
 
110
- make install
113
+ gmake install
111
114
 
112
115
  It is not at all recommended to mix a RubyGems installation with an
113
116
  installation done without RubyGems, however.
data/Rakefile CHANGED
@@ -33,6 +33,7 @@ def tags
33
33
  end
34
34
 
35
35
  cgit_url = "http://git.bogomips.org/cgit/unicorn.git"
36
+ git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/unicorn.git'
36
37
 
37
38
  desc 'prints news as an Atom feed'
38
39
  task :news_atom do
@@ -93,8 +94,6 @@ desc "print release notes for Rubyforge"
93
94
  task :release_notes do
94
95
  require 'rubygems'
95
96
 
96
- git_url = ENV['GIT_URL'] || 'git://git.bogomips.org/unicorn.git'
97
-
98
97
  spec = Gem::Specification.load('unicorn.gemspec')
99
98
  puts spec.description.strip
100
99
  puts ""
@@ -106,3 +105,41 @@ task :release_notes do
106
105
  print "\nChanges:\n\n"
107
106
  puts body
108
107
  end
108
+
109
+ desc "post to RAA"
110
+ task :raa_update do
111
+ require 'rubygems'
112
+ require 'net/http'
113
+ require 'net/netrc'
114
+ rc = Net::Netrc.locate('unicorn-raa') or abort "~/.netrc not found"
115
+ password = rc.password
116
+
117
+ s = Gem::Specification.load('unicorn.gemspec')
118
+ desc = [ s.description.strip ]
119
+ desc << ""
120
+ desc << "* #{s.email}"
121
+ desc << "* #{git_url}"
122
+ desc << "* #{cgit_url}"
123
+ desc = desc.join("\n")
124
+ uri = URI.parse('http://raa.ruby-lang.org/regist.rhtml')
125
+ form = {
126
+ :name => s.name,
127
+ :short_description => s.summary,
128
+ :version => s.version.to_s,
129
+ :status => 'stable',
130
+ :owner => s.authors.first,
131
+ :email => s.email,
132
+ :category_major => 'Library',
133
+ :category_minor => 'Web',
134
+ :url => s.homepage,
135
+ :download => "http://rubyforge.org/frs/?group_id=1306",
136
+ :license => "Ruby's",
137
+ :description_style => 'Plain',
138
+ :description => desc,
139
+ :pass => password,
140
+ :submit => "Update",
141
+ }
142
+ res = Net::HTTP.post_form(uri, form)
143
+ p res
144
+ puts res.body
145
+ end
data/SIGNALS CHANGED
@@ -50,6 +50,13 @@ automatically respawned.
50
50
  the current request, so multiple log lines for one request
51
51
  (as done by Rails) will not be split across multiple logs.
52
52
 
53
+ It is NOT recommended to send the USR1 signal directly to workers via
54
+ "killall -USR1 unicorn" if you are using user/group-switching support
55
+ in your workers. You will encounter incorrect file permissions and
56
+ workers will need to be respawned. Sending USR1 to the master process
57
+ first will ensure logs have the correct permissions before the master
58
+ forwards the USR1 signal to workers.
59
+
53
60
  === Procedure to replace a running unicorn executable
54
61
 
55
62
  You may replace a running instance of unicorn with a new one without
data/bin/unicorn_rails CHANGED
@@ -171,7 +171,6 @@ app = lambda do ||
171
171
  $stderr.puts "LogTailer not available for Rails < 2.3" unless daemonize
172
172
  $stderr.puts "Debugger not available" if $DEBUG
173
173
  map(map_path) do
174
- require 'unicorn/app/old_rails/static'
175
174
  use Unicorn::App::OldRails::Static
176
175
  run inner_app
177
176
  end
data/lib/unicorn.rb CHANGED
@@ -8,6 +8,14 @@ autoload :Rack, 'rack'
8
8
  # a Unicorn web server. It contains a minimalist HTTP server with just enough
9
9
  # functionality to service web application requests fast as possible.
10
10
  module Unicorn
11
+
12
+ # raised inside TeeInput when a client closes the socket inside the
13
+ # application dispatch. This is always raised with an empty backtrace
14
+ # since there is nothing in the application stack that is responsible
15
+ # for client shutdowns/disconnects.
16
+ class ClientShutdown < EOFError
17
+ end
18
+
11
19
  autoload :Const, 'unicorn/const'
12
20
  autoload :HttpRequest, 'unicorn/http_request'
13
21
  autoload :HttpResponse, 'unicorn/http_response'
@@ -136,6 +144,7 @@ module Unicorn
136
144
  # capabilities. Let the caller handle any and all errors.
137
145
  uid = Etc.getpwnam(user).uid
138
146
  gid = Etc.getgrnam(group).gid if group
147
+ Unicorn::Util.chown_logs(uid, gid)
139
148
  tmp.chown(uid, gid)
140
149
  if gid && Process.egid != gid
141
150
  Process.initgroups(user, gid)
@@ -368,7 +377,7 @@ module Unicorn
368
377
  end
369
378
  rescue Errno::EINTR
370
379
  retry
371
- rescue Object => e
380
+ rescue => e
372
381
  logger.error "Unhandled master loop exception #{e.inspect}."
373
382
  logger.error e.backtrace.join("\n")
374
383
  retry
@@ -470,7 +479,7 @@ module Unicorn
470
479
  logger.error "old PID:#{valid_pid?(old_pid)} running with " \
471
480
  "existing pid=#{old_pid}, refusing rexec"
472
481
  return
473
- rescue Object => e
482
+ rescue => e
474
483
  logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
475
484
  return
476
485
  end
@@ -507,7 +516,10 @@ module Unicorn
507
516
  # worker.
508
517
  def murder_lazy_workers
509
518
  WORKERS.dup.each_pair do |wpid, worker|
510
- (diff = (Time.now - worker.tmp.stat.ctime)) <= timeout and next
519
+ stat = worker.tmp.stat
520
+ # skip workers that disable fchmod or have never fchmod-ed
521
+ stat.mode == 0100600 and next
522
+ (diff = (Time.now - stat.ctime)) <= timeout and next
511
523
  logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
512
524
  "(#{diff}s > #{timeout}s), killing"
513
525
  kill_worker(:KILL, wpid) # take no prisoners for timeout violations
@@ -653,7 +665,7 @@ module Unicorn
653
665
  rescue Errno::EBADF
654
666
  nr < 0 or return
655
667
  end
656
- rescue Object => e
668
+ rescue => e
657
669
  if alive
658
670
  logger.error "Unhandled listen loop exception #{e.inspect}."
659
671
  logger.error e.backtrace.join("\n")
@@ -709,7 +721,7 @@ module Unicorn
709
721
  self.app = orig_app
710
722
  build_app! if preload_app
711
723
  logger.info "done reloading config_file=#{config.config_file}"
712
- rescue Object => e
724
+ rescue => e
713
725
  logger.error "error reloading config_file=#{config.config_file}: " \
714
726
  "#{e.class} #{e.message}"
715
727
  end
@@ -722,6 +734,10 @@ module Unicorn
722
734
 
723
735
  def build_app!
724
736
  if app.respond_to?(:arity) && app.arity == 0
737
+ # exploit COW in case of preload_app. Also avoids race
738
+ # conditions in Rainbows! since load/require are not thread-safe
739
+ Unicorn.constants.each { |x| Unicorn.const_get(x) }
740
+
725
741
  if defined?(Gem) && Gem.respond_to?(:refresh)
726
742
  logger.info "Refreshing Gem list"
727
743
  Gem.refresh
@@ -13,13 +13,15 @@ module Unicorn; module App; end; end
13
13
  # Implements a handler that can run Rails.
14
14
  class Unicorn::App::OldRails
15
15
 
16
+ autoload :Static, "unicorn/app/old_rails/static"
17
+
16
18
  def call(env)
17
19
  cgi = Unicorn::CGIWrapper.new(env)
18
20
  begin
19
21
  Dispatcher.dispatch(cgi,
20
22
  ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
21
23
  cgi.body)
22
- rescue Object => e
24
+ rescue => e
23
25
  err = env['rack.errors']
24
26
  err.write("#{e} #{e.message}\n")
25
27
  e.backtrace.each { |line| err.write("#{line}\n") }
@@ -21,9 +21,11 @@
21
21
  # fast as if you use a static server like nginx).
22
22
  class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server)
23
23
  FILE_METHODS = { 'GET' => true, 'HEAD' => true }
24
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
25
- REQUEST_URI = 'REQUEST_URI'.freeze
26
- PATH_INFO = 'PATH_INFO'.freeze
24
+
25
+ # avoid allocating new strings for hash lookups
26
+ REQUEST_METHOD = 'REQUEST_METHOD'
27
+ REQUEST_URI = 'REQUEST_URI'
28
+ PATH_INFO = 'PATH_INFO'
27
29
 
28
30
  def initialize(app)
29
31
  self.app = app
@@ -7,7 +7,7 @@ module Unicorn
7
7
 
8
8
  # Implements a simple DSL for configuring a Unicorn server.
9
9
  #
10
- # Example (when used with the unicorn config file):
10
+ # Example (when used with the Unicorn config file):
11
11
  # worker_processes 4
12
12
  # working_directory "/path/to/deploy/app/current"
13
13
  # listen '/tmp/my_app.sock', :backlog => 1
@@ -22,12 +22,16 @@ module Unicorn
22
22
  # GC.copy_on_write_friendly = true
23
23
  #
24
24
  # before_fork do |server, worker|
25
- # # the following is recomended for Rails + "preload_app true"
25
+ # # the following is highly recomended for Rails + "preload_app true"
26
26
  # # as there's no need for the master process to hold a connection
27
27
  # defined?(ActiveRecord::Base) and
28
28
  # ActiveRecord::Base.connection.disconnect!
29
29
  #
30
- # # the following allows a new master process to incrementally
30
+ # # The following is only recommended for memory/DB-constrained
31
+ # # installations. It is not needed if your system can house
32
+ # # twice as many worker_processes as you have configured.
33
+ #
34
+ # # This allows a new master process to incrementally
31
35
  # # phase out the old master process with SIGTTOU to avoid a
32
36
  # # thundering herd (especially in the "preload_app false" case)
33
37
  # # when doing a transparent upgrade. The last worker spawned
@@ -41,7 +45,7 @@ module Unicorn
41
45
  # end
42
46
  # end
43
47
  #
44
- # # optionally throttle the master from forking too quickly by sleeping
48
+ # # *optionally* throttle the master from forking too quickly by sleeping
45
49
  # sleep 1
46
50
  # end
47
51
  #
@@ -50,7 +54,7 @@ module Unicorn
50
54
  # addr = "127.0.0.1:#{9293 + worker.nr}"
51
55
  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
52
56
  #
53
- # # the following is required for Rails + "preload_app true",
57
+ # # the following is *required* for Rails + "preload_app true",
54
58
  # defined?(ActiveRecord::Base) and
55
59
  # ActiveRecord::Base.establish_connection
56
60
  #
@@ -203,7 +207,11 @@ module Unicorn
203
207
  end
204
208
 
205
209
  # sets the current number of worker_processes to +nr+. Each worker
206
- # process will serve exactly one client at a time.
210
+ # process will serve exactly one client at a time. You can
211
+ # increment or decrement this value at runtime by sending SIGTTIN
212
+ # or SIGTTOU respectively to the master process without reloading
213
+ # the rest of your Unicorn configuration. See the SIGNALS document
214
+ # for more information.
207
215
  def worker_processes(nr)
208
216
  Integer === nr or raise ArgumentError,
209
217
  "not an integer: worker_processes=#{nr.inspect}"
@@ -283,10 +291,22 @@ module Unicorn
283
291
  # +:delay+: seconds to wait between successive +tries+
284
292
  #
285
293
  # Default: 0.5 seconds
294
+ #
295
+ # +:umask+: sets the file mode creation mask for UNIX sockets
296
+ #
297
+ # Typically UNIX domain sockets are created with more liberal
298
+ # file permissions than the rest of the application. By default,
299
+ # we create UNIX domain sockets to be readable and writable by
300
+ # all local users to give them the same accessibility as
301
+ # locally-bound TCP listeners.
302
+ #
303
+ # This has no effect on TCP listeners.
304
+ #
305
+ # Default: 0 (world read/writable)
286
306
  def listen(address, opt = {})
287
307
  address = expand_addr(address)
288
308
  if String === address
289
- [ :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
309
+ [ :umask, :backlog, :sndbuf, :rcvbuf, :tries ].each do |key|
290
310
  value = opt[key] or next
291
311
  Integer === value or
292
312
  raise ArgumentError, "not an integer: #{key}=#{value.inspect}"
data/lib/unicorn/const.rb CHANGED
@@ -7,7 +7,7 @@ module Unicorn
7
7
  # gave about a 3% to 10% performance improvement over using the strings directly.
8
8
  # Symbols did not really improve things much compared to constants.
9
9
  module Const
10
- UNICORN_VERSION="0.94.0"
10
+ UNICORN_VERSION="0.95.0"
11
11
 
12
12
  DEFAULT_HOST = "0.0.0.0" # default TCP listen host address
13
13
  DEFAULT_PORT = 8080 # default TCP listen port
@@ -30,8 +30,8 @@ module Unicorn
30
30
 
31
31
  # A frozen format for this is about 15% faster
32
32
  REMOTE_ADDR="REMOTE_ADDR".freeze
33
- HTTP_EXPECT="HTTP_EXPECT".freeze
34
33
  RACK_INPUT="rack.input".freeze
34
+ HTTP_EXPECT="HTTP_EXPECT"
35
35
  end
36
36
 
37
37
  end
@@ -11,16 +11,23 @@ module Unicorn
11
11
  when /linux/
12
12
  # from /usr/include/linux/tcp.h
13
13
  TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
14
+
15
+ # do not send out partial frames (Linux)
14
16
  TCP_CORK = 3 unless defined?(TCP_CORK)
15
17
  when /freebsd(([1-4]\..{1,2})|5\.[0-4])/
16
18
  # Do nothing for httpready, just closing a bug when freebsd <= 5.4
17
- TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
19
+ TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH) # :nodoc:
18
20
  when /freebsd/
21
+ # do not send out partial frames (FreeBSD)
19
22
  TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
23
+
20
24
  # Use the HTTP accept filter if available.
21
25
  # The struct made by pack() is defined in /usr/include/sys/socket.h
22
26
  # as accept_filter_arg
23
27
  unless `/sbin/sysctl -nq net.inet.accf.http`.empty?
28
+ # set set the "httpready" accept filter in FreeBSD if available
29
+ # if other protocols are to be supported, this may be
30
+ # String#replace-d with "dataready" arguments instead
24
31
  FILTER_ARG = ['httpready', nil].pack('a16a240')
25
32
  end
26
33
  end
@@ -29,23 +36,23 @@ module Unicorn
29
36
 
30
37
  # highly portable, but off by default because we don't do keepalive
31
38
  if defined?(TCP_NODELAY) && ! (val = opt[:tcp_nodelay]).nil?
32
- sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0) rescue nil
39
+ sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
33
40
  end
34
41
 
35
42
  unless (val = opt[:tcp_nopush]).nil?
36
43
  val = val ? 1 : 0
37
44
  if defined?(TCP_CORK) # Linux
38
- sock.setsockopt(IPPROTO_TCP, TCP_CORK, val) rescue nil
45
+ sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
39
46
  elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is untested (FreeBSD)
40
- sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val) rescue nil
47
+ sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
41
48
  end
42
49
  end
43
50
 
44
51
  # No good reason to ever have deferred accepts off
45
52
  if defined?(TCP_DEFER_ACCEPT)
46
- sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1) rescue nil
53
+ sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, 1)
47
54
  elsif defined?(SO_ACCEPTFILTER) && defined?(FILTER_ARG)
48
- sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG) rescue nil
55
+ sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, FILTER_ARG)
49
56
  end
50
57
  end
51
58
 
@@ -61,6 +68,11 @@ module Unicorn
61
68
  log_buffer_sizes(sock, " after: ")
62
69
  end
63
70
  sock.listen(opt[:backlog] || 1024)
71
+ rescue => e
72
+ if respond_to?(:logger)
73
+ logger.error "error setting socket options: #{e.inspect}"
74
+ logger.error e.backtrace.join("\n")
75
+ end
64
76
  end
65
77
 
66
78
  def log_buffer_sizes(sock, pfx = '')
@@ -88,7 +100,7 @@ module Unicorn
88
100
  "socket=#{address} specified but it is not a socket!"
89
101
  end
90
102
  end
91
- old_umask = File.umask(0)
103
+ old_umask = File.umask(opt[:umask] || 0)
92
104
  begin
93
105
  UNIXServer.new(address)
94
106
  ensure
@@ -123,47 +123,65 @@ module Unicorn
123
123
 
124
124
  private
125
125
 
126
+ def client_error(e)
127
+ case e
128
+ when EOFError
129
+ # in case client only did a premature shutdown(SHUT_WR)
130
+ # we do support clients that shutdown(SHUT_WR) after the
131
+ # _entire_ request has been sent, and those will not have
132
+ # raised EOFError on us.
133
+ socket.close if socket
134
+ raise ClientShutdown, "bytes_read=#{@tmp.size}", []
135
+ when HttpParserError
136
+ e.set_backtrace([])
137
+ end
138
+ raise e
139
+ end
140
+
126
141
  # tees off a +length+ chunk of data from the input into the IO
127
- # backing store as well as returning it. +buf+ must be specified.
142
+ # backing store as well as returning it. +dst+ must be specified.
128
143
  # returns nil if reading from the input returns nil
129
144
  def tee(length, dst)
130
145
  unless parser.body_eof?
131
- begin
132
- if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
133
- @tmp.write(dst)
134
- @tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
135
- return dst
136
- end
137
- rescue EOFError
146
+ if parser.filter_body(dst, socket.readpartial(length, buf)).nil?
147
+ @tmp.write(dst)
148
+ @tmp.seek(0, IO::SEEK_END) # workaround FreeBSD/OSX + MRI 1.8.x bug
149
+ return dst
138
150
  end
139
151
  end
140
152
  finalize_input
153
+ rescue => e
154
+ client_error(e)
141
155
  end
142
156
 
143
157
  def finalize_input
144
158
  while parser.trailers(req, buf).nil?
159
+ # Don't worry about raising ClientShutdown here on EOFError, tee()
160
+ # will catch EOFError when app is processing it, otherwise in
161
+ # initialize we never get any chance to enter the app so the
162
+ # EOFError will just get trapped by Unicorn and not the Rack app
145
163
  buf << socket.readpartial(Const::CHUNK_SIZE)
146
164
  end
147
165
  self.socket = nil
148
166
  end
149
167
 
150
- # tee()s into +buf+ until it is of +length+ bytes (or until
168
+ # tee()s into +dst+ until it is of +length+ bytes (or until
151
169
  # we've reached the Content-Length of the request body).
152
- # Returns +buf+ (the exact object, not a duplicate)
170
+ # Returns +dst+ (the exact object, not a duplicate)
153
171
  # To continue supporting applications that need near-real-time
154
172
  # streaming input bodies, this is a no-op for
155
173
  # "Transfer-Encoding: chunked" requests.
156
- def ensure_length(buf, length)
174
+ def ensure_length(dst, length)
157
175
  # @size is nil for chunked bodies, so we can't ensure length for those
158
176
  # since they could be streaming bidirectionally and we don't want to
159
177
  # block the caller in that case.
160
- return buf if buf.nil? || @size.nil?
178
+ return dst if dst.nil? || @size.nil?
161
179
 
162
- while buf.size < length && @size != @tmp.pos
163
- buf << tee(length - buf.size, @buf2)
180
+ while dst.size < length && tee(length - dst.size, @buf2)
181
+ dst << @buf2
164
182
  end
165
183
 
166
- buf
184
+ dst
167
185
  end
168
186
 
169
187
  end
data/lib/unicorn/util.rb CHANGED
@@ -17,6 +17,22 @@ module Unicorn
17
17
  class Util
18
18
  class << self
19
19
 
20
+ def is_log?(fp)
21
+ append_flags = File::WRONLY | File::APPEND
22
+
23
+ ! fp.closed? &&
24
+ fp.sync &&
25
+ fp.path[0] == ?/ &&
26
+ (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
27
+ end
28
+
29
+ def chown_logs(uid, gid)
30
+ ObjectSpace.each_object(File) do |fp|
31
+ is_log?(fp) or next
32
+ fp.chown(uid, gid)
33
+ end
34
+ end
35
+
20
36
  # This reopens ALL logfiles in the process that have been rotated
21
37
  # using logrotate(8) (without copytruncate) or similar tools.
22
38
  # A +File+ object is considered for reopening if it is:
@@ -27,16 +43,13 @@ module Unicorn
27
43
  # Returns the number of files reopened
28
44
  def reopen_logs
29
45
  nr = 0
30
- append_flags = File::WRONLY | File::APPEND
31
46
 
32
47
  ObjectSpace.each_object(File) do |fp|
33
- next if fp.closed?
34
- next unless (fp.sync && fp.path[0] == ?/)
35
- next unless (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
36
-
48
+ is_log?(fp) or next
49
+ orig_st = fp.stat
37
50
  begin
38
- a, b = fp.stat, File.stat(fp.path)
39
- next if a.ino == b.ino && a.dev == b.dev
51
+ b = File.stat(fp.path)
52
+ next if orig_st.ino == b.ino && orig_st.dev == b.dev
40
53
  rescue Errno::ENOENT
41
54
  end
42
55
 
@@ -47,6 +60,10 @@ module Unicorn
47
60
  end
48
61
  fp.reopen(fp.path, open_arg)
49
62
  fp.sync = true
63
+ new_st = fp.stat
64
+ if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
65
+ fp.chown(orig_st.uid, orig_st.gid)
66
+ end
50
67
  nr += 1
51
68
  end # each_object
52
69
  nr
data/local.mk.sample CHANGED
@@ -5,27 +5,36 @@
5
5
  # This is depends on a bunch of GNU-isms from bash, sed, touch.
6
6
 
7
7
  DLEXT := so
8
- rack_ver := 1.0.0
8
+ gems := rack-1.0.1
9
9
 
10
10
  # Avoid loading rubygems to speed up tests because gmake is
11
11
  # fork+exec heavy with Ruby.
12
+ prefix = $(HOME)
12
13
  ifeq ($(r19),)
13
- ruby := $(HOME)/bin/ruby
14
- RUBYLIB := $(HOME)/lib/ruby/gems/1.8/gems/rack-$(rack_ver)/lib
14
+ ruby := $(prefix)/bin/ruby
15
+ gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.8/gems/,$(gems))
15
16
  else
16
- export PATH := $(HOME)/ruby-1.9/bin:$(PATH)
17
- ruby := $(HOME)/ruby-1.9/bin/ruby --disable-gems
18
- RUBYLIB := $(HOME)/ruby-1.9/lib/ruby/gems/1.9.1/gems/rack-$(rack_ver)/lib
17
+ prefix := $(prefix)/ruby-1.9
18
+ export PATH := $(prefix)/bin:$(PATH)
19
+ ruby := $(prefix)/bin/ruby --disable-gems
20
+ gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.9.1/gems/,$(gems))
19
21
  endif
20
22
 
21
- # pipefail is THE reason to use bash (v3+)
22
- SHELL := /bin/bash -e -o pipefail
23
+ ifdef gem_paths
24
+ sp :=
25
+ sp +=
26
+ export RUBYLIB := $(subst $(sp),:,$(addsuffix /lib,$(gem_paths)))
27
+ endif
28
+
29
+ # pipefail is THE reason to use bash (v3+) or never revisions of ksh93
30
+ # SHELL := /bin/bash -e -o pipefail
31
+ SHELL := /bin/ksh93 -e -o pipefail
23
32
 
24
33
  full-test: test-18 test-19
25
34
  test-18:
26
- $(MAKE) test test-rails 2>&1 | sed -u -e 's!^!1.8 !'
35
+ $(MAKE) test test-rails 2>&1 | sed -e 's!^!1.8 !'
27
36
  test-19:
28
- $(MAKE) test test-rails r19=1 2>&1 | sed -u -e 's!^!1.9 !'
37
+ $(MAKE) test test-rails r19=1 2>&1 | sed -e 's!^!1.9 !'
29
38
 
30
39
  latest: NEWS
31
40
  @awk 'BEGIN{RS="=== ";ORS=""}NR==2{sub(/\n$$/,"");print RS""$$0 }' < $<
@@ -12,11 +12,13 @@ include Unicorn
12
12
 
13
13
  class TestHandler
14
14
 
15
- def call(env)
16
- # response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n")
15
+ def call(env)
17
16
  while env['rack.input'].read(4096)
18
17
  end
19
18
  [200, { 'Content-Type' => 'text/plain' }, ['hello!\n']]
19
+ rescue Unicorn::ClientShutdown, Unicorn::HttpParserError => e
20
+ $stderr.syswrite("#{e.class}: #{e.message} #{e.backtrace.empty?}\n")
21
+ raise e
20
22
  end
21
23
  end
22
24
 
@@ -103,6 +105,92 @@ class WebServerTest < Test::Unit::TestCase
103
105
  assert_equal 'hello!\n', results[0], "Handler didn't really run"
104
106
  end
105
107
 
108
+ def test_client_shutdown_writes
109
+ sock = nil
110
+ buf = nil
111
+ bs = 15609315 * rand
112
+ assert_nothing_raised do
113
+ sock = TCPSocket.new('127.0.0.1', @port)
114
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
115
+ sock.syswrite("Host: example.com\r\n")
116
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
117
+ sock.syswrite("Trailer: X-Foo\r\n")
118
+ sock.syswrite("\r\n")
119
+ sock.syswrite("%x\r\n" % [ bs ])
120
+ sock.syswrite("F" * bs)
121
+ sock.syswrite("\r\n0\r\nX-")
122
+ "Foo: bar\r\n\r\n".each_byte do |x|
123
+ sock.syswrite x.chr
124
+ sleep 0.05
125
+ end
126
+ # we wrote the entire request before shutting down, server should
127
+ # continue to process our request and never hit EOFError on our sock
128
+ sock.shutdown(Socket::SHUT_WR)
129
+ buf = sock.read
130
+ end
131
+ assert_equal 'hello!\n', buf.split(/\r\n\r\n/).last
132
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
133
+ assert_equal 'hello!\n', next_client
134
+ lines = File.readlines("test_stderr.#$$.log")
135
+ assert lines.grep(/^Unicorn::ClientShutdown: /).empty?
136
+ assert_nothing_raised { sock.close }
137
+ end
138
+
139
+ def test_client_shutdown_write_truncates
140
+ sock = nil
141
+ buf = nil
142
+ bs = 15609315 * rand
143
+ assert_nothing_raised do
144
+ sock = TCPSocket.new('127.0.0.1', @port)
145
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
146
+ sock.syswrite("Host: example.com\r\n")
147
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
148
+ sock.syswrite("Trailer: X-Foo\r\n")
149
+ sock.syswrite("\r\n")
150
+ sock.syswrite("%x\r\n" % [ bs ])
151
+ sock.syswrite("F" * (bs / 2.0))
152
+
153
+ # shutdown prematurely, this will force the server to abort
154
+ # processing on us even during app dispatch
155
+ sock.shutdown(Socket::SHUT_WR)
156
+ IO.select([sock], nil, nil, 60) or raise "Timed out"
157
+ buf = sock.read
158
+ end
159
+ assert_equal "", buf
160
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
161
+ assert_equal 'hello!\n', next_client
162
+ lines = File.readlines("test_stderr.#$$.log")
163
+ lines = lines.grep(/^Unicorn::ClientShutdown: bytes_read=\d+/)
164
+ assert_equal 1, lines.size
165
+ assert_match %r{\AUnicorn::ClientShutdown: bytes_read=\d+ true$}, lines[0]
166
+ assert_nothing_raised { sock.close }
167
+ end
168
+
169
+ def test_client_malformed_body
170
+ sock = nil
171
+ buf = nil
172
+ bs = 15653984
173
+ assert_nothing_raised do
174
+ sock = TCPSocket.new('127.0.0.1', @port)
175
+ sock.syswrite("PUT /hello HTTP/1.1\r\n")
176
+ sock.syswrite("Host: example.com\r\n")
177
+ sock.syswrite("Transfer-Encoding: chunked\r\n")
178
+ sock.syswrite("Trailer: X-Foo\r\n")
179
+ sock.syswrite("\r\n")
180
+ sock.syswrite("%x\r\n" % [ bs ])
181
+ sock.syswrite("F" * bs)
182
+ end
183
+ begin
184
+ File.open("/dev/urandom", "rb") { |fp| sock.syswrite(fp.sysread(16384)) }
185
+ rescue
186
+ end
187
+ assert_nothing_raised { sock.close }
188
+ next_client = Net::HTTP.get(URI.parse("http://127.0.0.1:#@port/"))
189
+ assert_equal 'hello!\n', next_client
190
+ lines = File.readlines("test_stderr.#$$.log")
191
+ lines = lines.grep(/^Unicorn::HttpParserError: .* true$/)
192
+ assert_equal 1, lines.size
193
+ end
106
194
 
107
195
  def do_test(string, chunk, close_after=nil, shutdown_delay=0)
108
196
  # Do not use instance variables here, because it needs to be thread safe
@@ -63,6 +63,20 @@ class TestSocketHelper < Test::Unit::TestCase
63
63
  File.umask(old_umask)
64
64
  end
65
65
 
66
+ def test_bind_listen_unix_umask
67
+ old_umask = File.umask(0777)
68
+ tmp = Tempfile.new 'unix.sock'
69
+ @unix_listener_path = tmp.path
70
+ File.unlink(@unix_listener_path)
71
+ @unix_listener = bind_listen(@unix_listener_path, :umask => 077)
72
+ assert UNIXServer === @unix_listener
73
+ assert_equal @unix_listener_path, sock_name(@unix_listener)
74
+ assert_equal 0140700, File.stat(@unix_listener_path).mode
75
+ assert_equal 0777, File.umask
76
+ ensure
77
+ File.umask(old_umask)
78
+ end
79
+
66
80
  def test_bind_listen_unix_idempotent
67
81
  test_bind_listen_unix
68
82
  a = bind_listen(@unix_listener)
data/unicorn.gemspec CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |s|
14
14
  s.name = %q{unicorn}
15
15
  s.version = ENV["VERSION"]
16
16
 
17
- s.authors = ["Unicorn developers"]
17
+ s.authors = ["Unicorn hackers"]
18
18
  s.date = Time.now.utc.strftime('%Y-%m-%d')
19
19
  s.description = File.read("README").split(/\n\n/)[1]
20
20
  s.email = %q{mongrel-unicorn@rubyforge.org}
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: unicorn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.94.0
4
+ version: 0.95.0
5
5
  platform: ruby
6
6
  authors:
7
- - Unicorn developers
7
+ - Unicorn hackers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-05 00:00:00 -08:00
12
+ date: 2009-11-15 00:00:00 -08:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -35,6 +35,7 @@ executables:
35
35
  extensions:
36
36
  - ext/unicorn_http/extconf.rb
37
37
  extra_rdoc_files:
38
+ - FAQ
38
39
  - README
39
40
  - TUNING
40
41
  - PHILOSOPHY
@@ -76,6 +77,7 @@ files:
76
77
  - Documentation/GNUmakefile
77
78
  - Documentation/unicorn.1.txt
78
79
  - Documentation/unicorn_rails.1.txt
80
+ - FAQ
79
81
  - GIT-VERSION-FILE
80
82
  - GIT-VERSION-GEN
81
83
  - GNUmakefile