unicorn 4.7.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.document +0 -1
  3. data/.gitattributes +5 -0
  4. data/.gitignore +2 -2
  5. data/.manifest +14 -21
  6. data/.olddoc.yml +22 -0
  7. data/Application_Timeouts +7 -7
  8. data/DESIGN +2 -4
  9. data/Documentation/.gitignore +1 -3
  10. data/Documentation/unicorn.1 +222 -0
  11. data/Documentation/unicorn_rails.1 +207 -0
  12. data/FAQ +23 -6
  13. data/GIT-VERSION-FILE +1 -1
  14. data/GIT-VERSION-GEN +1 -1
  15. data/GNUmakefile +139 -92
  16. data/HACKING +13 -28
  17. data/ISSUES +82 -19
  18. data/KNOWN_ISSUES +18 -18
  19. data/LATEST +22 -44
  20. data/LICENSE +2 -2
  21. data/Links +24 -22
  22. data/NEWS +729 -0
  23. data/PHILOSOPHY +0 -6
  24. data/README +50 -48
  25. data/Rakefile +0 -44
  26. data/SIGNALS +12 -3
  27. data/Sandbox +11 -10
  28. data/TODO +0 -2
  29. data/TUNING +30 -9
  30. data/archive/.gitignore +3 -0
  31. data/archive/slrnpull.conf +4 -0
  32. data/bin/unicorn +4 -2
  33. data/bin/unicorn_rails +3 -3
  34. data/examples/big_app_gc.rb +1 -1
  35. data/examples/init.sh +36 -8
  36. data/examples/logrotate.conf +17 -2
  37. data/examples/nginx.conf +14 -14
  38. data/examples/unicorn.conf.minimal.rb +2 -2
  39. data/examples/unicorn.conf.rb +14 -6
  40. data/examples/unicorn.socket +11 -0
  41. data/examples/unicorn@.service +40 -0
  42. data/ext/unicorn_http/common_field_optimization.h +23 -5
  43. data/ext/unicorn_http/ext_help.h +0 -20
  44. data/ext/unicorn_http/extconf.rb +37 -1
  45. data/ext/unicorn_http/global_variables.h +1 -1
  46. data/ext/unicorn_http/httpdate.c +2 -2
  47. data/ext/unicorn_http/unicorn_http.c +940 -644
  48. data/ext/unicorn_http/unicorn_http.rl +167 -170
  49. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  50. data/lib/unicorn/configurator.rb +110 -46
  51. data/lib/unicorn/const.rb +2 -25
  52. data/lib/unicorn/http_request.rb +110 -31
  53. data/lib/unicorn/http_response.rb +17 -31
  54. data/lib/unicorn/http_server.rb +292 -199
  55. data/lib/unicorn/launcher.rb +1 -1
  56. data/lib/unicorn/oob_gc.rb +16 -6
  57. data/lib/unicorn/socket_helper.rb +58 -78
  58. data/lib/unicorn/stream_input.rb +9 -11
  59. data/lib/unicorn/tee_input.rb +16 -11
  60. data/lib/unicorn/tmpio.rb +10 -6
  61. data/lib/unicorn/util.rb +5 -4
  62. data/lib/unicorn/version.rb +1 -1
  63. data/lib/unicorn/worker.rb +99 -22
  64. data/lib/unicorn.rb +69 -42
  65. data/man/man1/unicorn.1 +124 -122
  66. data/man/man1/unicorn_rails.1 +113 -127
  67. data/t/.gitignore +0 -1
  68. data/t/GNUmakefile +3 -80
  69. data/t/README +4 -4
  70. data/t/t0002-parser-error.sh +3 -3
  71. data/t/t0011-active-unix-socket.sh +1 -1
  72. data/t/t0012-reload-empty-config.sh +2 -1
  73. data/t/t0300-no-default-middleware.sh +6 -1
  74. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  75. data/t/t0301.ru +13 -0
  76. data/t/test-lib.sh +2 -2
  77. data/test/benchmark/README +14 -4
  78. data/test/benchmark/ddstream.ru +50 -0
  79. data/test/benchmark/readinput.ru +40 -0
  80. data/test/benchmark/uconnect.perl +66 -0
  81. data/test/exec/test_exec.rb +74 -20
  82. data/test/test_helper.rb +42 -33
  83. data/test/unit/test_ccc.rb +91 -0
  84. data/test/unit/test_droplet.rb +1 -1
  85. data/test/unit/test_http_parser.rb +49 -19
  86. data/test/unit/test_http_parser_ng.rb +98 -115
  87. data/test/unit/test_request.rb +11 -11
  88. data/test/unit/test_response.rb +31 -19
  89. data/test/unit/test_server.rb +89 -15
  90. data/test/unit/test_signals.rb +9 -9
  91. data/test/unit/test_socket_helper.rb +20 -14
  92. data/test/unit/test_tee_input.rb +10 -0
  93. data/test/unit/test_upload.rb +10 -15
  94. data/test/unit/test_util.rb +28 -3
  95. data/unicorn.gemspec +28 -23
  96. data/unicorn_1 +1 -0
  97. data/unicorn_rails_1 +1 -0
  98. metadata +64 -134
  99. data/.wrongdoc.yml +0 -10
  100. data/ChangeLog +0 -4694
  101. data/Documentation/GNUmakefile +0 -30
  102. data/Documentation/unicorn.1.txt +0 -178
  103. data/Documentation/unicorn_rails.1.txt +0 -175
  104. data/examples/git.ru +0 -13
  105. data/lib/unicorn/app/exec_cgi.rb +0 -154
  106. data/lib/unicorn/app/inetd.rb +0 -109
  107. data/lib/unicorn/ssl_client.rb +0 -11
  108. data/lib/unicorn/ssl_configurator.rb +0 -104
  109. data/lib/unicorn/ssl_server.rb +0 -42
  110. data/local.mk.sample +0 -59
  111. data/script/isolate_for_tests +0 -32
  112. data/t/hijack.ru +0 -42
  113. data/t/sslgen.sh +0 -71
  114. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  115. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  116. data/t/t0200-rack-hijack.sh +0 -27
  117. data/t/t0600-https-server-basic.sh +0 -48
  118. data/test/unit/test_http_parser_xftrust.rb +0 -38
  119. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -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,59 +6,30 @@ require "unicorn/ssl_server"
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 from the
46
- # master and replace it with a new pipe after forking. This new
47
- # pipe is also used to wakeup from IO.select from inside (worker)
48
- # signal handlers. However, workers *close* the pipe descriptors in
49
- # the signal handlers to raise EBADF in IO.select instead of writing
50
- # like we do in the master. We cannot easily use the reader set for
51
- # IO.select because LISTENERS is already that set, and it's extra
52
- # work (and cycles) to distinguish the pipe FD from the reader set
53
- # once IO.select returns. So we're lazy and just close the pipe when
54
- # a (rare) signal arrives in the worker and reinitialize the pipe later.
55
- SELF_PIPE = []
56
-
57
- # signal queue used for self-piping
58
- SIG_QUEUE = []
59
-
60
- # list of signals we care about and trap in master.
61
- QUEUE_SIGS = [ :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
62
-
63
33
  # :startdoc:
64
34
  # We populate this at startup so we can figure out how to reexecute
65
35
  # and upgrade the currently running instance of Unicorn
@@ -68,7 +38,7 @@ class Unicorn::HttpServer
68
38
  # or even different installations of the same applications without
69
39
  # downtime. Keys of this constant Hash are described as follows:
70
40
  #
71
- # * 0 - the path to the unicorn/unicorn_rails executable
41
+ # * 0 - the path to the unicorn executable
72
42
  # * :argv - a deep copy of the ARGV array the executable originally saw
73
43
  # * :cwd - the working directory of the application, this is where
74
44
  # you originally started Unicorn.
@@ -77,9 +47,9 @@ class Unicorn::HttpServer
77
47
  # you can set the following in your Unicorn config file, HUP and then
78
48
  # continue with the traditional USR2 + QUIT upgrade steps:
79
49
  #
80
- # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/1.9.2/bin/unicorn"
50
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
81
51
  START_CTX = {
82
- :argv => ARGV.map { |arg| arg.dup },
52
+ :argv => ARGV.map(&:dup),
83
53
  0 => $0.dup,
84
54
  }
85
55
  # We favor ENV['PWD'] since it is (usually) symlink aware for Capistrano
@@ -99,8 +69,8 @@ class Unicorn::HttpServer
99
69
  # incoming requests on the socket.
100
70
  def initialize(app, options = {})
101
71
  @app = app
102
- @request = Unicorn::HttpRequest.new
103
- self.reexec_pid = 0
72
+ @reexec_pid = 0
73
+ @default_middleware = true
104
74
  options = options.dup
105
75
  @ready_pipe = options.delete(:ready_pipe)
106
76
  @init_listeners = options[:listeners] ? options[:listeners].dup : []
@@ -108,6 +78,20 @@ class Unicorn::HttpServer
108
78
  self.config = Unicorn::Configurator.new(options)
109
79
  self.listener_opts = {}
110
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
+
111
95
  # we try inheriting listeners first, so we bind them later.
112
96
  # we don't write the pid file until we've bound listeners in case
113
97
  # unicorn was started twice by mistake. Even though our #pid= method
@@ -119,7 +103,18 @@ class Unicorn::HttpServer
119
103
  # monitoring tools may also rely on pid files existing before we
120
104
  # attempt to connect to the listener(s)
121
105
  config.commit!(self, :skip => [:listeners, :pid])
122
- 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
123
118
  end
124
119
 
125
120
  # Runs the thing. Returns self so you can run join on it
@@ -127,29 +122,24 @@ class Unicorn::HttpServer
127
122
  inherit_listeners!
128
123
  # this pipe is used to wake us up from select(2) in #join when signals
129
124
  # are trapped. See trap_deferred.
130
- init_self_pipe!
125
+ @self_pipe.replace(Unicorn.pipe)
126
+ @master_pid = @worker_data ? Process.ppid : $$
131
127
 
132
128
  # setup signal handlers before writing pid file in case people get
133
129
  # trigger happy and send signals as soon as the pid file exists.
134
130
  # Note that signals don't actually get handled until the #join method
135
- QUEUE_SIGS.each { |sig| trap(sig) { SIG_QUEUE << sig; awaken_master } }
131
+ @queue_sigs.each { |sig| trap(sig) { @sig_queue << sig; awaken_master } }
136
132
  trap(:CHLD) { awaken_master }
137
133
 
138
134
  # write pid early for Mongrel compatibility if we're not inheriting sockets
139
- # This was needed for compatibility with some health checker a long time
140
- # ago. This unfortunately has the side effect of clobbering valid PID
141
- # files.
142
- self.pid = config[:pid] unless ENV["UNICORN_FD"]
135
+ # This is needed for compatibility some Monit setups at least.
136
+ # This unfortunately has the side effect of clobbering valid PID if
137
+ # we upgrade and the upgrade breaks during preload_app==true && build_app!
138
+ self.pid = config[:pid]
143
139
 
144
- self.master_pid = $$
145
140
  build_app! if preload_app
146
141
  bind_new_listeners!
147
142
 
148
- # Assuming preload_app==false, we drop the pid file after the app is ready
149
- # to process requests. If binding or build_app! fails with
150
- # preload_app==true, we'll never get here and the parent will recover
151
- self.pid = config[:pid] if ENV["UNICORN_FD"]
152
-
153
143
  spawn_missing_workers
154
144
  self
155
145
  end
@@ -159,7 +149,7 @@ class Unicorn::HttpServer
159
149
  def listeners=(listeners)
160
150
  cur_names, dead_names = [], []
161
151
  listener_names.each do |name|
162
- if ?/ == name[0]
152
+ if name.start_with?('/')
163
153
  # mark unlinked sockets as dead so we can rebind them
164
154
  (File.socket?(name) ? cur_names : dead_names) << name
165
155
  else
@@ -171,9 +161,6 @@ class Unicorn::HttpServer
171
161
 
172
162
  LISTENERS.delete_if do |io|
173
163
  if dead_names.include?(sock_name(io))
174
- IO_PURGATORY.delete_if do |pio|
175
- pio.fileno == io.fileno && (pio.close rescue nil).nil? # true
176
- end
177
164
  (io.close rescue nil).nil? # true
178
165
  else
179
166
  set_server_sockopt(io, listener_opts[sock_name(io)])
@@ -211,7 +198,7 @@ class Unicorn::HttpServer
211
198
  if path
212
199
  if x = valid_pid?(path)
213
200
  return path if pid && path == pid && x == $$
214
- if x == reexec_pid && pid =~ /\.oldbin\z/
201
+ if x == @reexec_pid && pid.end_with?('.oldbin')
215
202
  logger.warn("will not set pid=#{path} while reexec-ed "\
216
203
  "child is running PID:#{x}")
217
204
  return
@@ -254,7 +241,7 @@ class Unicorn::HttpServer
254
241
  begin
255
242
  io = bind_listen(address, opt)
256
243
  unless Kgio::TCPServer === io || Kgio::UNIXServer === io
257
- prevent_autoclose(io)
244
+ io.autoclose = false
258
245
  io = server_cast(io)
259
246
  end
260
247
  logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}"
