unicorn 5.0.1 → 5.3.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.manifest +1 -0
  3. data/.olddoc.yml +6 -4
  4. data/Documentation/unicorn.1.txt +2 -3
  5. data/Documentation/unicorn_rails.1.txt +3 -3
  6. data/FAQ +1 -1
  7. data/GIT-VERSION-FILE +1 -1
  8. data/GIT-VERSION-GEN +1 -1
  9. data/GNUmakefile +3 -3
  10. data/HACKING +1 -2
  11. data/ISSUES +21 -17
  12. data/KNOWN_ISSUES +2 -2
  13. data/LATEST +102 -28
  14. data/Links +3 -3
  15. data/NEWS +373 -0
  16. data/README +7 -19
  17. data/SIGNALS +1 -1
  18. data/Sandbox +6 -5
  19. data/TODO +0 -2
  20. data/TUNING +19 -1
  21. data/examples/big_app_gc.rb +1 -1
  22. data/examples/init.sh +36 -8
  23. data/examples/logrotate.conf +16 -1
  24. data/examples/nginx.conf +1 -1
  25. data/examples/unicorn.conf.minimal.rb +2 -2
  26. data/examples/unicorn.conf.rb +2 -2
  27. data/examples/unicorn@.service +7 -0
  28. data/ext/unicorn_http/common_field_optimization.h +2 -2
  29. data/ext/unicorn_http/ext_help.h +0 -20
  30. data/ext/unicorn_http/extconf.rb +1 -1
  31. data/ext/unicorn_http/global_variables.h +2 -2
  32. data/ext/unicorn_http/httpdate.c +2 -2
  33. data/ext/unicorn_http/unicorn_http.c +673 -358
  34. data/ext/unicorn_http/unicorn_http.rl +32 -16
  35. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  36. data/lib/unicorn/configurator.rb +71 -12
  37. data/lib/unicorn/http_request.rb +89 -9
  38. data/lib/unicorn/http_response.rb +5 -2
  39. data/lib/unicorn/http_server.rb +66 -22
  40. data/lib/unicorn/oob_gc.rb +4 -4
  41. data/lib/unicorn/socket_helper.rb +19 -4
  42. data/lib/unicorn/stream_input.rb +5 -4
  43. data/lib/unicorn/tee_input.rb +8 -10
  44. data/lib/unicorn/version.rb +1 -1
  45. data/lib/unicorn/worker.rb +17 -6
  46. data/lib/unicorn.rb +31 -20
  47. data/man/man1/unicorn.1 +54 -55
  48. data/man/man1/unicorn_rails.1 +47 -46
  49. data/t/t0011-active-unix-socket.sh +1 -1
  50. data/t/t0012-reload-empty-config.sh +2 -1
  51. data/t/test-lib.sh +2 -2
  52. data/test/exec/test_exec.rb +6 -5
  53. data/test/unit/test_ccc.rb +90 -0
  54. data/test/unit/test_http_parser.rb +31 -17
  55. data/test/unit/test_socket_helper.rb +8 -4
  56. data/test/unit/test_util.rb +2 -2
  57. data/unicorn.gemspec +15 -17
  58. metadata +6 -19
@@ -13,7 +13,7 @@
13
13
  #include "global_variables.h"
14
14
  #include "c_util.h"
15
15
 
16
- void init_unicorn_httpdate(void);
16
+ void init_unicorn_httpdate(VALUE mark_ary);
17
17
 
18
18
  #define UH_FL_CHUNKED 0x1
19
19
  #define UH_FL_HASBODY 0x2
@@ -442,11 +442,31 @@ post_exec: /* "_out:" also goes here */
442
442
  assert(hp->offset <= len && "offset longer than length");
443
443
  }
444
444
 
445
+ static void hp_mark(void *ptr)
446
+ {
447
+ struct http_parser *hp = ptr;
448
+
449
+ rb_gc_mark(hp->buf);
450
+ rb_gc_mark(hp->env);
451
+ rb_gc_mark(hp->cont);
452
+ }
453
+
454
+ static size_t hp_memsize(const void *ptr)
455
+ {
456
+ return sizeof(struct http_parser);
457
+ }
458
+
459
+ static const rb_data_type_t hp_type = {
460
+ "unicorn_http",
461
+ { hp_mark, RUBY_TYPED_DEFAULT_FREE, hp_memsize, /* reserved */ },
462
+ /* parent, data, [ flags ] */
463
+ };
464
+
445
465
  static struct http_parser *data_get(VALUE self)
