unicorn 4.9.0 → 6.0.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.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +5 -0
  3. data/.olddoc.yml +13 -6
  4. data/Application_Timeouts +7 -7
  5. data/DESIGN +2 -4
  6. data/Documentation/.gitignore +1 -3
  7. data/Documentation/unicorn.1 +222 -0
  8. data/Documentation/unicorn_rails.1 +207 -0
  9. data/FAQ +17 -8
  10. data/GIT-VERSION-GEN +1 -1
  11. data/GNUmakefile +121 -56
  12. data/HACKING +1 -2
  13. data/ISSUES +40 -41
  14. data/KNOWN_ISSUES +11 -11
  15. data/LICENSE +2 -2
  16. data/Links +24 -25
  17. data/PHILOSOPHY +0 -6
  18. data/README +46 -39
  19. data/SIGNALS +2 -2
  20. data/Sandbox +10 -9
  21. data/TODO +0 -2
  22. data/TUNING +30 -9
  23. data/archive/slrnpull.conf +1 -1
  24. data/bin/unicorn +4 -2
  25. data/bin/unicorn_rails +3 -3
  26. data/examples/big_app_gc.rb +1 -1
  27. data/examples/init.sh +36 -8
  28. data/examples/logrotate.conf +17 -2
  29. data/examples/nginx.conf +14 -14
  30. data/examples/unicorn.conf.minimal.rb +2 -2
  31. data/examples/unicorn.conf.rb +3 -6
  32. data/examples/unicorn.socket +11 -0
  33. data/examples/unicorn@.service +40 -0
  34. data/ext/unicorn_http/common_field_optimization.h +23 -5
  35. data/ext/unicorn_http/ext_help.h +0 -20
  36. data/ext/unicorn_http/extconf.rb +37 -1
  37. data/ext/unicorn_http/global_variables.h +1 -1
  38. data/ext/unicorn_http/httpdate.c +2 -2
  39. data/ext/unicorn_http/unicorn_http.rl +167 -170
  40. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  41. data/lib/unicorn.rb +66 -46
  42. data/lib/unicorn/configurator.rb +110 -44
  43. data/lib/unicorn/const.rb +2 -25
  44. data/lib/unicorn/http_request.rb +110 -31
  45. data/lib/unicorn/http_response.rb +17 -31
  46. data/lib/unicorn/http_server.rb +238 -157
  47. data/lib/unicorn/launcher.rb +1 -1
  48. data/lib/unicorn/oob_gc.rb +6 -6
  49. data/lib/unicorn/socket_helper.rb +58 -78
  50. data/lib/unicorn/stream_input.rb +8 -7
  51. data/lib/unicorn/tee_input.rb +8 -10
  52. data/lib/unicorn/tmpio.rb +8 -7
  53. data/lib/unicorn/util.rb +5 -4
  54. data/lib/unicorn/worker.rb +36 -23
  55. data/t/GNUmakefile +3 -72
  56. data/t/README +4 -4
  57. data/t/t0011-active-unix-socket.sh +1 -1
  58. data/t/t0012-reload-empty-config.sh +2 -1
  59. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  60. data/t/t0301.ru +13 -0
  61. data/t/test-lib.sh +2 -2
  62. data/test/benchmark/README +14 -4
  63. data/test/benchmark/ddstream.ru +50 -0
  64. data/test/benchmark/readinput.ru +40 -0
  65. data/test/benchmark/uconnect.perl +66 -0
  66. data/test/exec/test_exec.rb +73 -19
  67. data/test/test_helper.rb +40 -31
  68. data/test/unit/test_ccc.rb +91 -0
  69. data/test/unit/test_droplet.rb +1 -1
  70. data/test/unit/test_http_parser.rb +46 -16
  71. data/test/unit/test_http_parser_ng.rb +97 -114
  72. data/test/unit/test_request.rb +10 -10
  73. data/test/unit/test_response.rb +28 -16
  74. data/test/unit/test_server.rb +86 -12
  75. data/test/unit/test_signals.rb +8 -8
  76. data/test/unit/test_socket_helper.rb +14 -10
  77. data/test/unit/test_upload.rb +9 -14
  78. data/test/unit/test_util.rb +27 -2
  79. data/unicorn.gemspec +27 -19
  80. metadata +24 -45
  81. data/Documentation/GNUmakefile +0 -30
  82. data/Documentation/unicorn.1.txt +0 -185
  83. data/Documentation/unicorn_rails.1.txt +0 -175
  84. data/examples/git.ru +0 -13
  85. data/lib/unicorn/app/exec_cgi.rb +0 -154
  86. data/lib/unicorn/app/inetd.rb +0 -109
  87. data/lib/unicorn/ssl_client.rb +0 -11
  88. data/lib/unicorn/ssl_configurator.rb +0 -104
  89. data/lib/unicorn/ssl_server.rb +0 -42
  90. data/t/hijack.ru +0 -42
  91. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  92. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  93. data/t/t0200-rack-hijack.sh +0 -27
  94. data/test/unit/test_http_parser_xftrust.rb +0 -38
  95. data/test/unit/test_sni_hostnames.rb +0 -47
data/lib/unicorn/const.rb CHANGED
@@ -1,12 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
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
3
+ module Unicorn::Const # :nodoc:
10
4
  # default TCP listen host address (0.0.0.0, all interfaces)
11
5
  DEFAULT_HOST = "0.0.0.0"
12
6
 
@@ -23,22 +17,5 @@ module Unicorn::Const
23
17
  # temporary file for reading (112 kilobytes). This is the default
24
18
  # value of client_body_buffer_size.
25
19
  MAX_BODY = 1024 * 112
26
-
27
- # :stopdoc:
28
- # common errors we'll send back
29
- # (N.B. these are not used by unicorn, but we won't drop them until
30
- # unicorn 5.x to avoid breaking Rainbows!).
31
- ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n"
32
- ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n"
33
- ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n"
34
- ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n"
35
-
36
- EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n"
37
- EXPECT_100_RESPONSE_SUFFIXED = "100 Continue\r\n\r\nHTTP/1.1 "
38
-
39
- HTTP_RESPONSE_START = ['HTTP', '/1.1 ']
40
- HTTP_EXPECT = "HTTP_EXPECT"
41
-
42
- # :startdoc:
43
20
  end