@@ -280,21 +267,25 @@ class Unicorn::HttpServer
280
267
  # is signalling us too often.
281
268
  def join
282
269
  respawn = true
283
- last_check = Time.now
270
+ last_check = time_now
284
271
 
285
272
  proc_name 'master'
286
273
  logger.info "master process ready" # test_exec.rb relies on this message
287
274
  if @ready_pipe
288
- @ready_pipe.syswrite($$.to_s)
275
+ begin
276
+ @ready_pipe.syswrite($$.to_s)
277
+ rescue => e
278
+ logger.warn("grandparent died too soon?: #{e.message} (#{e.class})")
279
+ end
289
280
  @ready_pipe = @ready_pipe.close rescue nil
290
281
  end
291
282
  begin
292
283
  reap_all_workers
293
- case SIG_QUEUE.shift
284
+ case @sig_queue.shift
294
285
  when nil
295
286
  # avoid murdering workers after our master process (or the
296
287
  # machine) comes out of suspend/hibernation
297
- if (last_check + @timeout) >= (last_check = Time.now)
288
+ if (last_check + @timeout) >= (last_check = time_now)
298
289
  sleep_time = murder_lazy_workers
299
290
  else
300
291
  sleep_time = @timeout/2.0 + 1
@@ -311,17 +302,17 @@ class Unicorn::HttpServer
311
302
  logger.info "master reopening logs..."
