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 +1 -0
- data/Documentation/unicorn.1.txt +5 -0
- data/FAQ +45 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +1 -1
- data/HACKING +8 -5
- data/Rakefile +39 -2
- data/SIGNALS +7 -0
- data/bin/unicorn_rails +0 -1
- data/lib/unicorn.rb +21 -5
- data/lib/unicorn/app/old_rails.rb +3 -1
- data/lib/unicorn/app/old_rails/static.rb +5 -3
- data/lib/unicorn/configurator.rb +27 -7
- data/lib/unicorn/const.rb +2 -2
- data/lib/unicorn/socket_helper.rb +19 -7
- data/lib/unicorn/tee_input.rb +33 -15
- data/lib/unicorn/util.rb +24 -7
- data/local.mk.sample +19 -10
- data/test/unit/test_server.rb +90 -2
- data/test/unit/test_socket_helper.rb +14 -0
- data/unicorn.gemspec +1 -1
- metadata +5 -3
data/.document
CHANGED
data/Documentation/unicorn.1.txt
CHANGED
@@ -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
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.
|
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
|
-
|
31
|
+
gmake -j4 test
|
29
32
|
|
30
33
|
Running just one unit test:
|
31
34
|
|
32
|
-
|
35
|
+
gmake test/unit/test_http_parser.rb
|
33
36
|
|
34
37
|
Running just one test case in a unit test:
|
35
38
|
|
36
|
-
|
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
|
-
|
109
|
+
gmake install-gem
|
107
110
|
|
108
111
|
Without RubyGems (via setup.rb):
|
109
112
|
|
110
|
-
|
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
|
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
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
data/lib/unicorn/configurator.rb
CHANGED
@@ -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
|
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
|
-
# #
|
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.
|
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)
|
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)
|
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)
|
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)
|
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)
|
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
|
data/lib/unicorn/tee_input.rb
CHANGED
@@ -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. +
|
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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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 +
|
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 +
|
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(
|
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
|
178
|
+
return dst if dst.nil? || @size.nil?
|
161
179
|
|
162
|
-
while
|
163
|
-
|
180
|
+
while dst.size < length && tee(length - dst.size, @buf2)
|
181
|
+
dst << @buf2
|
164
182
|
end
|
165
183
|
|
166
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
39
|
-
next if
|
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
|
-
|
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 := $(
|
14
|
-
|
14
|
+
ruby := $(prefix)/bin/ruby
|
15
|
+
gem_paths := $(addprefix $(prefix)/lib/ruby/gems/1.8/gems/,$(gems))
|
15
16
|
else
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
22
|
-
|
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 -
|
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 -
|
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 }' < $<
|
data/test/unit/test_server.rb
CHANGED
@@ -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
|
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.
|
4
|
+
version: 0.95.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Unicorn
|
7
|
+
- Unicorn hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
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
|