44
- require 'unicorn/version'
21
+ require_relative 'version'
@@ -13,7 +13,8 @@ class Unicorn::HttpParser
13
13
  "rack.multiprocess" => true,
14
14
  "rack.multithread" => false,
15
15
  "rack.run_once" => false,
16
- "rack.version" => [1, 1],
16
+ "rack.version" => [1, 2],
17
+ "rack.hijack?" => true,
17
18
  "SCRIPT_NAME" => "",
18
19
 
19
20
  # this is not in the Rack spec, but some apps may rely on it
@@ -22,14 +23,12 @@ class Unicorn::HttpParser
22
23
 
23
24
  NULL_IO = StringIO.new("")
24
25
 
25
- attr_accessor :response_start_sent
26
-
27
26
  # :stopdoc:
28
- # A frozen format for this is about 15% faster
29
- REMOTE_ADDR = 'REMOTE_ADDR'.freeze
30
- RACK_INPUT = 'rack.input'.freeze
27
+ HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
28
+ EMPTY_ARRAY = [].freeze
31
29
  @@input_class = Unicorn::TeeInput
32
30
  @@check_client_connection = false
31
+ @@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
33
32
 
34
33
  def self.input_class
35
34
  @@input_class
@@ -63,17 +62,16 @@ def self.check_client_connection=(bool)
63
62
  # This does minimal exception trapping and it is up to the caller
64
63
  # to handle any socket errors (e.g. user aborted upload).
65
64
  def read(socket)
66
- clear
67
65
  e = env
68
66
 
69
- # From http://www.ietf.org/rfc/rfc3875:
67
+ # From https://www.ietf.org/rfc/rfc3875:
70
68
  # "Script authors should be aware that the REMOTE_ADDR and
71
69
  # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
72
70
  # may not identify the ultimate source of the request. They
73
71
  # identify the client for the immediate request to the server;
74
72
  # that client may be a proxy, gateway, or other intermediary
75
73
  # acting on behalf of the actual source client."
76
- e[REMOTE_ADDR] = socket.kgio_addr
74
+ e['REMOTE_ADDR'] = socket.kgio_addr
77
75
 
78
76
  # short circuit the common case with small GET requests first
79
77
  socket.kgio_read!(16384, buf)
@@ -83,40 +81,121 @@ def read(socket)
83
81
  false until add_parse(socket.kgio_read!(16384))
84
82
  end
85
83
 
86
- # detect if the socket is valid by writing a partial response:
87
- if @@check_client_connection && headers?
88
- @response_start_sent = true
89
- Unicorn::Const::HTTP_RESPONSE_START.each { |c| socket.write(c) }
90
- end
84
+ check_client_connection(socket) if @@check_client_connection
85
+
86
+ e['rack.input'] = 0 == content_length ?
87
+ NULL_IO : @@input_class.new(socket, self)
88
+
89
+ # for Rack hijacking in Rack 1.5 and later
90
+ e['unicorn.socket'] = socket
91
+ e['rack.hijack'] = self
91
92
 
92
- e[RACK_INPUT] = 0 == content_length ?
93
- NULL_IO : @@input_class.new(socket, self)
94
- hijack_setup(e, socket)
95
93
  e.merge!(DEFAULTS)
96
94
  end
97
95
 
98
- # Rack 1.5.0 (protocol version 1.2) adds hijack request support
99
- if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
100
- DEFAULTS["rack.hijack?"] = true
101
- DEFAULTS["rack.version"] = [1, 2]
96
+ # for rack.hijack, we respond to this method so no extra allocation
97
+ # of a proc object
98
+ def call
99
+ hijacked!
100
+ env['rack.hijack_io'] = env['unicorn.socket']
101
+ end
102
102
 
103
- RACK_HIJACK = "rack.hijack".freeze
104
- RACK_HIJACK_IO = "rack.hijack_io".freeze
103
+ def hijacked?
104
+ env.include?('rack.hijack_io'.freeze)
105
+ end
105
106
 
106
- def hijacked?
107
- env.include?(RACK_HIJACK_IO)
107
+ if Raindrops.const_defined?(:TCP_Info)
108
+ TCPI = Raindrops::TCP_Info.allocate
109
+
110
+ def check_client_connection(socket) # :nodoc:
111
+ if Unicorn::TCPClient === socket
112
+ # Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
113
+ raise Errno::EPIPE, "client closed connection".freeze,
114
+ EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
115
+ else
116
+ write_http_header(socket)
117
+ end
108
118
  end
109
119
 
110
- def hijack_setup(e, socket)
111
- e[RACK_HIJACK] = proc { e[RACK_HIJACK_IO] = socket }
120
+ if Raindrops.const_defined?(:TCP)
121
+ # raindrops 0.18.0+ supports FreeBSD + Linux using the same names
122
+ # Evaluate these hash lookups at load time so we can
123
+ # generate an opt_case_dispatch instruction
124
+ eval <<-EOS
125
+ def closed_state?(state) # :nodoc:
126
+ case state
127
+ when #{Raindrops::TCP[:ESTABLISHED]}
128
+ false
129
+ when #{Raindrops::TCP.values_at(
130
+ :CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
131
+ true
132
+ else
133
+ false
134
+ end
135
+ end
136
+ EOS
137
+ else
138
+ # raindrops before 0.18 only supported TCP_INFO under Linux
139
+ def closed_state?(state) # :nodoc:
140
+ case state
141
+ when 1 # ESTABLISHED
142
+ false
143
+ when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
144
+ true
145
+ else
146
+ false
147
+ end
148
+ end
112
149
  end
113
150
  else
114
- # old Rack, do nothing.
115
- def hijack_setup(e, _)
151
+
152
+ # Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
153
+ # Not that efficient, but probably still better than doing unnecessary
154
+ # work after a client gives up.
155
+ def check_client_connection(socket) # :nodoc:
156
+ if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
157
+ opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
158
+ if opt =~ /\bstate=(\S+)/
159
+ raise Errno::EPIPE, "client closed connection".freeze,
160
+ EMPTY_ARRAY if closed_state_str?($1)
161
+ else
162
+ @@tcpi_inspect_ok = false
163
+ write_http_header(socket)
164
+ end
165
+ opt.clear
166
+ else
167
+ write_http_header(socket)
168
+ end
116
169
  end