312
303
  Unicorn::Util.reopen_logs
313
304
  logger.info "master done reopening logs"
314
- kill_each_worker(:USR1)
305
+ soft_kill_each_worker(:USR1)
315
306
  when :USR2 # exec binary, stay alive in case something went wrong
316
307
  reexec
317
308
  when :WINCH
318
- if Unicorn::Configurator::RACKUP[:daemonized]
309
+ if $stdin.tty?
310
+ logger.info "SIGWINCH ignored because we're not daemonized"
311
+ else
319
312
  respawn = false
320
313
  logger.info "gracefully stopping all workers"
321
- kill_each_worker(:QUIT)
314
+ soft_kill_each_worker(:QUIT)
322
315
  self.worker_processes = 0
323
- else
324
- logger.info "SIGWINCH ignored because we're not daemonized"
325
316
  end
326
317
  when :TTIN
327
318
  respawn = true
@@ -348,9 +339,13 @@ class Unicorn::HttpServer
348
339
  # Terminates all workers, but does not exit master process
349
340
  def stop(graceful = true)
350
341
  self.listeners = []
351
- limit = Time.now + timeout
352
- until WORKERS.empty? || Time.now > limit
353
- kill_each_worker(graceful ? :QUIT : :TERM)
342
+ limit = time_now + timeout
343
+ until @workers.empty? || time_now > limit
344
+ if graceful
345
+ soft_kill_each_worker(:QUIT)
346
+ else
347
+ kill_each_worker(:TERM)
348
+ end
354
349
  sleep(0.1)
