diff options
Diffstat (limited to 'lib/unicorn/http_server.rb')
-rw-r--r-- | lib/unicorn/http_server.rb | 91 |
1 files changed, 29 insertions, 62 deletions
diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index 21f2a05..08fbe40 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -1,4 +1,5 @@ # -*- encoding: binary -*- +# frozen_string_literal: false # This is the process manager of Unicorn. This manages worker # processes which in turn handle the I/O and application process. @@ -77,6 +78,7 @@ class Unicorn::HttpServer options[:use_defaults] = true self.config = Unicorn::Configurator.new(options) self.listener_opts = {} + @immortal = [] # immortal inherited sockets from systemd # We use @self_pipe differently in the master and worker processes: # @@ -110,9 +112,7 @@ class Unicorn::HttpServer @worker_data = if worker_data = ENV['UNICORN_WORKER'] worker_data = worker_data.split(',').map!(&:to_i) - worker_data[1] = worker_data.slice!(1..2).map do |i| - Kgio::Pipe.for_fd(i) - end + worker_data[1] = worker_data.slice!(1..2).map { |i| IO.for_fd(i) } worker_data end end @@ -158,6 +158,7 @@ class Unicorn::HttpServer end set_names = listener_names(listeners) dead_names.concat(cur_names - set_names).uniq! + dead_names -= @immortal.map { |io| sock_name(io) } LISTENERS.delete_if do |io| if dead_names.include?(sock_name(io)) @@ -187,7 +188,8 @@ class Unicorn::HttpServer rescue Errno::EEXIST retry end - fp.syswrite("#$$\n") + fp.sync = true + fp.write("#$$\n") File.rename(fp.path, path) fp.close end @@ -240,10 +242,6 @@ class Unicorn::HttpServer tries = opt[:tries] || 5 begin io = bind_listen(address, opt) - unless Kgio::TCPServer === io || Kgio::UNIXServer === io - io.autoclose = false - io = server_cast(io) - end logger.info "listening on addr=#{sock_name(io)} fd=#{io.fileno}" LISTENERS << io io @@ -386,12 +384,13 @@ class Unicorn::HttpServer # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+). # Most reads are only one byte here and uncommon, so it's not worth a # persistent buffer, either: - @self_pipe[0].kgio_tryread(11) + @self_pipe[0].read_nonblock(11, exception: false) end def awaken_master return if $$ != @master_pid - @self_pipe[1].kgio_trywrite('.') # wakeup master process from select + # wakeup master process from select + @self_pipe[1].write_nonblock('.', exception: false) end # reaps all unreaped workers @@ -445,11 +444,6 @@ class Unicorn::HttpServer Dir.chdir(START_CTX[:cwd]) cmd = [ START_CTX[0] ].concat(START_CTX[:argv]) - # avoid leaking FDs we don't know about, but let before_exec - # unset FD_CLOEXEC, if anything else in the app eventually - # relies on FD inheritence. - close_sockets_on_exec(listener_fds) - # exec(command, hash) works in at least 1.9.1+, but will only be # required in 1.9.4/2.0.0 at earliest. cmd << listener_fds @@ -471,29 +465,15 @@ class Unicorn::HttpServer worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno] env['UNICORN_WORKER'] = worker_info.join(',') - close_sockets_on_exec(listener_fds) - Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds) end def listener_sockets listener_fds = {} - LISTENERS.each do |sock| - sock.close_on_exec = false - listener_fds[sock.fileno] = sock - end + LISTENERS.each { |sock| listener_fds[sock.fileno] = sock } listener_fds end - def close_sockets_on_exec(sockets) - (3..1024).each do |io| - next if sockets.include?(io) - io = IO.for_fd(io) rescue next - io.autoclose = false - io.close_on_exec = true - end - end - # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File def murder_lazy_workers next_sleep = @timeout - 1 @@ -581,29 +561,19 @@ class Unicorn::HttpServer 500 end if code - client.kgio_trywrite(err_response(code, @request.response_start_sent)) + code = err_response(code, @request.response_start_sent) + client.write_nonblock(code, exception: false) end client.close rescue end def e103_response_write(client, headers) - response = if @request.response_start_sent - "103 Early Hints\r\n" - else - "HTTP/1.1 103 Early Hints\r\n" - end - - headers.each_pair do |k, vs| - next if !vs || vs.empty? - values = vs.to_s.split("\n".freeze) - values.each do |v| - response << "#{k}: #{v}\r\n" - end - end - response << "\r\n".freeze - response << "HTTP/1.1 ".freeze if @request.response_start_sent - client.write(response) + rss = @request.response_start_sent + buf = rss ? "103 Early Hints\r\n" : "HTTP/1.1 103 Early Hints\r\n" + headers.each { |key, value| append_header(buf, key, value) } + buf << (rss ? "\r\nHTTP/1.1 ".freeze : "\r\n".freeze) + client.write(buf) end def e100_response_write(client, env) @@ -619,9 +589,9 @@ class Unicorn::HttpServer # once a client is accepted, it is processed in its entirety here # in 3 easy steps: read request, call app, write app response - def process_client(client) + def process_client(client, ai) @request = Unicorn::HttpRequest.new - env = @request.read(client) + env = @request.read_headers(client, ai) if early_hints env["rack.early_hints"] = lambda do |headers| @@ -733,10 +703,9 @@ class Unicorn::HttpServer reopen = reopen_worker_logs(worker.nr) if reopen worker.tick = time_now.to_i while sock = ready.shift - # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all, - # but that will return false - if client = sock.kgio_tryaccept - process_client(client) + client_ai = sock.accept_nonblock(exception: false) + if client_ai != :wait_readable + process_client(*client_ai) worker.tick = time_now.to_i end break if reopen @@ -834,21 +803,21 @@ class Unicorn::HttpServer def inherit_listeners! # inherit sockets from parents, they need to be plain Socket objects - # before they become Kgio::UNIXServer or Kgio::TCPServer inherited = ENV['UNICORN_FD'].to_s.split(',') + immortal = [] # emulate sd_listen_fds() for systemd sd_pid, sd_fds = ENV.values_at('LISTEN_PID', 'LISTEN_FDS') if sd_pid.to_i == $$ # n.b. $$ can never be zero # 3 = SD_LISTEN_FDS_START - inherited.concat((3...(3 + sd_fds.to_i)).to_a) + immortal = (3...(3 + sd_fds.to_i)).to_a + inherited.concat(immortal) end # to ease debugging, we will not unset LISTEN_PID and LISTEN_FDS inherited.map! do |fd| io = Socket.for_fd(fd.to_i) - io.autoclose = false - io = server_cast(io) + @immortal << io if immortal.include?(fd) set_server_sockopt(io, listener_opts[sock_name(io)]) logger.info "inherited addr=#{sock_name(io)} fd=#{io.fileno}" io @@ -857,11 +826,9 @@ class Unicorn::HttpServer config_listeners = config[:listeners].dup LISTENERS.replace(inherited) - # we start out with generic Socket objects that get cast to either - # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket - # objects share the same OS-level file descriptor as the higher-level - # *Server objects; we need to prevent Socket objects from being - # garbage-collected + # we only use generic Socket objects for aggregate Socket#accept_nonblock + # return value [ Socket, Addrinfo ]. This allows us to avoid having to + # make getpeername(2) syscalls later on to fill in env['REMOTE_ADDR'] config_listeners -= listener_names if config_listeners.empty? && LISTENERS.empty? config_listeners << Unicorn::Const::DEFAULT_LISTEN |