boourns-unicorn 4.4.1
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/.CHANGELOG.old +25 -0
- data/.document +29 -0
- data/.gitignore +24 -0
- data/.mailmap +26 -0
- data/.wrongdoc.yml +10 -0
- data/Application_Timeouts +77 -0
- data/CONTRIBUTORS +35 -0
- data/COPYING +674 -0
- data/DESIGN +97 -0
- data/Documentation/.gitignore +5 -0
- data/Documentation/GNUmakefile +30 -0
- data/Documentation/unicorn.1.txt +174 -0
- data/Documentation/unicorn_rails.1.txt +175 -0
- data/FAQ +53 -0
- data/GIT-VERSION-GEN +40 -0
- data/GNUmakefile +267 -0
- data/HACKING +134 -0
- data/ISSUES +36 -0
- data/KNOWN_ISSUES +79 -0
- data/LICENSE +64 -0
- data/Links +56 -0
- data/PHILOSOPHY +145 -0
- data/README +149 -0
- data/Rakefile +97 -0
- data/SIGNALS +114 -0
- data/Sandbox +96 -0
- data/TODO +5 -0
- data/TUNING +98 -0
- data/bin/unicorn +121 -0
- data/bin/unicorn_rails +209 -0
- data/examples/big_app_gc.rb +2 -0
- data/examples/echo.ru +27 -0
- data/examples/git.ru +13 -0
- data/examples/init.sh +74 -0
- data/examples/logger_mp_safe.rb +25 -0
- data/examples/logrotate.conf +29 -0
- data/examples/nginx.conf +156 -0
- data/examples/unicorn.conf.minimal.rb +13 -0
- data/examples/unicorn.conf.rb +94 -0
- data/ext/unicorn_http/CFLAGS +13 -0
- data/ext/unicorn_http/c_util.h +124 -0
- data/ext/unicorn_http/common_field_optimization.h +111 -0
- data/ext/unicorn_http/ext_help.h +86 -0
- data/ext/unicorn_http/extconf.rb +10 -0
- data/ext/unicorn_http/global_variables.h +97 -0
- data/ext/unicorn_http/httpdate.c +82 -0
- data/ext/unicorn_http/unicorn_http.rl +1036 -0
- data/ext/unicorn_http/unicorn_http_common.rl +76 -0
- data/lib/unicorn.rb +107 -0
- data/lib/unicorn/app/exec_cgi.rb +154 -0
- data/lib/unicorn/app/inetd.rb +109 -0
- data/lib/unicorn/app/old_rails.rb +35 -0
- data/lib/unicorn/app/old_rails/static.rb +59 -0
- data/lib/unicorn/cgi_wrapper.rb +147 -0
- data/lib/unicorn/configurator.rb +630 -0
- data/lib/unicorn/const.rb +40 -0
- data/lib/unicorn/http_request.rb +83 -0
- data/lib/unicorn/http_response.rb +45 -0
- data/lib/unicorn/http_server.rb +755 -0
- data/lib/unicorn/launcher.rb +62 -0
- data/lib/unicorn/oob_gc.rb +71 -0
- data/lib/unicorn/preread_input.rb +33 -0
- data/lib/unicorn/socket_helper.rb +208 -0
- data/lib/unicorn/ssl_client.rb +11 -0
- data/lib/unicorn/ssl_configurator.rb +104 -0
- data/lib/unicorn/ssl_server.rb +42 -0
- data/lib/unicorn/stream_input.rb +149 -0
- data/lib/unicorn/tee_input.rb +126 -0
- data/lib/unicorn/tmpio.rb +29 -0
- data/lib/unicorn/util.rb +69 -0
- data/lib/unicorn/worker.rb +88 -0
- data/local.mk.sample +59 -0
- data/script/isolate_for_tests +32 -0
- data/setup.rb +1586 -0
- data/t/.gitignore +5 -0
- data/t/GNUmakefile +82 -0
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +17 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/broken-app.ru +12 -0
- data/t/detach.ru +11 -0
- data/t/env.ru +3 -0
- data/t/heartbeat-timeout.ru +12 -0
- data/t/listener_names.ru +4 -0
- data/t/my-tap-lib.sh +201 -0
- data/t/oob_gc.ru +21 -0
- data/t/oob_gc_path.ru +21 -0
- data/t/pid.ru +3 -0
- data/t/preread_input.ru +17 -0
- data/t/rack-input-tests.ru +21 -0
- data/t/sslgen.sh +71 -0
- data/t/t0000-http-basic.sh +50 -0
- data/t/t0001-reload-bad-config.sh +53 -0
- data/t/t0002-config-conflict.sh +49 -0
- data/t/t0002-parser-error.sh +94 -0
- data/t/t0003-working_directory.sh +51 -0
- data/t/t0004-heartbeat-timeout.sh +69 -0
- data/t/t0004-working_directory_broken.sh +24 -0
- data/t/t0005-working_directory_app.rb.sh +37 -0
- data/t/t0006-reopen-logs.sh +83 -0
- data/t/t0006.ru +13 -0
- data/t/t0007-working_directory_no_embed_cli.sh +44 -0
- data/t/t0008-back_out_of_upgrade.sh +110 -0
- data/t/t0009-broken-app.sh +56 -0
- data/t/t0009-winch_ttin.sh +59 -0
- data/t/t0010-reap-logging.sh +55 -0
- data/t/t0011-active-unix-socket.sh +79 -0
- data/t/t0012-reload-empty-config.sh +85 -0
- data/t/t0013-rewindable-input-false.sh +24 -0
- data/t/t0013.ru +12 -0
- data/t/t0014-rewindable-input-true.sh +24 -0
- data/t/t0014.ru +12 -0
- data/t/t0015-configurator-internals.sh +25 -0
- data/t/t0016-trust-x-forwarded-false.sh +30 -0
- data/t/t0017-trust-x-forwarded-true.sh +30 -0
- data/t/t0018-write-on-close.sh +23 -0
- data/t/t0019-max_header_len.sh +49 -0
- data/t/t0020-at_exit-handler.sh +49 -0
- data/t/t0021-process_detach.sh +29 -0
- data/t/t0022-listener_names-preload_app.sh +32 -0
- data/t/t0100-rack-input-tests.sh +124 -0
- data/t/t0116-client_body_buffer_size.sh +80 -0
- data/t/t0116.ru +16 -0
- data/t/t0600-https-server-basic.sh +48 -0
- data/t/t9000-preread-input.sh +48 -0
- data/t/t9001-oob_gc.sh +47 -0
- data/t/t9002-oob_gc-path.sh +75 -0
- data/t/test-lib.sh +113 -0
- data/t/write-on-close.ru +11 -0
- data/test/aggregate.rb +15 -0
- data/test/benchmark/README +50 -0
- data/test/benchmark/dd.ru +18 -0
- data/test/benchmark/stack.ru +8 -0
- data/test/exec/README +5 -0
- data/test/exec/test_exec.rb +1041 -0
- data/test/test_helper.rb +300 -0
- data/test/unit/test_configurator.rb +158 -0
- data/test/unit/test_droplet.rb +28 -0
- data/test/unit/test_http_parser.rb +860 -0
- data/test/unit/test_http_parser_ng.rb +716 -0
- data/test/unit/test_http_parser_xftrust.rb +38 -0
- data/test/unit/test_request.rb +197 -0
- data/test/unit/test_response.rb +99 -0
- data/test/unit/test_server.rb +289 -0
- data/test/unit/test_signals.rb +207 -0
- data/test/unit/test_sni_hostnames.rb +47 -0
- data/test/unit/test_socket_helper.rb +192 -0
- data/test/unit/test_stream_input.rb +204 -0
- data/test/unit/test_tee_input.rb +296 -0
- data/test/unit/test_upload.rb +306 -0
- data/test/unit/test_util.rb +99 -0
- data/unicorn.gemspec +44 -0
- metadata +333 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
|
|
3
|
+
# :enddoc:
|
|
4
|
+
# Frequently used constants when constructing requests or responses.
|
|
5
|
+
# Many times the constant just refers to a string with the same
|
|
6
|
+
# contents. Using these constants gave about a 3% to 10% performance
|
|
7
|
+
# improvement over using the strings directly. Symbols did not really
|
|
8
|
+
# improve things much compared to constants.
|
|
9
|
+
module Unicorn::Const
|
|
10
|
+
|
|
11
|
+
UNICORN_VERSION = "4.4.0"
|
|
12
|
+
|
|
13
|
+
# default TCP listen host address (0.0.0.0, all interfaces)
|
|
14
|
+
DEFAULT_HOST = "0.0.0.0"
|
|
15
|
+
|
|
16
|
+
# default TCP listen port (8080)
|
|
17
|
+
DEFAULT_PORT = 8080
|
|
18
|
+
|
|
19
|
+
# default TCP listen address and port (0.0.0.0:8080)
|
|
20
|
+
DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}"
|
|
21
|
+
|
|
22
|
+
# The basic request body size we'll try to read at once (16 kilobytes).
|
|
23
|
+
CHUNK_SIZE = 16 * 1024
|
|
24
|
+
|
|
25
|
+
# Maximum request body size before it is moved out of memory and into a
|
|
26
|
+
# temporary file for reading (112 kilobytes). This is the default
|
|
27
|
+
# value of client_body_buffer_size.
|
|
28
|
+
MAX_BODY = 1024 * 112
|
|
29
|
+
|
|
30
|
+
# :stopdoc:
|
|
31
|
+
# common errors we'll send back
|
|
32
|
+
ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
|
|
33
|
+
ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n"
|
|
34
|
+
ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
|
|
35
|
+
ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
|
|
36
|
+
EXPECT_100_RESPONSE = "100 Continue\r\n\r\n HTTP/1.1"
|
|
37
|
+
|
|
38
|
+
HTTP_EXPECT = "HTTP_EXPECT"
|
|
39
|
+
# :startdoc:
|
|
40
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
# no stable API here
|
|
4
|
+
require 'unicorn_http'
|
|
5
|
+
|
|
6
|
+
# TODO: remove redundant names
|
|
7
|
+
Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
|
|
8
|
+
class Unicorn::HttpParser
|
|
9
|
+
|
|
10
|
+
# default parameters we merge into the request env for Rack handlers
|
|
11
|
+
DEFAULTS = {
|
|
12
|
+
"rack.errors" => $stderr,
|
|
13
|
+
"rack.multiprocess" => true,
|
|
14
|
+
"rack.multithread" => false,
|
|
15
|
+
"rack.run_once" => false,
|
|
16
|
+
"rack.version" => [1, 1],
|
|
17
|
+
"SCRIPT_NAME" => "",
|
|
18
|
+
|
|
19
|
+
# this is not in the Rack spec, but some apps may rely on it
|
|
20
|
+
"SERVER_SOFTWARE" => "Unicorn #{Unicorn::Const::UNICORN_VERSION}"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
NULL_IO = StringIO.new("")
|
|
24
|
+
|
|
25
|
+
# :stopdoc:
|
|
26
|
+
# A frozen format for this is about 15% faster
|
|
27
|
+
REMOTE_ADDR = 'REMOTE_ADDR'.freeze
|
|
28
|
+
RACK_INPUT = 'rack.input'.freeze
|
|
29
|
+
@@input_class = Unicorn::TeeInput
|
|
30
|
+
|
|
31
|
+
def self.input_class
|
|
32
|
+
@@input_class
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.input_class=(klass)
|
|
36
|
+
@@input_class = klass
|
|
37
|
+
end
|
|
38
|
+
# :startdoc:
|
|
39
|
+
|
|
40
|
+
# Does the majority of the IO processing. It has been written in
|
|
41
|
+
# Ruby using about 8 different IO processing strategies.
|
|
42
|
+
#
|
|
43
|
+
# It is currently carefully constructed to make sure that it gets
|
|
44
|
+
# the best possible performance for the common case: GET requests
|
|
45
|
+
# that are fully complete after a single read(2)
|
|
46
|
+
#
|
|
47
|
+
# Anyone who thinks they can make it faster is more than welcome to
|
|
48
|
+
# take a crack at it.
|
|
49
|
+
#
|
|
50
|
+
# returns an environment hash suitable for Rack if successful
|
|
51
|
+
# This does minimal exception trapping and it is up to the caller
|
|
52
|
+
# to handle any socket errors (e.g. user aborted upload).
|
|
53
|
+
def read(socket)
|
|
54
|
+
clear
|
|
55
|
+
e = env
|
|
56
|
+
|
|
57
|
+
# From http://www.ietf.org/rfc/rfc3875:
|
|
58
|
+
# "Script authors should be aware that the REMOTE_ADDR and
|
|
59
|
+
# REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
|
|
60
|
+
# may not identify the ultimate source of the request. They
|
|
61
|
+
# identify the client for the immediate request to the server;
|
|
62
|
+
# that client may be a proxy, gateway, or other intermediary
|
|
63
|
+
# acting on behalf of the actual source client."
|
|
64
|
+
e[REMOTE_ADDR] = socket.kgio_addr
|
|
65
|
+
|
|
66
|
+
# short circuit the common case with small GET requests first
|
|
67
|
+
socket.kgio_read!(16384, buf)
|
|
68
|
+
if parse.nil?
|
|
69
|
+
# Parser is not done, queue up more data to read and continue parsing
|
|
70
|
+
# an Exception thrown from the parser will throw us out of the loop
|
|
71
|
+
false until add_parse(socket.kgio_read!(16384))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# detect if the socket is valid by writing a partial response:
|
|
75
|
+
if headers?
|
|
76
|
+
"HTTP/1.1 ".each_char { |c| socket.write(c) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
e[RACK_INPUT] = 0 == content_length ?
|
|
80
|
+
NULL_IO : @@input_class.new(socket, self)
|
|
81
|
+
e.merge!(DEFAULTS)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
# :enddoc:
|
|
3
|
+
# Writes a Rack response to your client using the HTTP/1.1 specification.
|
|
4
|
+
# You use it by simply doing:
|
|
5
|
+
#
|
|
6
|
+
# status, headers, body = rack_app.call(env)
|
|
7
|
+
# http_response_write(socket, status, headers, body)
|
|
8
|
+
#
|
|
9
|
+
# Most header correctness (including Content-Length and Content-Type)
|
|
10
|
+
# is the job of Rack, with the exception of the "Date" and "Status" header.
|
|
11
|
+
module Unicorn::HttpResponse
|
|
12
|
+
|
|
13
|
+
# Every standard HTTP code mapped to the appropriate message.
|
|
14
|
+
CODES = Rack::Utils::HTTP_STATUS_CODES.inject({}) { |hash,(code,msg)|
|
|
15
|
+
hash[code] = "#{code} #{msg}"
|
|
16
|
+
hash
|
|
17
|
+
}
|
|
18
|
+
CRLF = "\r\n"
|
|
19
|
+
|
|
20
|
+
# writes the rack_response to socket as an HTTP response
|
|
21
|
+
def http_response_write(socket, status, headers, body)
|
|
22
|
+
status = CODES[status.to_i] || status
|
|
23
|
+
|
|
24
|
+
if headers
|
|
25
|
+
buf = "#{status}\r\n" \
|
|
26
|
+
"Date: #{httpdate}\r\n" \
|
|
27
|
+
"Status: #{status}\r\n" \
|
|
28
|
+
"Connection: close\r\n"
|
|
29
|
+
headers.each do |key, value|
|
|
30
|
+
next if %r{\A(?:Date\z|Connection\z)}i =~ key
|
|
31
|
+
if value =~ /\n/
|
|
32
|
+
# avoiding blank, key-only cookies with /\n+/
|
|
33
|
+
buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
|
|
34
|
+
else
|
|
35
|
+
buf << "#{key}: #{value}\r\n"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
socket.write(buf << CRLF)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
body.each { |chunk| socket.write(chunk) }
|
|
42
|
+
ensure
|
|
43
|
+
body.respond_to?(:close) and body.close
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
# -*- encoding: binary -*-
|
|
2
|
+
require "unicorn/ssl_server"
|
|
3
|
+
|
|
4
|
+
# This is the process manager of Unicorn. This manages worker
|
|
5
|
+
# processes which in turn handle the I/O and application process.
|
|
6
|
+
# Listener sockets are started in the master process and shared with
|
|
7
|
+
# forked worker children.
|
|
8
|
+
#
|
|
9
|
+
# Users do not need to know the internals of this class, but reading the
|
|
10
|
+
# {source}[http://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
|
|
11
|
+
# is education for programmers wishing to learn how \Unicorn works.
|
|
12
|
+
# See Unicorn::Configurator for information on how to configure \Unicorn.
|
|
13
|
+
class Unicorn::HttpServer
|
|
14
|
+
# :stopdoc:
|
|
15
|
+
attr_accessor :app, :request, :timeout, :worker_processes,
|
|
16
|
+
:before_fork, :after_fork, :before_exec,
|
|
17
|
+
:listener_opts, :preload_app,
|
|
18
|
+
:reexec_pid, :orig_app, :init_listeners,
|
|
19
|
+
:master_pid, :config, :ready_pipe, :user
|
|
20
|
+
attr_reader :pid, :logger
|
|
21
|
+
include Unicorn::SocketHelper
|
|
22
|
+
include Unicorn::HttpResponse
|
|
23
|
+
include Unicorn::SSLServer
|
|
24
|
+
|
|
25
|
+
# backwards compatibility with 1.x
|
|
26
|
+
Worker = Unicorn::Worker
|
|
27
|
+
|
|
28
|
+
# all bound listener sockets
|
|
29
|
+
LISTENERS = []
|
|
30
|
+
|
|
31
|
+
# listeners we have yet to bind
|
|
32
|
+
NEW_LISTENERS = []
|
|
33
|
+
|
|
34
|
+
# This hash maps PIDs to Workers
|
|
35
|
+
WORKERS = {}
|
|
36
|
+
|
|
37
|
+
# We use SELF_PIPE differently in the master and worker processes:
|
|
38
|
+
#
|
|
39
|
+
# * The master process never closes or reinitializes this once
|
|
40
|
+
# initialized. Signal handlers in the master process will write to
|
|
41
|
+
# it to wake up the master from IO.select in exactly the same manner
|
|
42
|
+
# djb describes in http://cr.yp.to/docs/selfpipe.html
|
|
43
|
+
#
|
|
44
|
+
# * The workers immediately close the pipe they inherit from the
|
|
45
|
+
# master and replace it with a new pipe after forking. This new
|
|
46
|
+
# pipe is also used to wakeup from IO.select from inside (worker)
|
|
47
|
+
# signal handlers. However, workers *close* the pipe descriptors in
|
|
48
|
+
# the signal handlers to raise EBADF in IO.select instead of writing
|
|
49
|
+
# like we do in the master. We cannot easily use the reader set for
|
|
50
|
+
# IO.select because LISTENERS is already that set, and it's extra
|
|
51
|
+
# work (and cycles) to distinguish the pipe FD from the reader set
|
|
52
|
+
# once IO.select returns. So we're lazy and just close the pipe when
|
|
53
|
+
# a (rare) signal arrives in the worker and reinitialize the pipe later.
|
|
54
|
+
SELF_PIPE = []
|
|
55
|
+
|
|
56
|
+
# signal queue used for self-piping
|
|
57
|
+
SIG_QUEUE = []
|
|
58
|
+
|
|
59
|
+
# list of signals we care about and trap in master.
|
|
60
|
+
QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
|
|
61
|
+
|
|
62
|
+
# :startdoc:
|
|
63
|
+
# We populate this at startup so we can figure out how to reexecute
|
|
64
|
+
# and upgrade the currently running instance of Unicorn
|
|
65
|
+
# This Hash is considered a stable interface and changing its contents
|
|
66
|
+
# will allow you to switch between different installations of Unicorn
|
|
67
|
+
# or even different installations of the same applications without
|
|
68
|
+
# downtime. Keys of this constant Hash are described as follows:
|
|
69
|
+
#
|
|
70
|
+
# * 0 - the path to the unicorn/unicorn_rails executable
|
|
71
|
+
# * :argv - a deep copy of the ARGV array the executable originally saw
|
|
72
|
+
# * :cwd - the working directory of the application, this is where
|
|
73
|
+
# you originally started Unicorn.
|
|
74
|
+
#
|
|
75
|
+
# To change your unicorn executable to a different path without downtime,
|
|
76
|
+
# you can set the following in your Unicorn config file, HUP and then
|
|
77
|
+
# continue with the traditional USR2 + QUIT upgrade steps:
|
|
78
|
+
#
|
|
79
|
+
# Unicorn::HttpServer::START_CTX[0] = "/home/bofh/1.9.2/bin/unicorn"
|
|
80
|
+
START_CTX = {
|
|
81
|
+
:argv => ARGV.map { |arg| arg.dup },
|
|
82
|
+
0 => $0.dup,
|
|
83
|
+
}
|
|
84
|
+
# We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
|
|
85
|
+
# and like systems
|
|
86
|
+
START_CTX[:cwd] = begin
|
|
87
|
+
a = File.stat(pwd = ENV['PWD'])
|
|
88
|
+
b = File.stat(Dir.pwd)
|
|
89
|
+
a.ino == b.ino && a.dev == b.dev ? pwd : Dir.pwd
|
|
90
|
+
rescue
|
|
91
|
+
Dir.pwd
|
|
92
|
+
end
|
|
93
|
+
# :stopdoc:
|
|
94
|
+
|
|
95
|
+
# Creates a working server on host:port (strange things happen if
|
|
96
|
+
# port isn't a Number). Use HttpServer::run to start the server and
|
|
97
|
+
# HttpServer.run.join to join the thread that's processing
|
|
98
|
+
# incoming requests on the socket.
|
|
99
|
+
def initialize(app, options = {})
|
|
100
|
+
@app = app
|
|
101
|
+
@request = Unicorn::HttpRequest.new
|
|
102
|
+
self.reexec_pid = 0
|
|
103
|
+
options = options.dup
|
|
104
|
+
@ready_pipe = options.delete(:ready_pipe)
|
|
105
|
+
@init_listeners = options[:listeners] ? options[:listeners].dup : []
|
|
106
|
+
options[:use_defaults] = true
|
|
107
|
+
self.config = Unicorn::Configurator.new(options)
|
|
108
|
+
self.listener_opts = {}
|
|
109
|
+
|
|
110
|
+
# we try inheriting listeners first, so we bind them later.
|
|
111
|
+
# we don't write the pid file until we've bound listeners in case
|
|
112
|
+
# unicorn was started twice by mistake. Even though our #pid= method
|
|
113
|
+
# checks for stale/existing pid files, race conditions are still
|
|
114
|
+
# possible (and difficult/non-portable to avoid) and can be likely
|
|
115
|
+
# to clobber the pid if the second start was in quick succession
|
|
116
|
+
# after the first, so we rely on the listener binding to fail in
|
|
117
|
+
# that case. Some tests (in and outside of this source tree) and
|
|
118
|
+
# monitoring tools may also rely on pid files existing before we
|
|
119
|
+
# attempt to connect to the listener(s)
|
|
120
|
+
config.commit!(self, :skip => [:listeners, :pid])
|
|
121
|
+
self.orig_app = app
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Runs the thing. Returns self so you can run join on it
|
|
125
|
+
def start
|
|
126
|
+
inherit_listeners!
|
|
127
|
+
# this pipe is used to wake us up from select(2) in #join when signals
|
|
128
|
+
# are trapped. See trap_deferred.
|
|
129
|
+
init_self_pipe!
|
|
130
|
+
|
|
131
|
+
# setup signal handlers before writing pid file in case people get
|
|
132
|
+
# trigger happy and send signals as soon as the pid file exists.
|
|
133
|
+
# Note that signals don't actually get handled until the #join method
|
|
134
|
+
QUEUE_SIGS.each { |sig| trap(sig) { SIG_QUEUE << sig; awaken_master } }
|
|
135
|
+
trap(:CHLD) { awaken_master }
|
|
136
|
+
self.pid = config[:pid]
|
|
137
|
+
|
|
138
|
+
self.master_pid = $$
|
|
139
|
+
build_app! if preload_app
|
|
140
|
+
bind_new_listeners!
|
|
141
|
+
spawn_missing_workers
|
|
142
|
+
self
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# replaces current listener set with +listeners+. This will
|
|
146
|
+
# close the socket if it will not exist in the new listener set
|
|
147
|
+
def listeners=(listeners)
|
|
148
|
+
cur_names, dead_names = [], []
|
|
149
|
+
listener_names.each do |name|
|
|
150
|
+
if ?/ == name[0]
|
|
151
|
+
# mark unlinked sockets as dead so we can rebind them
|
|
152
|
+
(File.socket?(name) ? cur_names : dead_names) << name
|
|
153
|
+
else
|
|
154
|
+
cur_names << name
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
set_names = listener_names(listeners)
|
|
158
|
+
dead_names.concat(cur_names - set_names).uniq!
|
|
159
|
+
|
|
160
|
+
LISTENERS.delete_if do |io|
|
|
161
|
+
if dead_names.include?(sock_name(io))
|
|
162
|
+
IO_PURGATORY.delete_if do |pio|
|
|
163
|
+
pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
|
|
164
|
+
end
|
|
165
|
+
(io.close rescue nil).nil? # true
|
|
166
|
+
else
|
|
167
|
+
set_server_sockopt(io, listener_opts[sock_name(io)])
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
(set_names - cur_names).each { |addr| listen(addr) }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def stdout_path=(path); redirect_io($stdout, path); end
|
|
176
|
+
def stderr_path=(path); redirect_io($stderr, path); end
|
|
177
|
+
|
|
178
|
+
def logger=(obj)
|
|
179
|
+
Unicorn::HttpRequest::DEFAULTS["rack.logger"] = @logger = obj
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# sets the path for the PID file of the master process
|
|
183
|
+
def pid=(path)
|
|
184
|
+
if path
|
|
185
|
+
if x = valid_pid?(path)
|
|
186
|
+
return path if pid && path == pid && x == $$
|
|
187
|
+
if x == reexec_pid && pid =~ /\.oldbin\z/
|
|
188
|
+
logger.warn("will not set pid=#{path} while reexec-ed "\
|
|
189
|
+
"child is running PID:#{x}")
|
|
190
|
+
return
|
|
191
|
+
end
|
|
192
|
+
raise ArgumentError, "Already running on PID:#{x} " \
|
|
193
|
+
"(or pid=#{path} is stale)"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
unlink_pid_safe(pid) if pid
|
|
197
|
+
|
|
198
|
+
if path
|
|
199
|
+
fp = begin
|
|
200
|
+
tmp = "#{File.dirname(path)}/#{rand}.#$$"
|
|
201
|
+
File.open(tmp, File::RDWR|File::CREAT|File::EXCL, 0644)
|
|
202
|
+
rescue Errno::EEXIST
|
|
203
|
+
retry
|
|
204
|
+
end
|
|
205
|
+
fp.syswrite("#$$\n")
|
|
206
|
+
File.rename(fp.path, path)
|
|
207
|
+
fp.close
|
|
208
|
+
end
|
|
209
|
+
@pid = path
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# add a given address to the +listeners+ set, idempotently
|
|
213
|
+
# Allows workers to add a private, per-process listener via the
|
|
214
|
+
# after_fork hook. Very useful for debugging and testing.
|
|
215
|
+
# +:tries+ may be specified as an option for the number of times
|
|
216
|
+
# to retry, and +:delay+ may be specified as the time in seconds
|
|
217
|
+
# to delay between retries.
|
|
218
|
+
# A negative value for +:tries+ indicates the listen will be
|
|
219
|
+
# retried indefinitely, this is useful when workers belonging to
|
|
220
|
+
# different masters are spawned during a transparent upgrade.
|
|
221
|
+
def listen(address, opt = {}.merge(listener_opts[address] || {}))
|
|
222
|
+
address = config.expand_addr(address)
|
|
223
|
+
return if String === address && listener_names.include?(address)
|
|
224
|
+
|
|
225
|
+
delay = opt[:delay] || 0.5
|
|
226
|
+
tries = opt[:tries] || 5
|
|
227
|
+
begin
|
|
228
|
+
io = bind_listen(address, opt)
|
|
229
|
+
unless Kgio::TCPServer === io || Kgio::UNIXServer === io
|
|
230
|
+
IO_PURGATORY << io
|
|
231
|
+
io = server_cast(io)
|
|
232
|
+
end
|
|
233
|
+
logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
|
|
234
|
+
LISTENERS << io
|
|
235
|
+
io
|
|
236
|
+
rescue Errno::EADDRINUSE => err
|
|
237
|
+
logger.error "adding listener failed addr=#{address} (in use)"
|
|
238
|
+
raise err if tries == 0
|
|
239
|
+
tries -= 1
|
|
240
|
+
logger.error "retrying in #{delay} seconds " \
|
|
241
|
+
"(#{tries < 0 ? 'infinite' : tries} tries left)"
|
|
242
|
+
sleep(delay)
|
|
243
|
+
retry
|
|
244
|
+
rescue => err
|
|
245
|
+
logger.fatal "error adding listener addr=#{address}"
|
|
246
|
+
raise err
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# monitors children and receives signals forever
|
|
251
|
+
# (or until a termination signal is sent). This handles signals
|
|
252
|
+
# one-at-a-time time and we'll happily drop signals in case somebody
|
|
253
|
+
# is signalling us too often.
|
|
254
|
+
def join
|
|
255
|
+
respawn = true
|
|
256
|
+
last_check = Time.now
|
|
257
|
+
|
|
258
|
+
proc_name 'master'
|
|
259
|
+
logger.info "master process ready" # test_exec.rb relies on this message
|
|
260
|
+
if @ready_pipe
|
|
261
|
+
@ready_pipe.syswrite($$.to_s)
|
|
262
|
+
@ready_pipe = @ready_pipe.close rescue nil
|
|
263
|
+
end
|
|
264
|
+
begin
|
|
265
|
+
reap_all_workers
|
|
266
|
+
case SIG_QUEUE.shift
|
|
267
|
+
when nil
|
|
268
|
+
# avoid murdering workers after our master process (or the
|
|
269
|
+
# machine) comes out of suspend/hibernation
|
|
270
|
+
if (last_check + @timeout) >= (last_check = Time.now)
|
|
271
|
+
sleep_time = murder_lazy_workers
|
|
272
|
+
else
|
|
273
|
+
sleep_time = @timeout/2.0 + 1
|
|
274
|
+
@logger.debug("waiting #{sleep_time}s after suspend/hibernation")
|
|
275
|
+
end
|
|
276
|
+
maintain_worker_count if respawn
|
|
277
|
+
master_sleep(sleep_time)
|
|
278
|
+
when :QUIT # graceful shutdown
|
|
279
|
+
break
|
|
280
|
+
when :TERM, :INT # immediate shutdown
|
|
281
|
+
stop(false)
|
|
282
|
+
break
|
|
283
|
+
when :USR1 # rotate logs
|
|
284
|
+
logger.info "master reopening logs..."
|
|
285
|
+
Unicorn::Util.reopen_logs
|
|
286
|
+
logger.info "master done reopening logs"
|
|
287
|
+
kill_each_worker(:USR1)
|
|
288
|
+
when :USR2 # exec binary, stay alive in case something went wrong
|
|
289
|
+
reexec
|
|
290
|
+
when :WINCH
|
|
291
|
+
if Unicorn::Configurator::RACKUP[:daemonized]
|
|
292
|
+
respawn = false
|
|
293
|
+
logger.info "gracefully stopping all workers"
|
|
294
|
+
kill_each_worker(:QUIT)
|
|
295
|
+
self.worker_processes = 0
|
|
296
|
+
else
|
|
297
|
+
logger.info "SIGWINCH ignored because we're not daemonized"
|
|
298
|
+
end
|
|
299
|
+
when :TTIN
|
|
300
|
+
respawn = true
|
|
301
|
+
self.worker_processes += 1
|
|
302
|
+
when :TTOU
|
|
303
|
+
self.worker_processes -= 1 if self.worker_processes > 0
|
|
304
|
+
when :HUP
|
|
305
|
+
respawn = true
|
|
306
|
+
if config.config_file
|
|
307
|
+
load_config!
|
|
308
|
+
else # exec binary and exit if there's no config file
|
|
309
|
+
logger.info "config_file not present, reexecuting binary"
|
|
310
|
+
reexec
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
rescue => e
|
|
314
|
+
Unicorn.log_error(@logger, "master loop error", e)
|
|
315
|
+
end while true
|
|
316
|
+
stop # gracefully shutdown all workers on our way out
|
|
317
|
+
logger.info "master complete"
|
|
318
|
+
unlink_pid_safe(pid) if pid
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Terminates all workers, but does not exit master process
|
|
322
|
+
def stop(graceful = true)
|
|
323
|
+
self.listeners = []
|
|
324
|
+
limit = Time.now + timeout
|
|
325
|
+
until WORKERS.empty? || Time.now > limit
|
|
326
|
+
kill_each_worker(graceful ? :QUIT : :TERM)
|
|
327
|
+
sleep(0.1)
|
|
328
|
+
reap_all_workers
|
|
329
|
+
end
|
|
330
|
+
kill_each_worker(:KILL)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def rewindable_input
|
|
334
|
+
Unicorn::HttpRequest.input_class.method_defined?(:rewind)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def rewindable_input=(bool)
|
|
338
|
+
Unicorn::HttpRequest.input_class = bool ?
|
|
339
|
+
Unicorn::TeeInput : Unicorn::StreamInput
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def client_body_buffer_size
|
|
343
|
+
Unicorn::TeeInput.client_body_buffer_size
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def client_body_buffer_size=(bytes)
|
|
347
|
+
Unicorn::TeeInput.client_body_buffer_size = bytes
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def trust_x_forwarded
|
|
351
|
+
Unicorn::HttpParser.trust_x_forwarded?
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def trust_x_forwarded=(bool)
|
|
355
|
+
Unicorn::HttpParser.trust_x_forwarded = bool
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
private
|
|
359
|
+
|
|
360
|
+
# wait for a signal hander to wake us up and then consume the pipe
|
|
361
|
+
def master_sleep(sec)
|
|
362
|
+
IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
|
|
363
|
+
SELF_PIPE[0].kgio_tryread(11)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def awaken_master
|
|
367
|
+
SELF_PIPE[1].kgio_trywrite('.') # wakeup master process from select
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# reaps all unreaped workers
|
|
371
|
+
def reap_all_workers
|
|
372
|
+
begin
|
|
373
|
+
wpid, status = Process.waitpid2(-1, Process::WNOHANG)
|
|
374
|
+
wpid or return
|
|
375
|
+
if reexec_pid == wpid
|
|
376
|
+
logger.error "reaped #{status.inspect} exec()-ed"
|
|
377
|
+
self.reexec_pid = 0
|
|
378
|
+
self.pid = pid.chomp('.oldbin') if pid
|
|
379
|
+
proc_name 'master'
|
|
380
|
+
else
|
|
381
|
+
worker = WORKERS.delete(wpid) and worker.close rescue nil
|
|
382
|
+
m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
|
|
383
|
+
status.success? ? logger.info(m) : logger.error(m)
|
|
384
|
+
end
|
|
385
|
+
rescue Errno::ECHILD
|
|
386
|
+
break
|
|
387
|
+
end while true
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# reexecutes the START_CTX with a new binary
|
|
391
|
+
def reexec
|
|
392
|
+
if reexec_pid > 0
|
|
393
|
+
begin
|
|
394
|
+
Process.kill(0, reexec_pid)
|
|
395
|
+
logger.error "reexec-ed child already running PID:#{reexec_pid}"
|
|
396
|
+
return
|
|
397
|
+
rescue Errno::ESRCH
|
|
398
|
+
self.reexec_pid = 0
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
if pid
|
|
403
|
+
old_pid = "#{pid}.oldbin"
|
|
404
|
+
begin
|
|
405
|
+
self.pid = old_pid # clear the path for a new pid file
|
|
406
|
+
rescue ArgumentError
|
|
407
|
+
logger.error "old PID:#{valid_pid?(old_pid)} running with " \
|
|
408
|
+
"existing pid=#{old_pid}, refusing rexec"
|
|
409
|
+
return
|
|
410
|
+
rescue => e
|
|
411
|
+
logger.error "error writing pid=#{old_pid} #{e.class} #{e.message}"
|
|
412
|
+
return
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
self.reexec_pid = fork do
|
|
417
|
+
listener_fds = Hash[LISTENERS.map do |sock|
|
|
418
|
+
# IO#close_on_exec= will be available on any future version of
|
|
419
|
+
# Ruby that sets FD_CLOEXEC by default on new file descriptors
|
|
420
|
+
# ref: http://redmine.ruby-lang.org/issues/5041
|
|
421
|
+
sock.close_on_exec = false if sock.respond_to?(:close_on_exec=)
|
|
422
|
+
[ sock.fileno, sock ]
|
|
423
|
+
end]
|
|
424
|
+
ENV['UNICORN_FD'] = listener_fds.keys.join(',')
|
|
425
|
+
Dir.chdir(START_CTX[:cwd])
|
|
426
|
+
cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
|
|
427
|
+
|
|
428
|
+
# avoid leaking FDs we don't know about, but let before_exec
|
|
429
|
+
# unset FD_CLOEXEC, if anything else in the app eventually
|
|
430
|
+
# relies on FD inheritence.
|
|
431
|
+
(3..1024).each do |io|
|
|
432
|
+
next if listener_fds.include?(io)
|
|
433
|
+
io = IO.for_fd(io) rescue next
|
|
434
|
+
IO_PURGATORY << io
|
|
435
|
+
io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
# exec(command, hash) works in at least 1.9.1+, but will only be
|
|
439
|
+
# required in 1.9.4/2.0.0 at earliest.
|
|
440
|
+
cmd << listener_fds if RUBY_VERSION >= "1.9.1"
|
|
441
|
+
logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
|
|
442
|
+
before_exec.call(self)
|
|
443
|
+
exec(*cmd)
|
|
444
|
+
end
|
|
445
|
+
proc_name 'master (old)'
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
# forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
|
|
449
|
+
def murder_lazy_workers
|
|
450
|
+
next_sleep = @timeout - 1
|
|
451
|
+
now = Time.now.to_i
|
|
452
|
+
WORKERS.dup.each_pair do |wpid, worker|
|
|
453
|
+
tick = worker.tick
|
|
454
|
+
0 == tick and next # skip workers that haven't processed any clients
|
|
455
|
+
diff = now - tick
|
|
456
|
+
tmp = @timeout - diff
|
|
457
|
+
if tmp >= 0
|
|
458
|
+
next_sleep > tmp and next_sleep = tmp
|
|
459
|
+
next
|
|
460
|
+
end
|
|
461
|
+
next_sleep = 0
|
|
462
|
+
logger.error "worker=#{worker.nr} PID:#{wpid} timeout " \
|
|
463
|
+
"(#{diff}s > #{@timeout}s), killing"
|
|
464
|
+
kill_worker(:KILL, wpid) # take no prisoners for timeout violations
|
|
465
|
+
end
|
|
466
|
+
next_sleep <= 0 ? 1 : next_sleep
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def after_fork_internal
|
|
470
|
+
@ready_pipe.close if @ready_pipe
|
|
471
|
+
Unicorn::Configurator::RACKUP.clear
|
|
472
|
+
@ready_pipe = @init_listeners = @before_exec = @before_fork = nil
|
|
473
|
+
|
|
474
|
+
srand # http://redmine.ruby-lang.org/issues/4338
|
|
475
|
+
|
|
476
|
+
# The OpenSSL PRNG is seeded with only the pid, and apps with frequently
|
|
477
|
+
# dying workers can recycle pids
|
|
478
|
+
OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def spawn_missing_workers
|
|
482
|
+
worker_nr = -1
|
|
483
|
+
until (worker_nr += 1) == @worker_processes
|
|
484
|
+
WORKERS.value?(worker_nr) and next
|
|
485
|
+
worker = Worker.new(worker_nr)
|
|
486
|
+
before_fork.call(self, worker)
|
|
487
|
+
if pid = fork
|
|
488
|
+
WORKERS[pid] = worker
|
|
489
|
+
else
|
|
490
|
+
after_fork_internal
|
|
491
|
+
worker_loop(worker)
|
|
492
|
+
exit
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
rescue => e
|
|
496
|
+
@logger.error(e) rescue nil
|
|
497
|
+
exit!
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def maintain_worker_count
|
|
501
|
+
(off = WORKERS.size - worker_processes) == 0 and return
|
|
502
|
+
off < 0 and return spawn_missing_workers
|
|
503
|
+
WORKERS.dup.each_pair { |wpid,w|
|
|
504
|
+
w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
|
|
505
|
+
}
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# if we get any error, try to write something back to the client
|
|
509
|
+
# assuming we haven't closed the socket, but don't get hung up
|
|
510
|
+
# if the socket is already closed or broken. We'll always ensure
|
|
511
|
+
# the socket is closed at the end of this function
|
|
512
|
+
def handle_error(client, e)
|
|
513
|
+
msg = case e
|
|
514
|
+
when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF,
|
|
515
|
+
Errno::ENOTCONN
|
|
516
|
+
Unicorn::Const::ERROR_500_RESPONSE
|
|
517
|
+
when Unicorn::RequestURITooLongError
|
|
518
|
+
Unicorn::Const::ERROR_414_RESPONSE
|
|
519
|
+
when Unicorn::RequestEntityTooLargeError
|
|
520
|
+
Unicorn::Const::ERROR_413_RESPONSE
|
|
521
|
+
when Unicorn::HttpParserError # try to tell the client they're bad
|
|
522
|
+
Unicorn::Const::ERROR_400_RESPONSE
|
|
523
|
+
else
|
|
524
|
+
Unicorn.log_error(@logger, "app error", e)
|
|
525
|
+
Unicorn::Const::ERROR_500_RESPONSE
|
|
526
|
+
end
|
|
527
|
+
client.kgio_trywrite(msg)
|
|
528
|
+
client.close
|
|
529
|
+
rescue
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
# once a client is accepted, it is processed in its entirety here
|
|
533
|
+
# in 3 easy steps: read request, call app, write app response
|
|
534
|
+
def process_client(client)
|
|
535
|
+
status, headers, body = @app.call(env = @request.read(client))
|
|
536
|
+
|
|
537
|
+
if 100 == status.to_i
|
|
538
|
+
client.write(Unicorn::Const::EXPECT_100_RESPONSE)
|
|
539
|
+
env.delete(Unicorn::Const::HTTP_EXPECT)
|
|
540
|
+
status, headers, body = @app.call(env)
|
|
541
|
+
end
|
|
542
|
+
@request.headers? or headers = nil
|
|
543
|
+
http_response_write(client, status, headers, body)
|
|
544
|
+
client.shutdown # in case of fork() in Rack app
|
|
545
|
+
client.close # flush and uncork socket immediately, no keepalive
|
|
546
|
+
rescue => e
|
|
547
|
+
handle_error(client, e)
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
EXIT_SIGS = [ :QUIT, :TERM, :INT ]
|
|
551
|
+
WORKER_QUEUE_SIGS = QUEUE_SIGS - EXIT_SIGS
|
|
552
|
+
|
|
553
|
+
# gets rid of stuff the worker has no business keeping track of
|
|
554
|
+
# to free some resources and drops all sig handlers.
|
|
555
|
+
# traps for USR1, USR2, and HUP may be set in the after_fork Proc
|
|
556
|
+
# by the user.
|
|
557
|
+
def init_worker_process(worker)
|
|
558
|
+
# we'll re-trap :QUIT later for graceful shutdown iff we accept clients
|
|
559
|
+
EXIT_SIGS.each { |sig| trap(sig) { exit!(0) } }
|
|
560
|
+
exit!(0) if (SIG_QUEUE & EXIT_SIGS)[0]
|
|
561
|
+
WORKER_QUEUE_SIGS.each { |sig| trap(sig, nil) }
|
|
562
|
+
trap(:CHLD, 'DEFAULT')
|
|
563
|
+
SIG_QUEUE.clear
|
|
564
|
+
proc_name "worker[#{worker.nr}]"
|
|
565
|
+
START_CTX.clear
|
|
566
|
+
init_self_pipe!
|
|
567
|
+
WORKERS.clear
|
|
568
|
+
LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
|
569
|
+
after_fork.call(self, worker) # can drop perms
|
|
570
|
+
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
|
571
|
+
self.timeout /= 2.0 # halve it for select()
|
|
572
|
+
@config = nil
|
|
573
|
+
build_app! unless preload_app
|
|
574
|
+
ssl_enable!
|
|
575
|
+
@after_fork = @listener_opts = @orig_app = nil
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
def reopen_worker_logs(worker_nr)
|
|
579
|
+
logger.info "worker=#{worker_nr} reopening logs..."
|
|
580
|
+
Unicorn::Util.reopen_logs
|
|
581
|
+
logger.info "worker=#{worker_nr} done reopening logs"
|
|
582
|
+
init_self_pipe!
|
|
583
|
+
rescue => e
|
|
584
|
+
logger.error(e) rescue nil
|
|
585
|
+
exit!(77) # EX_NOPERM in sysexits.h
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
# runs inside each forked worker, this sits around and waits
|
|
589
|
+
# for connections and doesn't die until the parent dies (or is
|
|
590
|
+
# given a INT, QUIT, or TERM signal)
|
|
591
|
+
def worker_loop(worker)
|
|
592
|
+
ppid = master_pid
|
|
593
|
+
init_worker_process(worker)
|
|
594
|
+
nr = 0 # this becomes negative if we need to reopen logs
|
|
595
|
+
l = LISTENERS.dup
|
|
596
|
+
ready = l.dup
|
|
597
|
+
|
|
598
|
+
# closing anything we IO.select on will raise EBADF
|
|
599
|
+
trap(:USR1) { nr = -65536; SELF_PIPE[0].close rescue nil }
|
|
600
|
+
trap(:QUIT) { worker = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
|
|
601
|
+
logger.info "worker=#{worker.nr} ready"
|
|
602
|
+
|
|
603
|
+
begin
|
|
604
|
+
nr < 0 and reopen_worker_logs(worker.nr)
|
|
605
|
+
nr = 0
|
|
606
|
+
|
|
607
|
+
worker.tick = Time.now.to_i
|
|
608
|
+
while sock = ready.shift
|
|
609
|
+
if client = sock.kgio_tryaccept
|
|
610
|
+
process_client(client)
|
|
611
|
+
nr += 1
|
|
612
|
+
worker.tick = Time.now.to_i
|
|
613
|
+
end
|
|
614
|
+
break if nr < 0
|
|
615
|
+
end
|
|
616
|
+
|
|
617
|
+
# make the following bet: if we accepted clients this round,
|
|
618
|
+
# we're probably reasonably busy, so avoid calling select()
|
|
619
|
+
# and do a speculative non-blocking accept() on ready listeners
|
|
620
|
+
# before we sleep again in select().
|
|
621
|
+
unless nr == 0 # (nr < 0) => reopen logs (unlikely)
|
|
622
|
+
ready = l.dup
|
|
623
|
+
redo
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
ppid == Process.ppid or return
|
|
627
|
+
|
|
628
|
+
# timeout used so we can detect parent death:
|
|
629
|
+
worker.tick = Time.now.to_i
|
|
630
|
+
ret = IO.select(l, nil, SELF_PIPE, @timeout) and ready = ret[0]
|
|
631
|
+
rescue => e
|
|
632
|
+
redo if nr < 0 && (Errno::EBADF === e || IOError === e) # reopen logs
|
|
633
|
+
Unicorn.log_error(@logger, "listen loop error", e) if worker
|
|
634
|
+
end while worker
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
# delivers a signal to a worker and fails gracefully if the worker
|
|
638
|
+
# is no longer running.
|
|
639
|
+
def kill_worker(signal, wpid)
|
|
640
|
+
Process.kill(signal, wpid)
|
|
641
|
+
rescue Errno::ESRCH
|
|
642
|
+
worker = WORKERS.delete(wpid) and worker.close rescue nil
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
# delivers a signal to each worker
|
|
646
|
+
def kill_each_worker(signal)
|
|
647
|
+
WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
# unlinks a PID file at given +path+ if it contains the current PID
|
|
651
|
+
# still potentially racy without locking the directory (which is
|
|
652
|
+
# non-portable and may interact badly with other programs), but the
|
|
653
|
+
# window for hitting the race condition is small
|
|
654
|
+
def unlink_pid_safe(path)
|
|
655
|
+
(File.read(path).to_i == $$ and File.unlink(path)) rescue nil
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
# returns a PID if a given path contains a non-stale PID file,
|
|
659
|
+
# nil otherwise.
|
|
660
|
+
def valid_pid?(path)
|
|
661
|
+
wpid = File.read(path).to_i
|
|
662
|
+
wpid <= 0 and return
|
|
663
|
+
Process.kill(0, wpid)
|
|
664
|
+
wpid
|
|
665
|
+
rescue Errno::EPERM
|
|
666
|
+
logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
|
|
667
|
+
nil
|
|
668
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
|
669
|
+
# don't unlink stale pid files, racy without non-portable locking...
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
def load_config!
|
|
673
|
+
loaded_app = app
|
|
674
|
+
logger.info "reloading config_file=#{config.config_file}"
|
|
675
|
+
config[:listeners].replace(@init_listeners)
|
|
676
|
+
config.reload
|
|
677
|
+
config.commit!(self)
|
|
678
|
+
kill_each_worker(:QUIT)
|
|
679
|
+
Unicorn::Util.reopen_logs
|
|
680
|
+
self.app = orig_app
|
|
681
|
+
build_app! if preload_app
|
|
682
|
+
logger.info "done reloading config_file=#{config.config_file}"
|
|
683
|
+
rescue StandardError, LoadError, SyntaxError => e
|
|
684
|
+
Unicorn.log_error(@logger,
|
|
685
|
+
"error reloading config_file=#{config.config_file}", e)
|
|
686
|
+
self.app = loaded_app
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
# returns an array of string names for the given listener array
|
|
690
|
+
def listener_names(listeners = LISTENERS)
|
|
691
|
+
listeners.map { |io| sock_name(io) }
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
def build_app!
|
|
695
|
+
if app.respond_to?(:arity) && app.arity == 0
|
|
696
|
+
if defined?(Gem) && Gem.respond_to?(:refresh)
|
|
697
|
+
logger.info "Refreshing Gem list"
|
|
698
|
+
Gem.refresh
|
|
699
|
+
end
|
|
700
|
+
self.app = app.call
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
def proc_name(tag)
|
|
705
|
+
$0 = ([ File.basename(START_CTX[0]), tag
|
|
706
|
+
]).concat(START_CTX[:argv]).join(' ')
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def redirect_io(io, path)
|
|
710
|
+
File.open(path, 'ab') { |fp| io.reopen(fp) } if path
|
|
711
|
+
io.sync = true
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
def init_self_pipe!
|
|
715
|
+
SELF_PIPE.each { |io| io.close rescue nil }
|
|
716
|
+
SELF_PIPE.replace(Kgio::Pipe.new)
|
|
717
|
+
SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
def inherit_listeners!
|
|
721
|
+
# inherit sockets from parents, they need to be plain Socket objects
|
|
722
|
+
# before they become Kgio::UNIXServer or Kgio::TCPServer
|
|
723
|
+
inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
|
|
724
|
+
io = Socket.for_fd(fd.to_i)
|
|
725
|
+
set_server_sockopt(io, listener_opts[sock_name(io)])
|
|
726
|
+
IO_PURGATORY << io
|
|
727
|
+
logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
|
|
728
|
+
server_cast(io)
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
config_listeners = config[:listeners].dup
|
|
732
|
+
LISTENERS.replace(inherited)
|
|
733
|
+
|
|
734
|
+
# we start out with generic Socket objects that get cast to either
|
|
735
|
+
# Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
|
|
736
|
+
# objects share the same OS-level file descriptor as the higher-level
|
|
737
|
+
# *Server objects; we need to prevent Socket objects from being
|
|
738
|
+
# garbage-collected
|
|
739
|
+
config_listeners -= listener_names
|
|
740
|
+
if config_listeners.empty? && LISTENERS.empty?
|
|
741
|
+
config_listeners << Unicorn::Const::DEFAULT_LISTEN
|
|
742
|
+
@init_listeners << Unicorn::Const::DEFAULT_LISTEN
|
|
743
|
+
START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
|
|
744
|
+
end
|
|
745
|
+
NEW_LISTENERS.replace(config_listeners)
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
# call only after calling inherit_listeners!
|
|
749
|
+
# This binds any listeners we did NOT inherit from the parent
|
|
750
|
+
def bind_new_listeners!
|
|
751
|
+
NEW_LISTENERS.each { |addr| listen(addr) }
|
|
752
|
+
raise ArgumentError, "no listeners" if LISTENERS.empty?
|
|
753
|
+
NEW_LISTENERS.clear
|
|
754
|
+
end
|
|
755
|
+
end
|