355
350
  reap_all_workers
356
351
  end
@@ -374,14 +369,6 @@ class Unicorn::HttpServer
374
369
  Unicorn::TeeInput.client_body_buffer_size = bytes
375
370
  end
376
371
 
377
- def trust_x_forwarded
378
- Unicorn::HttpParser.trust_x_forwarded?
379
- end
380
-
381
- def trust_x_forwarded=(bool)
382
- Unicorn::HttpParser.trust_x_forwarded = bool
383
- end
384
-
385
372
  def check_client_connection
386
373
  Unicorn::HttpRequest.check_client_connection
387
374
  end
@@ -394,12 +381,17 @@ class Unicorn::HttpServer
394
381
 
395
382
  # wait for a signal hander to wake us up and then consume the pipe
396
383
  def master_sleep(sec)
397
- IO.select([ SELF_PIPE[0] ], nil, nil, sec) or return
398
- SELF_PIPE[0].kgio_tryread(11)
384
+ @self_pipe[0].wait(sec) or return
385
+ # 11 bytes is the maximum string length which can be embedded within
386
+ # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
387
+ # Most reads are only one byte here and uncommon, so it's not worth a
388
+ # persistent buffer, either:
389
+ @self_pipe[0].kgio_tryread(11)
399
390
  end
400
391
 