117
170
 
118
- def hijacked?
119
- false
171
+ def closed_state_str?(state)
172
+ case state
173
+ when 'ESTABLISHED'
174
+ false
175
+ # not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
176
+ when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
177
+ true
178
+ else
179
+ false
180
+ end
181
+ end
182
+ end
183
+
184
+ def write_http_header(socket) # :nodoc:
185
+ if headers?
186
+ self.response_start_sent = true
187
+ HTTP_RESPONSE_START.each { |c| socket.write(c) }
188
+ end
189
+ end
190
+
191
+ # called by ext/unicorn_http/unicorn_http.rl via rb_funcall
192
+ def self.is_chunked?(v) # :nodoc:
193
+ vals = v.split(/[ \t]*,[ \t]*/).map!(&:downcase)
194
+ if vals.pop == 'chunked'.freeze
195
+ return true unless vals.include?('chunked'.freeze)
196
+ raise Unicorn::HttpParserError, 'double chunked', []
120
197
  end
198
+ return false unless vals.include?('chunked'.freeze)
199
+ raise Unicorn::HttpParserError, 'chunked not last', []
121
200
  end
122
201
  end
@@ -10,66 +10,52 @@
10
10
  # is the job of Rack, with the exception of the "Date" and "Status" header.
11
11
  module Unicorn::HttpResponse
12
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"
13
+ STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
14
+ Rack::Utils::HTTP_STATUS_CODES : {}
19
15
 
16
+ # internal API, code will always be common-enough-for-even-old-Rack
20
17
  def err_response(code, response_start_sent)
21
- "#{response_start_sent ? '' : 'HTTP/1.1 '}#{CODES[code]}\r\n\r\n"
18
+ "#{response_start_sent ? '' : 'HTTP/1.1 '}" \
19
+ "#{code} #{STATUS_CODES[code]}\r\n\r\n"
22
20
  end
23
21
 
24
22
  # writes the rack_response to socket as an HTTP response
25
23
  def http_response_write(socket, status, headers, body,
26
- response_start_sent=false)
27
- status = CODES[status.to_i] || status
24
+ req = Unicorn::HttpRequest.new)
28
25
  hijack = nil
29
26
 
30
- http_response_start = response_start_sent ? '' : 'HTTP/1.1 '
31
27
  if headers
32
- buf = "#{http_response_start}#{status}\r\n" \
28
+ code = status.to_i
29
+ msg = STATUS_CODES[code]
30
+ start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
31
+ buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
33
32
  "Date: #{httpdate}\r\n" \
34
- "Status: #{status}\r\n" \
35
33
  "Connection: close\r\n"
36
34
  headers.each do |key, value|
37
35
  case key
38
- when %r{\A(?:Date\z|Connection\z)}i
36
+ when %r{\A(?:Date|Connection)\z}i
39
37
  next
40
38
  when "rack.hijack"
41
- # this was an illegal key in Rack < 1.5, so it should be
42
- # OK to silently discard it for those older versions
43
- hijack = hijack_prepare(value)
39
+ # This should only be hit under Rack >= 1.5, as this was an illegal
40
+ # key in Rack < 1.5
41
+ hijack = value
44
42
  else
45
43
  if value =~ /\n/
46
44
  # avoiding blank, key-only cookies with /\n+/
47
- buf << value.split(/\n+/).map! { |v| "#{key}: #{v}\r\n" }.join
45
+ value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" }
48
46
  else
49
47
  buf << "#{key}: #{value}\r\n"
50
48
  end
51
49
  end
52
50
  end
53
- socket.write(buf << CRLF)
51
+ socket.write(buf << "\r\n".freeze)
54
52
  end
55
53
 
56
54
  if hijack
57
- body = nil # ensure we do not close body
55
+ req.hijacked!
58
56
  hijack.call(socket)
59
57
  else
60
58
  body.each { |chunk| socket.write(chunk) }
61
59
  end
62
- ensure
63
- body.respond_to?(:close) and body.close
64
- end
65
-
66
- # Rack 1.5.0 (protocol version 1.2) adds response hijacking support
67
- if ((Rack::VERSION[0] << 8) | Rack::VERSION[1]) >= 0x0102
68
- def hijack_prepare(value)
69
- value
70
- end
71
- else
72
- def hijack_prepare(_)
73
- end
74
60
  end
75
61
  end
@@ -1,5 +1,4 @@
1
1
  # -*- encoding: binary -*-
2
- require "unicorn/ssl_server"
3
2
 
4
3
  # This is the process manager of Unicorn. This manages worker
5
4
  # processes which in turn handle the I/O and application process.
@@ -7,51 +6,30 @@
7
6
  # forked worker children.
8
7
  #
9
8
  # 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.
9
+ # {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
10
+ # is education for programmers wishing to learn how unicorn works.
11
+ # See Unicorn::Configurator for information on how to configure unicorn.
13
12
  class Unicorn::HttpServer
14
13
  # :stopdoc:
15
- attr_accessor :app, :request, :timeout, :worker_processes,
14
+ attr_accessor :app, :timeout, :worker_processes,
16
15
  :before_fork, :after_fork, :before_exec,
17
16
  :listener_opts, :preload_app,
18
- :reexec_pid, :orig_app, :init_listeners,
19
- :master_pid, :config, :ready_pipe, :user
17
+ :orig_app, :config, :ready_pipe, :user,
18
+ :default_middleware, :early_hints
19
+ attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
20
20
 
21
21
  attr_reader :pid, :logger
22
22
  include Unicorn::SocketHelper
23
23
  include Unicorn::HttpResponse
24
- include Unicorn::SSLServer
25
-
26
- # backwards compatibility with 1.x
27
- Worker = Unicorn::Worker
28
24
 
29
25
  # all bound listener sockets
26
+ # note: this is public used by raindrops, but not recommended for use
27
+ # in new projects
30
28
  LISTENERS = []
31
29
 
32
30
  # listeners we have yet to bind
