unicorn 0.94.0 → 0.95.0
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.
- 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
|