401
392
  def awaken_master
402
- SELF_PIPE[1].kgio_trywrite('.') # wakeup master process from select
393
+ return if $$ != @master_pid
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 @@ class Unicorn::HttpServer
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 @@ class Unicorn::HttpServer
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 @@ class Unicorn::HttpServer
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 @@ class Unicorn::HttpServer
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 @@ class Unicorn::HttpServer
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 @@ class Unicorn::HttpServer
503
516
  end
504
517
 
505
518
  def after_fork_internal
519
+ @self_pipe.each(&:close).clear # this is master-only, now
506
520
  @ready_pipe.close if @ready_pipe
507
521
  Unicorn::Configurator::RACKUP.clear
508
522
  @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
509
523
 
510
- srand # http://redmine.ruby-lang.org/issues/4338
511
-
512
524
  # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
513
525
  # dying workers can recycle pids
514
526
  OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
515
527
  end
516
528
 
517
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
+
518
537
  worker_nr = -1
519
538
  until (worker_nr += 1) == @worker_processes
520
- WORKERS.value?(worker_nr) and next
521
- worker = Worker.new(worker_nr)
539
+ @workers.value?(worker_nr) and next
540
+ worker = Unicorn::Worker.new(worker_nr)
522
541
  before_fork.call(self, worker)
523
- if pid = fork
524
- WORKERS[pid] = worker
525
- else
542
+
543
+ pid = @worker_exec ? worker_spawn(worker) : fork
544
+
545
+ unless pid
526
546
  after_fork_internal
527
547
  worker_loop(worker)
528
548
  exit
529
549
  end
550
+
551
+ @workers[pid] = worker
552
+ worker.atfork_parent
530
553
  end
531
- rescue => e
532
- @logger.error(e) rescue nil
533
- exit!
554
+ rescue => e
555
+ @logger.error(e) rescue nil
556
+ exit!
534
557
  end
535
558
 
536
559
  def maintain_worker_count
537
- (off = WORKERS.size - worker_processes) == 0 and return
560
+ (off = @workers.size - worker_processes) == 0 and return
538
561
  off < 0 and return spawn_missing_workers
539
- WORKERS.dup.each_pair { |wpid,w|
540
- w.nr >= worker_processes and kill_worker(:QUIT, wpid) rescue nil
541
- }
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,103 +584,154 @@ class Unicorn::HttpServer
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
660
+ def nuke_listeners!(readers)
661
+ # only called from the worker, ordering is important here
662
+ tmp = readers.dup
663
+ readers.replace([false]) # ensure worker does not continue ASAP
664
+ tmp.each { |io| io.close rescue nil } # break out of IO.select
665
+ end
602
666
 
603
667
  # gets rid of stuff the worker has no business keeping track of
604
668
  # to free some resources and drops all sig handlers.
605
669
  # traps for USR1, USR2, and HUP may be set in the after_fork Proc
606
670
  # by the user.
607
671
  def init_worker_process(worker)
672
+ worker.atfork_child
608
673
  # we'll re-trap :QUIT later for graceful shutdown iff we accept clients
609
- EXIT_SIGS.each { |sig| trap(sig) { exit!(0) } }
610
- exit!(0) if (SIG_QUEUE & EXIT_SIGS)[0]
611
- 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) }
612
678
  trap(:CHLD, 'DEFAULT')