33
31
  NEW_LISTENERS = []
34
32
 
35
- # This hash maps PIDs to Workers
36
- WORKERS = {}
37
-
38
- # We use SELF_PIPE differently in the master and worker processes:
39
- #
40
- # * The master process never closes or reinitializes this once
41
- # initialized. Signal handlers in the master process will write to
42
- # it to wake up the master from IO.select in exactly the same manner
43
- # djb describes in http://cr.yp.to/docs/selfpipe.html
44
- #
45
- # * The workers immediately close the pipe they inherit. See the
46
- # Unicorn::Worker class for the pipe workers use.
47
- SELF_PIPE = []
48
-
49
- # signal queue used for self-piping
50
- SIG_QUEUE = []
51
-
52
- # list of signals we care about and trap in master.
53
- QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
54
-
55
33
  # :startdoc:
56
34
  # We populate this at startup so we can figure out how to reexecute
57
35
  # and upgrade the currently running instance of Unicorn
@@ -60,7 +38,7 @@ class Unicorn::HttpServer
60
38
  # or even different installations of the same applications without
61
39
  # downtime. Keys of this constant Hash are described as follows:
62
40
  #
63
- # * 0 - the path to the unicorn/unicorn_rails executable
41
+ # * 0 - the path to the unicorn executable
64
42
  # * :argv - a deep copy of the ARGV array the executable originally saw
65
43
  # * :cwd - the working directory of the application, this is where
66
44
  # you originally started Unicorn.
@@ -69,9 +47,9 @@ class Unicorn::HttpServer
69
47
  # you can set the following in your Unicorn config file, HUP and then
70
48
  # continue with the traditional USR2 + QUIT upgrade steps:
71
49
  #
72
- # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.2.0/bin/unicorn"
50
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
73
51
  START_CTX = {
74
- :argv => ARGV.map { |arg| arg.dup },
52
+ :argv => ARGV.map(&:dup),
75
53
  0 => $0.dup,
76
54
  }
77
55
  # We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
@@ -91,8 +69,8 @@ class Unicorn::HttpServer
91
69
  # incoming requests on the socket.
92
70
  def initialize(app, options = {})
93
71
  @app = app
94
- @request = Unicorn::HttpRequest.new
95
- self.reexec_pid = 0
72
+ @reexec_pid = 0
73
+ @default_middleware = true
96
74
  options = options.dup
97
75
  @ready_pipe = options.delete(:ready_pipe)
98
76
  @init_listeners = options[:listeners] ? options[:listeners].dup : []
@@ -100,6 +78,20 @@ def initialize(app, options = {})
100
78
  self.config = Unicorn::Configurator.new(options)
101
79
  self.listener_opts = {}
102
80
 
81
+ # We use @self_pipe differently in the master and worker processes:
82
+ #
83
+ # * The master process never closes or reinitializes this once
84
+ # initialized. Signal handlers in the master process will write to
85
+ # it to wake up the master from IO.select in exactly the same manner
86
+ # djb describes in https://cr.yp.to/docs/selfpipe.html
87
+ #
88
+ # * The workers immediately close the pipe they inherit. See the
89
+ # Unicorn::Worker class for the pipe workers use.
90
+ @self_pipe = []
91
+ @workers = {} # hash maps PIDs to Workers
92
+ @sig_queue = [] # signal queue used for self-piping
93
+ @pid = nil
94
+
103
95
  # we try inheriting listeners first, so we bind them later.
104
96
  # we don't write the pid file until we've bound listeners in case
105
97
  # unicorn was started twice by mistake. Even though our #pid= method
@@ -111,7 +103,18 @@ def initialize(app, options = {})
111
103
  # monitoring tools may also rely on pid files existing before we
112
104
  # attempt to connect to the listener(s)
113
105
  config.commit!(self, :skip => [:listeners, :pid])