446
466
  {
447
467
  struct http_parser *hp;
448
468
 
449
- Data_Get_Struct(self, struct http_parser, hp);
469
+ TypedData_Get_Struct(self, struct http_parser, &hp_type, hp);
450
470
  assert(hp && "failed to extract http_parser struct");
451
471
  return hp;
452
472
  }
@@ -552,21 +572,12 @@ static void finalize_header(struct http_parser *hp)
552
572
  rb_hash_aset(hp->env, g_query_string, rb_str_new(NULL, 0));
553
573
  }
554
574
 
555
- static void hp_mark(void *ptr)
556
- {
557
- struct http_parser *hp = ptr;
558
-
559
- rb_gc_mark(hp->buf);
560
- rb_gc_mark(hp->env);
561
- rb_gc_mark(hp->cont);
562
- }
563
-
564
575
  static VALUE HttpParser_alloc(VALUE klass)
565
576
  {
566
577
  struct http_parser *hp;
567
- return Data_Make_Struct(klass, struct http_parser, hp_mark, -1, hp);
568
- }
569
578
 
579
+ return TypedData_Make_Struct(klass, struct http_parser, &hp_type, hp);
580
+ }
570
581
 
571
582
  /**
572
583
  * call-seq:
@@ -906,8 +917,11 @@ static VALUE HttpParser_rssget(VALUE self)
906
917
 
907
918
  void Init_unicorn_http(void)
908
919
  {
920
+ static VALUE mark_ary;
909
921
  VALUE mUnicorn, cHttpParser;
910
922
 
923
+ mark_ary = rb_ary_new();
924
+ rb_global_variable(&mark_ary);
911
925
  mUnicorn = rb_define_module("Unicorn");
912
926
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
913
927
  eHttpParserError =
@@ -917,7 +931,7 @@ void Init_unicorn_http(void)
917
931
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
918
932
  eHttpParserError);
919
933
 
920
- init_globals();
934
+ init_globals(mark_ary);
921
935
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
922
936
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
923
937
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -953,14 +967,16 @@ void Init_unicorn_http(void)
953
967
 
954
968
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
955
969
 
956
- init_common_fields();
970
+ init_common_fields(mark_ary);
957
971
  SET_GLOBAL(g_http_host, "HOST");
958
972
  SET_GLOBAL(g_http_trailer, "TRAILER");
959
973
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
960
974
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
961
975
  SET_GLOBAL(g_http_connection, "CONNECTION");
962
976
  id_set_backtrace = rb_intern("set_backtrace");
963
- init_unicorn_httpdate();
977
+ init_unicorn_httpdate(mark_ary);
978
+
979
+ OBJ_FREEZE(mark_ary);
964
980
 
965
981
  #ifndef HAVE_RB_HASH_CLEAR
966
982
  id_clear = rb_intern("clear");
@@ -4,7 +4,7 @@
4
4
 
5
5
  #### HTTP PROTOCOL GRAMMAR
6
6
  # line endings
7
- CRLF = "\r\n";
7
+   CRLF = ("\r\n" | "\n");
8
8
 
9
9
  # character types
10
10
  CTL = (cntrl | 127);
@@ -3,11 +3,11 @@ require 'logger'
3
3
 
4
4
  # Implements a simple DSL for configuring a unicorn server.
5
5
  #
6
- # See http://unicorn.bogomips.org/examples/unicorn.conf.rb and
7
- # http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
6
+ # See https://bogomips.org/unicorn/examples/unicorn.conf.rb and
7
+ # https://bogomips.org/unicorn/examples/unicorn.conf.minimal.rb
8
8
  # example configuration files. An example config file for use with
9
9
  # nginx is also available at
10
- # http://unicorn.bogomips.org/examples/nginx.conf
10
+ # https://bogomips.org/unicorn/examples/nginx.conf
11
11
  #
12
12
  # See the link:/TUNING.html document for more information on tuning unicorn.
13
13
  class Unicorn::Configurator
@@ -41,10 +41,22 @@ class Unicorn::Configurator
41
41
  :before_exec => lambda { |server|
42
42
  server.logger.info("forked child re-executing...")
43
43
  },
44
+ :after_worker_exit => lambda { |server, worker, status|
45
+ m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
46
+ if status.success?
47
+ server.logger.info(m)
48
+ else
49
+ server.logger.error(m)
50
+ end
51
+ },
52
+ :after_worker_ready => lambda { |server, worker|
53
+ server.logger.info("worker=#{worker.nr} ready")
54
+ },
44
55
  :pid => nil,
56
+ :worker_exec => false,
45
57
  :preload_app => false,
46
58
  :check_client_connection => false,
47
- :rewindable_input => true, # for Rack 2.x: (Rack::VERSION[0] <= 1),
59
+ :rewindable_input => true,
48
60
  :client_body_buffer_size => Unicorn::Const::MAX_BODY,
49
61
  }
50
62
  #:startdoc:
@@ -151,6 +163,38 @@ class Unicorn::Configurator
151
163
  set_hook(:after_fork, block_given? ? block : args[0])
152
164
  end
153
165
 
166
+ # sets after_worker_exit hook to a given block. This block will be called
167
+ # by the master process after a worker exits:
168
+ #
169
+ # after_worker_exit do |server,worker,status|
170
+ # # status is a Process::Status instance for the exited worker process
171
+ # unless status.success?
172
+ # server.logger.error("worker process failure: #{status.inspect}")
173
+ # end
174
+ # end
175
+ #
176
+ # after_worker_exit is only available in unicorn 5.3.0+
177
+ def after_worker_exit(*args, &block)
178
+ set_hook(:after_worker_exit, block_given? ? block : args[0], 3)
179
+ end
180
+
181
+ # sets after_worker_ready hook to a given block. This block will be called
182
+ # by a worker process after it has been fully loaded, directly before it
183
+ # starts responding to requests:
184
+ #
185
+ # after_worker_ready do |server,worker|
186
+ # server.logger.info("worker #{worker.nr} ready, dropping privileges")
187
+ # worker.user('username', 'groupname')
188
+ # end
189
+ #
190
+ # Do not use Configurator#user if you rely on changing users in the
191
+ # after_worker_ready hook.
192
+ #
193
+ # after_worker_ready is only available in unicorn 5.3.0+
194
+ def after_worker_ready(*args, &block)
195
+ set_hook(:after_worker_ready, block_given? ? block : args[0])
196
+ end
197
+
154
198
  # sets before_fork got be a given Proc object. This Proc
155
199
  # object will be called by the master process before forking
156
200
  # each worker.
@@ -181,8 +225,6 @@ class Unicorn::Configurator
181
225
  # to have nginx always retry backends that may have had workers
182
226
  # SIGKILL-ed due to timeouts.
183
227
  #
184
- # # See http://wiki.nginx.org/NginxHttpUpstreamModule for more details
185
- # # on nginx upstream configuration:
186
228
  # upstream unicorn_backend {
187
229
  # # for UNIX domain socket setups:
188
230
  # server unix:/path/to/.unicorn.sock fail_timeout=0;
@@ -192,6 +234,9 @@ class Unicorn::Configurator
192
234
  # server 192.168.0.8:8080 fail_timeout=0;
193
235
  # server 192.168.0.9:8080 fail_timeout=0;
194
236
  # }
237
+ #
238
+ # See http://nginx.org/en/docs/http/ngx_http_upstream_module.html
239
+ # for more details on nginx upstream configuration.
195
240
  def timeout(seconds)
196
241
  set_int(:timeout, seconds, 3)
197
242
  # POSIX says 31 days is the smallest allowed maximum timeout for select()
@@ -199,6 +244,17 @@ class Unicorn::Configurator
199
244
  set[:timeout] = seconds > max ? max : seconds
200
245
  end
201
246
 
247
+ # Whether to exec in each worker process after forking. This changes the
248
+ # memory layout of each worker process, which is a security feature designed
249
+ # to defeat possible address space discovery attacks. Note that using
250
+ # worker_exec only makes sense if you are not preloading the application,
251
+ # and will result in higher memory usage.
252
+ #
253
+ # worker_exec is only available in unicorn 5.3.0+
254
+ def worker_exec(bool)
255
+ set_bool(:worker_exec, bool)
256
+ end
257
+
202
258
  # sets the current number of worker_processes to +nr+. Each worker
203
259
  # process will serve exactly one client at a time. You can
204
260
  # increment or decrement this value at runtime by sending SIGTTIN
@@ -465,13 +521,12 @@ class Unicorn::Configurator
465
521
  # Disabling rewindability can improve performance by lowering
466
522
  # I/O and memory usage for applications that accept uploads.
467
523
  # Keep in mind that the Rack 1.x spec requires
468
- # \env[\"rack.input\"] to be rewindable, so this allows
469
- # intentionally violating the current Rack 1.x spec.
524
+ # \env[\"rack.input\"] to be rewindable,
525
+ # but the Rack 2.x spec does not.
470
526
  #
471
- # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
472
- # Rack conformance. When Rack 2.x is finalized, this will most
473
- # likely default to +false+ while still conforming to the newer
474
- # (less demanding) spec.
527
+ # +rewindable_input+ defaults to +true+ for compatibility.
528
+ # Setting it to +false+ may be safe for applications and
529
+ # frameworks developed for Rack 2.x and later.
475
530
  def rewindable_input(bool)
476
531
  set_bool(:rewindable_input, bool)
477
532
  end
@@ -547,6 +602,10 @@ class Unicorn::Configurator
547
602
  # This switch will occur after calling the after_fork hook, and only
548
603
  # if the Worker#user method is not called in the after_fork hook
549
604
  # +group+ is optional and will not change if unspecified.
605
+ #
606
+ # Do not use Configurator#user if you rely on changing users in the
607
+ # after_worker_ready hook. Instead, you need to call Worker#user
608
+ # directly in after_worker_ready.
550
609
  def user(user, group = nil)
551
610
  # raises ArgumentError on invalid user/group
552
611
  Etc.getpwnam(user)
@@ -2,6 +2,7 @@
2
2
  # :enddoc:
3
3
  # no stable API here
4
4
  require 'unicorn_http'
5
+ require 'raindrops'
5
6
 
6
7
  # TODO: remove redundant names
7
8
  Unicorn.const_set(:HttpRequest, Unicorn::HttpParser)
@@ -24,12 +25,11 @@ class Unicorn::HttpParser
24
25
  NULL_IO = StringIO.new("")
25
26
 
26
27
  # :stopdoc:
27
- # A frozen format for this is about 15% faster
28
- # Drop these frozen strings when Ruby 2.2 becomes more prevalent,
29
- # 2.2+ optimizes hash assignments when used with literal string keys
30
- HTTP_RESPONSE_START = [ 'HTTP', '/1.1 ']
28
+ HTTP_RESPONSE_START = [ 'HTTP'.freeze, '/1.1 '.freeze ]
29
+ EMPTY_ARRAY = [].freeze
31
30
  @@input_class = Unicorn::TeeInput
32
31
  @@check_client_connection = false
32
+ @@tcpi_inspect_ok = Socket.const_defined?(:TCP_INFO)
33
33
 
34
34
  def self.input_class
35
35
  @@input_class
@@ -83,11 +83,7 @@ class Unicorn::HttpParser
83
83
  false until add_parse(socket.kgio_read!(16384))
84
84
  end
85
85
 
86
- # detect if the socket is valid by writing a partial response:
87
- if @@check_client_connection && headers?
88
- self.response_start_sent = true
89
- HTTP_RESPONSE_START.each { |c| socket.write(c) }
90
- end
86
+ check_client_connection(socket) if @@check_client_connection
91
87
 
92
88
  e['rack.input'] = 0 == content_length ?
93
89
  NULL_IO : @@input_class.new(socket, self)
@@ -108,4 +104,88 @@ class Unicorn::HttpParser
108
104
  def hijacked?
109
105
  env.include?('rack.hijack_io'.freeze)
110
106
  end
107
+
108
+ if Raindrops.const_defined?(:TCP_Info)
109
+ TCPI = Raindrops::TCP_Info.allocate
110
+
111
+ def check_client_connection(socket) # :nodoc:
112
+ if Unicorn::TCPClient === socket
113
+ # Raindrops::TCP_Info#get!, #state (reads struct tcp_info#tcpi_state)
114
+ raise Errno::EPIPE, "client closed connection".freeze,
115
+ EMPTY_ARRAY if closed_state?(TCPI.get!(socket).state)
116
+ else
117
+ write_http_header(socket)
118
+ end
119
+ end
120
+
121
+ if Raindrops.const_defined?(:TCP)
122
+ # raindrops 0.18.0+ supports FreeBSD + Linux using the same names
123
+ # Evaluate these hash lookups at load time so we can
124
+ # generate an opt_case_dispatch instruction
125
+ eval <<-EOS
126
+ def closed_state?(state) # :nodoc:
127
+ case state
128
+ when #{Raindrops::TCP[:ESTABLISHED]}
129
+ false
130
+ when #{Raindrops::TCP.values_at(
131
+ :CLOSE_WAIT, :TIME_WAIT, :CLOSE, :LAST_ACK, :CLOSING).join(',')}
132
+ true
133
+ else
134
+ false
135
+ end
136
+ end
137
+ EOS
138
+ else
139
+ # raindrops before 0.18 only supported TCP_INFO under Linux
140
+ def closed_state?(state) # :nodoc:
141
+ case state
142
+ when 1 # ESTABLISHED
143
+ false
144
+ when 8, 6, 7, 9, 11 # CLOSE_WAIT, TIME_WAIT, CLOSE, LAST_ACK, CLOSING
145
+ true
146
+ else
147
+ false
148
+ end
149
+ end
150
+ end
151
+ else
152
+
153
+ # Ruby 2.2+ can show struct tcp_info as a string Socket::Option#inspect.
154
+ # Not that efficient, but probably still better than doing unnecessary
155
+ # work after a client gives up.
156
+ def check_client_connection(socket) # :nodoc:
157
+ if Unicorn::TCPClient === socket && @@tcpi_inspect_ok
158
+ opt = socket.getsockopt(Socket::IPPROTO_TCP, Socket::TCP_INFO).inspect
159
+ if opt =~ /\bstate=(\S+)/
160
+ raise Errno::EPIPE, "client closed connection".freeze,
161
+ EMPTY_ARRAY if closed_state_str?($1)
162
+ else
163
+ @@tcpi_inspect_ok = false
164
+ write_http_header(socket)
165
+ end
166
+ opt.clear
167
+ else
168
+ write_http_header(socket)
169
+ end
170
+ end
171
+
172
+ def closed_state_str?(state)
173
+ case state
174
+ when 'ESTABLISHED'
175
+ false
176
+ # not a typo, ruby maps TCP_CLOSE (no 'D') to state=CLOSED (w/ 'D')
177
+ when 'CLOSE_WAIT', 'TIME_WAIT', 'CLOSED', 'LAST_ACK', 'CLOSING'
178
+ true
179
+ else
180
+ false
181
+ end
182
+ end
183
+ end
184
+
185
+ def write_http_header(socket) # :nodoc:
186
+ if headers?
187
+ self.response_start_sent = true
188
+ HTTP_RESPONSE_START.each { |c| socket.write(c) }
189
+ end
190
+ end
111
191
  end
@@ -10,10 +10,13 @@
10
10
  # is the job of Rack, with the exception of the "Date" and "Status" header.
11
11
  module Unicorn::HttpResponse
12
12
 
13
+ STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ?
14
+ Rack::Utils::HTTP_STATUS_CODES : {}
15
+
13
16
  # internal API, code will always be common-enough-for-even-old-Rack
14
17
  def err_response(code, response_start_sent)
15
18
  "#{response_start_sent ? '' : 'HTTP/1.1 '}" \
16
- "#{code} #{Rack::Utils::HTTP_STATUS_CODES[code]}\r\n\r\n"
19
+ "#{code} #{STATUS_CODES[code]}\r\n\r\n"
17
20
  end
18
21
 
19
22
  # writes the rack_response to socket as an HTTP response
@@ -23,7 +26,7 @@ module Unicorn::HttpResponse
23
26
 
24
27
  if headers
25
28
  code = status.to_i
26
- msg = Rack::Utils::HTTP_STATUS_CODES[code]
29
+ msg = STATUS_CODES[code]
27
30
  start = response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze
28
31
  buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \
29
32
  "Date: #{httpdate}\r\n" \
@@ -6,7 +6,7 @@
6
6
  # forked worker children.
7
7
  #
8
8
  # Users do not need to know the internals of this class, but reading the
9
- # {source}[http://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
9
+ # {source}[https://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
10
10
  # is education for programmers wishing to learn how unicorn works.
11
11
  # See Unicorn::Configurator for information on how to configure unicorn.
12
12
  class Unicorn::HttpServer
@@ -15,6 +15,7 @@ class Unicorn::HttpServer
15
15
  :before_fork, :after_fork, :before_exec,
16
16
  :listener_opts, :preload_app,
17
17
  :orig_app, :config, :ready_pipe, :user
18
+ attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
18
19
 
19
20
  attr_reader :pid, :logger
20
21
  include Unicorn::SocketHelper
@@ -36,7 +37,7 @@ class Unicorn::HttpServer
36
37
  # or even different installations of the same applications without
37
38
  # downtime. Keys of this constant Hash are described as follows:
38
39
  #
39
- # * 0 - the path to the unicorn/unicorn_rails executable
40
+ # * 0 - the path to the unicorn executable
40
41
  # * :argv - a deep copy of the ARGV array the executable originally saw
41
42
  # * :cwd - the working directory of the application, this is where
42
43
  # you originally started Unicorn.
@@ -45,7 +46,7 @@ class Unicorn::HttpServer
45
46
  # you can set the following in your Unicorn config file, HUP and then
46
47
  # continue with the traditional USR2 + QUIT upgrade steps:
47
48
  #
48
- # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.2.0/bin/unicorn"
49
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
49
50
  START_CTX = {
50
51
  :argv => ARGV.map(&:dup),
51
52
  0 => $0.dup,
@@ -88,6 +89,7 @@ class Unicorn::HttpServer
88
89
  @self_pipe = []
89
90
  @workers = {} # hash maps PIDs to Workers
90
91
  @sig_queue = [] # signal queue used for self-piping
92
+ @pid = nil
91
93
 
92
94
  # we try inheriting listeners first, so we bind them later.
93
95
  # we don't write the pid file until we've bound listeners in case
@@ -104,6 +106,14 @@ class Unicorn::HttpServer
104
106
  # list of signals we care about and trap in master.
105
107
  @queue_sigs = [
106
108
  :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
109
+
110
+ @worker_data = if worker_data = ENV['UNICORN_WORKER']
111
+ worker_data = worker_data.split(',').map!(&:to_i)
112
+ worker_data[1] = worker_data.slice!(1..2).map do |i|
113
+ Kgio::Pipe.for_fd(i)
114
+ end
115
+ worker_data
116
+ end
107
117
  end
108
118
 
109
119
  # Runs the thing. Returns self so you can run join on it
@@ -112,7 +122,7 @@ class Unicorn::HttpServer
112
122
  # this pipe is used to wake us up from select(2) in #join when signals
113
123
  # are trapped. See trap_deferred.
114
124
  @self_pipe.replace(Unicorn.pipe)
115
- @master_pid = $$
125
+ @master_pid = @worker_data ? Process.ppid : $$
116
126
 
117
127
  # setup signal handlers before writing pid file in case people get
118
128
  # trigger happy and send signals as soon as the pid file exists.
@@ -395,8 +405,7 @@ class Unicorn::HttpServer
395
405
  proc_name 'master'
396
406
  else
397
407
  worker = @workers.delete(wpid) and worker.close rescue nil
398
- m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
399
- status.success? ? logger.info(m) : logger.error(m)
408
+ @after_worker_exit.call(self, worker, status)
400
409
  end
401
410
  rescue Errno::ECHILD
402
411
  break
@@ -430,11 +439,7 @@ class Unicorn::HttpServer
430
439
  end
431
440
 
432
441
  @reexec_pid = fork do
433
- listener_fds = {}
434
- LISTENERS.each do |sock|
435
- sock.close_on_exec = false
436
- listener_fds[sock.fileno] = sock
437
- end
442
+ listener_fds = listener_sockets
438
443
  ENV['UNICORN_FD'] = listener_fds.keys.join(',')
439
444
  Dir.chdir(START_CTX[:cwd])
440
445
  cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
@@ -442,12 +447,7 @@ class Unicorn::HttpServer
442
447
  # avoid leaking FDs we don't know about, but let before_exec
443
448
  # unset FD_CLOEXEC, if anything else in the app eventually
444
449
  # relies on FD inheritence.
445
- (3..1024).each do |io|
446
- next if listener_fds.include?(io)
447
- io = IO.for_fd(io) rescue next
448
- io.autoclose = false
449
- io.close_on_exec = true
450
- end
450
+ close_sockets_on_exec(listener_fds)
451
451
 
452
452
  # exec(command, hash) works in at least 1.9.1+, but will only be
453
453
  # required in 1.9.4/2.0.0 at earliest.
@@ -459,6 +459,40 @@ class Unicorn::HttpServer
459
459
  proc_name 'master (old)'
460
460
  end
461
461
 
462
+ def worker_spawn(worker)
463
+ listener_fds = listener_sockets
464
+ env = {}
465
+ env['UNICORN_FD'] = listener_fds.keys.join(',')
466
+
467
+ listener_fds[worker.to_io.fileno] = worker.to_io
468
+ listener_fds[worker.master.fileno] = worker.master
469
+
470
+ worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
471
+ env['UNICORN_WORKER'] = worker_info.join(',')
472
+
473
+ close_sockets_on_exec(listener_fds)
474
+
475
+ Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
476
+ end
477
+
478
+ def listener_sockets
479
+ listener_fds = {}
480
+ LISTENERS.each do |sock|
481
+ sock.close_on_exec = false
482
+ listener_fds[sock.fileno] = sock
483
+ end
484
+ listener_fds
485
+ end
486
+
487
+ def close_sockets_on_exec(sockets)
488
+ (3..1024).each do |io|
489
+ next if sockets.include?(io)
490
+ io = IO.for_fd(io) rescue next
491
+ io.autoclose = false
492
+ io.close_on_exec = true
493
+ end
494
+ end
495
+
462
496
  # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
463
497
  def murder_lazy_workers
464
498
  next_sleep = @timeout - 1
@@ -495,19 +529,29 @@ class Unicorn::HttpServer
495
529
  end
496
530
 
497
531
  def spawn_missing_workers
532
+ if @worker_data
533
+ worker = Unicorn::Worker.new(*@worker_data)
534
+ after_fork_internal
535
+ worker_loop(worker)
536
+ exit
537
+ end
538
+
498
539
  worker_nr = -1
499
540
  until (worker_nr += 1) == @worker_processes
500
541
  @workers.value?(worker_nr) and next
501
542
  worker = Unicorn::Worker.new(worker_nr)
502
543
  before_fork.call(self, worker)
503
- if pid = fork
504
- @workers[pid] = worker
505
- worker.atfork_parent
506
- else
544
+
545
+ pid = @worker_exec ? worker_spawn(worker) : fork
546
+
547
+ unless pid
507
548
  after_fork_internal
508
549
  worker_loop(worker)
509
550
  exit
510
551
  end
552
+
553
+ @workers[pid] = worker
554
+ worker.atfork_parent
511
555
  end
512
556
  rescue => e
513
557
  @logger.error(e) rescue nil
@@ -644,7 +688,7 @@ class Unicorn::HttpServer
644
688
  trap(:USR1) { nr = -65536 }
645
689
 
646
690
  ready = readers.dup
647
- @logger.info "worker=#{worker.nr} ready"
691
+ @after_worker_ready.call(self, worker)
648
692
 
649
693
  begin
650
694
  nr < 0 and reopen_worker_logs(worker.nr)
@@ -43,8 +43,9 @@
43
43
  # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
44
44
  #
45
45
  # Feedback from users of early implementations of this module:
46
- # * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
47
- # * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
46
+ # * https://bogomips.org/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
47
+ # * https://bogomips.org/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
48
+
48
49
  module Unicorn::OobGC
49
50
 
50
51
  # this pretends to be Rack middleware because it used to be
@@ -65,10 +66,9 @@ module Unicorn::OobGC
65
66
  end
66
67
 
67
68
  #:stopdoc:
68
- PATH_INFO = "PATH_INFO"
69
69
  def process_client(client)
70
70
  super(client) # Unicorn::HttpServer#process_client
71
- if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
71
+ if OOBGC_PATH =~ OOBGC_ENV['PATH_INFO'] && ((@@nr -= 1) <= 0)
72
72
  @@nr = OOBGC_INTERVAL
73
73
  OOBGC_ENV.clear
74
74
  disabled = GC.enable
@@ -3,6 +3,18 @@
3
3
  require 'socket'
4
4
 
5
5
  module Unicorn
6
+
7
+ # Instead of using a generic Kgio::Socket for everything,
8
+ # tag TCP sockets so we can use TCP_INFO under Linux without
9
+ # incurring extra syscalls for Unix domain sockets.
10
+ # TODO: remove these when we remove kgio
11
+ TCPClient = Class.new(Kgio::Socket) # :nodoc:
12
+ class TCPSrv < Kgio::TCPServer # :nodoc:
13
+ def kgio_tryaccept # :nodoc:
14
+ super(TCPClient)
15
+ end
16
+ end
17
+
6
18
  module SocketHelper
7
19
 
8
20
  # internal interface
@@ -63,12 +75,15 @@ module Unicorn
63
75
  elsif respond_to?(:accf_arg)
64
76
  name = opt[:accept_filter]
65
77
  name = DEFAULTS[:accept_filter] if name.nil?
78
+ sock.listen(opt[:backlog])
79
+ got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
80
+ arg = accf_arg(name)
66
81
  begin
67
- sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, accf_arg(name))
82
+ sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
68
83
  rescue => e
69
84
  logger.error("#{sock_name(sock)} " \
70
85
  "failed to set accept_filter=#{name} (#{e.inspect})")
71
- end
86
+ end if arg != got
72
87
  end
73
88
  end
74
89
 
@@ -148,7 +163,7 @@ module Unicorn
148
163
  end
149
164
  sock.bind(Socket.pack_sockaddr_in(port, addr))
150
165
  sock.autoclose = false
151
- Kgio::TCPServer.for_fd(sock.fileno)
166
+ TCPSrv.for_fd(sock.fileno)
152
167
  end
153
168
 
154
169
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
@@ -185,7 +200,7 @@ module Unicorn
185
200
  def server_cast(sock)
186
201
  begin
187
202
  Socket.unpack_sockaddr_in(sock.getsockname)
188
- Kgio::TCPServer.for_fd(sock.fileno)
203
+ TCPSrv.for_fd(sock.fileno)
189
204
  rescue ArgumentError
190
205
  Kgio::UNIXServer.for_fd(sock.fileno)
191
206
  end
@@ -1,16 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # When processing uploads, Unicorn may expose a StreamInput object under
4
- # "rack.input" of the (future) Rack (2.x) environment.
3
+ # When processing uploads, unicorn may expose a StreamInput object under
4
+ # "rack.input" of the Rack environment when
5
+ # Unicorn::Configurator#rewindable_input is set to +false+
5
6
  class Unicorn::StreamInput
6
7
  # The I/O chunk size (in +bytes+) for I/O operations where
7
8
  # the size cannot be user-specified when a method is called.
8
9
  # The default is 16 kilobytes.
9
- @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
10
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
10
11
 
11
12
  # Initializes a new StreamInput object. You normally do not have to call
12
13
  # this unless you are writing an HTTP server.
13
- def initialize(socket, request)
14
+ def initialize(socket, request) # :nodoc:
14
15
  @chunked = request.content_length.nil?
15
16
  @socket = socket
16
17
  @parser = request