613
- SIG_QUEUE.clear
679
+ @sig_queue.clear
614
680
  proc_name "worker[#{worker.nr}]"
615
681
  START_CTX.clear
616
- init_self_pipe!
617
- WORKERS.clear
618
- LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
619
- after_fork.call(self, worker) # can drop perms
682
+ @workers.clear
683
+
684
+ after_fork.call(self, worker) # can drop perms and create listeners
685
+ LISTENERS.each { |sock| sock.close_on_exec = true }
686
+
620
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
621
688
  self.timeout /= 2.0 # halve it for select()
622
689
  @config = nil
623
690
  build_app! unless preload_app
624
- ssl_enable!
625
691
  @after_fork = @listener_opts = @orig_app = nil
692
+ readers = LISTENERS.dup
693
+ readers << worker
694
+ trap(:QUIT) { nuke_listeners!(readers) }
695
+ readers
626
696
  end
627
697
 
628
698
  def reopen_worker_logs(worker_nr)
629
699
  logger.info "worker=#{worker_nr} reopening logs..."
630
700
  Unicorn::Util.reopen_logs
631
701
  logger.info "worker=#{worker_nr} done reopening logs"
632
- init_self_pipe!
633
- rescue => e
634
- logger.error(e) rescue nil
635
- exit!(77) # EX_NOPERM in sysexits.h
702
+ rescue => e
703
+ logger.error(e) rescue nil
704
+ exit!(77) # EX_NOPERM in sysexits.h
636
705
  end
637
706
 
638
707
  # runs inside each forked worker, this sits around and waits
639
708
  # for connections and doesn't die until the parent dies (or is
640
709
  # given a INT, QUIT, or TERM signal)
641
710
  def worker_loop(worker)
642
- ppid = master_pid
643
- init_worker_process(worker)
711
+ ppid = @master_pid
712
+ readers = init_worker_process(worker)
644
713
  nr = 0 # this becomes negative if we need to reopen logs
645
- l = LISTENERS.dup
646
- ready = l.dup
647
714
 
