unicorn 5.2.0 → 5.3.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.
@@ -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)