unicorn 5.2.0 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -64,13 +64,13 @@ static VALUE httpdate(VALUE self)
64
64
  return buf;
65
65
  }
66
66
 
67
- void init_unicorn_httpdate(void)
67
+ void init_unicorn_httpdate(VALUE mark_ary)
68
68
  {
69
69
  VALUE mod = rb_define_module("Unicorn");
70
70
  mod = rb_define_module_under(mod, "HttpResponse");
71
71
 
72
72
  buf = rb_str_new(0, buf_capa - 1);
73
- rb_global_variable(&buf);
73
+ rb_ary_push(mark_ary, buf);
74
74
  buf_ptr = RSTRING_PTR(buf);
75
75
  httpdate(Qnil);
76
76
 
@@ -15,7 +15,7 @@
15
15
  #include "global_variables.h"
16
16
  #include "c_util.h"
17
17
 
18
- void init_unicorn_httpdate(void);
18
+ void init_unicorn_httpdate(VALUE mark_ary);
19
19
 
20
20
  #define UH_FL_CHUNKED 0x1
21
21
  #define UH_FL_HASBODY 0x2
@@ -4211,8 +4211,10 @@ static VALUE HttpParser_rssget(VALUE self)
4211
4211
 
4212
4212
  void Init_unicorn_http(void)
4213
4213
  {
4214
+ static VALUE mark_ary;
4214
4215
  VALUE mUnicorn, cHttpParser;
4215
4216
 
4217
+ mark_ary = rb_ary_new();
4216
4218
  mUnicorn = rb_define_module("Unicorn");
4217
4219
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
4218
4220
  eHttpParserError =
@@ -4222,7 +4224,7 @@ void Init_unicorn_http(void)
4222
4224
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
4223
4225
  eHttpParserError);
4224
4226
 
4225
- init_globals();
4227
+ init_globals(mark_ary);
4226
4228
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
4227
4229
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
4228
4230
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -4258,14 +4260,17 @@ void Init_unicorn_http(void)
4258
4260
 
4259
4261
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
4260
4262
 
4261
- init_common_fields();
4263
+ init_common_fields(mark_ary);
4262
4264
  SET_GLOBAL(g_http_host, "HOST");
4263
4265
  SET_GLOBAL(g_http_trailer, "TRAILER");
4264
4266
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
4265
4267
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
4266
4268
  SET_GLOBAL(g_http_connection, "CONNECTION");
4267
4269
  id_set_backtrace = rb_intern("set_backtrace");
4268
- init_unicorn_httpdate();
4270
+ init_unicorn_httpdate(mark_ary);
4271
+
4272
+ OBJ_FREEZE(mark_ary);
4273
+ rb_global_variable(&mark_ary);
4269
4274
 
4270
4275
  #ifndef HAVE_RB_HASH_CLEAR
4271
4276
  id_clear = rb_intern("clear");
@@ -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
@@ -917,8 +917,10 @@ static VALUE HttpParser_rssget(VALUE self)
917
917
 
918
918
  void Init_unicorn_http(void)
919
919
  {
920
+ static VALUE mark_ary;
920
921
  VALUE mUnicorn, cHttpParser;
921
922
 
923
+ mark_ary = rb_ary_new();
922
924
  mUnicorn = rb_define_module("Unicorn");
923
925
  cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject);
924
926
  eHttpParserError =
@@ -928,7 +930,7 @@ void Init_unicorn_http(void)
928
930
  e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError",
929
931
  eHttpParserError);
930
932
 
931
- init_globals();
933
+ init_globals(mark_ary);
932
934
  rb_define_alloc_func(cHttpParser, HttpParser_alloc);
933
935
  rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
934
936
  rb_define_method(cHttpParser, "clear", HttpParser_clear, 0);
@@ -964,14 +966,17 @@ void Init_unicorn_http(void)
964
966
 
965
967
  rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1);
966
968
 
967
- init_common_fields();
969
+ init_common_fields(mark_ary);
968
970
  SET_GLOBAL(g_http_host, "HOST");
969
971
  SET_GLOBAL(g_http_trailer, "TRAILER");
970
972
  SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING");
971
973
  SET_GLOBAL(g_content_length, "CONTENT_LENGTH");
972
974
  SET_GLOBAL(g_http_connection, "CONNECTION");
973
975
  id_set_backtrace = rb_intern("set_backtrace");
974
- init_unicorn_httpdate();
976
+ init_unicorn_httpdate(mark_ary);
977
+
978
+ OBJ_FREEZE(mark_ary);
979
+ rb_global_variable(&mark_ary);
975
980
 
976
981
  #ifndef HAVE_RB_HASH_CLEAR
977
982
  id_clear = rb_intern("clear");
data/lib/unicorn.rb CHANGED
@@ -95,7 +95,7 @@ def self.builder(ru, op)
95
95
 
96
96
  # returns an array of strings representing TCP listen socket addresses
97
97
  # and Unix domain socket paths. This is useful for use with
98
- # Raindrops::Middleware under Linux: http://raindrops.bogomips.org/
98
+ # Raindrops::Middleware under Linux: https://bogomips.org/raindrops/
99
99
  def self.listener_names
100
100
  Unicorn::HttpServer::LISTENERS.map do |io|
101
101
  Unicorn::SocketHelper.sock_name(io)
@@ -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 @@ def after_fork(*args, &block)
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.
@@ -200,6 +244,17 @@ def timeout(seconds)
200
244
  set[:timeout] = seconds > max ? max : seconds
201
245
  end
202
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
+
203
258
  # sets the current number of worker_processes to +nr+. Each worker
204
259
  # process will serve exactly one client at a time. You can
205
260
  # increment or decrement this value at runtime by sending SIGTTIN
@@ -466,13 +521,12 @@ def preload_app(bool)
466
521
  # Disabling rewindability can improve performance by lowering
467
522
  # I/O and memory usage for applications that accept uploads.
468
523
  # Keep in mind that the Rack 1.x spec requires
469
- # \env[\"rack.input\"] to be rewindable, so this allows
470
- # intentionally violating the current Rack 1.x spec.
524
+ # \env[\"rack.input\"] to be rewindable,
525
+ # but the Rack 2.x spec does not.
471
526
  #
472
- # +rewindable_input+ defaults to +true+ when used with Rack 1.x for
473
- # Rack conformance. When Rack 2.x is finalized, this will most
474
- # likely default to +false+ while still conforming to the newer
475
- # (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.
476
530
  def rewindable_input(bool)
477
531
  set_bool(:rewindable_input, bool)
478
532
  end
@@ -548,6 +602,10 @@ def working_directory(path)
548
602
  # This switch will occur after calling the after_fork hook, and only
549
603
  # if the Worker#user method is not called in the after_fork hook
550
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.
551
609
  def user(user, group = nil)
552
610
  # raises ArgumentError on invalid user/group
553
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 @@ def read(socket)
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 @@ def call
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
@@ -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
@@ -88,6 +89,7 @@ def initialize(app, options = {})
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 @@ def initialize(app, options = {})
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 @@ def start
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 @@ def reap_all_workers
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 @@ def reexec
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 @@ def reexec
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 @@ def reexec
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 @@ def after_fork_internal
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 @@ def worker_loop(worker)
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)