648
- # closing anything we IO.select on will raise EBADF
649
- trap(:USR1) { nr = -65536; SELF_PIPE[0].close rescue nil }
650
- trap(:QUIT) { worker = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
651
- logger.info "worker=#{worker.nr} ready"
715
+ # this only works immediately if the master sent us the signal
716
+ # (which is the normal case)
717
+ trap(:USR1) { nr = -65536 }
718
+
719
+ ready = readers.dup
720
+ nr_listeners = readers.size
721
+ @after_worker_ready.call(self, worker)
652
722
 
653
723
  begin
654
724
  nr < 0 and reopen_worker_logs(worker.nr)
655
725
  nr = 0
656
-
657
- worker.tick = Time.now.to_i
658
- while sock = ready.shift
726
+ worker.tick = time_now.to_i
727
+ tmp = ready.dup
728
+ while sock = tmp.shift
729
+ # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
730
+ # but that will return false
659
731
  if client = sock.kgio_tryaccept
660
732
  process_client(client)
661
733
  nr += 1
662
- worker.tick = Time.now.to_i
734
+ worker.tick = time_now.to_i
663
735
  end
664
736
  break if nr < 0
665
737
  end
@@ -668,33 +740,37 @@ class Unicorn::HttpServer
668
740
  # we're probably reasonably busy, so avoid calling select()
669
741
  # and do a speculative non-blocking accept() on ready listeners
670
742
  # before we sleep again in select().
671
- unless nr == 0 # (nr < 0) => reopen logs (unlikely)
672
- ready = l.dup
743
+ if nr == nr_listeners
744
+ tmp = ready.dup
673
745
  redo
674
746
  end
675
747
 
676
748
  ppid == Process.ppid or return
677
749
 
678
750
  # timeout used so we can detect parent death:
679
- worker.tick = Time.now.to_i
680
- ret = IO.select(l, nil, SELF_PIPE, @timeout) and ready = ret[0]
751
+ worker.tick = time_now.to_i
752
+ ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
681
753
  rescue => e
682
- redo if nr < 0 && (Errno::EBADF === e || IOError === e) # reopen logs
683
- Unicorn.log_error(@logger, "listen loop error", e) if worker
684
- end while worker
754
+ redo if nr < 0 && readers[0]
755
+ Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
756
+ end while readers[0]
685
757
  end
686
758
 
687
759
  # delivers a signal to a worker and fails gracefully if the worker
688
760
  # is no longer running.
689
761
  def kill_worker(signal, wpid)
690
762
  Process.kill(signal, wpid)
691
- rescue Errno::ESRCH
692
- worker = WORKERS.delete(wpid) and worker.close rescue nil
763
+ rescue Errno::ESRCH
764
+ worker = @workers.delete(wpid) and worker.close rescue nil
693
765
  end
694
766
 
695
767
  # delivers a signal to each worker
696
768
  def kill_each_worker(signal)
697
- WORKERS.keys.each { |wpid| kill_worker(signal, wpid) }
769
+ @workers.keys.each { |wpid| kill_worker(signal, wpid) }
770
+ end
771
+
772
+ def soft_kill_each_worker(signal)
773
+ @workers.each_value { |worker| worker.soft_kill(signal) }
698
774
  end
699
775
 
700
776
  # unlinks a PID file at given +path+ if it contains the current PID
@@ -712,11 +788,11 @@ class Unicorn::HttpServer
712
788
  wpid <= 0 and return
713
789
  Process.kill(0, wpid)
714
790
  wpid
715
- rescue Errno::EPERM
716
- logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
717
- nil
718
- rescue Errno::ESRCH, Errno::ENOENT
719
- # 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...
720
796
  end
721
797
 
722
798
  def load_config!
@@ -725,9 +801,9 @@ class Unicorn::HttpServer
725
801
  config[:listeners].replace(@init_listeners)
726
802
  config.reload
727
803
  config.commit!(self)
728
- kill_each_worker(:QUIT)
804
+ soft_kill_each_worker(:QUIT)
729
805
  Unicorn::Util.reopen_logs
730
- self.app = orig_app
806
+ self.app = @orig_app
731
807
  build_app! if preload_app
732
808
  logger.info "done reloading config_file=#{config.config_file}"
733
809
  rescue StandardError, LoadError, SyntaxError => e
@@ -742,12 +818,12 @@ class Unicorn::HttpServer
742
818
  end
743
819
 
744
820
  def build_app!
745
- if app.respond_to?(:arity) && app.arity == 0
821
+ if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
746
822
  if defined?(Gem) && Gem.respond_to?(:refresh)
747
823
  logger.info "Refreshing Gem list"
748
824
  Gem.refresh
749
825
  end
750
- self.app = app.call
826
+ self.app = app.arity == 0 ? app.call : app.call(nil, self)
751
827
  end
752
828
  end
753
829
 
@@ -761,21 +837,26 @@ class Unicorn::HttpServer
761
837
  io.sync = true
762
838
  end
763
839
 
764
- def init_self_pipe!
765
- SELF_PIPE.each { |io| io.close rescue nil }
766
- SELF_PIPE.replace(Kgio::Pipe.new)
767
- SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
768
- end
769
-
770
840
  def inherit_listeners!
771
841
  # inherit sockets from parents, they need to be plain Socket objects
772
842
  # before they become Kgio::UNIXServer or Kgio::TCPServer
773
- 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|
774
854
  io = Socket.for_fd(fd.to_i)
855
+ io.autoclose = false
856
+ io = server_cast(io)
775
857
  set_server_sockopt(io, listener_opts[sock_name(io)])
776
- prevent_autoclose(io)
777
- logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
778
- server_cast(io)
858
+ logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}"
859
+ io
779
860
  end
780
861
 
781
862
  config_listeners = config[:listeners].dup
@@ -798,8 +879,20 @@ class Unicorn::HttpServer
798
879
  # call only after calling inherit_listeners!
799
880
  # This binds any listeners we did NOT inherit from the parent
800
881
  def bind_new_listeners!
801
- NEW_LISTENERS.each { |addr| listen(addr) }
882
+ NEW_LISTENERS.each { |addr| listen(addr) }.clear
802
883
  raise ArgumentError, "no listeners" if LISTENERS.empty?
803
- 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
804
897
  end
805
898
  end