114
- self.orig_app = app
106
+ @orig_app = app
107
+ # list of signals we care about and trap in master.
108
+ @queue_sigs = [
109
+ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
110
+
111
+ @worker_data = if worker_data = ENV['UNICORN_WORKER']
112
+ worker_data = worker_data.split(',').map!(&:to_i)
113
+ worker_data[1] = worker_data.slice!(1..2).map do |i|
114
+ Kgio::Pipe.for_fd(i)
115
+ end
116
+ worker_data
117
+ end
115
118
  end
116
119
 
117
120
  # Runs the thing. Returns self so you can run join on it
@@ -119,13 +122,13 @@ def start
119
122
  inherit_listeners!
120
123
  # this pipe is used to wake us up from select(2) in #join when signals
121
124
  # are trapped. See trap_deferred.
122
- SELF_PIPE.replace(Unicorn.pipe)
123
- @master_pid = $$
125
+ @self_pipe.replace(Unicorn.pipe)
126
+ @master_pid = @worker_data ? Process.ppid : $$
124
127
 
125
128
  # setup signal handlers before writing pid file in case people get
126
129
  # trigger happy and send signals as soon as the pid file exists.
127
130
  # Note that signals don't actually get handled until the #join method
128
- QUEUE_SIGS.each { |sig| trap(sig) { SIG_QUEUE << sig; awaken_master } }
131
+ @queue_sigs.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
129
132
  trap(:CHLD) { awaken_master }
130
133
 
131
134
  # write pid early for Mongrel compatibility if we're not inheriting sockets
@@ -146,7 +149,7 @@ def start
146
149
  def listeners=(listeners)
147
150
  cur_names, dead_names = [], []
148
151
  listener_names.each do |name|
149
- if ?/ == name[0]
152
+ if name.start_with?('/')
150
153
  # mark unlinked sockets as dead so we can rebind them
151
154
  (File.socket?(name) ? cur_names : dead_names) << name
152
155
  else
@@ -158,9 +161,6 @@ def listeners=(listeners)
158
161
 
159
162
  LISTENERS.delete_if do |io|
160
163
  if dead_names.include?(sock_name(io))
161
- IO_PURGATORY.delete_if do |pio|
162
- pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
163
- end
164
164
  (io.close rescue nil).nil? # true
165
165
  else
166
166
  set_server_sockopt(io, listener_opts[sock_name(io)])
@@ -198,7 +198,7 @@ def pid=(path)
198
198
  if path
199
199
  if x = valid_pid?(path)
200
200
  return path if pid && path == pid && x == $$
201
- if x == reexec_pid && pid =~ /\.oldbin\z/
201
+ if x == @reexec_pid && pid.end_with?('.oldbin')
202
202
  logger.warn("will not set pid=#{path} while reexec-ed "\
203
203
  "child is running PID:#{x}")
204
204
  return
@@ -241,7 +241,7 @@ def listen(address, opt = {}.merge(listener_opts[address] || {}))
241
241
  begin
242
242
  io = bind_listen(address, opt)
243
243
  unless Kgio::TCPServer === io || Kgio::UNIXServer === io
244
- prevent_autoclose(io)
244
+ io.autoclose = false
245
245
  io = server_cast(io)
246
246
  end
247
247
  logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
@@ -267,7 +267,7 @@ def listen(address, opt = {}.merge(listener_opts[address] || {}))
267
267
  # is signalling us too often.
268
268
  def join
269
269
  respawn = true
270
- last_check = Time.now
270
+ last_check = time_now
271
271
 
272
272
  proc_name 'master'
273
273
  logger.info "master process ready" # test_exec.rb relies on this message
@@ -281,11 +281,11 @@ def join
281
281
  end
282
282
  begin
283
283
  reap_all_workers
284
- case SIG_QUEUE.shift
284
+ case @sig_queue.shift
285
285
  when nil
286
286
  # avoid murdering workers after our master process (or the
287
287
  # machine) comes out of suspend/hibernation
288
- if (last_check + @timeout) >= (last_check = Time.now)
288
+ if (last_check + @timeout) >= (last_check = time_now)
289
289
  sleep_time = murder_lazy_workers
290
290
  else
291
291
  sleep_time = @timeout/2.0 + 1
@@ -306,13 +306,13 @@ def join
306
306
  when :USR2 # exec binary, stay alive in case something went wrong
307
307
  reexec
308
308
  when :WINCH
309
- if Unicorn::Configurator::RACKUP[:daemonized]
309
+ if $stdin.tty?
310
+ logger.info "SIGWINCH ignored because we're not daemonized"
311
+ else
310
312
  respawn = false
311
313
  logger.info "gracefully stopping all workers"
312
314
  soft_kill_each_worker(:QUIT)
313
315
  self.worker_processes = 0
314
- else
315
- logger.info "SIGWINCH ignored because we're not daemonized"
316
316
  end
317
317
  when :TTIN
318
318
  respawn = true
@@ -339,8 +339,8 @@ def join
339
339
  # Terminates all workers, but does not exit master process
340
340
  def stop(graceful = true)
341
341
  self.listeners = []
342
- limit = Time.now + timeout
343
- until WORKERS.empty? || Time.now > limit
342
+ limit = time_now + timeout
343
+ until @workers.empty? || time_now > limit
344
344
  if graceful
345
345
  soft_kill_each_worker(:QUIT)
346
346
  else
@@ -369,14 +369,6 @@ def client_body_buffer_size=(bytes)
369
369
  Unicorn::TeeInput.client_body_buffer_size = bytes
370
370
  end
371
371
 
372
- def trust_x_forwarded
373
- Unicorn::HttpParser.trust_x_forwarded?
374
- end
375
-
376
- def trust_x_forwarded=(bool)
377
- Unicorn::HttpParser.trust_x_forwarded = bool
378
- end
379
-
380
372
  def check_client_connection
381
373
  Unicorn::HttpRequest.check_client_connection
382
374
  end
@@ -389,17 +381,17 @@ def check_client_connection=(bool)
389
381
 
390
382
  # wait for a signal hander to wake us up and then consume the pipe
391
383
  def master_sleep(sec)
384
+ @self_pipe[0].wait(sec) or return
392
385
  # 11 bytes is the maximum string length which can be embedded within
393
386
  # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
394
387
  # Most reads are only one byte here and uncommon, so it's not worth a
395
388
  # persistent buffer, either:
396
- IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
397
- SELF_PIPE[0].kgio_tryread(11)
389
+ @self_pipe[0].kgio_tryread(11)
398
390
  end
399
391
 
400
392
  def awaken_master
401
393
  return if $$ != @master_pid
402
- SELF_PIPE[1].kgio_trywrite('.') # wakeup master process from select
394
+ @self_pipe[1].kgio_trywrite('.') # wakeup master process from select
403
395
  end
404
396
 
405
397
  # reaps all unreaped workers
@@ -407,15 +399,14 @@ def reap_all_workers
407
399
  begin
408
400
  wpid, status = Process.waitpid2(-1, Process::WNOHANG)
409
401
  wpid or return
410
- if reexec_pid == wpid
402
+ if @reexec_pid == wpid
411
403
  logger.error "reaped #{status.inspect} exec()-ed"
412
- self.reexec_pid = 0
404
+ @reexec_pid = 0
413
405
  self.pid = pid.chomp('.oldbin') if pid
414
406
  proc_name 'master'
415
407
  else
416
- worker = WORKERS.delete(wpid) and worker.close rescue nil
417
- m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
418
- status.success? ? logger.info(m) : logger.error(m)
408
+ worker = @workers.delete(wpid) and worker.close rescue nil
409
+ @after_worker_exit.call(self, worker, status)
419
410
  end
420
411
  rescue Errno::ECHILD
421
412
  break
@@ -424,13 +415,13 @@ def reap_all_workers
424
415
 
425
416
  # reexecutes the START_CTX with a new binary
426
417
  def reexec
427
- if reexec_pid > 0
418
+ if @reexec_pid > 0
428
419
  begin
429
- Process.kill(0, reexec_pid)
430
- logger.error "reexec-ed child already running PID:#{reexec_pid}"
420
+ Process.kill(0, @reexec_pid)
421
+ logger.error "reexec-ed child already running PID:#@reexec_pid"
431
422
  return
432
423
  rescue Errno::ESRCH
433
- self.reexec_pid = 0
424
+ @reexec_pid = 0
434
425
  end
435
426
  end
436
427
 
@@ -448,15 +439,8 @@ def reexec
448
439
  end
449
440
  end
450
441
 
451
- self.reexec_pid = fork do
452
- listener_fds = {}
453
- LISTENERS.each do |sock|
454
- # IO#close_on_exec= will be available on any future version of
455
- # Ruby that sets FD_CLOEXEC by default on new file descriptors
456
- # ref: http://redmine.ruby-lang.org/issues/5041
457
- sock.close_on_exec = false if sock.respond_to?(:close_on_exec=)
458
- listener_fds[sock.fileno] = sock
459
- end
442
+ @reexec_pid = fork do
443
+ listener_fds = listener_sockets
460
444
  ENV['UNICORN_FD'] = listener_fds.keys.join(',')
461
445
  Dir.chdir(START_CTX[:cwd])
462
446
  cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
@@ -464,16 +448,11 @@ def reexec
464
448
  # avoid leaking FDs we don't know about, but let before_exec
465
449
  # unset FD_CLOEXEC, if anything else in the app eventually
466
450
  # relies on FD inheritence.
467
- (3..1024).each do |io|
468
- next if listener_fds.include?(io)
469
- io = IO.for_fd(io) rescue next
470
- prevent_autoclose(io)
471
- io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
472
- end
451
+ close_sockets_on_exec(listener_fds)
473
452
 
474
453
  # exec(command, hash) works in at least 1.9.1+, but will only be
475
454
  # required in 1.9.4/2.0.0 at earliest.
476
- cmd << listener_fds if RUBY_VERSION >= "1.9.1"
455
+ cmd << listener_fds
477
456
  logger.info "executing #{cmd.inspect} (in #{Dir.pwd})"
478
457
  before_exec.call(self)
479
458
  exec(*cmd)
@@ -481,11 +460,45 @@ def reexec
481
460
  proc_name 'master (old)'
482
461
  end
483
462
 
463
+ def worker_spawn(worker)
464
+ listener_fds = listener_sockets
465
+ env = {}
466
+ env['UNICORN_FD'] = listener_fds.keys.join(',')
467
+
468
+ listener_fds[worker.to_io.fileno] = worker.to_io
469
+ listener_fds[worker.master.fileno] = worker.master
470
+
471
+ worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
472
+ env['UNICORN_WORKER'] = worker_info.join(',')
473
+
474
+ close_sockets_on_exec(listener_fds)
475
+
476
+ Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
477
+ end
478
+
479
+ def listener_sockets
480
+ listener_fds = {}
481
+ LISTENERS.each do |sock|
482
+ sock.close_on_exec = false
483
+ listener_fds[sock.fileno] = sock
484
+ end
485
+ listener_fds
486
+ end
487
+
488
+ def close_sockets_on_exec(sockets)
489
+ (3..1024).each do |io|
490
+ next if sockets.include?(io)
491
+ io = IO.for_fd(io) rescue next
492
+ io.autoclose = false
493
+ io.close_on_exec = true
494
+ end
495
+ end
496
+
484
497
  # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
485
498
  def murder_lazy_workers
486
499
  next_sleep = @timeout - 1
487
- now = Time.now.to_i
488
- WORKERS.dup.each_pair do |wpid, worker|
500
+ now = time_now.to_i
501
+ @workers.dup.each_pair do |wpid, worker|
489
502
  tick = worker.tick
490
503
  0 == tick and next # skip workers that haven't processed any clients
491
504
  diff = now - tick
@@ -503,42 +516,50 @@ def murder_lazy_workers
503
516
  end
504
517
 
505
518
  def after_fork_internal
506
- SELF_PIPE.each { |io| io.close }.clear # this is master-only, now
519
+ @self_pipe.each(&:close).clear # this is master-only, now
507
520
  @ready_pipe.close if @ready_pipe
508
521
  Unicorn::Configurator::RACKUP.clear
509
522
  @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
510
523
 
511
- srand # http://redmine.ruby-lang.org/issues/4338
512
-
513
524
  # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
514
525
  # dying workers can recycle pids
515
526
  OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
516
527
  end
517
528
 
518
529
  def spawn_missing_workers
530
+ if @worker_data
531
+ worker = Unicorn::Worker.new(*@worker_data)
532
+ after_fork_internal
533
+ worker_loop(worker)
534
+ exit
535
+ end
536
+
519
537
  worker_nr = -1
520
538
  until (worker_nr += 1) == @worker_processes
521
- WORKERS.value?(worker_nr) and next
522
- worker = Worker.new(worker_nr)
539
+ @workers.value?(worker_nr) and next
540
+ worker = Unicorn::Worker.new(worker_nr)
523
541
  before_fork.call(self, worker)
524
- if pid = fork
525
- WORKERS[pid] = worker
526
- worker.atfork_parent
527
- else
542
+
543
+ pid = @worker_exec ? worker_spawn(worker) : fork
544
+
545
+ unless pid
528
546
  after_fork_internal
529
547
  worker_loop(worker)
530
548
  exit
531
549
  end
550
+
551
+ @workers[pid] = worker
552
+ worker.atfork_parent
532
553
  end
533
- rescue => e
534
- @logger.error(e) rescue nil
535
- exit!
554
+ rescue => e
555
+ @logger.error(e) rescue nil
556
+ exit!
536
557
  end
537
558
 
538
559
  def maintain_worker_count
539
- (off = WORKERS.size - worker_processes) == 0 and return
560
+ (off = @workers.size - worker_processes) == 0 and return
540
561
  off < 0 and return spawn_missing_workers
541
- WORKERS.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
562
+ @workers.each_value { |w| w.nr >= worker_processes and w.soft_kill(:QUIT) }
542
563
  end
543
564
 
544
565
  # if we get any error, try to write something back to the client
@@ -563,43 +584,79 @@ def handle_error(client, e)
563
584
  client.kgio_trywrite(err_response(code, @request.response_start_sent))
564
585
  end
565
586
  client.close
566
- rescue
587
+ rescue
567
588
  end
568
589
 
569
- def expect_100_response
570
- if @request.response_start_sent
571
- Unicorn::Const::EXPECT_100_RESPONSE_SUFFIXED
590
+ def e103_response_write(client, headers)
591
+ response = if @request.response_start_sent
592
+ "103 Early Hints\r\n"
572
593
  else
573
- Unicorn::Const::EXPECT_100_RESPONSE
594
+ "HTTP/1.1 103 Early Hints\r\n"
574
595
  end
596
+
597
+ headers.each_pair do |k, vs|
598
+ next if !vs || vs.empty?
599
+ values = vs.to_s.split("\n".freeze)
600
+ values.each do |v|
601
+ response << "#{k}: #{v}\r\n"
602
+ end
603
+ end
604
+ response << "\r\n".freeze
605
+ response << "HTTP/1.1 ".freeze if @request.response_start_sent
606
+ client.write(response)
607
+ end
608
+
609
+ def e100_response_write(client, env)
610
+ # We use String#freeze to avoid allocations under Ruby 2.1+
611
+ # Not many users hit this code path, so it's better to reduce the
612
+ # constant table sizes even for 1.9.3-2.0 users who'll hit extra
613
+ # allocations here.
614
+ client.write(@request.response_start_sent ?
615
+ "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
616
+ "HTTP/1.1 100 Continue\r\n\r\n".freeze)
617
+ env.delete('HTTP_EXPECT'.freeze)
575
618
  end
576
619
 
577
620
  # once a client is accepted, it is processed in its entirety here
578
621
  # in 3 easy steps: read request, call app, write app response
579
622
  def process_client(client)
580
- status, headers, body = @app.call(env = @request.read(client))
581
- return if @request.hijacked?
623
+ @request = Unicorn::HttpRequest.new
624
+ env = @request.read(client)
625
+
626
+ if early_hints
627
+ env["rack.early_hints"] = lambda do |headers|
628
+ e103_response_write(client, headers)
629
+ end
630
+ end
582
631
 
583
- if 100 == status.to_i
584
- client.write(expect_100_response)
585
- env.delete(Unicorn::Const::HTTP_EXPECT)
586
- status, headers, body = @app.call(env)
632
+ env["rack.after_reply"] = []
633
+
634
+ status, headers, body = @app.call(env)
635
+
636
+ begin
587
637
  return if @request.hijacked?
638
+
639
+ if 100 == status.to_i
640
+ e100_response_write(client, env)
641
+ status, headers, body = @app.call(env)
642
+ return if @request.hijacked?
643
+ end
644
+ @request.headers? or headers = nil
645
+ http_response_write(client, status, headers, body, @request)
646
+ ensure
647
+ body.respond_to?(:close) and body.close
588
648
  end
589
- @request.headers? or headers = nil
590
- http_response_write(client, status, headers, body,
591
- @request.response_start_sent)
649
+
592
650
  unless client.closed? # rack.hijack may've close this for us
593
651
  client.shutdown # in case of fork() in Rack app
594
652
  client.close # flush and uncork socket immediately, no keepalive
595
653
  end
596
654
  rescue => e
597
655
  handle_error(client, e)
656
+ ensure
657
+ env["rack.after_reply"].each(&:call) if env
598
658
  end
599
659
 
600
- EXIT_SIGS = [ :QUIT, :TERM, :INT ]
601
- WORKER_QUEUE_SIGS = QUEUE_SIGS - EXIT_SIGS
602
-
603
660
  def nuke_listeners!(readers)
604
661
  # only called from the worker, ordering is important here
605
662
  tmp = readers.dup
@@ -614,23 +671,23 @@ def nuke_listeners!(readers)
614
671
  def init_worker_process(worker)
615
672
  worker.atfork_child
616
673
  # we'll re-trap :QUIT later for graceful shutdown iff we accept clients
617
- EXIT_SIGS.each { |sig| trap(sig) { exit!(0) } }
618
- exit!(0) if (SIG_QUEUE & EXIT_SIGS)[0]
619
- WORKER_QUEUE_SIGS.each { |sig| trap(sig, nil) }
674
+ exit_sigs = [ :QUIT, :TERM, :INT ]
675
+ exit_sigs.each { |sig| trap(sig) { exit!(0) } }
676
+ exit!(0) if (@sig_queue & exit_sigs)[0]
677
+ (@queue_sigs - exit_sigs).each { |sig| trap(sig, nil) }
620
678
  trap(:CHLD, 'DEFAULT')
621
- SIG_QUEUE.clear
679
+ @sig_queue.clear
622
680
  proc_name "worker[#{worker.nr}]"
623
681
  START_CTX.clear
624
- WORKERS.clear
682
+ @workers.clear
625
683
 
626
684
  after_fork.call(self, worker) # can drop perms and create listeners
627
- LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
685
+ LISTENERS.each { |sock| sock.close_on_exec = true }
628
686
 
629
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
630
688
  self.timeout /= 2.0 # halve it for select()
631
689
  @config = nil
632
690
  build_app! unless preload_app
633
- ssl_enable!
634
691
  @after_fork = @listener_opts = @orig_app = nil
635
692
  readers = LISTENERS.dup
636
693
  readers << worker
@@ -642,16 +699,16 @@ def reopen_worker_logs(worker_nr)
642
699
  logger.info "worker=#{worker_nr} reopening logs..."
643
700
  Unicorn::Util.reopen_logs
644
701
  logger.info "worker=#{worker_nr} done reopening logs"
645
- rescue => e
646
- logger.error(e) rescue nil
647
- exit!(77) # EX_NOPERM in sysexits.h
702
+ rescue => e
703
+ logger.error(e) rescue nil
704
+ exit!(77) # EX_NOPERM in sysexits.h
648
705
  end
649
706
 
650
707
  # runs inside each forked worker, this sits around and waits
651
708
  # for connections and doesn't die until the parent dies (or is
652
709
  # given a INT, QUIT, or TERM signal)
653
710
  def worker_loop(worker)
654
- ppid = master_pid
711
+ ppid = @master_pid
655
712
  readers = init_worker_process(worker)
656
713
  nr = 0 # this becomes negative if we need to reopen logs
657
714
 
@@ -660,12 +717,13 @@ def worker_loop(worker)
660
717
  trap(:USR1) { nr = -65536 }
661
718
 
662
719
  ready = readers.dup
663
- @logger.info "worker=#{worker.nr} ready"
720
+ nr_listeners = readers.size
721
+ @after_worker_ready.call(self, worker)
664
722
 
665
723
  begin
666
724
  nr < 0 and reopen_worker_logs(worker.nr)
667
725
  nr = 0
668
- worker.tick = Time.now.to_i
726
+ worker.tick = time_now.to_i
669
727
  tmp = ready.dup
670
728
  while sock = tmp.shift
671
729
  # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
@@ -673,7 +731,7 @@ def worker_loop(worker)
673
731
  if client = sock.kgio_tryaccept
674
732
  process_client(client)
675
733
  nr += 1
676
- worker.tick = Time.now.to_i
734
+ worker.tick = time_now.to_i
677
735
  end
678
736
  break if nr < 0
679
737
  end
@@ -682,7 +740,7 @@ def worker_loop(worker)
682
740
  # we're probably reasonably busy, so avoid calling select()
683
741
  # and do a speculative non-blocking accept() on ready listeners
684
742
  # before we sleep again in select().
685
- unless nr == 0
743
+ if nr == nr_listeners
686
744
  tmp = ready.dup
687
745
  redo
688
746
  end
@@ -690,7 +748,7 @@ def worker_loop(worker)
690
748
  ppid == Process.ppid or return
691
749
 
692
750
  # timeout used so we can detect parent death:
693
- worker.tick = Time.now.to_i
751
+ worker.tick = time_now.to_i
694
752
  ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
695
753
  rescue => e
696
754
  redo if nr < 0 && readers[0]
@@ -702,17 +760,17 @@ def worker_loop(worker)
702
760
  # is no longer running.
703
761
  def kill_worker(signal, wpid)
704
762
  Process.kill(signal, wpid)
705
- rescue Errno::ESRCH
706
- worker = WORKERS.delete(wpid) and worker.close rescue nil
763
+ rescue Errno::ESRCH
764
+ worker = @workers.delete(wpid) and worker.close rescue nil
707
765
  end
708
766
 
709
767
  # delivers a signal to each worker
710
768
  def kill_each_worker(signal)
711
- WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
769
+ @workers.keys.each { |wpid| kill_worker(signal, wpid) }
712
770
  end
713
771
 
714
772
  def soft_kill_each_worker(signal)
715
- WORKERS.each_value { |worker| worker.soft_kill(signal) }
773
+ @workers.each_value { |worker| worker.soft_kill(signal) }
716
774
  end
717
775
 
718
776
  # unlinks a PID file at given +path+ if it contains the current PID
@@ -730,11 +788,11 @@ def valid_pid?(path)
730
788
  wpid <= 0 and return
731
789
  Process.kill(0, wpid)
732
790
  wpid
733
- rescue Errno::EPERM
734
- logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
735
- nil
736
- rescue Errno::ESRCH, Errno::ENOENT
737
- # don't unlink stale pid files, racy without non-portable locking...
791
+ rescue Errno::EPERM
792
+ logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
793
+ nil
794
+ rescue Errno::ESRCH, Errno::ENOENT
795
+ # don't unlink stale pid files, racy without non-portable locking...
738
796
  end
739
797
 
740
798
  def load_config!
@@ -745,7 +803,7 @@ def load_config!
745
803
  config.commit!(self)
746
804
  soft_kill_each_worker(:QUIT)
747
805
  Unicorn::Util.reopen_logs
748
- self.app = orig_app
806
+ self.app = @orig_app
749
807
  build_app! if preload_app
750
808
  logger.info "done reloading config_file=#{config.config_file}"
751
809
  rescue StandardError, LoadError, SyntaxError => e
@@ -760,12 +818,12 @@ def listener_names(listeners = LISTENERS)
760
818
  end
761
819
 
762
820
  def build_app!
763
- if app.respond_to?(:arity) && app.arity == 0
821
+ if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
764
822
  if defined?(Gem) && Gem.respond_to?(:refresh)
765
823
  logger.info "Refreshing Gem list"
766
824
  Gem.refresh
767
825
  end
768
- self.app = app.call
826
+ self.app = app.arity == 0 ? app.call : app.call(nil, self)
769
827
  end
770
828
  end
771
829
 
@@ -782,12 +840,23 @@ def redirect_io(io, path)
782
840
  def inherit_listeners!
783
841
  # inherit sockets from parents, they need to be plain Socket objects
784
842
  # before they become Kgio::UNIXServer or Kgio::TCPServer
785
- inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
843
+ inherited = ENV['UNICORN_FD'].to_s.split(',')
844
+
845
+ # emulate sd_listen_fds() for systemd
846
+ sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS')
847
+ if sd_pid.to_i == $$ # n.b. $$ can never be zero
848
+ # 3 = SD_LISTEN_FDS_START
849
+ inherited.concat((3...(3 + sd_fds.to_i)).to_a)
850
+ end
851
+ # to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS
852
+
853
+ inherited.map! do |fd|
786
854
  io = Socket.for_fd(fd.to_i)
855
+ io.autoclose = false
856
+ io = server_cast(io)
787
857
  set_server_sockopt(io, listener_opts[sock_name(io)])
788
- prevent_autoclose(io)
789
- logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
790
- server_cast(io)
858
+ logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
859
+ io
791
860
  end
792
861
 
793
862
  config_listeners = config[:listeners].dup
@@ -810,8 +879,20 @@ def inherit_listeners!
810
879
  # call only after calling inherit_listeners!
811
880
  # This binds any listeners we did NOT inherit from the parent
812
881
  def bind_new_listeners!
813
- NEW_LISTENERS.each { |addr| listen(addr) }
882
+ NEW_LISTENERS.each { |addr| listen(addr) }.clear
814
883
  raise ArgumentError, "no listeners" if LISTENERS.empty?
815
- NEW_LISTENERS.clear
884
+ end
885
+
886
+ # try to use the monotonic clock in Ruby >= 2.1, it is immune to clock
887
+ # offset adjustments and generates less garbage (Float vs Time object)
888
+ begin
889
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
890
+ def time_now
891
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
892
+ end
893
+ rescue NameError, NoMethodError
894
+ def time_now # Ruby <= 2.0
895
+ Time.now
896
+ end
816
897
  end
817
898
  end