├── archive ├── .gitignore └── slrnpull.conf ├── Documentation └── .gitignore ├── t ├── .gitignore ├── GNUmakefile ├── pid.ru ├── env.ru ├── listener_names.ru ├── t0014.ru ├── t0013.ru ├── fails-rack-lint.ru ├── detach.ru ├── t0301.ru ├── client_body_buffer_size.ru ├── reopen-logs.ru ├── heartbeat-timeout.ru ├── oob_gc.ru ├── oob_gc_path.ru ├── t0300-no-default-middleware.sh ├── t0014-rewindable-input-true.sh ├── t0013-rewindable-input-false.sh ├── t0301-no-default-middleware-ignored-in-config.sh ├── t0015-configurator-internals.sh ├── t0021-process_detach.sh ├── preread_input.ru ├── t0022-listener_names-preload_app.sh ├── reopen-logs.t ├── t0010-reap-logging.sh ├── t0020-at_exit-handler.sh ├── t9001-oob_gc.sh ├── bin │ └── unused_listen ├── reload-bad-config.t ├── back-out-of-upgrade.t ├── README ├── heartbeat-timeout.t ├── winch_ttin.t ├── t0012-reload-empty-config.sh ├── working_directory.t ├── t9002-oob_gc-path.sh ├── client_body_buffer_size.t ├── test-lib.sh ├── active-unix-socket.t ├── integration.ru └── my-tap-lib.sh ├── TODO ├── .gitattributes ├── examples ├── big_app_gc.rb ├── unicorn.socket ├── unicorn.conf.minimal.rb ├── echo.ru ├── logger_mp_safe.rb ├── unicorn@.service ├── logrotate.conf ├── init.sh └── unicorn.conf.rb ├── test ├── benchmark │ ├── stack.ru │ ├── dd.ru │ ├── readinput.ru │ ├── ddstream.ru │ ├── uconnect.perl │ └── README ├── exec │ └── README ├── aggregate.rb └── unit │ ├── test_droplet.rb │ ├── test_waiter.rb │ ├── test_util.rb │ └── test_socket_helper.rb ├── lib ├── unicorn │ ├── select_waiter.rb │ ├── const.rb │ ├── preread_input.rb │ ├── tmpio.rb │ ├── app │ │ ├── old_rails.rb │ │ └── old_rails │ │ │ └── static.rb │ ├── launcher.rb │ ├── util.rb │ ├── oob_gc.rb │ ├── http_response.rb │ ├── stream_input.rb │ ├── tee_input.rb │ └── cgi_wrapper.rb └── unicorn.rb ├── .gitignore ├── ext └── unicorn_http │ ├── CFLAGS │ ├── extconf.rb │ ├── ext_help.h │ ├── httpdate.c │ ├── unicorn_http_common.rl │ ├── c_util.h │ ├── global_variables.h │ ├── common_field_optimization.h │ └── epollexclusive.h ├── .document ├── Rakefile ├── .olddoc.yml ├── GIT-VERSION-GEN ├── CONTRIBUTORS ├── .CHANGELOG.old ├── .mailmap ├── unicorn.gemspec ├── Links ├── FAQ ├── LICENSE ├── Application_Timeouts ├── KNOWN_ISSUES ├── Sandbox ├── bin └── unicorn ├── HACKING ├── DESIGN ├── ISSUES ├── TUNING └── SIGNALS /archive/.gitignore: -------------------------------------------------------------------------------- 1 | /data 2 | /news 3 | /requests 4 | -------------------------------------------------------------------------------- /Documentation/.gitignore: -------------------------------------------------------------------------------- 1 | *.gz 2 | *.html 3 | *.txt 4 | -------------------------------------------------------------------------------- /t/.gitignore: -------------------------------------------------------------------------------- 1 | /random_blob 2 | /.dep+* 3 | /*.crt 4 | /*.key 5 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * improve test suite (port to Perl 5 for stability and maintainability) 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gemspec diff=ruby 2 | *.rb diff=ruby 3 | *.ru diff=ruby 4 | Rakefile diff=ruby 5 | bin/* diff=ruby 6 | -------------------------------------------------------------------------------- /t/GNUmakefile: -------------------------------------------------------------------------------- 1 | # there used to be more, here, but we stopped relying on recursive make 2 | all:: 3 | $(MAKE) -C .. test-integration 4 | 5 | .PHONY: all 6 | -------------------------------------------------------------------------------- /t/pid.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | use Rack::ContentLength 3 | use Rack::ContentType, "text/plain" 4 | run lambda { |env| [ 200, {}, [ "#$$\n" ] ] } 5 | -------------------------------------------------------------------------------- /t/env.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | use Rack::ContentLength 3 | use Rack::ContentType, "text/plain" 4 | run lambda { |env| [ 200, {}, [ env.inspect << "\n" ] ] } 5 | -------------------------------------------------------------------------------- /examples/big_app_gc.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # see {Unicorn::OobGC}[https://yhbt.net/unicorn/Unicorn/OobGC.html] 3 | # Unicorn::OobGC was broken in Unicorn v3.3.1 - v3.6.1 and fixed in v3.6.2 4 | -------------------------------------------------------------------------------- /archive/slrnpull.conf: -------------------------------------------------------------------------------- 1 | # group_name max expire headers_only 2 | gmane.comp.lang.ruby.unicorn.general 1000000000 1000000000 0 3 | 4 | # usage: slrnpull -d $PWD -h news.gmane.io --no-post 5 | -------------------------------------------------------------------------------- /t/listener_names.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | use Rack::ContentLength 3 | use Rack::ContentType, "text/plain" 4 | names = Unicorn.listener_names.inspect # rely on preload_app=true 5 | run(lambda { |_| [ 200, {}, [ names ] ] }) 6 | -------------------------------------------------------------------------------- /test/benchmark/stack.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | run(lambda { |env| 3 | body = "#{caller.size}\n" 4 | h = { 5 | "Content-Length" => body.size.to_s, 6 | "Content-Type" => "text/plain", 7 | } 8 | [ 200, h, [ body ] ] 9 | }) 10 | -------------------------------------------------------------------------------- /examples/unicorn.socket: -------------------------------------------------------------------------------- 1 | # ==> /etc/systemd/system/unicorn.socket <== 2 | [Unit] 3 | Description = unicorn sockets 4 | 5 | [Socket] 6 | ListenStream = 127.0.0.1:8080 7 | ListenStream = /tmp/path/to/.unicorn.sock 8 | Service = unicorn@1.service 9 | 10 | [Install] 11 | WantedBy = sockets.target 12 | -------------------------------------------------------------------------------- /t/t0014.ru: -------------------------------------------------------------------------------- 1 | #\ -E none 2 | # frozen_string_literal: false 3 | use Rack::ContentLength 4 | use Rack::ContentType, 'text/plain' 5 | app = lambda do |env| 6 | case env['rack.input'] 7 | when Unicorn::TeeInput 8 | [ 200, {}, %w(OK) ] 9 | else 10 | [ 500, {}, %w(NO) ] 11 | end 12 | end 13 | run app 14 | -------------------------------------------------------------------------------- /test/exec/README: -------------------------------------------------------------------------------- 1 | These tests require the "unicorn" executable script to be installed in 2 | PATH and rack being directly "require"-able ("rubygems" will not be 3 | loaded for you). The tester is responsible for setting up RUBYLIB and 4 | PATH environment variables (or running tests via GNU Make instead of 5 | Rake). 6 | -------------------------------------------------------------------------------- /t/t0013.ru: -------------------------------------------------------------------------------- 1 | #\ -E none 2 | # frozen_string_literal: false 3 | use Rack::ContentLength 4 | use Rack::ContentType, 'text/plain' 5 | app = lambda do |env| 6 | case env['rack.input'] 7 | when Unicorn::StreamInput 8 | [ 200, {}, %w(OK) ] 9 | else 10 | [ 500, {}, %w(NO) ] 11 | end 12 | end 13 | run app 14 | -------------------------------------------------------------------------------- /lib/unicorn/select_waiter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # fallback for non-Linux and Linux <4.5 systems w/o EPOLLEXCLUSIVE 3 | class Unicorn::SelectWaiter # :nodoc: 4 | def get_readers(ready, readers, timeout) # :nodoc: 5 | ret = IO.select(readers, nil, nil, timeout) and ready.replace(ret[0]) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /t/fails-rack-lint.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # This rack app returns an invalid status code, which will cause 3 | # Rack::Lint to throw an exception if it is present. This 4 | # is used to check whether Rack::Lint is in the stack or not. 5 | 6 | run lambda {|env| return [42, {}, ["Rack::Lint wasn't there if you see this"]]} 7 | -------------------------------------------------------------------------------- /t/detach.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | use Rack::ContentType, "text/plain" 3 | fifo_path = ENV["TEST_FIFO"] or abort "TEST_FIFO not set" 4 | run lambda { |env| 5 | pid = fork do 6 | File.open(fifo_path, "wb") do |fp| 7 | fp.write "HIHI" 8 | end 9 | end 10 | Process.detach(pid) 11 | [ 200, {}, [ pid.to_s ] ] 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.bundle 3 | *.log 4 | *.so 5 | *.rbc 6 | .DS_Store 7 | /.config 8 | /InstalledFiles 9 | /doc 10 | /local.mk 11 | /test/rbx-* 12 | /test/ruby-* 13 | ext/unicorn_http/Makefile 14 | ext/unicorn_http/unicorn_http.c 15 | log/ 16 | pkg/ 17 | /vendor 18 | /NEWS* 19 | /.manifest 20 | /GIT-VERSION-FILE 21 | /man 22 | /tmp 23 | /LATEST 24 | /lib/unicorn/version.rb 25 | /*_1 26 | -------------------------------------------------------------------------------- /t/t0301.ru: -------------------------------------------------------------------------------- 1 | #\-N --debug 2 | # frozen_string_literal: false 3 | run(lambda do |env| 4 | case env['PATH_INFO'] 5 | when '/vars' 6 | b = "debug=#{$DEBUG.inspect}\n" \ 7 | "lint=#{caller.grep(%r{rack/lint\.rb})[0].split(':')[0]}\n" 8 | end 9 | h = { 10 | 'content-length' => b.size.to_s, 11 | 'content-type' => 'text/plain', 12 | } 13 | [ 200, h, [ b ] ] 14 | end) 15 | -------------------------------------------------------------------------------- /t/client_body_buffer_size.ru: -------------------------------------------------------------------------------- 1 | #\ -E none 2 | # frozen_string_literal: false 3 | app = lambda do |env| 4 | input = env['rack.input'] 5 | case env["PATH_INFO"] 6 | when "/tmp_class" 7 | body = input.instance_variable_get(:@tmp).class.name 8 | when "/input_class" 9 | body = input.class.name 10 | else 11 | return [ 500, {}, [] ] 12 | end 13 | [ 200, {}, [ body ] ] 14 | end 15 | run app 16 | -------------------------------------------------------------------------------- /t/reopen-logs.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | use Rack::ContentLength 3 | use Rack::ContentType, "text/plain" 4 | run lambda { |env| 5 | 6 | # our File objects for stderr/stdout should always have #path 7 | # and be sync=true 8 | ok = $stderr.sync && 9 | $stdout.sync && 10 | String === $stderr.path && 11 | String === $stdout.path 12 | 13 | [ 200, {}, [ "#{ok}\n" ] ] 14 | } 15 | -------------------------------------------------------------------------------- /t/heartbeat-timeout.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | use Rack::ContentLength 3 | headers = { 'content-type' => 'text/plain' } 4 | run lambda { |env| 5 | case env['PATH_INFO'] 6 | when "/block-forever" 7 | Process.kill(:STOP, $$) 8 | sleep # in case STOP signal is not received in time 9 | [ 500, headers, [ "Should never get here\n" ] ] 10 | else 11 | [ 200, headers, [ "#$$" ] ] 12 | end 13 | } 14 | -------------------------------------------------------------------------------- /t/oob_gc.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | # frozen_string_literal: false 3 | require 'unicorn/oob_gc' 4 | use Rack::ContentLength 5 | use Rack::ContentType, "text/plain" 6 | use Unicorn::OobGC 7 | $gc_started = false 8 | 9 | # Mock GC.start 10 | def GC.start 11 | $gc_started = true 12 | end 13 | run lambda { |env| 14 | if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"] 15 | $gc_started = false 16 | end 17 | [ 200, {}, [ "#$gc_started\n" ] ] 18 | } 19 | -------------------------------------------------------------------------------- /t/oob_gc_path.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | # frozen_string_literal: false 3 | require 'unicorn/oob_gc' 4 | use Rack::ContentLength 5 | use Rack::ContentType, "text/plain" 6 | use Unicorn::OobGC, 5, /BAD/ 7 | $gc_started = false 8 | 9 | # Mock GC.start 10 | def GC.start 11 | $gc_started = true 12 | end 13 | run lambda { |env| 14 | if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"] 15 | $gc_started = false 16 | end 17 | [ 200, {}, [ "#$gc_started\n" ] ] 18 | } 19 | -------------------------------------------------------------------------------- /test/aggregate.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -n 2 | # -*- encoding: binary -*- 3 | # frozen_string_literal: false 4 | 5 | BEGIN { $tests = $assertions = $failures = $errors = 0 } 6 | 7 | $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next 8 | $tests += $1.to_i 9 | $assertions += $2.to_i 10 | $failures += $3.to_i 11 | $errors += $4.to_i 12 | 13 | END { 14 | printf("\n%d tests, %d assertions, %d failures, %d errors\n", 15 | $tests, $assertions, $failures, $errors) 16 | } 17 | -------------------------------------------------------------------------------- /ext/unicorn_http/CFLAGS: -------------------------------------------------------------------------------- 1 | # CFLAGS used for development (gcc-dependent) 2 | # source this file if you want/need them 3 | CFLAGS= 4 | CFLAGS="$CFLAGS -Wall" 5 | CFLAGS="$CFLAGS -Wwrite-strings" 6 | CFLAGS="$CFLAGS -Wdeclaration-after-statement" 7 | CFLAGS="$CFLAGS -Wcast-qual" 8 | CFLAGS="$CFLAGS -Wstrict-prototypes" 9 | CFLAGS="$CFLAGS -Wshadow" 10 | CFLAGS="$CFLAGS -Wextra" 11 | CFLAGS="$CFLAGS -Wno-deprecated-declarations" 12 | CFLAGS="$CFLAGS -Waggregate-return" 13 | CFLAGS="$CFLAGS -Wchar-subscripts" 14 | -------------------------------------------------------------------------------- /.document: -------------------------------------------------------------------------------- 1 | FAQ 2 | README 3 | TUNING 4 | PHILOSOPHY 5 | HACKING 6 | DESIGN 7 | CONTRIBUTORS 8 | LICENSE 9 | SIGNALS 10 | KNOWN_ISSUES 11 | TODO 12 | NEWS 13 | LATEST 14 | lib/unicorn.rb 15 | lib/unicorn/configurator.rb 16 | lib/unicorn/http_server.rb 17 | lib/unicorn/preread_input.rb 18 | lib/unicorn/stream_input.rb 19 | lib/unicorn/tee_input.rb 20 | lib/unicorn/util.rb 21 | lib/unicorn/oob_gc.rb 22 | lib/unicorn/worker.rb 23 | unicorn_1 24 | unicorn_rails_1 25 | ISSUES 26 | Sandbox 27 | Links 28 | Application_Timeouts 29 | -------------------------------------------------------------------------------- /t/t0300-no-default-middleware.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 3 "test the -N / --no-default-middleware option" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | unicorn -N -D -c $unicorn_config fails-rack-lint.ru 8 | unicorn_wait_start 9 | } 10 | 11 | t_begin "check exit status with Rack::Lint not present" && { 12 | test 500 -ne "$(curl -sf -o/dev/null -w'%{http_code}' http://$listen/)" 13 | } 14 | 15 | t_begin "killing succeeds" && { 16 | kill $unicorn_pid 17 | check_stderr 18 | } 19 | 20 | t_done 21 | -------------------------------------------------------------------------------- /t/t0014-rewindable-input-true.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 4 "rewindable_input toggled to true" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | echo rewindable_input true >> $unicorn_config 8 | unicorn -D -c $unicorn_config t0014.ru 9 | unicorn_wait_start 10 | } 11 | 12 | t_begin "ensure worker is started" && { 13 | test xOK = x$(curl -T t0014.ru -sSf http://$listen/) 14 | } 15 | 16 | t_begin "killing succeeds" && { 17 | kill $unicorn_pid 18 | } 19 | 20 | t_begin "check stderr" && { 21 | check_stderr 22 | } 23 | 24 | t_done 25 | -------------------------------------------------------------------------------- /t/t0013-rewindable-input-false.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 4 "rewindable_input toggled to false" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | echo rewindable_input false >> $unicorn_config 8 | unicorn -D -c $unicorn_config t0013.ru 9 | unicorn_wait_start 10 | } 11 | 12 | t_begin "ensure worker is started" && { 13 | test xOK = x$(curl -T t0013.ru -H Expect: -vsSf http://$listen/) 14 | } 15 | 16 | t_begin "killing succeeds" && { 17 | kill $unicorn_pid 18 | } 19 | 20 | t_begin "check stderr" && { 21 | check_stderr 22 | } 23 | 24 | t_done 25 | -------------------------------------------------------------------------------- /t/t0301-no-default-middleware-ignored-in-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 3 "-N / --no-default-middleware option not supported in config.ru" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | RACK_ENV=development unicorn -D -c $unicorn_config t0301.ru 8 | unicorn_wait_start 9 | } 10 | 11 | t_begin "check switches parsed as expected and -N ignored for Rack::Lint" && { 12 | debug=false 13 | lint= 14 | eval "$(curl -sf http://$listen/vars)" 15 | test x"$debug" = xtrue 16 | test x"$lint" != x 17 | test -f "$lint" 18 | } 19 | 20 | t_begin "killing succeeds" && { 21 | kill $unicorn_pid 22 | check_stderr 23 | } 24 | 25 | t_done 26 | -------------------------------------------------------------------------------- /t/t0015-configurator-internals.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 4 "configurator internals tests (from FAQ)" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | cat >> $unicorn_config <"https"' 17 | } 18 | 19 | t_begin "killing succeeds" && { 20 | kill $unicorn_pid 21 | } 22 | 23 | t_begin "no errors" && check_stderr 24 | 25 | t_done 26 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # optional rake-compiler support in case somebody needs to cross compile 3 | begin 4 | mk = "ext/unicorn_http/Makefile" 5 | if File.readable?(mk) 6 | warn "run 'gmake -C ext/unicorn_http clean' and\n" \ 7 | "remove #{mk} before using rake-compiler" 8 | elsif ENV['VERSION'] 9 | unless File.readable?("ext/unicorn_http/unicorn_http.c") 10 | abort "run 'gmake ragel' or 'make ragel' to generate the Ragel source" 11 | end 12 | spec = Gem::Specification.load('unicorn.gemspec') 13 | require 'rake/extensiontask' 14 | Rake::ExtensionTask.new('unicorn_http', spec) 15 | end 16 | rescue LoadError 17 | end 18 | -------------------------------------------------------------------------------- /examples/unicorn.conf.minimal.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # Minimal sample configuration file for Unicorn (not Rack) when used 3 | # with daemonization (unicorn -D) started in your working directory. 4 | # 5 | # See https://yhbt.net/unicorn/Unicorn/Configurator.html for complete 6 | # documentation. 7 | # See also https://yhbt.net/unicorn/examples/unicorn.conf.rb for 8 | # a more verbose configuration using more features. 9 | 10 | listen 2007 # by default Unicorn listens on port 8080 11 | worker_processes 2 # this should be >= nr_cpus 12 | pid "/path/to/app/shared/pids/unicorn.pid" 13 | stderr_path "/path/to/app/shared/log/unicorn.log" 14 | stdout_path "/path/to/app/shared/log/unicorn.log" 15 | -------------------------------------------------------------------------------- /t/t0021-process_detach.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | 4 | t_plan 5 "Process.detach on forked background process works" 5 | 6 | t_begin "setup and startup" && { 7 | t_fifos process_detach 8 | unicorn_setup 9 | TEST_FIFO=$process_detach \ 10 | unicorn -E none -D detach.ru -c $unicorn_config 11 | unicorn_wait_start 12 | } 13 | 14 | t_begin "read detached PID with HTTP/1.0" && { 15 | detached_pid=$(curl -0 -sSf http://$listen/) 16 | t_info "detached_pid=$detached_pid" 17 | } 18 | 19 | t_begin "read background FIFO" && { 20 | test xHIHI = x"$(cat $process_detach)" 21 | } 22 | 23 | t_begin "killing succeeds" && { 24 | kill $unicorn_pid 25 | } 26 | 27 | t_begin "check stderr" && check_stderr 28 | 29 | t_done 30 | -------------------------------------------------------------------------------- /examples/echo.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | # frozen_string_literal: false 3 | # 4 | # Example application that echoes read data back to the HTTP client. 5 | # This emulates the old echo protocol people used to run. 6 | # 7 | # An example of using this in a client would be to run: 8 | # curl --no-buffer -T- http://host:port/ 9 | # 10 | # Then type random stuff in your terminal to watch it get echoed back! 11 | 12 | class EchoBody < Struct.new(:input) 13 | 14 | def each(&block) 15 | while buf = input.read(4096) 16 | yield buf 17 | end 18 | self 19 | end 20 | 21 | end 22 | 23 | run lambda { |env| 24 | /\A100-continue\z/i =~ env['HTTP_EXPECT'] and return [100, {}, []] 25 | [ 200, { 'Content-Type' => 'application/octet-stream' }, 26 | EchoBody.new(env['rack.input']) ] 27 | } 28 | -------------------------------------------------------------------------------- /lib/unicorn/const.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | module Unicorn::Const # :nodoc: 5 | # default TCP listen host address (0.0.0.0, all interfaces) 6 | DEFAULT_HOST = "0.0.0.0" 7 | 8 | # default TCP listen port (8080) 9 | DEFAULT_PORT = 8080 10 | 11 | # default TCP listen address and port (0.0.0.0:8080) 12 | DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}" 13 | 14 | # The basic request body size we'll try to read at once (16 kilobytes). 15 | CHUNK_SIZE = 16 * 1024 16 | 17 | # Maximum request body size before it is moved out of memory and into a 18 | # temporary file for reading (112 kilobytes). This is the default 19 | # value of client_body_buffer_size. 20 | MAX_BODY = 1024 * 112 21 | end 22 | require_relative 'version' 23 | -------------------------------------------------------------------------------- /t/preread_input.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | # frozen_string_literal: false 3 | require 'digest/md5' 4 | require 'unicorn/preread_input' 5 | use Unicorn::PrereadInput 6 | nr = 0 7 | run lambda { |env| 8 | $stderr.write "app dispatch: #{nr += 1}\n" 9 | input = env["rack.input"] 10 | dig = Digest::MD5.new 11 | if buf = input.read(16384) 12 | begin 13 | dig.update(buf) 14 | end while input.read(16384, buf) 15 | buf.clear # remove this call if Ruby ever gets escape analysis 16 | end 17 | if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i 18 | cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']] 19 | cmd5_bin = cmd5_b64.unpack('m')[0] 20 | return [500, {}, [ cmd5_b64 ] ] if cmd5_bin != dig.digest 21 | end 22 | [ 200, {}, [ dig.hexdigest ] ] 23 | } 24 | -------------------------------------------------------------------------------- /t/t0022-listener_names-preload_app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | 4 | # Raindrops::Middleware depends on Unicorn.listener_names, 5 | # ensure we don't break Raindrops::Middleware when preload_app is true 6 | 7 | t_plan 4 "Unicorn.listener_names available with preload_app=true" 8 | 9 | t_begin "setup and startup" && { 10 | unicorn_setup 11 | echo preload_app true >> $unicorn_config 12 | unicorn -E none -D listener_names.ru -c $unicorn_config 13 | unicorn_wait_start 14 | } 15 | 16 | t_begin "read listener names includes listener" && { 17 | resp=$(curl -sSf http://$listen/) 18 | ok=false 19 | t_info "resp=$resp" 20 | case $resp in 21 | *\"$listen\"*) ok=true ;; 22 | esac 23 | $ok 24 | } 25 | 26 | t_begin "killing succeeds" && { 27 | kill $unicorn_pid 28 | } 29 | 30 | t_begin "check stderr" && check_stderr 31 | 32 | t_done 33 | -------------------------------------------------------------------------------- /test/benchmark/dd.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # This benchmark is the simplest test of the I/O facilities in 3 | # unicorn. It is meant to return a fixed-sized blob to test 4 | # the performance of things in Unicorn, _NOT_ the app. 5 | # 6 | # Adjusting this benchmark is done via the "bs" (byte size) and "count" 7 | # environment variables. "count" designates the count of elements of 8 | # "bs" length in the Rack response body. The defaults are bs=4096, count=1 9 | # to return one 4096-byte chunk. 10 | bs = ENV['bs'] ? ENV['bs'].to_i : 4096 11 | count = ENV['count'] ? ENV['count'].to_i : 1 12 | slice = (' ' * bs).freeze 13 | body = (1..count).map { slice }.freeze 14 | hdr = { 15 | 'Content-Length' => (bs * count).to_s.freeze, 16 | 'Content-Type' => 'text/plain'.freeze 17 | }.freeze 18 | response = [ 200, hdr, body ].freeze 19 | run(lambda { |env| response }) 20 | -------------------------------------------------------------------------------- /lib/unicorn/preread_input.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | module Unicorn 5 | # This middleware is used to ensure input is buffered to memory 6 | # or disk (depending on size) before the application is dispatched 7 | # by entirely consuming it (from TeeInput) beforehand. 8 | # 9 | # Usage (in config.ru): 10 | # 11 | # require 'unicorn/preread_input' 12 | # if defined?(Unicorn) 13 | # use Unicorn::PrereadInput 14 | # end 15 | # run YourApp.new 16 | class PrereadInput 17 | 18 | # :stopdoc: 19 | def initialize(app) 20 | @app = app 21 | end 22 | 23 | def call(env) 24 | buf = "" 25 | input = env["rack.input"] 26 | if input.respond_to?(:rewind) 27 | true while input.read(16384, buf) 28 | input.rewind 29 | end 30 | @app.call(env) 31 | end 32 | # :startdoc: 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /test/unit/test_droplet.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'unicorn' 4 | 5 | class TestDroplet < Test::Unit::TestCase 6 | def test_create_many_droplets 7 | now = Time.now.to_i 8 | (0..1024).each do |i| 9 | droplet = Unicorn::Worker.new(i) 10 | assert droplet.respond_to?(:tick) 11 | assert_equal 0, droplet.tick 12 | assert_equal(now, droplet.tick = now) 13 | assert_equal now, droplet.tick 14 | assert_equal(0, droplet.tick = 0) 15 | assert_equal 0, droplet.tick 16 | end 17 | end 18 | 19 | def test_shared_process 20 | droplet = Unicorn::Worker.new(0) 21 | _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) }) 22 | assert status.success?, status.inspect 23 | assert_equal 1, droplet.tick 24 | 25 | _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) }) 26 | assert status.success?, status.inspect 27 | assert_equal 2, droplet.tick 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /examples/logger_mp_safe.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # Multi-Processing-safe monkey patch for Logger 3 | # 4 | # This monkey patch fixes the case where "preload_app true" is used and 5 | # the application spawns a background thread upon being loaded. 6 | # 7 | # This removes all lock from the Logger code and solely relies on the 8 | # underlying filesystem to handle write(2) system calls atomically when 9 | # O_APPEND is used. This is safe in the presence of both multiple 10 | # threads (native or green) and multiple processes when writing to 11 | # a filesystem with POSIX O_APPEND semantics. 12 | # 13 | # It should be noted that the original locking on Logger could _never_ be 14 | # considered reliable on non-POSIX filesystems with multiple processes, 15 | # either, so nothing is lost in that case. 16 | 17 | require 'logger' 18 | class Logger::LogDevice 19 | def write(message) 20 | @dev.syswrite(message) 21 | end 22 | 23 | def close 24 | @dev.close 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/unicorn/tmpio.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | # :stopdoc: 4 | require 'tmpdir' 5 | 6 | # some versions of Ruby had a broken Tempfile which didn't work 7 | # well with unlinked files. This one is much shorter, easier 8 | # to understand, and slightly faster. 9 | class Unicorn::TmpIO < File 10 | 11 | # creates and returns a new File object. The File is unlinked 12 | # immediately, switched to binary mode, and userspace output 13 | # buffering is disabled 14 | def self.new 15 | path = nil 16 | 17 | # workaround File#path being tainted: 18 | # https://bugs.ruby-lang.org/issues/14485 19 | fp = begin 20 | path = "#{Dir::tmpdir}/#{rand}" 21 | super(path, RDWR|CREAT|EXCL, 0600) 22 | rescue Errno::EEXIST 23 | retry 24 | end 25 | 26 | unlink(path) 27 | fp.binmode 28 | fp.sync = true 29 | fp 30 | end 31 | 32 | # pretend we're Tempfile for Rack::TempfileReaper 33 | alias close! close 34 | end 35 | -------------------------------------------------------------------------------- /.olddoc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | cgit_url: https://yhbt.net/unicorn.git 3 | rdoc_url: https://yhbt.net/unicorn/ 4 | ml_url: 5 | - https://yhbt.net/unicorn-public/ 6 | - http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/unicorn-public/ 7 | merge_html: 8 | unicorn_1: Documentation/unicorn.1.html 9 | unicorn_rails_1: Documentation/unicorn_rails.1.html 10 | noindex: 11 | - Unicorn::Const 12 | - LATEST 13 | - TODO 14 | - unicorn_rails_1 15 | public_email: unicorn-public@yhbt.net 16 | imap_url: 17 | - imaps://;AUTH=ANONYMOUS@yhbt.net/inbox.comp.lang.ruby.unicorn.0 18 | - imap://;AUTH=ANONYMOUS@7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.lang.ruby.unicorn.0 19 | nntp_url: 20 | - nntps://news.public-inbox.org/inbox.comp.lang.ruby.unicorn 21 | - nntp://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.lang.ruby.unicorn 22 | - nntp://news.gmane.io/gmane.comp.lang.ruby.unicorn.general 23 | source_code: 24 | - git clone https://yhbt.net/unicorn.git 25 | - torsocks git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/unicorn.git 26 | -------------------------------------------------------------------------------- /lib/unicorn/app/old_rails.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | # :enddoc: 5 | # This code is based on the original Rails handler in Mongrel 6 | # Copyright (c) 2005 Zed A. Shaw 7 | # Copyright (c) 2009 Eric Wong 8 | # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or 9 | # the GPLv2+ (GPLv3+ preferred) 10 | # Additional work donated by contributors. See CONTRIBUTORS for more info. 11 | require 'unicorn/cgi_wrapper' 12 | require 'dispatcher' 13 | 14 | module Unicorn; module App; end; end 15 | 16 | # Implements a handler that can run Rails. 17 | class Unicorn::App::OldRails 18 | 19 | autoload :Static, "unicorn/app/old_rails/static" 20 | 21 | def call(env) 22 | cgi = Unicorn::CGIWrapper.new(env) 23 | begin 24 | Dispatcher.dispatch(cgi, 25 | ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, 26 | cgi.body) 27 | rescue => e 28 | err = env['rack.errors'] 29 | err.write("#{e} #{e.message}\n") 30 | e.backtrace.each { |line| err.write("#{line}\n") } 31 | end 32 | cgi.out # finalize the response 33 | cgi.rack_response 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /ext/unicorn_http/extconf.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | require 'mkmf' 4 | 5 | have_func("rb_hash_clear", "ruby.h") or abort 'Ruby 2.0+ required' 6 | 7 | message('checking if String#-@ (str_uminus) dedupes... ') 8 | begin 9 | a = -(%w(t e s t).join) 10 | b = -(%w(t e s t).join) 11 | if a.equal?(b) 12 | $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=1 ' 13 | message("yes\n") 14 | else 15 | $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 ' 16 | message("no, needs Ruby 2.5+\n") 17 | end 18 | rescue NoMethodError 19 | $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 ' 20 | message("no, String#-@ not available\n") 21 | end 22 | 23 | message('checking if Hash#[]= (rb_hash_aset) dedupes... ') 24 | h = {} 25 | x = {} 26 | r = rand.to_s 27 | h[%W(#{r}).join('')] = :foo 28 | x[%W(#{r}).join('')] = :foo 29 | if x.keys[0].equal?(h.keys[0]) 30 | $CPPFLAGS += ' -DHASH_ASET_DEDUPE=1 ' 31 | message("yes\n") 32 | else 33 | $CPPFLAGS += ' -DHASH_ASET_DEDUPE=0 ' 34 | message("no, needs Ruby 2.6+\n") 35 | end 36 | 37 | if have_func('epoll_create1', %w(sys/epoll.h)) 38 | have_func('rb_io_descriptor') # Ruby 3.1+ 39 | end 40 | create_makefile("unicorn_http") 41 | -------------------------------------------------------------------------------- /t/reopen-logs.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | use v5.14; BEGIN { require './t/lib.perl' }; 5 | use autodie; 6 | my $srv = tcp_server(); 7 | my $out_log = "$tmpdir/out.log"; 8 | write_file '>', $u_conf, < $srv } ); 14 | my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 15 | is($bdy, "true\n", 'logs opened'); 16 | 17 | rename($err_log, "$err_log.rot"); 18 | rename($out_log, "$out_log.rot"); 19 | 20 | $auto_reap->do_kill('USR1'); 21 | 22 | my $tries = 1000; 23 | while (!-f $err_log && --$tries) { sleep 0.01 }; 24 | while (!-f $out_log && --$tries) { sleep 0.01 }; 25 | 26 | ok(-f $out_log, 'stdout_path recreated after USR1'); 27 | ok(-f $err_log, 'stderr_path recreated after USR1'); 28 | 29 | ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 30 | is($bdy, "true\n", 'logs reopened with sync==true'); 31 | 32 | $auto_reap->join('QUIT'); 33 | is($?, 0, 'no error on exit'); 34 | check_stderr; 35 | undef $tmpdir; 36 | done_testing; 37 | -------------------------------------------------------------------------------- /ext/unicorn_http/ext_help.h: -------------------------------------------------------------------------------- 1 | #ifndef ext_help_h 2 | #define ext_help_h 3 | 4 | /* not all Ruby implementations support frozen objects (Rubinius does not) */ 5 | #if defined(OBJ_FROZEN) 6 | # define assert_frozen(f) assert(OBJ_FROZEN(f) && "unfrozen object") 7 | #else 8 | # define assert_frozen(f) do {} while (0) 9 | #endif /* !defined(OBJ_FROZEN) */ 10 | 11 | static inline int str_cstr_eq(VALUE val, const char *ptr, long len) 12 | { 13 | return (RSTRING_LEN(val) == len && !memcmp(ptr, RSTRING_PTR(val), len)); 14 | } 15 | 16 | #define STR_CSTR_EQ(val, const_str) \ 17 | str_cstr_eq(val, const_str, sizeof(const_str) - 1) 18 | 19 | /* strcasecmp isn't locale independent */ 20 | static int str_cstr_case_eq(VALUE val, const char *ptr, long len) 21 | { 22 | if (RSTRING_LEN(val) == len) { 23 | const char *v = RSTRING_PTR(val); 24 | 25 | for (; len--; ++ptr, ++v) { 26 | if ((*ptr == *v) || (*v >= 'A' && *v <= 'Z' && (*v | 0x20) == *ptr)) 27 | continue; 28 | return 0; 29 | } 30 | return 1; 31 | } 32 | return 0; 33 | } 34 | 35 | #define STR_CSTR_CASE_EQ(val, const_str) \ 36 | str_cstr_case_eq(val, const_str, sizeof(const_str) - 1) 37 | 38 | #endif /* ext_help_h */ 39 | -------------------------------------------------------------------------------- /test/unit/test_waiter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | require 'test/unit' 3 | require 'unicorn' 4 | require 'unicorn/select_waiter' 5 | class TestSelectWaiter < Test::Unit::TestCase 6 | 7 | def test_select_timeout # n.b. this is level-triggered 8 | sw = Unicorn::SelectWaiter.new 9 | IO.pipe do |r,w| 10 | sw.get_readers(ready = [], [r], 0) 11 | assert_equal [], ready 12 | w.syswrite '.' 13 | sw.get_readers(ready, [r], 1000) 14 | assert_equal [r], ready 15 | sw.get_readers(ready, [r], 0) 16 | assert_equal [r], ready 17 | end 18 | end 19 | 20 | def test_linux # ugh, also level-triggered, unlikely to change 21 | IO.pipe do |r,w| 22 | wtr = Unicorn::Waiter.prep_readers([r]) 23 | wtr.get_readers(ready = [], [r], 0) 24 | assert_equal [], ready 25 | w.syswrite '.' 26 | wtr.get_readers(ready = [], [r], 1000) 27 | assert_equal [r], ready 28 | wtr.get_readers(ready = [], [r], 1000) 29 | assert_equal [r], ready, 'still ready (level-triggered :<)' 30 | assert_nil wtr.close 31 | end 32 | rescue SystemCallError => e 33 | warn "#{e.message} (#{e.class})" 34 | end if Unicorn.const_defined?(:Waiter) 35 | end 36 | -------------------------------------------------------------------------------- /GIT-VERSION-GEN: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | DEF_VER = "v6.1.0" 3 | CONSTANT = "Unicorn::Const::UNICORN_VERSION" 4 | RVF = "lib/unicorn/version.rb" 5 | GVF = "GIT-VERSION-FILE" 6 | vn = DEF_VER 7 | 8 | # First see if there is a version file (included in release tarballs), 9 | # then try git-describe, then default. 10 | if File.exist?(".git") 11 | describe = `git describe --abbrev=4 HEAD 2>/dev/null`.strip 12 | case describe 13 | when /\Av[0-9]*/ 14 | vn = describe 15 | system(*%w(git update-index -q --refresh)) 16 | unless `git diff-index --name-only HEAD --`.chomp.empty? 17 | vn << "-dirty" 18 | end 19 | vn.tr!('-', '.') 20 | end 21 | end 22 | 23 | vn = vn.sub!(/\Av/, "") 24 | 25 | # generate the Ruby constant 26 | new_ruby_version = "#{CONSTANT} = '#{vn}'\n" 27 | cur_ruby_version = File.read(RVF) rescue nil 28 | if new_ruby_version != cur_ruby_version 29 | File.open(RVF, "w") { |fp| fp.write(new_ruby_version) } 30 | end 31 | 32 | # generate the makefile snippet 33 | new_make_version = "GIT_VERSION = #{vn}\n" 34 | cur_make_version = File.read(GVF) rescue nil 35 | if new_make_version != cur_make_version 36 | File.open(GVF, "w") { |fp| fp.write(new_make_version) } 37 | end 38 | 39 | puts vn if $0 == __FILE__ 40 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Unicorn developers (let us know if we forgot you, ...or if you no longer wish 2 | to be associated with the doofus running this disaster :P): 3 | * Eric Wong (Bozo Doofus For Life, Bastard Operator From Hell) 4 | 5 | There's numerous contributors over email the years, all of our mail 6 | is archived @ https://yhbt.net/unicorn-public/ 7 | * Suraj N. Kurapati 8 | * Andrey Stikheev 9 | * Wayne Larsen 10 | * Iñaki Baz Castillo 11 | * Augusto Becciu 12 | * Hongli Lai 13 | * ... (help wanted) 14 | 15 | We would like to thank following folks for helping make Unicorn possible: 16 | 17 | * Ezra Zygmuntowicz - for helping Eric decide on a sane configuration 18 | format and reasonable defaults. 19 | * Christian Neukirchen - for Rack, which let us put more focus on the server 20 | and drastically cut down on the amount of code we have to maintain. 21 | * Zed A. Shaw - for Mongrel, without which Unicorn would not be possible 22 | 23 | The original Mongrel contributors: 24 | 25 | * Luis Lavena 26 | * Wilson Bilkovich 27 | * why the lucky stiff 28 | * Dan Kubb 29 | * MenTaLguY 30 | * Filipe Lautert 31 | * Rick Olson 32 | * Wayne E. Seguin 33 | * Kirk Haines 34 | * Bradley Taylor 35 | * Matt Pelletier 36 | * Ry Dahl 37 | * Nick Sieger 38 | * Evan Weaver 39 | * Marc-André Cournoyer 40 | -------------------------------------------------------------------------------- /t/t0010-reap-logging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 9 "reap worker logging messages" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | cat >> $unicorn_config < $r_err 29 | } 30 | 31 | t_begin "kill 2nd worker gracefully" && { 32 | pid_2=$(curl http://$listen/) 33 | kill -QUIT $pid_2 34 | } 35 | 36 | t_begin "wait for 3rd worker=0 to start " && { 37 | test '.' = $(cat $fifo) 38 | } 39 | 40 | t_begin "ensure log of 2nd reap is a INFO" && { 41 | grep 'INFO.*reaped.*worker=0' $r_err | grep $pid_2 42 | > $r_err 43 | } 44 | 45 | t_begin "killing succeeds" && { 46 | kill $unicorn_pid 47 | wait 48 | kill -0 $unicorn_pid && false 49 | } 50 | 51 | t_begin "check stderr" && { 52 | check_stderr 53 | } 54 | 55 | t_done 56 | -------------------------------------------------------------------------------- /t/t0020-at_exit-handler.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | 4 | t_plan 5 "at_exit/END handlers work as expected" 5 | 6 | t_begin "setup and startup" && { 7 | unicorn_setup 8 | cat >> $unicorn_config </dev/null 2>&1 30 | do 31 | sleep 1 32 | done 33 | } 34 | 35 | t_begin "check stderr" && check_stderr 36 | 37 | dbgcat r_err 38 | dbgcat r_out 39 | 40 | t_begin "all at_exit handlers ran" && { 41 | grep "$worker_pid BOTH" $r_out 42 | grep "$unicorn_pid BOTH" $r_out 43 | grep "$worker_pid END BOTH" $r_out 44 | grep "$unicorn_pid END BOTH" $r_out 45 | grep "$worker_pid WORKER ONLY" $r_out 46 | grep "$worker_pid END WORKER ONLY" $r_out 47 | } 48 | 49 | t_done 50 | -------------------------------------------------------------------------------- /t/t9001-oob_gc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 9 "OobGC test" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | unicorn -D -c $unicorn_config oob_gc.ru 8 | unicorn_wait_start 9 | } 10 | 11 | t_begin "test default interval (4 requests)" && { 12 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 13 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 14 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 15 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 16 | } 17 | 18 | t_begin "GC starting-request returns immediately" && { 19 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 20 | } 21 | 22 | t_begin "GC is started after 5 requests" && { 23 | test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp) 24 | } 25 | 26 | t_begin "reset GC" && { 27 | test xfalse = x$(curl -vsSf -X POST http://$listen/gc_reset 2>> $tmp) 28 | } 29 | 30 | t_begin "test default interval again (3 requests)" && { 31 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 32 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 33 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 34 | } 35 | 36 | t_begin "GC is started after 5 requests" && { 37 | test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp) 38 | } 39 | 40 | t_begin "killing succeeds" && { 41 | kill -QUIT $unicorn_pid 42 | } 43 | 44 | t_begin "check_stderr" && check_stderr 45 | dbgcat r_err 46 | 47 | t_done 48 | -------------------------------------------------------------------------------- /t/bin/unused_listen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # -*- encoding: binary -*- 3 | # this is to remain compatible with the unused_port function in the 4 | # Unicorn test/test_helper.rb file 5 | require 'socket' 6 | require 'tmpdir' 7 | 8 | default_port = 8080 9 | addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' 10 | retries = 100 11 | base = 5000 12 | port = sock = lock_path = nil 13 | 14 | begin 15 | begin 16 | port = base + rand(32768 - base) 17 | while port == default_port 18 | port = base + rand(32768 - base) 19 | end 20 | 21 | sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) 22 | sock.bind(Socket.pack_sockaddr_in(port, addr)) 23 | sock.listen(5) 24 | rescue Errno::EADDRINUSE, Errno::EACCES 25 | sock.close rescue nil 26 | retry if (retries -= 1) >= 0 27 | end 28 | 29 | # since we'll end up closing the random port we just got, there's a race 30 | # condition could allow the random port we just chose to reselect itself 31 | # when running tests in parallel with gmake. Create a lock file while 32 | # we have the port here to ensure that does not happen. 33 | lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock" 34 | lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600) 35 | rescue Errno::EEXIST 36 | sock.close rescue nil 37 | retry 38 | end 39 | sock.close rescue nil 40 | puts %Q(listen=#{addr}:#{port} T_RM_LIST="$T_RM_LIST #{lock_path}") 41 | -------------------------------------------------------------------------------- /.CHANGELOG.old: -------------------------------------------------------------------------------- 1 | v0.91.0 - HTTP/0.9 support, multiline header support, small fixes 2 | v0.90.0 - switch chunking+trailer handling to Ragel, v0.8.4 fixes 3 | v0.9.2 - Ruby 1.9.2 preview1 compatibility 4 | v0.9.1 - FD_CLOEXEC portability fix (v0.8.2 port) 5 | v0.9.0 - bodies: "Transfer-Encoding: chunked", rewindable streaming 6 | v0.8.4 - pass through unknown HTTP status codes 7 | v0.8.3 - Ruby 1.9.2 preview1 compatibility 8 | v0.8.2 - socket handling bugfixes and usability tweaks 9 | v0.8.1 - safer timeout handling, more consistent reload behavior 10 | v0.8.0 - enforce Rack dependency, minor performance improvements and fixes 11 | v0.7.1 - minor fixes, cleanups and documentation improvements 12 | v0.7.0 - rack.version is 1.0 13 | v0.6.0 - cleanups + optimizations, signals to {in,de}crement processes 14 | v0.5.4 - fix data corruption with some small uploads (not curl) 15 | v0.5.3 - fix 100% CPU usage when idle, small cleanups 16 | v0.5.2 - force Status: header for compat, small cleanups 17 | v0.5.1 - exit correctly on INT/TERM, QUIT is still recommended, however 18 | v0.5.0 - {after,before}_fork API change, small tweaks/fixes 19 | v0.4.2 - fix Rails ARStore, FD leak prevention, descriptive proctitles 20 | v0.4.1 - Rails support, per-listener backlog and {snd,rcv}buf 21 | v0.2.3 - Unlink Tempfiles after use (they were closed, just not unlinked) 22 | v0.2.2 - small bug fixes, fix Rack multi-value headers (Set-Cookie:) 23 | v0.2.1 - Fix broken Manifest that cause unicorn_rails to not be bundled 24 | v0.2.0 - unicorn_rails launcher script. 25 | v0.1.0 - Unicorn - UNIX-only fork of Mongrel free of threading 26 | -------------------------------------------------------------------------------- /examples/unicorn@.service: -------------------------------------------------------------------------------- 1 | # ==> /etc/systemd/system/unicorn@.service <== 2 | # Since SIGUSR2 upgrades do not work under systemd, this service file 3 | # allows starting two simultaneous services during upgrade time 4 | # (e.g. unicorn@1 unicorn@2) with the intention that they take 5 | # turns running in-between upgrades. This should allow upgrading 6 | # without downtime. 7 | 8 | [Unit] 9 | Description = unicorn Rack application server %i 10 | Wants = unicorn.socket 11 | After = unicorn.socket 12 | 13 | [Service] 14 | # bundler users must use the "--keep-file-descriptors" switch, here: 15 | # ExecStart = bundle exec --keep-file-descriptors unicorn -c ... 16 | ExecStart = /usr/bin/unicorn -c /path/to/unicorn.conf.rb /path/to/config.ru 17 | 18 | # NonBlocking MUST be true if using socket activation with unicorn. 19 | # Otherwise, there's a small window in-between when the non-blocking 20 | # flag is set by us and our accept4 call where systemd can momentarily 21 | # make the socket blocking, causing us to block on accept4: 22 | NonBlocking = true 23 | Sockets = unicorn.socket 24 | 25 | KillSignal = SIGQUIT 26 | User = nobody 27 | Group = nogroup 28 | ExecReload = /bin/kill -HUP $MAINPID 29 | 30 | # This is based on the Unicorn::Configurator#timeout directive, 31 | # adding a few seconds for scheduling differences: 32 | TimeoutStopSec = 62 33 | 34 | # Only kill the master process, it may be harmful to signal 35 | # workers via default "control-group" setting since some 36 | # Ruby extensions and applications misbehave on interrupts 37 | KillMode = process 38 | 39 | [Install] 40 | WantedBy = multi-user.target 41 | -------------------------------------------------------------------------------- /test/benchmark/readinput.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # This app is intended to test large HTTP requests with or without 3 | # a fully-buffering reverse proxy such as nginx. Without a fully-buffering 4 | # reverse proxy, unicorn will be unresponsive when client count exceeds 5 | # worker_processes. 6 | 7 | DOC = < 3 | # License: GPL-3.0+ 4 | use v5.14; BEGIN { require './t/lib.perl' }; 5 | use autodie; 6 | my $srv = tcp_server(); 7 | my $host_port = tcp_host_port($srv); 8 | my $ru = "$tmpdir/config.ru"; 9 | 10 | write_file '>', $ru, <<'EOM'; 11 | use Rack::ContentLength 12 | use Rack::ContentType, 'text/plain' 13 | config = ru = "hello world\n" # check for config variable conflicts, too 14 | run lambda { |env| [ 200, {}, [ ru.to_s ] ] } 15 | EOM 16 | 17 | write_file '>', $u_conf, < $srv }); 23 | my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 24 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid at start'); 25 | is($bdy, "hello world\n", 'body matches expected'); 26 | 27 | write_file '>>', $ru, <<'EOM'; 28 | ....this better be a syntax error in any version of ruby... 29 | EOM 30 | 31 | $ar->do_kill('HUP'); # reload 32 | my @l; 33 | for (1..1000) { 34 | @l = grep(/(?:done|error) reloading/, slurp($err_log)) and 35 | last; 36 | sleep 0.011; 37 | } 38 | diag slurp($err_log) if $ENV{V}; 39 | ok(grep(/error reloading/, @l), 'got error reloading'); 40 | open my $fh, '>', $err_log; # truncate 41 | close $fh; 42 | 43 | ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 44 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid afte reload'); 45 | is($bdy, "hello world\n", 'body matches expected after reload'); 46 | 47 | check_stderr; 48 | undef $tmpdir; # quiet t/lib.perl END{} 49 | done_testing; 50 | -------------------------------------------------------------------------------- /t/back-out-of-upgrade.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | # test backing out of USR2 upgrade 5 | use v5.14; BEGIN { require './t/lib.perl' }; 6 | use autodie; 7 | my $srv = tcp_server(); 8 | mkfifo_die $fifo; 9 | write_file '>', $u_conf, < $srv }); 16 | 17 | like(my $wpid_orig_1 = slurp($fifo), qr/\Apid=\d+\z/a, 'got worker pid'); 18 | 19 | ok $ar->do_kill('USR2'), 'USR2 to start upgrade'; 20 | ok $ar->do_kill('WINCH'), 'drop old worker'; 21 | 22 | like(my $wpid_new = slurp($fifo), qr/\Apid=\d+\z/a, 'got pid from new master'); 23 | chomp(my $new_pid = slurp($pid_file)); 24 | isnt $new_pid, $ar->{pid}, 'PID file changed'; 25 | chomp(my $pid_oldbin = slurp("$pid_file.oldbin")); 26 | is $pid_oldbin, $ar->{pid}, '.oldbin PID valid'; 27 | 28 | ok $ar->do_kill('HUP'), 'HUP old master'; 29 | like(my $wpid_orig_2 = slurp($fifo), qr/\Apid=\d+\z/a, 'got worker new pid'); 30 | ok kill('QUIT', $new_pid), 'abort old master'; 31 | kill_until_dead $new_pid; 32 | 33 | my ($st, $hdr, $req_pid) = do_req $srv, 'GET /'; 34 | chomp $req_pid; 35 | is $wpid_orig_2, "pid=$req_pid", 'new worker on old worker serves'; 36 | 37 | ok !-f "$pid_file.oldbin", '.oldbin PID file gone'; 38 | chomp(my $old_pid = slurp($pid_file)); 39 | is $old_pid, $ar->{pid}, 'PID file restored'; 40 | 41 | my @log = grep !/ERROR -- : reaped .*? exec\(\)-ed/, slurp($err_log); 42 | check_stderr @log; 43 | undef $tmpdir; 44 | done_testing; 45 | -------------------------------------------------------------------------------- /examples/logrotate.conf: -------------------------------------------------------------------------------- 1 | # example logrotate config file, I usually keep this in 2 | # /etc/logrotate.d/unicorn_app on my Debian systems 3 | # 4 | # See the logrotate(8) manpage for more information: 5 | # https://linux.die.net/man/8/logrotate 6 | # 7 | # public logrotate-related discussion in our archives: 8 | # https://yhbt.net/unicorn-public/?q=logrotate 9 | 10 | # Modify the following glob to match the logfiles your app writes to: 11 | /var/log/unicorn_app/*.log { 12 | # this first block is mostly just personal preference, though 13 | # I wish logrotate offered an "hourly" option... 14 | daily 15 | missingok 16 | rotate 180 17 | compress # must use with delaycompress below 18 | dateext 19 | 20 | # this is important if using "compress" since we need to call 21 | # the "lastaction" script below before compressing: 22 | delaycompress 23 | 24 | # note the lack of the evil "copytruncate" option in this 25 | # config. Unicorn supports the USR1 signal and we send it 26 | # as our "lastaction" action: 27 | lastaction 28 | # For systemd users, assuming you use two services 29 | # (as recommended) to allow zero-downtime upgrades. 30 | # Only one service needs to be started, but signaling 31 | # both here is harmless as long as they're both enabled 32 | systemctl kill -s SIGUSR1 unicorn@1.service 33 | systemctl kill -s SIGUSR1 unicorn@2.service 34 | 35 | # Examples for other process management systems appreciated 36 | # Mail us at unicorn-public@yhbt.net 37 | # (see above for archives) 38 | 39 | # If you use a pid file and assuming your pid file 40 | # is in /var/run/unicorn_app/pid 41 | pid=/var/run/unicorn_app/pid 42 | test -s $pid && kill -USR1 "$(cat $pid)" 43 | endscript 44 | } 45 | -------------------------------------------------------------------------------- /t/README: -------------------------------------------------------------------------------- 1 | = Unicorn integration test suite 2 | 3 | These are all integration tests that start the server on random, unused 4 | TCP ports or Unix domain sockets. They're all designed to run 5 | concurrently with other tests to minimize test time, but tests may be 6 | run independently as well. 7 | 8 | New tests are written in Perl 5 because we need a stable language 9 | to test real-world behavior and Ruby introduces incompatibilities 10 | at a far faster rate than Perl 5. Perl is Ruby's older cousin, so 11 | it should be easy-to-learn for Rubyists. 12 | 13 | Old tests are in Bourne shell and slowly being ported to Perl 5. 14 | 15 | == Requirements 16 | 17 | * {Ruby 2.5.0+}[https://www.ruby-lang.org/en/] 18 | * {Perl 5.14+}[https://www.perl.org/] # your distro should have it 19 | * {GNU make}[https://www.gnu.org/software/make/] 20 | * {curl}[https://curl.haxx.se/] 21 | 22 | We do not use bashisms or any non-portable, non-POSIX constructs 23 | in our shell code. We use the "pipefail" option if available and 24 | mainly test with {ksh}[http://kornshell.com/], but occasionally 25 | with {dash}[http://gondor.apana.org.au/~herbert/dash/] and 26 | {bash}[https://www.gnu.org/software/bash/], too. 27 | 28 | == Running Tests 29 | 30 | To run the entire test suite with 8 tests running at once: 31 | 32 | make -j8 && prove -vw 33 | 34 | To run one individual test (Perl5): 35 | 36 | prove -vw t/integration.t 37 | 38 | To run one individual test (shell): 39 | 40 | make t0000-simple-http.sh 41 | 42 | You may also increase verbosity by setting the "V" variable for 43 | GNU make. To disable trapping of stdout/stderr: 44 | 45 | make V=1 46 | 47 | To enable the "set -x" option in shell scripts to trace execution 48 | 49 | make V=2 50 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # This list is used by "git shortlog" to fixup the ugly faux email addresses 2 | # "" that the "git svn" tool creates by default. 3 | 4 | # Eric Wong started this .mailmap file (and is the maintainer of it...) 5 | Eric Wong normalperson 6 | 7 | # This also includes all the Mongrel contributors that committed to the 8 | # Rubyforge SVN repo. Some real names were looked up on rubyforge.org 9 | # (http://rubyforge.org/users/$user), but we're not going expose any email 10 | # addresses here without their permission. 11 | 12 | Austin Godber godber godber 13 | Bradley Taylor bktaylor 14 | Ezra Zygmuntowicz ezmobius 15 | Filipe Lautert filipe 16 | Luis Lavena luislavena 17 | Matt Pelletier bricolage 18 | MenTaLguY mental 19 | Nick Sieger nicksieger 20 | Rick Olson technoweenie 21 | Wayne E. Seguin wayneeseguin 22 | Zed A. Shaw 23 | why the lucky stiff 24 | 25 | # Evan had his email address in the git history we branched from anyways 26 | Evan Weaver evanweaver 27 | -------------------------------------------------------------------------------- /test/benchmark/ddstream.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # This app is intended to test large HTTP responses with or without 3 | # a fully-buffering reverse proxy such as nginx. Without a fully-buffering 4 | # reverse proxy, unicorn will be unresponsive when client count exceeds 5 | # worker_processes. 6 | # 7 | # To demonstrate how bad unicorn is at slowly reading clients: 8 | # 9 | # # in one terminal, start unicorn with one worker: 10 | # unicorn -E none -l 127.0.0.1:8080 test/benchmark/ddstream.ru 11 | # 12 | # # in a different terminal, start more slow curl processes than 13 | # # unicorn workers and watch time outputs 14 | # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null & 15 | # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null & 16 | # wait 17 | # 18 | # The last client won't see a response until the first one is done reading 19 | # 20 | # nginx note: do not change the default "proxy_buffering" behavior. 21 | # Setting "proxy_buffering off" prevents nginx from protecting unicorn. 22 | 23 | # totally standalone rack app to stream a giant response 24 | class BigResponse 25 | def initialize(bs, count) 26 | @buf = "#{bs.to_s(16)}\r\n#{' ' * bs}\r\n" 27 | @count = count 28 | @res = [ 200, 29 | { 'Transfer-Encoding' => -'chunked', 'Content-Type' => 'text/plain' }, 30 | self 31 | ] 32 | end 33 | 34 | # rack response body iterator 35 | def each 36 | (1..@count).each { yield @buf } 37 | yield -"0\r\n\r\n" 38 | end 39 | 40 | # rack app entry endpoint 41 | def call(_env) 42 | @res 43 | end 44 | end 45 | 46 | # default to a giant (128M) response because kernel socket buffers 47 | # can be ridiculously large on some systems 48 | bs = ENV['bs'] ? ENV['bs'].to_i : 65536 49 | count = ENV['count'] ? ENV['count'].to_i : 2048 50 | warn "serving response with bs=#{bs} count=#{count} (#{bs*count} bytes)" 51 | run BigResponse.new(bs, count) 52 | -------------------------------------------------------------------------------- /t/heartbeat-timeout.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | use v5.14; BEGIN { require './t/lib.perl' }; 5 | use autodie; 6 | use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC); 7 | mkdir "$tmpdir/alt"; 8 | my $srv = tcp_server(); 9 | write_file '>', $u_conf, < $srv }); 17 | 18 | my ($status, $hdr, $wpid) = do_req($srv, 'GET /pid HTTP/1.0'); 19 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds'); 20 | like($wpid, qr/\A[0-9]+\z/, 'worker is running'); 21 | 22 | my $t0 = clock_gettime(CLOCK_MONOTONIC); 23 | my $c = tcp_start($srv, 'GET /block-forever HTTP/1.0'); 24 | vec(my $rvec = '', fileno($c), 1) = 1; 25 | is(select($rvec, undef, undef, 6), 1, 'got readiness'); 26 | $c->blocking(0); 27 | is(sysread($c, my $buf, 128), 0, 'got EOF response'); 28 | my $elapsed = clock_gettime(CLOCK_MONOTONIC) - $t0; 29 | ok($elapsed > 3, 'timeout took >3s'); 30 | 31 | my @timeout_err = slurp($err_log); 32 | truncate($err_log, 0); 33 | is(grep(/timeout \(\d+s > 3s\), killing/, @timeout_err), 1, 34 | 'noted timeout error') or diag explain(\@timeout_err); 35 | 36 | # did it respawn? 37 | ($status, $hdr, my $new_pid) = do_req($srv, 'GET /pid HTTP/1.0'); 38 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds'); 39 | isnt($new_pid, $wpid, 'spawned new worker'); 40 | 41 | diag 'SIGSTOP for 4 seconds...'; 42 | $ar->do_kill('STOP'); 43 | sleep 4; 44 | $ar->do_kill('CONT'); 45 | for my $i (1..2) { 46 | ($status, $hdr, my $spid) = do_req($srv, 'GET /pid HTTP/1.0'); 47 | like($status, qr!\AHTTP/1\.[01] 200\b!, 48 | "PID request succeeds #$i after STOP+CONT"); 49 | is($new_pid, $spid, "worker pid unchanged after STOP+CONT #$i"); 50 | if ($i == 1) { 51 | diag 'sleeping 2s to ensure timeout is not delayed'; 52 | sleep 2; 53 | } 54 | } 55 | 56 | $ar->join('TERM'); 57 | check_stderr; 58 | undef $tmpdir; 59 | 60 | done_testing; 61 | -------------------------------------------------------------------------------- /unicorn.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | manifest = File.exist?('.manifest') ? 4 | IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n") 5 | 6 | # don't bother with tests that fork, not worth our time to get working 7 | # with `gem check -t` ... (of course we care for them when testing with 8 | # GNU make when they can run in parallel) 9 | test_files = manifest.grep(%r{\Atest/unit/test_.*\.rb\z}).map do |f| 10 | File.readlines(f).grep(/\bfork\b/).empty? ? f : nil 11 | end.compact 12 | 13 | Gem::Specification.new do |s| 14 | s.name = %q{unicorn} 15 | s.version = (ENV['VERSION'] || '6.1.0').dup 16 | s.authors = ['unicorn hackers'] 17 | s.summary = 'Rack HTTP server for fast clients and Unix' 18 | s.description = File.read('README').split("\n\n")[1] 19 | s.email = %q{unicorn-public@yhbt.net} 20 | s.executables = %w(unicorn unicorn_rails) 21 | s.extensions = %w(ext/unicorn_http/extconf.rb) 22 | s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f| 23 | File.exist?(f) 24 | end 25 | s.files = manifest 26 | s.homepage = 'https://yhbt.net/unicorn/' 27 | s.test_files = test_files 28 | 29 | # 2.5.0 is the minimum supported version. We don't specify 30 | # a maximum version to make it easier to test pre-releases, 31 | # but we do warn users if they install unicorn on an untested 32 | # version in extconf.rb 33 | s.required_ruby_version = ">= 2.5.0" 34 | 35 | # We do not have a hard dependency on rack, it's possible to load 36 | # things which respond to #call. HTTP status lines in responses 37 | # won't have descriptive text, only the numeric status. 38 | s.add_development_dependency(%q) 39 | 40 | s.add_dependency(%q, '~> 0.7') 41 | 42 | s.add_development_dependency('test-unit', '~> 3.0') 43 | 44 | # Note: To avoid ambiguity, we intentionally avoid the SPDX-compatible 45 | # 'Ruby' here since Ruby 1.9.3 switched to BSD-2-Clause, but we 46 | # inherited our license from Mongrel when Ruby was at 1.8. 47 | # We cannot automatically switch licenses when Ruby changes. 48 | s.licenses = ['GPL-2.0+', 'Ruby-1.8'] 49 | end 50 | -------------------------------------------------------------------------------- /t/winch_ttin.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | use v5.14; BEGIN { require './t/lib.perl' }; 5 | use autodie; 6 | use POSIX qw(mkfifo); 7 | my $u_sock = "$tmpdir/u.sock"; 8 | my $fifo = "$tmpdir/fifo"; 9 | mkfifo($fifo, 0666) or die "mkfifo($fifo): $!"; 10 | 11 | write_file '>', $u_conf, <join; 22 | is($?, 0, 'daemonized properly'); 23 | open my $fh, '<', "$tmpdir/pid"; 24 | chomp(my $pid = <$fh>); 25 | ok(kill(0, $pid), 'daemonized PID works'); 26 | my $quit = sub { kill('QUIT', $pid) if $pid; $pid = undef }; 27 | END { $quit->() }; 28 | 29 | open $fh, '<', $fifo; 30 | my $worker_nr = <$fh>; 31 | close $fh; 32 | is($worker_nr, '0', 'initial worker spawned'); 33 | 34 | my ($status, $hdr, $worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0'); 35 | like($status, qr/ 200\b/, 'got 200 response'); 36 | like($worker_pid, qr/\A[0-9]+\n\z/s, 'PID in response'); 37 | chomp $worker_pid; 38 | ok(kill(0, $worker_pid), 'worker_pid is valid'); 39 | 40 | ok(kill('WINCH', $pid), 'SIGWINCH can be sent'); 41 | 42 | my $tries = 1000; 43 | while (CORE::kill(0, $worker_pid) && --$tries) { sleep 0.01 } 44 | ok(!CORE::kill(0, $worker_pid), 'worker not running'); 45 | 46 | ok(kill('TTIN', $pid), 'SIGTTIN to restart worker'); 47 | 48 | open $fh, '<', $fifo; 49 | $worker_nr = <$fh>; 50 | close $fh; 51 | is($worker_nr, '0', 'worker restarted'); 52 | 53 | ($status, $hdr, my $new_worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0'); 54 | like($status, qr/ 200\b/, 'got 200 response'); 55 | like($new_worker_pid, qr/\A[0-9]+\n\z/, 'got new worker PID'); 56 | chomp $new_worker_pid; 57 | ok(kill(0, $new_worker_pid), 'got a valid worker PID'); 58 | isnt($worker_pid, $new_worker_pid, 'worker PID changed'); 59 | 60 | $quit->(); 61 | 62 | check_stderr; 63 | undef $tmpdir; 64 | done_testing; 65 | -------------------------------------------------------------------------------- /Links: -------------------------------------------------------------------------------- 1 | = Related Projects 2 | 3 | If you're interested in unicorn, you may be interested in some of the projects 4 | listed below. If you have any links to add/change/remove, please tell us at 5 | mailto:unicorn-public@yhbt.net! 6 | 7 | == Disclaimer 8 | 9 | The unicorn project is not responsible for the content in these links. 10 | Furthermore, the unicorn project has never, does not and will never endorse: 11 | 12 | * any for-profit entities or services 13 | * any non-{Free Software}[https://www.gnu.org/philosophy/free-sw.html] 14 | 15 | The existence of these links does not imply endorsement of any entities 16 | or services behind them. 17 | 18 | === For use with unicorn 19 | 20 | * {Bluepill}[https://github.com/arya/bluepill] - 21 | a simple process monitoring tool written in Ruby 22 | 23 | * {golden_brindle}[https://github.com/simonoff/golden_brindle] - tool to 24 | manage multiple unicorn instances/applications on a single server 25 | 26 | * {raindrops}[https://yhbt.net/raindrops/] - real-time stats for 27 | preforking Rack servers 28 | 29 | * {UnXF}[https://yhbt.net/unxf/] Un-X-Forward* the Rack environment, 30 | useful since unicorn is designed to be deployed behind a reverse proxy. 31 | 32 | === unicorn is written to work with 33 | 34 | * {Rack}[https://rack.github.io/] - a minimal interface between webservers 35 | supporting Ruby and Ruby frameworks 36 | 37 | * {Ruby}[https://www.ruby-lang.org/en/] - the programming language of 38 | Rack and unicorn 39 | 40 | * {nginx}[https://nginx.org/] (Free versions) - 41 | the reverse proxy for use with unicorn 42 | 43 | === Derivatives 44 | 45 | * {Green Unicorn}[https://gunicorn.org/] - a Python version of unicorn 46 | 47 | * {Starman}[https://metacpan.org/release/Starman/] - Plack/PSGI version 48 | of unicorn 49 | 50 | === Prior Work 51 | 52 | * {Mongrel}[https://rubygems.org/gems/mongrel] - the awesome webserver 53 | unicorn is based on. A historical archive of the mongrel dev list 54 | featuring early discussions of unicorn is available at: 55 | https://yhbt.net/mongrel-devel/ 56 | 57 | * {david}[https://yhbt.net/david.git] - a tool to explain why you need 58 | nginx in front of unicorn 59 | -------------------------------------------------------------------------------- /test/benchmark/uconnect.perl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # Benchmark script to spawn some processes and hammer a local unicorn 3 | # to test accept loop performance. This only does Unix sockets. 4 | # There's plenty of TCP benchmarking tools out there, and TCP port reuse 5 | # has predictability problems since unicorn can't do persistent connections. 6 | # Written in Perl for the same reason: predictability. 7 | # Ruby GC is not as predictable as Perl refcounting. 8 | use strict; 9 | use Socket qw(AF_UNIX SOCK_STREAM sockaddr_un); 10 | use POSIX qw(:sys_wait_h); 11 | use Getopt::Std; 12 | # -c / -n switches stolen from ab(1) 13 | my $usage = "$0 [-c CONCURRENCY] [-n NUM_REQUESTS] SOCKET_PATH\n"; 14 | our $opt_c = 2; 15 | our $opt_n = 1000; 16 | getopts('c:n:') or die $usage; 17 | my $unix_path = shift or die $usage; 18 | use constant REQ => "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"; 19 | use constant REQ_LEN => length(REQ); 20 | use constant BUFSIZ => 8192; 21 | $^F = 99; # don't waste syscall time with FD_CLOEXEC 22 | 23 | my %workers; # pid => worker num 24 | die "-n $opt_n not evenly divisible by -c $opt_c\n" if $opt_n % $opt_c; 25 | my $n_per_worker = $opt_n / $opt_c; 26 | my $addr = sockaddr_un($unix_path); 27 | 28 | for my $num (1..$opt_c) { 29 | defined(my $pid = fork) or die "fork failed: $!\n"; 30 | if ($pid) { 31 | $workers{$pid} = $num; 32 | } else { 33 | work($n_per_worker); 34 | } 35 | } 36 | 37 | reap_worker(0) while scalar keys %workers; 38 | exit; 39 | 40 | sub work { 41 | my ($n) = @_; 42 | my ($buf, $x); 43 | for (1..$n) { 44 | socket(S, AF_UNIX, SOCK_STREAM, 0) or die "socket: $!"; 45 | connect(S, $addr) or die "connect: $!"; 46 | defined($x = syswrite(S, REQ)) or die "write: $!"; 47 | $x == REQ_LEN or die "short write: $x != ".REQ_LEN."\n"; 48 | do { 49 | $x = sysread(S, $buf, BUFSIZ); 50 | unless (defined $x) { 51 | next if $!{EINTR}; 52 | die "sysread: $!\n"; 53 | } 54 | } until ($x == 0); 55 | } 56 | exit 0; 57 | } 58 | 59 | sub reap_worker { 60 | my ($flags) = @_; 61 | my $pid = waitpid(-1, $flags); 62 | return if !defined $pid || $pid <= 0; 63 | my $p = delete $workers{$pid} || '(unknown)'; 64 | warn("$pid [$p] exited with $?\n") if $?; 65 | $p; 66 | } 67 | -------------------------------------------------------------------------------- /test/benchmark/README: -------------------------------------------------------------------------------- 1 | = Performance 2 | 3 | Unicorn is pretty fast, and we want it to get faster. Unicorn strives 4 | to get HTTP requests to your application and write HTTP responses back 5 | as quickly as possible. Unicorn does not do any background processing 6 | while your app runs, so your app will get all the CPU time provided to 7 | it by your OS kernel. 8 | 9 | A gentle reminder: Unicorn is NOT for serving clients over slow network 10 | connections. Use nginx (or something similar) to complement Unicorn if 11 | you have slow clients. 12 | 13 | == dd.ru 14 | 15 | This is a pure I/O benchmark. In the context of Unicorn, this is the 16 | only one that matters. It is a standard rackup-compatible .ru file and 17 | may be used with other Rack-compatible servers. 18 | 19 | unicorn -E none dd.ru 20 | 21 | You can change the size and number of chunks in the response with 22 | the "bs" and "count" environment variables. The following command 23 | will cause dd.ru to return 4 chunks of 16384 bytes each, leading to 24 | 65536 byte response: 25 | 26 | bs=16384 count=4 unicorn -E none dd.ru 27 | 28 | Or if you want to add logging (small performance impact): 29 | 30 | unicorn -E deployment dd.ru 31 | 32 | Eric runs then runs clients on a LAN it in several different ways: 33 | 34 | client@host1 -> unicorn@host1(tcp) 35 | client@host2 -> unicorn@host1(tcp) 36 | client@host3 -> nginx@host1 -> unicorn@host1(tcp) 37 | client@host3 -> nginx@host1 -> unicorn@host1(unix) 38 | client@host3 -> nginx@host2 -> unicorn@host1(tcp) 39 | 40 | The benchmark client is usually httperf. 41 | 42 | Another gentle reminder: performance with slow networks/clients 43 | is NOT our problem. That is the job of nginx (or similar). 44 | 45 | == ddstream.ru 46 | 47 | Standalone Rack app intended to show how BAD we are at slow clients. 48 | See usage in comments. 49 | 50 | == readinput.ru 51 | 52 | Standalone Rack app intended to show how bad we are with slow uploaders. 53 | See usage in comments. 54 | 55 | == Contributors 56 | 57 | This directory is intended to remain stable. Do not make changes 58 | to benchmarking code which can change performance and invalidate 59 | results across revisions. Instead, write new benchmarks and update 60 | coments/documentation as necessary. 61 | -------------------------------------------------------------------------------- /lib/unicorn/launcher.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | # :enddoc: 5 | $stdout.sync = $stderr.sync = true 6 | $stdin.binmode 7 | $stdout.binmode 8 | $stderr.binmode 9 | 10 | require 'unicorn' 11 | 12 | module Unicorn::Launcher 13 | 14 | # We don't do a lot of standard daemonization stuff: 15 | # * umask is whatever was set by the parent process at startup 16 | # and can be set in config.ru and config_file, so making it 17 | # 0000 and potentially exposing sensitive log data can be bad 18 | # policy. 19 | # * don't bother to chdir("/") here since unicorn is designed to 20 | # run inside APP_ROOT. Unicorn will also re-chdir() to 21 | # the directory it was started in when being re-executed 22 | # to pickup code changes if the original deployment directory 23 | # is a symlink or otherwise got replaced. 24 | def self.daemonize!(options) 25 | cfg = Unicorn::Configurator 26 | $stdin.reopen("/dev/null") 27 | 28 | # We only start a new process group if we're not being reexecuted 29 | # and inheriting file descriptors from our parent 30 | unless ENV['UNICORN_FD'] 31 | # grandparent - reads pipe, exits when master is ready 32 | # \_ parent - exits immediately ASAP 33 | # \_ unicorn master - writes to pipe when ready 34 | 35 | rd, wr = Unicorn.pipe 36 | grandparent = $$ 37 | if fork 38 | wr.close # grandparent does not write 39 | else 40 | rd.close # unicorn master does not read 41 | Process.setsid 42 | exit if fork # parent dies now 43 | end 44 | 45 | if grandparent == $$ 46 | # this will block until HttpServer#join runs (or it dies) 47 | master_pid = (rd.readpartial(16) rescue nil).to_i 48 | unless master_pid > 1 49 | warn "master failed to start, check stderr log for details" 50 | exit!(1) 51 | end 52 | exit 0 53 | else # unicorn master process 54 | options[:ready_pipe] = wr 55 | end 56 | end 57 | # $stderr/$stderr can/will be redirected separately in the Unicorn config 58 | cfg::DEFAULTS[:stderr_path] ||= "/dev/null" 59 | cfg::DEFAULTS[:stdout_path] ||= "/dev/null" 60 | cfg::RACKUP[:daemonized] = true 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /lib/unicorn/app/old_rails/static.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | # :enddoc: 4 | # This code is based on the original Rails handler in Mongrel 5 | # Copyright (c) 2005 Zed A. Shaw 6 | # Copyright (c) 2009 Eric Wong 7 | # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or 8 | # the GPLv3 9 | 10 | # Static file handler for Rails < 2.3. This handler is only provided 11 | # as a convenience for developers. Performance-minded deployments should 12 | # use nginx (or similar) for serving static files. 13 | # 14 | # This supports page caching directly and will try to resolve a 15 | # request in the following order: 16 | # 17 | # * If the requested exact PATH_INFO exists as a file then serve it. 18 | # * If it exists at PATH_INFO+rest_operator+".html" exists 19 | # then serve that. 20 | # 21 | # This means that if you are using page caching it will actually work 22 | # with Unicorn and you should see a decent speed boost (but not as 23 | # fast as if you use a static server like nginx). 24 | class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server) 25 | FILE_METHODS = { 'GET' => true, 'HEAD' => true } 26 | 27 | # avoid allocating new strings for hash lookups 28 | REQUEST_METHOD = 'REQUEST_METHOD' 29 | REQUEST_URI = 'REQUEST_URI' 30 | PATH_INFO = 'PATH_INFO' 31 | 32 | def initialize(app) 33 | self.app = app 34 | self.root = "#{::RAILS_ROOT}/public" 35 | self.file_server = ::Rack::File.new(root) 36 | end 37 | 38 | def call(env) 39 | # short circuit this ASAP if serving non-file methods 40 | FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env) 41 | 42 | # first try the path as-is 43 | path_info = env[PATH_INFO].chomp("/") 44 | if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}") 45 | # File exists as-is so serve it up 46 | env[PATH_INFO] = path_info 47 | return file_server.call(env) 48 | end 49 | 50 | # then try the cached version: 51 | path_info << ActionController::Base.page_cache_extension 52 | 53 | if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}") 54 | env[PATH_INFO] = path_info 55 | return file_server.call(env) 56 | end 57 | 58 | app.call(env) # call OldRails 59 | end 60 | end if defined?(Unicorn::App::OldRails) 61 | -------------------------------------------------------------------------------- /t/t0012-reload-empty-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 9 "reloading unset config resets defaults" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | rtmpfiles unicorn_config_orig before_reload after_reload 8 | cat $unicorn_config > $unicorn_config_orig 9 | cat >> $unicorn_config < $tmp 37 | } 38 | 39 | t_begin "replace config file with original(-ish)" && { 40 | grep -v ^pid < $unicorn_config_orig > $unicorn_config 41 | cat >> $unicorn_config </dev/null 51 | do 52 | sleep 1 53 | done 54 | while ! grep reaped < $r_err >/dev/null 55 | do 56 | sleep 1 57 | done 58 | grep 'done reloading' $r_err >/dev/null 59 | } 60 | 61 | t_begin "ensure worker is started" && { 62 | curl -sSf http://$listen/ > $tmp 63 | } 64 | 65 | t_begin "pid file no longer exists" && { 66 | if test -f $pid 67 | then 68 | die "pid=$pid should not exist" 69 | fi 70 | } 71 | 72 | t_begin "killing succeeds" && { 73 | kill $unicorn_pid 74 | } 75 | 76 | t_begin "check stderr" && { 77 | check_stderr 78 | } 79 | 80 | t_begin "ensure reloading restored settings" && { 81 | awk < $after_reload -F'|' ' 82 | $1 != "before_fork" && $2 != $3 { print $0; exit(1) } 83 | ' 84 | } 85 | 86 | t_done 87 | -------------------------------------------------------------------------------- /examples/init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | ### BEGIN INIT INFO 4 | # Provides: unicorn 5 | # Required-Start: $local_fs $network 6 | # Required-Stop: $local_fs $network 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: Start/stop unicorn Rack app server 10 | ### END INIT INFO 11 | 12 | # Example init script, this can be used with nginx, too, 13 | # since nginx and unicorn accept the same signals. 14 | 15 | # Feel free to change any of the following variables for your app: 16 | TIMEOUT=${TIMEOUT-60} 17 | APP_ROOT=/home/x/my_app/current 18 | PID=$APP_ROOT/tmp/pids/unicorn.pid 19 | CMD="/usr/bin/unicorn -D -c $APP_ROOT/config/unicorn.rb" 20 | INIT_CONF=$APP_ROOT/config/init.conf 21 | UPGRADE_DELAY=${UPGRADE_DELAY-2} 22 | action="$1" 23 | set -u 24 | 25 | test -f "$INIT_CONF" && . $INIT_CONF 26 | 27 | OLD="$PID.oldbin" 28 | 29 | cd $APP_ROOT || exit 1 30 | 31 | sig () { 32 | test -s "$PID" && kill -$1 $(cat $PID) 33 | } 34 | 35 | oldsig () { 36 | test -s "$OLD" && kill -$1 $(cat $OLD) 37 | } 38 | 39 | case $action in 40 | start) 41 | sig 0 && echo >&2 "Already running" && exit 0 42 | $CMD 43 | ;; 44 | stop) 45 | sig QUIT && exit 0 46 | echo >&2 "Not running" 47 | ;; 48 | force-stop) 49 | sig TERM && exit 0 50 | echo >&2 "Not running" 51 | ;; 52 | restart|reload) 53 | sig HUP && echo reloaded OK && exit 0 54 | echo >&2 "Couldn't reload, starting '$CMD' instead" 55 | $CMD 56 | ;; 57 | upgrade) 58 | if oldsig 0 59 | then 60 | echo >&2 "Old upgraded process still running with $OLD" 61 | exit 1 62 | fi 63 | 64 | cur_pid= 65 | if test -s "$PID" 66 | then 67 | cur_pid=$(cat $PID) 68 | fi 69 | 70 | if test -n "$cur_pid" && 71 | kill -USR2 "$cur_pid" && 72 | sleep $UPGRADE_DELAY && 73 | new_pid=$(cat $PID) && 74 | test x"$new_pid" != x"$cur_pid" && 75 | kill -0 "$new_pid" && 76 | kill -QUIT "$cur_pid" 77 | then 78 | n=$TIMEOUT 79 | while kill -0 "$cur_pid" 2>/dev/null && test $n -ge 0 80 | do 81 | printf '.' && sleep 1 && n=$(( $n - 1 )) 82 | done 83 | echo 84 | 85 | if test $n -lt 0 && kill -0 "$cur_pid" 2>/dev/null 86 | then 87 | echo >&2 "$cur_pid still running after $TIMEOUT seconds" 88 | exit 1 89 | fi 90 | exit 0 91 | fi 92 | echo >&2 "Couldn't upgrade, starting '$CMD' instead" 93 | $CMD 94 | ;; 95 | reopen-logs) 96 | sig USR1 97 | ;; 98 | *) 99 | echo >&2 "Usage: $0 " 100 | exit 1 101 | ;; 102 | esac 103 | -------------------------------------------------------------------------------- /t/working_directory.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | use v5.14; BEGIN { require './t/lib.perl' }; 5 | use autodie; 6 | mkdir "$tmpdir/alt"; 7 | my $ru = "$tmpdir/alt/config.ru"; 8 | write_file '>', $u_conf, <', $ru, <join; # will daemonize 28 | chomp($daemon_pid = slurp($pid_file)); 29 | 30 | my ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0'); 31 | is($bdy, "1\n", 'got expected $master_ppid'); 32 | 33 | stop_daemon; 34 | check_stderr; 35 | 36 | if ('test without CLI switches in config.ru') { 37 | truncate $err_log, 0; 38 | write_file '>', $ru, $common_ru; 39 | 40 | unicorn('-D', '-l', $u_sock, '-c', $u_conf)->join; # will daemonize 41 | chomp($daemon_pid = slurp($pid_file)); 42 | 43 | ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0'); 44 | is($bdy, "1\n", 'got expected $master_ppid'); 45 | 46 | stop_daemon; 47 | check_stderr; 48 | } 49 | 50 | if ('ensures broken working_directory (missing config.ru) is OK') { 51 | truncate $err_log, 0; 52 | unlink $ru; 53 | 54 | my $auto_reap = unicorn('-c', $u_conf); 55 | $auto_reap->join; 56 | isnt($?, 0, 'exited with error due to missing config.ru'); 57 | 58 | like(slurp($err_log), qr/rackup file \Q(config.ru)\E not readable/, 59 | 'noted unreadability of config.ru in stderr'); 60 | } 61 | 62 | if ('fooapp.rb (not config.ru) works with working_directory') { 63 | truncate $err_log, 0; 64 | my $fooapp = "$tmpdir/alt/fooapp.rb"; 65 | write_file '>', $fooapp, < 'text/plain', 'content-length' => b.bytesize.to_s } 70 | [ 200, h, [ b ] ] 71 | end 72 | end 73 | EOM 74 | my $srv = tcp_server; 75 | my $auto_reap = unicorn(qw(-c), $u_conf, qw(-I. fooapp.rb), 76 | { -C => '/', 3 => $srv }); 77 | ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 78 | is($bdy, "dir=$tmpdir/alt", 79 | 'fooapp.rb (w/o config.ru) w/ working_directory'); 80 | $auto_reap->join('TERM'); 81 | is($?, 0, 'fooapp.rb process exited'); 82 | check_stderr; 83 | } 84 | 85 | undef $tmpdir; 86 | done_testing; 87 | -------------------------------------------------------------------------------- /t/t9002-oob_gc-path.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 12 "OobGC test with limited path" 4 | 5 | t_begin "setup and start" && { 6 | unicorn_setup 7 | unicorn -D -c $unicorn_config oob_gc_path.ru 8 | unicorn_wait_start 9 | } 10 | 11 | t_begin "test default is noop" && { 12 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 13 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 14 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 15 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 16 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 17 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 18 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 19 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 20 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 21 | } 22 | 23 | t_begin "4 bad requests to bump counter" && { 24 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 25 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 26 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 27 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 28 | } 29 | 30 | t_begin "GC-starting request returns immediately" && { 31 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 32 | } 33 | 34 | t_begin "GC was started after 5 requests" && { 35 | test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp) 36 | } 37 | 38 | t_begin "reset GC" && { 39 | test xfalse = x$(curl -vsSf -X POST http://$listen/gc_reset 2>> $tmp) 40 | } 41 | 42 | t_begin "test default is noop" && { 43 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 44 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 45 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 46 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 47 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 48 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 49 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 50 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 51 | test xfalse = x$(curl -vsSf http://$listen/ 2>> $tmp) 52 | } 53 | 54 | t_begin "4 bad requests to bump counter" && { 55 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 56 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 57 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 58 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 59 | } 60 | 61 | t_begin "GC-starting request returns immediately" && { 62 | test xfalse = x$(curl -vsSf http://$listen/BAD 2>> $tmp) 63 | } 64 | 65 | t_begin "GC was started after 5 requests" && { 66 | test xtrue = x$(curl -vsSf http://$listen/ 2>> $tmp) 67 | } 68 | 69 | t_begin "killing succeeds" && { 70 | kill -QUIT $unicorn_pid 71 | } 72 | 73 | t_begin "check_stderr" && check_stderr 74 | 75 | t_done 76 | -------------------------------------------------------------------------------- /ext/unicorn_http/httpdate.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static const size_t buf_capa = sizeof("Thu, 01 Jan 1970 00:00:00 GMT"); 7 | static VALUE buf; 8 | static char *buf_ptr; 9 | static const char week[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat"; 10 | static const char months[] = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0" 11 | "Jul\0Aug\0Sep\0Oct\0Nov\0Dec"; 12 | 13 | /* for people on wonky systems only */ 14 | #ifndef HAVE_GMTIME_R 15 | # warning using fake gmtime_r 16 | static struct tm * my_gmtime_r(time_t *now, struct tm *tm) 17 | { 18 | struct tm *global = gmtime(now); 19 | if (global) 20 | *tm = *global; 21 | return tm; 22 | } 23 | # define gmtime_r my_gmtime_r 24 | #endif 25 | 26 | 27 | /* 28 | * Returns a string which represents the time as rfc1123-date of HTTP-date 29 | * defined by RFC 2616: 30 | * 31 | * day-of-week, DD month-name CCYY hh:mm:ss GMT 32 | * 33 | * Note that the result is always GMT. 34 | * 35 | * This method is identical to Time#httpdate in the Ruby standard library, 36 | * except it is implemented in C for performance. We always saw 37 | * Time#httpdate at or near the top of the profiler output so we 38 | * decided to rewrite this in C. 39 | * 40 | * Caveats: it relies on a Ruby implementation with the global VM lock, 41 | * a thread-safe version will be provided when a Unix-only, GVL-free Ruby 42 | * implementation becomes viable. 43 | */ 44 | static VALUE httpdate(VALUE self) 45 | { 46 | static time_t last; 47 | struct timeval now; 48 | struct tm tm; 49 | 50 | /* 51 | * Favor gettimeofday(2) over time(2), as the latter can return the 52 | * wrong value in the first 1 .. 2.5 ms of every second(!) 53 | * 54 | * https://lore.kernel.org/git/20230320230507.3932018-1-gitster@pobox.com/ 55 | * https://inbox.sourceware.org/libc-alpha/20230306160321.2942372-1-adhemerval.zanella@linaro.org/T/ 56 | * https://sourceware.org/bugzilla/show_bug.cgi?id=30200 57 | */ 58 | if (gettimeofday(&now, NULL)) 59 | rb_sys_fail("gettimeofday"); 60 | 61 | if (last == now.tv_sec) 62 | return buf; 63 | last = now.tv_sec; 64 | gmtime_r(&now.tv_sec, &tm); 65 | 66 | /* we can make this thread-safe later if our Ruby loses the GVL */ 67 | snprintf(buf_ptr, buf_capa, 68 | "%s, %02d %s %4d %02d:%02d:%02d GMT", 69 | week + (tm.tm_wday * 4), 70 | tm.tm_mday, 71 | months + (tm.tm_mon * 4), 72 | tm.tm_year + 1900, 73 | tm.tm_hour, 74 | tm.tm_min, 75 | tm.tm_sec); 76 | 77 | return buf; 78 | } 79 | 80 | void init_unicorn_httpdate(void) 81 | { 82 | VALUE mod = rb_define_module("Unicorn"); 83 | mod = rb_define_module_under(mod, "HttpResponse"); 84 | 85 | buf = rb_str_new(0, buf_capa - 1); 86 | rb_gc_register_mark_object(buf); 87 | buf_ptr = RSTRING_PTR(buf); 88 | httpdate(Qnil); 89 | 90 | rb_define_method(mod, "httpdate", httpdate, 0); 91 | } 92 | -------------------------------------------------------------------------------- /t/client_body_buffer_size.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | 5 | use v5.14; BEGIN { require './t/lib.perl' }; 6 | use autodie; 7 | my $conf_fh = write_file '>', $u_conf, <autoflush(1); 11 | my $srv = tcp_server(); 12 | my $host_port = tcp_host_port($srv); 13 | my @uarg = (qw(-E none t/client_body_buffer_size.ru -c), $u_conf); 14 | my $ar = unicorn(@uarg, { 3 => $srv }); 15 | my ($c, $status, $hdr); 16 | my $mem_class = 'StringIO'; 17 | my $fs_class = 'Unicorn::TmpIO'; 18 | 19 | $c = tcp_start($srv, "PUT /input_class HTTP/1.0\r\nContent-Length: 0"); 20 | ($status, $hdr) = slurp_hdr($c); 21 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 22 | is(readline($c), $mem_class, 'zero-byte file is StringIO'); 23 | 24 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1"); 25 | print $c '.'; 26 | ($status, $hdr) = slurp_hdr($c); 27 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 28 | is(readline($c), $fs_class, '1 byte file is filesystem-backed'); 29 | 30 | 31 | my $fifo = "$tmpdir/fifo"; 32 | POSIX::mkfifo($fifo, 0600) or die "mkfifo: $!"; 33 | seek($conf_fh, 0, SEEK_SET); 34 | truncate($conf_fh, 0); 35 | print $conf_fh <do_kill('HUP'); 39 | open my $fifo_fh, '<', $fifo; 40 | like(my $wpid = readline($fifo_fh), qr/\Apid=\d+\z/a , 41 | 'reloaded w/ default client_body_buffer_size'); 42 | 43 | 44 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1"); 45 | ($status, $hdr) = slurp_hdr($c); 46 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 47 | is(readline($c), $mem_class, 'class for a 1 byte file is memory-backed'); 48 | 49 | 50 | my $one_meg = 1024 ** 2; 51 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg"); 52 | ($status, $hdr) = slurp_hdr($c); 53 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 54 | is(readline($c), $fs_class, '1 megabyte file is FS-backed'); 55 | 56 | # reload with bigger client_body_buffer_size 57 | say $conf_fh "client_body_buffer_size $one_meg"; 58 | $ar->do_kill('HUP'); 59 | open $fifo_fh, '<', $fifo; 60 | like($wpid = readline($fifo_fh), qr/\Apid=\d+\z/a , 61 | 'reloaded w/ bigger client_body_buffer_size'); 62 | 63 | 64 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg"); 65 | ($status, $hdr) = slurp_hdr($c); 66 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 67 | is(readline($c), $mem_class, '1 megabyte file is now memory-backed'); 68 | 69 | my $too_big = $one_meg + 1; 70 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $too_big"); 71 | ($status, $hdr) = slurp_hdr($c); 72 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 73 | is(readline($c), $fs_class, '1 megabyte + 1 byte file is FS-backed'); 74 | 75 | 76 | undef $ar; 77 | check_stderr; 78 | undef $tmpdir; 79 | done_testing; 80 | -------------------------------------------------------------------------------- /FAQ: -------------------------------------------------------------------------------- 1 | = Frequently Asked Questions about Unicorn 2 | 3 | === Why is nginx getting ECONNRESET as a reverse proxy? 4 | 5 | Request body data (commonly from POST and PUT requests) may not be 6 | drained entirely by the application. This may happen when request 7 | bodies are gzipped, as unicorn reads request body data lazily to avoid 8 | overhead from bad requests. 9 | 10 | Ref: https://yhbt.net/unicorn-public/FC91211E-FD32-432C-92FC-0318714C2170@zendesk.com/ 11 | 12 | === Why aren't my Rails log files rotated when I use SIGUSR1? 13 | 14 | The Rails autoflush_log option must remain disabled with multiprocess 15 | servers such as unicorn. Buffering in userspace may cause lines to be 16 | partially written and lead to corruption in the presence of multiple 17 | processes. With reasonable amounts of logging, the performance impact 18 | of autoflush_log should be negligible on Linux and other modern kernels. 19 | 20 | === Why are my redirects going to "http" URLs when my site uses https? 21 | 22 | If your site is entirely behind https, then Rack applications that use 23 | "rack.url_scheme" can set the following in the Unicorn config file: 24 | 25 | HttpRequest::DEFAULTS["rack.url_scheme"] = "https" 26 | 27 | For frameworks that do not use "rack.url_scheme", you can also 28 | try setting one or both of the following: 29 | 30 | HttpRequest::DEFAULTS["HTTPS"] = "on" 31 | HttpRequest::DEFAULTS["HTTP_X_FORWARDED_PROTO"] = "https" 32 | 33 | Otherwise, you can configure your proxy (nginx) to send the 34 | "X-Forwarded-Proto: https" header only for parts of the site that use 35 | https. For nginx, you can do it with the following line in appropriate 36 | "location" blocks of your nginx config file: 37 | 38 | proxy_set_header X-Forwarded-Proto https; 39 | 40 | === Why are log messages from Unicorn are unformatted when using Rails? 41 | 42 | Current versions of Rails unfortunately overrides the default Logger 43 | formatter. 44 | 45 | You can undo this behavior with the default logger in your Unicorn 46 | config file: 47 | 48 | Configurator::DEFAULTS[:logger].formatter = Logger::Formatter.new 49 | 50 | Of course you can specify an entirely different logger as well 51 | with the "logger" directive described by Unicorn::Configurator. 52 | 53 | === Why am I getting "connection refused"/502 errors under high load? 54 | 55 | Short answer: your application cannot keep up. 56 | 57 | You can increase the size of the :backlog parameter if your kernel 58 | supports a larger listen() queue, but keep in mind having a large listen 59 | queue makes failover to a different machine more difficult. 60 | 61 | See the TUNING and Unicorn::Configurator documents for more information 62 | on :backlog-related topics. 63 | 64 | === I've installed Rack 1.1.x, why can't Unicorn load Rails (2.3.5)? 65 | 66 | Rails 2.3.5 is not compatible with Rack 1.1.x. Unicorn is compatible 67 | with both Rack 1.1.x and Rack 1.0.x, and RubyGems will load the latest 68 | version of Rack installed on the system. Uninstalling the Rack 1.1.x 69 | gem should solve gem loading issues with Rails 2.3.5. Rails 2.3.6 70 | and later correctly support Rack 1.1.x. 71 | -------------------------------------------------------------------------------- /t/test-lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (c) 2009 Rainbows! hackers 3 | # Copyright (c) 2010 Unicorn hackers 4 | . ./my-tap-lib.sh 5 | 6 | set +u 7 | 8 | # sometimes we rely on http_proxy to avoid wasting bandwidth with Isolate 9 | # and multiple Ruby versions 10 | NO_PROXY=${UNICORN_TEST_ADDR-127.0.0.1} 11 | export NO_PROXY 12 | 13 | set -e 14 | RUBY="${RUBY-ruby}" 15 | RUBY_VERSION=${RUBY_VERSION-$($RUBY -e 'puts RUBY_VERSION')} 16 | RUBY_ENGINE=${RUBY_ENGINE-$($RUBY -e 'puts((RUBY_ENGINE rescue "ruby"))')} 17 | t_pfx=$PWD/trash/$T-$RUBY_ENGINE-$RUBY_VERSION 18 | set -u 19 | 20 | PATH=$PWD/bin:$PATH 21 | export PATH 22 | 23 | test -x $PWD/bin/unused_listen || die "must be run in 't' directory" 24 | 25 | wait_for_pid () { 26 | path="$1" 27 | nr=30 28 | while ! test -s "$path" && test $nr -gt 0 29 | do 30 | nr=$(($nr - 1)) 31 | sleep 1 32 | done 33 | } 34 | 35 | # "unix_time" is not in POSIX, but in GNU, and FreeBSD 9.0 (possibly earlier) 36 | unix_time () { 37 | $RUBY -e 'puts Time.now.to_i' 38 | } 39 | 40 | # "wc -l" outputs leading whitespace on *BSDs, filter it out for portability 41 | count_lines () { 42 | wc -l | tr -d '[:space:]' 43 | } 44 | 45 | # "wc -c" outputs leading whitespace on *BSDs, filter it out for portability 46 | count_bytes () { 47 | wc -c | tr -d '[:space:]' 48 | } 49 | 50 | # given a list of variable names, create temporary files and assign 51 | # the pathnames to those variables 52 | rtmpfiles () { 53 | for id in "$@" 54 | do 55 | name=$id 56 | 57 | case $name in 58 | *fifo) 59 | _tmp=$t_pfx.$id 60 | eval "$id=$_tmp" 61 | rm -f $_tmp 62 | mkfifo $_tmp 63 | T_RM_LIST="$T_RM_LIST $_tmp" 64 | ;; 65 | *socket) 66 | _tmp="$(mktemp -t $id.$$.XXXXXXXX)" 67 | if test $(printf "$_tmp" |count_bytes) -gt 108 68 | then 69 | echo >&2 "$_tmp too long, tests may fail" 70 | echo >&2 "Try to set TMPDIR to a shorter path" 71 | fi 72 | eval "$id=$_tmp" 73 | rm -f $_tmp 74 | T_RM_LIST="$T_RM_LIST $_tmp" 75 | ;; 76 | *) 77 | _tmp=$t_pfx.$id 78 | eval "$id=$_tmp" 79 | > $_tmp 80 | T_OK_RM_LIST="$T_OK_RM_LIST $_tmp" 81 | ;; 82 | esac 83 | done 84 | } 85 | 86 | dbgcat () { 87 | id=$1 88 | eval '_file=$'$id 89 | echo "==> $id <==" 90 | sed -e "s/^/$id:/" < $_file 91 | } 92 | 93 | check_stderr () { 94 | set +u 95 | _r_err=${1-${r_err}} 96 | set -u 97 | if grep -v $T $_r_err | grep -i Error | \ 98 | grep -v NameError.*Unicorn::Waiter 99 | then 100 | die "Errors found in $_r_err" 101 | elif grep SIGKILL $_r_err 102 | then 103 | die "SIGKILL found in $_r_err" 104 | fi 105 | } 106 | 107 | # unicorn_setup 108 | unicorn_setup () { 109 | eval $(unused_listen) 110 | port=$(expr $listen : '[^:]*:\([0-9]*\)') 111 | host=$(expr $listen : '\([^:][^:]*\):[0-9][0-9]*') 112 | 113 | rtmpfiles unicorn_config pid r_err r_out fifo tmp ok 114 | cat > $unicorn_config <"); 15 | unsafe = (CTL | " " | "#" | "%" | sorta_safe); 16 | national = any -- (alpha | digit | reserved | extra | safe | unsafe); 17 | unreserved = (alpha | digit | safe | extra | national); 18 | escape = ("%" xdigit xdigit); 19 | uchar = (unreserved | escape | sorta_safe); 20 | pchar = (uchar | ":" | "@" | "&" | "=" | "+"); 21 | tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); 22 | lws = (" " | "\t"); 23 | content = ((any -- CTL) | lws); 24 | 25 | # elements 26 | token = (ascii -- (CTL | tspecials)); 27 | 28 | # URI schemes and absolute paths 29 | scheme = ( "http"i ("s"i)? ) $downcase_char >mark %scheme; 30 | hostname = ((alnum | "-" | "." | "_")+ | ("[" (":" | xdigit)+ "]")); 31 | host_with_port = (hostname (":" digit*)?) >mark %host; 32 | userinfo = ((unreserved | escape | ";" | ":" | "&" | "=" | "+")+ "@")*; 33 | 34 | path = ( pchar+ ( "/" pchar* )* ) ; 35 | query = ( uchar | reserved )* %query_string ; 36 | param = ( pchar | "/" )* ; 37 | params = ( param ( ";" param )* ) ; 38 | rel_path = (path? (";" params)? %request_path) ("?" %start_query query)?; 39 | absolute_path = ( "/"+ rel_path ); 40 | path_uri = absolute_path > mark %request_uri; 41 | Absolute_URI = (scheme "://" userinfo host_with_port path_uri); 42 | 43 | Request_URI = ((absolute_path | "*") >mark %request_uri) | Absolute_URI; 44 | Fragment = ( uchar | reserved )* >mark %fragment; 45 | Method = (token){1,20} >mark %request_method; 46 | GetOnly = "GET" >mark %request_method; 47 | 48 | http_number = ( digit+ "." digit+ ) ; 49 | HTTP_Version = ( "HTTP/" http_number ) >mark %http_version ; 50 | Request_Line = ( Method " " Request_URI ("#" Fragment){0,1} " " HTTP_Version CRLF ) ; 51 | 52 | field_name = ( token -- ":" )+ >start_field $upcase_field %write_field; 53 | 54 | field_value = content* >start_value %write_value; 55 | 56 | value_cont = lws+ content* >start_value %write_cont_value; 57 | 58 | message_header = ((field_name ":" lws* field_value)|value_cont) :> CRLF; 59 | chunk_ext_val = token*; 60 | chunk_ext_name = token*; 61 | chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*; 62 | last_chunk = "0"+ chunk_extension CRLF; 63 | chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size; 64 | chunk_end = CRLF; 65 | chunk_body = any >skip_chunk_data; 66 | chunk_begin = chunk_size chunk_extension CRLF; 67 | chunk = chunk_begin chunk_body chunk_end; 68 | ChunkedBody := chunk* last_chunk @end_chunked_body; 69 | Trailers := (message_header)* CRLF @end_trailers; 70 | 71 | FullRequest = Request_Line (message_header)* CRLF @header_done; 72 | SimpleRequest = GetOnly " " Request_URI ("#"Fragment){0,1} CRLF @header_done; 73 | 74 | main := FullRequest | SimpleRequest; 75 | 76 | }%% 77 | -------------------------------------------------------------------------------- /lib/unicorn/util.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | require 'fcntl' 5 | module Unicorn::Util # :nodoc: 6 | 7 | # :stopdoc: 8 | def self.is_log?(fp) 9 | append_flags = File::WRONLY | File::APPEND 10 | 11 | ! fp.closed? && 12 | fp.stat.file? && 13 | fp.sync && 14 | (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags 15 | rescue IOError, Errno::EBADF 16 | false 17 | end 18 | 19 | def self.chown_logs(uid, gid) 20 | ObjectSpace.each_object(File) do |fp| 21 | fp.chown(uid, gid) if is_log?(fp) 22 | end 23 | end 24 | # :startdoc: 25 | 26 | # This reopens ALL logfiles in the process that have been rotated 27 | # using logrotate(8) (without copytruncate) or similar tools. 28 | # A +File+ object is considered for reopening if it is: 29 | # 1) opened with the O_APPEND and O_WRONLY flags 30 | # 2) the current open file handle does not match its original open path 31 | # 3) unbuffered (as far as userspace buffering goes, not O_SYNC) 32 | # Returns the number of files reopened 33 | # 34 | # In Unicorn 3.5.x and earlier, files must be opened with an absolute 35 | # path to be considered a log file. 36 | def self.reopen_logs 37 | to_reopen = [] 38 | nr = 0 39 | ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp } 40 | 41 | to_reopen.each do |fp| 42 | orig_st = begin 43 | fp.stat 44 | rescue IOError, Errno::EBADF # race 45 | next 46 | end 47 | 48 | begin 49 | b = File.stat(fp.path) 50 | next if orig_st.ino == b.ino && orig_st.dev == b.dev 51 | rescue Errno::ENOENT 52 | end 53 | 54 | begin 55 | # stdin, stdout, stderr are special. The following dance should 56 | # guarantee there is no window where `fp' is unwritable in MRI 57 | # (or any correct Ruby implementation). 58 | # 59 | # Fwiw, GVL has zero bearing here. This is tricky because of 60 | # the unavoidable existence of stdio FILE * pointers for 61 | # std{in,out,err} in all programs which may use the standard C library 62 | if fp.fileno <= 2 63 | # We do not want to hit fclose(3)->dup(2) window for std{in,out,err} 64 | # MRI will use freopen(3) here internally on std{in,out,err} 65 | fp.reopen(fp.path, "a") 66 | else 67 | # We should not need this workaround, Ruby can be fixed: 68 | # https://bugs.ruby-lang.org/issues/9036 69 | # MRI will not call call fclose(3) or freopen(3) here 70 | # since there's no associated std{in,out,err} FILE * pointer 71 | # This should atomically use dup3(2) (or dup2(2)) syscall 72 | File.open(fp.path, "a") { |tmpfp| fp.reopen(tmpfp) } 73 | end 74 | 75 | fp.sync = true 76 | fp.flush # IO#sync=true may not implicitly flush 77 | new_st = fp.stat 78 | 79 | # this should only happen in the master: 80 | if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid 81 | fp.chown(orig_st.uid, orig_st.gid) 82 | end 83 | 84 | nr += 1 85 | rescue IOError, Errno::EBADF 86 | # not much we can do... 87 | end 88 | end 89 | nr 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unicorn is copyrighted free software by all contributors, see logs in 2 | revision control for names and email addresses of all of them. 3 | 4 | You can redistribute it and/or modify it under either the terms of the 5 | GNU General Public License (GPL) as published by the Free Software 6 | Foundation (FSF), either version 2 of the License, or (at your option) 7 | any later version. We currently prefer the GPLv3 or later for 8 | derivative works, but the GPLv2 is fine. 9 | 10 | The complete texts of the GPLv2 and GPLv3 are below: 11 | GPLv2 - https://www.gnu.org/licenses/gpl-2.0.txt 12 | GPLv3 - https://www.gnu.org/licenses/gpl-3.0.txt 13 | 14 | You may (against our _preference_) also use the Ruby 1.8 license terms 15 | which we inherited from the original Mongrel project when we forked it: 16 | 17 | === Ruby 1.8-specific terms (if you're not using the GPL) 18 | 19 | 1. You may make and give away verbatim copies of the source form of the 20 | software without restriction, provided that you duplicate all of the 21 | original copyright notices and associated disclaimers. 22 | 23 | 2. You may modify your copy of the software in any way, provided that 24 | you do at least ONE of the following: 25 | 26 | a) place your modifications in the Public Domain or otherwise make them 27 | Freely Available, such as by posting said modifications to Usenet or an 28 | equivalent medium, or by allowing the author to include your 29 | modifications in the software. 30 | 31 | b) use the modified software only within your corporation or 32 | organization. 33 | 34 | c) rename any non-standard executables so the names do not conflict with 35 | standard executables, which must also be provided. 36 | 37 | d) make other distribution arrangements with the author. 38 | 39 | 3. You may distribute the software in object code or executable 40 | form, provided that you do at least ONE of the following: 41 | 42 | a) distribute the executables and library files of the software, 43 | together with instructions (in the manual page or equivalent) on where 44 | to get the original distribution. 45 | 46 | b) accompany the distribution with the machine-readable source of the 47 | software. 48 | 49 | c) give non-standard executables non-standard names, with 50 | instructions on where to get the original software distribution. 51 | 52 | d) make other distribution arrangements with the author. 53 | 54 | 4. You may modify and include the part of the software into any other 55 | software (possibly commercial). But some files in the distribution 56 | are not written by the author, so that they are not under this terms. 57 | 58 | 5. The scripts and library files supplied as input to or produced as 59 | output from the software do not automatically fall under the 60 | copyright of the software, but belong to whomever generated them, 61 | and may be sold commercially, and may be aggregated with this 62 | software. 63 | 64 | 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR 65 | IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 66 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 67 | PURPOSE. 68 | -------------------------------------------------------------------------------- /Application_Timeouts: -------------------------------------------------------------------------------- 1 | = Application Timeouts 2 | 3 | This article focuses on _application_ setup for Rack applications, but 4 | can be expanded to all applications that connect to external resources 5 | and expect short response times. 6 | 7 | This article is not specific to unicorn, but exists to discourage 8 | the overuse of the built-in 9 | {timeout}[link:Unicorn/Configurator.html#method-i-timeout] directive 10 | in unicorn. 11 | 12 | == ALL External Resources Are Considered Unreliable 13 | 14 | Network reliability can _never_ be guaranteed. Network failures cannot 15 | be detected reliably by the client (Rack application) in a reasonable 16 | timeframe, not even on a LAN. 17 | 18 | Thus, application authors must configure timeouts when interacting with 19 | external resources. 20 | 21 | Most database adapters allow configurable timeouts. 22 | 23 | Net::HTTP and Net::SMTP in the Ruby standard library allow 24 | configurable timeouts. 25 | 26 | Even for things as fast as {memcached}[https://memcached.org/], 27 | {dalli}[https://rubygems.org/gems/dalli], 28 | {memcached}[https://rubygems.org/gems/memcached] and 29 | {memcache-client}[https://rubygems.org/gems/memcache-client] RubyGems all 30 | offer configurable timeouts. 31 | 32 | Consult the relevant documentation for the libraries you use on 33 | how to configure these timeouts. 34 | 35 | == Rolling Your Own Socket Code 36 | 37 | Use non-blocking I/O and IO.select with a timeout to wait on sockets. 38 | 39 | == Timeout module in the Ruby standard library 40 | 41 | Ruby offers a Timeout module in its standard library. It has several 42 | caveats and is not always reliable: 43 | 44 | * /Some/ Ruby C extensions are not interrupted/timed-out gracefully by 45 | this module (report these bugs to extension authors, please) but 46 | pure-Ruby components should be. 47 | 48 | * Long-running tasks may run inside `ensure' clauses after timeout 49 | fires, causing the timeout to be ineffective. 50 | 51 | The Timeout module is a second-to-last-resort solution, timeouts using 52 | IO.select (or similar) are more reliable. If you depend on libraries 53 | that do not offer timeouts when connecting to external resources, kindly 54 | ask those library authors to provide configurable timeouts. 55 | 56 | === A Note About Filesystems 57 | 58 | Most operations to regular files on POSIX filesystems are NOT 59 | interruptable. Thus, the "timeout" module in the Ruby standard library 60 | can not reliably timeout systems with massive amounts of iowait. 61 | 62 | If your app relies on the filesystem, ensure all the data your 63 | application works with is small enough to fit in the kernel page cache. 64 | Otherwise increase the amount of physical memory you have to match, or 65 | employ a fast, low-latency storage system (solid state). 66 | 67 | Volumes mounted over NFS (and thus a potentially unreliable network) 68 | must be mounted with timeouts and applications must be prepared to 69 | handle network/server failures. 70 | 71 | == The Last Line Of Defense 72 | 73 | The {timeout}[link:Unicorn/Configurator.html#method-i-timeout] mechanism 74 | in unicorn is an extreme solution that should be avoided whenever 75 | possible. It will help catch bugs in your application where and when 76 | your application forgets to use timeouts, but it is expensive as it 77 | kills and respawns a worker process. 78 | -------------------------------------------------------------------------------- /ext/unicorn_http/c_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Generic C functions and macros go here, there are no dependencies 3 | * on Unicorn internal structures or the Ruby C API in here. 4 | */ 5 | 6 | #ifndef UH_util_h 7 | #define UH_util_h 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #define MIN(a,b) (a < b ? a : b) 14 | #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) 15 | 16 | #if SIZEOF_OFF_T == SIZEOF_INT 17 | # define UH_OFF_T_MAX INT_MAX 18 | #elif SIZEOF_OFF_T == SIZEOF_LONG_LONG 19 | # define UH_OFF_T_MAX LLONG_MAX 20 | #else 21 | # error off_t size unknown for this platform! 22 | #endif /* SIZEOF_OFF_T check */ 23 | 24 | /* 25 | * ragel enforces fpc as a const, and merely casting can make picky 26 | * compilers unhappy, so we have this little helper do our dirty work 27 | */ 28 | static inline void *deconst(const void *in) 29 | { 30 | union { const void *in; void *out; } tmp; 31 | 32 | tmp.in = in; 33 | 34 | return tmp.out; 35 | } 36 | 37 | /* 38 | * Capitalizes all lower-case ASCII characters, locale-agnostic. 39 | * We don't .tr('-', '_') here since we need to ban /^content_length$/i 40 | * and /^transfer_encoding$/i to avoid confusion and smuggling attacks. 41 | */ 42 | static void upcase_char(char *c) 43 | { 44 | if (*c >= 'a' && *c <= 'z') 45 | *c &= ~0x20; 46 | } 47 | 48 | /* Downcases a single ASCII character. Locale-agnostic. */ 49 | static void downcase_char(char *c) 50 | { 51 | if (*c >= 'A' && *c <= 'Z') 52 | *c |= 0x20; 53 | } 54 | 55 | static int hexchar2int(int xdigit) 56 | { 57 | if (xdigit >= 'A' && xdigit <= 'F') 58 | return xdigit - 'A' + 10; 59 | if (xdigit >= 'a' && xdigit <= 'f') 60 | return xdigit - 'a' + 10; 61 | 62 | /* Ragel already does runtime range checking for us in Unicorn: */ 63 | assert(xdigit >= '0' && xdigit <= '9' && "invalid digit character"); 64 | 65 | return xdigit - '0'; 66 | } 67 | 68 | /* 69 | * multiplies +i+ by +base+ and increments the result by the parsed 70 | * integer value of +xdigit+. +xdigit+ is a character byte 71 | * representing a number the range of 0..(base-1) 72 | * returns the new value of +i+ on success 73 | * returns -1 on errors (including overflow) 74 | */ 75 | static off_t step_incr(off_t i, int xdigit, const int base) 76 | { 77 | static const off_t max = UH_OFF_T_MAX; 78 | const off_t next_max = (max - (max % base)) / base; 79 | off_t offset = hexchar2int(xdigit); 80 | 81 | if (offset > (base - 1)) 82 | return -1; 83 | if (i > next_max) 84 | return -1; 85 | i *= base; 86 | 87 | if ((offset > (base - 1)) || ((max - i) < offset)) 88 | return -1; 89 | 90 | return i + offset; 91 | } 92 | 93 | /* 94 | * parses a non-negative length according to base-10 and 95 | * returns it as an off_t value. Returns -1 on errors 96 | * (including overflow). 97 | */ 98 | static off_t parse_length(const char *value, size_t length) 99 | { 100 | off_t rv; 101 | 102 | for (rv = 0; length-- && rv >= 0; ++value) { 103 | if (*value >= '0' && *value <= '9') 104 | rv = step_incr(rv, *value, 10); 105 | else 106 | return -1; 107 | } 108 | 109 | return rv; 110 | } 111 | 112 | #define CONST_MEM_EQ(const_p, buf, len) \ 113 | ((sizeof(const_p) - 1) == len && !memcmp(const_p, buf, sizeof(const_p) - 1)) 114 | 115 | #endif /* UH_util_h */ 116 | -------------------------------------------------------------------------------- /lib/unicorn/oob_gc.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | # Strongly consider https://github.com/tmm1/gctools if using Ruby 2.1+ 5 | # It is built on new APIs in Ruby 2.1, so it is more intelligent than 6 | # this historical implementation. 7 | # 8 | # Users on Ruby 2.0 (not 2.1+) may also want to check out 9 | # lib/middleware/unicorn_oobgc.rb from the Discourse project 10 | # (https://github.com/discourse/discourse) 11 | # 12 | # The following information is only for historical versions of Ruby. 13 | # 14 | # Runs GC after requests, after closing the client socket and 15 | # before attempting to accept more connections. 16 | # 17 | # This shouldn't hurt overall performance as long as the server cluster 18 | # is at <50% CPU capacity, and improves the performance of most memory 19 | # intensive requests. This serves to improve _client-visible_ 20 | # performance (possibly at the cost of overall performance). 21 | # 22 | # Increasing the number of +worker_processes+ may be necessary to 23 | # improve average client response times because some of your workers 24 | # will be busy doing GC and unable to service clients. Think of 25 | # using more workers with this module as a poor man's concurrent GC. 26 | # 27 | # We'll call GC after each request is been written out to the socket, so 28 | # the client never sees the extra GC hit it. 29 | # 30 | # This middleware is _only_ effective for applications that use a lot 31 | # of memory, and will hurt simpler apps/endpoints that can process 32 | # multiple requests before incurring GC. 33 | # 34 | # This middleware is only designed to work with unicorn, as it harms 35 | # performance with keepalive-enabled servers. 36 | # 37 | # Example (in config.ru): 38 | # 39 | # require 'unicorn/oob_gc' 40 | # 41 | # # GC ever two requests that hit /expensive/foo or /more_expensive/foo 42 | # # in your app. By default, this will GC once every 5 requests 43 | # # for all endpoints in your app 44 | # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)} 45 | # 46 | # Feedback from users of early implementations of this module: 47 | # * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/ 48 | # * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/ 49 | 50 | module Unicorn::OobGC 51 | 52 | # this pretends to be Rack middleware because it used to be 53 | # But we need to hook into unicorn internals so we need to close 54 | # the socket before clearing the request env. 55 | # 56 | # +interval+ is the number of requests matching the +path+ regular 57 | # expression before invoking GC. 58 | def self.new(app, interval = 5, path = %r{\A/}) 59 | @@nr = interval 60 | self.const_set :OOBGC_PATH, path 61 | self.const_set :OOBGC_INTERVAL, interval 62 | ObjectSpace.each_object(Unicorn::HttpServer) do |s| 63 | s.extend(self) 64 | end 65 | app # pretend to be Rack middleware since it was in the past 66 | end 67 | 68 | #:stopdoc: 69 | def process_client(*args) 70 | super(*args) # Unicorn::HttpServer#process_client 71 | env = instance_variable_get(:@request).env 72 | if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0) 73 | @@nr = OOBGC_INTERVAL 74 | env.clear 75 | disabled = GC.enable 76 | GC.start 77 | GC.disable if disabled 78 | end 79 | end 80 | 81 | # :startdoc: 82 | end 83 | -------------------------------------------------------------------------------- /lib/unicorn/http_response.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | # :enddoc: 4 | # Writes a Rack response to your client using the HTTP/1.1 specification. 5 | # You use it by simply doing: 6 | # 7 | # status, headers, body = rack_app.call(env) 8 | # http_response_write(socket, status, headers, body) 9 | # 10 | # Most header correctness (including Content-Length and Content-Type) 11 | # is the job of Rack, with the exception of the "Date" and "Status" header. 12 | module Unicorn::HttpResponse 13 | 14 | STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ? 15 | Rack::Utils::HTTP_STATUS_CODES : {} 16 | STATUS_WITH_NO_ENTITY_BODY = defined?( 17 | Rack::Utils::STATUS_WITH_NO_ENTITY_BODY) ? 18 | Rack::Utils::STATUS_WITH_NO_ENTITY_BODY : begin 19 | warn 'Rack::Utils::STATUS_WITH_NO_ENTITY_BODY missing' 20 | {} 21 | end 22 | 23 | # internal API, code will always be common-enough-for-even-old-Rack 24 | def err_response(code, response_start_sent) 25 | "#{response_start_sent ? '' : 'HTTP/1.1 '}" \ 26 | "#{code} #{STATUS_CODES[code]}\r\n\r\n" 27 | end 28 | 29 | def append_header(buf, key, value) 30 | case value 31 | when Array # Rack 3 32 | value.each { |v| buf << "#{key}: #{v}\r\n" } 33 | when /\n/ # Rack 2 34 | # avoiding blank, key-only cookies with /\n+/ 35 | value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" } 36 | else 37 | buf << "#{key}: #{value}\r\n" 38 | end 39 | end 40 | 41 | # writes the rack_response to socket as an HTTP response 42 | def http_response_write(socket, status, headers, body, 43 | req = Unicorn::HttpRequest.new) 44 | hijack = nil 45 | do_chunk = false 46 | if headers 47 | code = status.to_i 48 | msg = STATUS_CODES[code] 49 | start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze 50 | term = STATUS_WITH_NO_ENTITY_BODY.include?(code) || false 51 | buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \ 52 | "Date: #{httpdate}\r\n" \ 53 | "Connection: close\r\n" 54 | headers.each do |key, value| 55 | case key 56 | when %r{\A(?:Date|Connection)\z}i 57 | next 58 | when %r{\AContent-Length\z}i 59 | append_header(buf, key, value) 60 | term = true 61 | when %r{\ATransfer-Encoding\z}i 62 | append_header(buf, key, value) 63 | term = true if /\bchunked\b/i === value # value may be Array :x 64 | when "rack.hijack" 65 | # This should only be hit under Rack >= 1.5, as this was an illegal 66 | # key in Rack < 1.5 67 | hijack = value 68 | else 69 | append_header(buf, key, value) 70 | end 71 | end 72 | if !hijack && !term && req.chunkable_response? 73 | do_chunk = true 74 | buf << "Transfer-Encoding: chunked\r\n".freeze 75 | end 76 | socket.write(buf << "\r\n".freeze) 77 | buf.clear # remove this line if C Ruby gets escape analysis 78 | end 79 | 80 | if hijack 81 | req.hijacked! 82 | hijack.call(socket) 83 | elsif do_chunk 84 | begin 85 | body.each do |b| 86 | socket.write("#{b.bytesize.to_s(16)}\r\n", b, "\r\n".freeze) 87 | end 88 | ensure 89 | socket.write("0\r\n\r\n".freeze) 90 | end 91 | else 92 | body.each { |chunk| socket.write(chunk) } 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /ext/unicorn_http/global_variables.h: -------------------------------------------------------------------------------- 1 | #ifndef global_variables_h 2 | #define global_variables_h 3 | static VALUE eHttpParserError; 4 | static VALUE e413; 5 | static VALUE e414; 6 | 7 | static VALUE g_rack_url_scheme; 8 | static VALUE g_request_method; 9 | static VALUE g_request_uri; 10 | static VALUE g_fragment; 11 | static VALUE g_query_string; 12 | static VALUE g_http_version; 13 | static VALUE g_request_path; 14 | static VALUE g_path_info; 15 | static VALUE g_server_name; 16 | static VALUE g_server_port; 17 | static VALUE g_server_protocol; 18 | static VALUE g_http_host; 19 | static VALUE g_http_x_forwarded_proto; 20 | static VALUE g_http_x_forwarded_ssl; 21 | static VALUE g_http_transfer_encoding; 22 | static VALUE g_content_length; 23 | static VALUE g_http_trailer; 24 | static VALUE g_http_connection; 25 | static VALUE g_port_80; 26 | static VALUE g_port_443; 27 | static VALUE g_localhost; 28 | static VALUE g_http; 29 | static VALUE g_https; 30 | static VALUE g_http_09; 31 | static VALUE g_http_10; 32 | static VALUE g_http_11; 33 | 34 | /** Defines common length and error messages for input length validation. */ 35 | #define DEF_MAX_LENGTH(N, length) \ 36 | static const size_t MAX_##N##_LENGTH = length; \ 37 | static const char * const MAX_##N##_LENGTH_ERR = \ 38 | "HTTP element " # N " is longer than the " # length " allowed length." 39 | 40 | NORETURN(static void parser_raise(VALUE klass, const char *)); 41 | 42 | /** 43 | * Validates the max length of given input and throws an HttpParserError 44 | * exception if over. 45 | */ 46 | #define VALIDATE_MAX_LENGTH(len, N) do { \ 47 | if (len > MAX_##N##_LENGTH) \ 48 | parser_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \ 49 | } while (0) 50 | 51 | #define VALIDATE_MAX_URI_LENGTH(len, N) do { \ 52 | if (len > MAX_##N##_LENGTH) \ 53 | parser_raise(e414, MAX_##N##_LENGTH_ERR); \ 54 | } while (0) 55 | 56 | /** Defines global strings in the init method. */ 57 | #define DEF_GLOBAL(N, val) do { \ 58 | g_##N = str_new_dd_freeze(val, (long)sizeof(val) - 1); \ 59 | rb_gc_register_mark_object(g_##N); \ 60 | } while (0) 61 | 62 | /* Defines the maximum allowed lengths for various input elements.*/ 63 | DEF_MAX_LENGTH(FIELD_NAME, 256); 64 | DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024); 65 | DEF_MAX_LENGTH(REQUEST_URI, 1024 * 15); 66 | DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */ 67 | DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */ 68 | DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); 69 | 70 | static void init_globals(void) 71 | { 72 | DEF_GLOBAL(rack_url_scheme, "rack.url_scheme"); 73 | DEF_GLOBAL(request_method, "REQUEST_METHOD"); 74 | DEF_GLOBAL(request_uri, "REQUEST_URI"); 75 | DEF_GLOBAL(fragment, "FRAGMENT"); 76 | DEF_GLOBAL(query_string, "QUERY_STRING"); 77 | DEF_GLOBAL(http_version, "HTTP_VERSION"); 78 | DEF_GLOBAL(request_path, "REQUEST_PATH"); 79 | DEF_GLOBAL(path_info, "PATH_INFO"); 80 | DEF_GLOBAL(server_name, "SERVER_NAME"); 81 | DEF_GLOBAL(server_port, "SERVER_PORT"); 82 | DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL"); 83 | DEF_GLOBAL(http_x_forwarded_proto, "HTTP_X_FORWARDED_PROTO"); 84 | DEF_GLOBAL(http_x_forwarded_ssl, "HTTP_X_FORWARDED_SSL"); 85 | DEF_GLOBAL(port_80, "80"); 86 | DEF_GLOBAL(port_443, "443"); 87 | DEF_GLOBAL(localhost, "localhost"); 88 | DEF_GLOBAL(http, "http"); 89 | DEF_GLOBAL(https, "https"); 90 | DEF_GLOBAL(http_11, "HTTP/1.1"); 91 | DEF_GLOBAL(http_10, "HTTP/1.0"); 92 | DEF_GLOBAL(http_09, "HTTP/0.9"); 93 | } 94 | 95 | #undef DEF_GLOBAL 96 | 97 | #endif /* global_variables_h */ 98 | -------------------------------------------------------------------------------- /t/active-unix-socket.t: -------------------------------------------------------------------------------- 1 | #!perl -w 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | 5 | use v5.14; BEGIN { require './t/lib.perl' }; 6 | use IO::Socket::UNIX; 7 | use autodie; 8 | no autodie 'kill'; 9 | my %to_kill; 10 | END { kill('TERM', values(%to_kill)) if keys %to_kill } 11 | my $u1 = "$tmpdir/u1.sock"; 12 | my $u2 = "$tmpdir/u2.sock"; 13 | { 14 | write_file '>', "$tmpdir/u1.conf.rb", <', "$tmpdir/u2.conf.rb", <', "$tmpdir/u3.conf.rb", <join; 40 | is($?, 0, 'daemonized 1st process'); 41 | chomp($to_kill{u1} = slurp("$tmpdir/u.pid")); 42 | like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file'); 43 | 44 | chomp(my $worker_pid = readline(unix_start($u1, 'GET /pid'))); 45 | like($worker_pid, qr/\A\d+\z/s, 'captured worker pid'); 46 | ok(kill(0, $worker_pid), 'worker is kill-able'); 47 | 48 | 49 | # 2nd process conflicts on PID 50 | unicorn('-c', "$tmpdir/u2.conf.rb", @uarg)->join; 51 | isnt($?, 0, 'conflicting PID file fails to start'); 52 | 53 | chomp(my $pidf = slurp("$tmpdir/u.pid")); 54 | is($pidf, $to_kill{u1}, 'pid file contents unchanged after start failure'); 55 | 56 | chomp(my $pid2 = readline(unix_start($u1, 'GET /pid'))); 57 | is($worker_pid, $pid2, 'worker PID unchanged'); 58 | 59 | 60 | # 3rd process conflicts on socket 61 | unicorn('-c', "$tmpdir/u3.conf.rb", @uarg)->join; 62 | isnt($?, 0, 'conflicting UNIX socket fails to start'); 63 | 64 | chomp($pid2 = readline(unix_start($u1, 'GET /pid'))); 65 | is($worker_pid, $pid2, 'worker PID still unchanged'); 66 | 67 | chomp($pidf = slurp("$tmpdir/u.pid")); 68 | is($pidf, $to_kill{u1}, 'pid file contents unchanged after 2nd start failure'); 69 | 70 | { # teardown initial process via SIGKILL 71 | ok(kill('KILL', delete $to_kill{u1}), 'SIGKILL initial daemon'); 72 | close $p1; 73 | vec(my $rvec = '', fileno($p0), 1) = 1; 74 | is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP'); 75 | is(my $undef = <$p0>, undef, 'process closed pipe writer at exit'); 76 | ok(-f "$tmpdir/u.pid", 'pid file stayed after SIGKILL'); 77 | ok(-S $u1, 'socket stayed after SIGKILL'); 78 | is(IO::Socket::UNIX->new(Peer => $u1, Type => SOCK_STREAM), undef, 79 | 'fail to connect to u1'); 80 | for (1..50) { # wait for init process to reap worker 81 | kill(0, $worker_pid) or last; 82 | sleep 0.011; 83 | } 84 | ok(!kill(0, $worker_pid), 'worker gone after parent dies'); 85 | } 86 | 87 | # restart the first instance 88 | { 89 | pipe($p0, $p1); 90 | fcntl($p1, POSIX::F_SETFD, 0); 91 | unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join; 92 | is($?, 0, 'daemonized 1st process'); 93 | chomp($to_kill{u1} = slurp("$tmpdir/u.pid")); 94 | like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file'); 95 | 96 | chomp($pid2 = readline(unix_start($u1, 'GET /pid'))); 97 | like($pid2, qr/\A\d+\z/, 'worker running'); 98 | 99 | ok(kill('TERM', delete $to_kill{u1}), 'SIGTERM restarted daemon'); 100 | close $p1; 101 | vec(my $rvec = '', fileno($p0), 1) = 1; 102 | is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP'); 103 | is(my $undef = <$p0>, undef, 'process closed pipe writer at exit'); 104 | ok(!-f "$tmpdir/u.pid", 'pid file gone after SIGTERM'); 105 | ok(-S $u1, 'socket stays after SIGTERM'); 106 | } 107 | 108 | check_stderr; 109 | undef $tmpdir; 110 | done_testing; 111 | -------------------------------------------------------------------------------- /ext/unicorn_http/common_field_optimization.h: -------------------------------------------------------------------------------- 1 | #ifndef common_field_optimization 2 | #define common_field_optimization 3 | #include "ruby.h" 4 | #include "c_util.h" 5 | 6 | struct common_field { 7 | const signed long len; 8 | const char *name; 9 | VALUE value; 10 | }; 11 | 12 | /* 13 | * A list of common HTTP headers we expect to receive. 14 | * This allows us to avoid repeatedly creating identical string 15 | * objects to be used with rb_hash_aset(). 16 | */ 17 | static struct common_field common_http_fields[] = { 18 | # define f(N) { (sizeof(N) - 1), N, Qnil } 19 | f("ACCEPT"), 20 | f("ACCEPT_CHARSET"), 21 | f("ACCEPT_ENCODING"), 22 | f("ACCEPT_LANGUAGE"), 23 | f("ALLOW"), 24 | f("AUTHORIZATION"), 25 | f("CACHE_CONTROL"), 26 | f("CONNECTION"), 27 | f("CONTENT_ENCODING"), 28 | f("CONTENT_LENGTH"), 29 | f("CONTENT_TYPE"), 30 | f("COOKIE"), 31 | f("DATE"), 32 | f("EXPECT"), 33 | f("FROM"), 34 | f("HOST"), 35 | f("IF_MATCH"), 36 | f("IF_MODIFIED_SINCE"), 37 | f("IF_NONE_MATCH"), 38 | f("IF_RANGE"), 39 | f("IF_UNMODIFIED_SINCE"), 40 | f("KEEP_ALIVE"), /* Firefox sends this */ 41 | f("MAX_FORWARDS"), 42 | f("PRAGMA"), 43 | f("PROXY_AUTHORIZATION"), 44 | f("RANGE"), 45 | f("REFERER"), 46 | f("TE"), 47 | f("TRAILER"), 48 | f("TRANSFER_ENCODING"), 49 | f("UPGRADE"), 50 | f("USER_AGENT"), 51 | f("VIA"), 52 | f("X_FORWARDED_FOR"), /* common for proxies */ 53 | f("X_FORWARDED_PROTO"), /* common for proxies */ 54 | f("X_REAL_IP"), /* common for proxies */ 55 | f("WARNING") 56 | # undef f 57 | }; 58 | 59 | #define HTTP_PREFIX "HTTP_" 60 | #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1) 61 | static ID id_uminus; 62 | 63 | /* this dedupes under Ruby 2.5+ (December 2017) */ 64 | static VALUE str_dd_freeze(VALUE str) 65 | { 66 | if (STR_UMINUS_DEDUPE) 67 | return rb_funcall(str, id_uminus, 0); 68 | 69 | /* freeze,since it speeds up older MRI slightly */ 70 | OBJ_FREEZE(str); 71 | return str; 72 | } 73 | 74 | static VALUE str_new_dd_freeze(const char *ptr, long len) 75 | { 76 | return str_dd_freeze(rb_str_new(ptr, len)); 77 | } 78 | 79 | /* this function is not performance-critical, called only at load time */ 80 | static void init_common_fields(void) 81 | { 82 | int i; 83 | struct common_field *cf = common_http_fields; 84 | char tmp[64]; 85 | 86 | memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN); 87 | 88 | for(i = ARRAY_SIZE(common_http_fields); --i >= 0; cf++) { 89 | /* Rack doesn't like certain headers prefixed with "HTTP_" */ 90 | if (!strcmp("CONTENT_LENGTH", cf->name) || 91 | !strcmp("CONTENT_TYPE", cf->name)) { 92 | cf->value = str_new_dd_freeze(cf->name, cf->len); 93 | } else { 94 | memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1); 95 | cf->value = str_new_dd_freeze(tmp, HTTP_PREFIX_LEN + cf->len); 96 | } 97 | rb_gc_register_mark_object(cf->value); 98 | } 99 | } 100 | 101 | /* this function is called for every header set */ 102 | static VALUE find_common_field(const char *field, size_t flen) 103 | { 104 | int i; 105 | struct common_field *cf = common_http_fields; 106 | 107 | for(i = ARRAY_SIZE(common_http_fields); --i >= 0; cf++) { 108 | if (cf->len == (long)flen && !memcmp(cf->name, field, flen)) 109 | return cf->value; 110 | } 111 | return Qnil; 112 | } 113 | 114 | /* 115 | * We got a strange header that we don't have a memoized value for. 116 | * Fallback to creating a new string to use as a hash key. 117 | */ 118 | static VALUE uncommon_field(const char *field, size_t flen) 119 | { 120 | VALUE f = rb_str_new(NULL, HTTP_PREFIX_LEN + flen); 121 | memcpy(RSTRING_PTR(f), HTTP_PREFIX, HTTP_PREFIX_LEN); 122 | memcpy(RSTRING_PTR(f) + HTTP_PREFIX_LEN, field, flen); 123 | assert(*(RSTRING_PTR(f) + RSTRING_LEN(f)) == '\0' && 124 | "string didn't end with \\0"); /* paranoia */ 125 | return HASH_ASET_DEDUPE ? f : str_dd_freeze(f); 126 | } 127 | 128 | #endif /* common_field_optimization_h */ 129 | -------------------------------------------------------------------------------- /KNOWN_ISSUES: -------------------------------------------------------------------------------- 1 | = Known Issues 2 | 3 | Occasionally odd {issues}[link:ISSUES.html] arise without a transparent or 4 | acceptable solution. Those issues are documented here. 5 | 6 | * Some libraries/applications may install signal handlers which conflict 7 | with signal handlers unicorn uses. Leaving "preload_app false" 8 | (the default) will allow unicorn to always override existing signal 9 | handlers. 10 | 11 | * Issues with FreeBSD jails can be worked around as documented by Tatsuya Ono: 12 | https://yhbt.net/unicorn-public/CAHBuKRj09FdxAgzsefJWotexw-7JYZGJMtgUp_dhjPz9VbKD6Q@mail.gmail.com/ 13 | 14 | * PRNGs (pseudo-random number generators) loaded before forking 15 | (e.g. "preload_app true") may need to have their internal state 16 | reset in the after_fork hook. Starting with unicorn 3.6.1, we 17 | have builtin workarounds for Kernel#rand and OpenSSL::Random users, 18 | but applications may use other PRNGs. 19 | 20 | * For notes on sandboxing tools such as Bundler or Isolate, 21 | see the {Sandbox}[link:Sandbox.html] page. 22 | 23 | * nginx with "sendfile on" under FreeBSD 8 is broken when 24 | uploads are buffered to disk. Disabling sendfile is required to 25 | work around this bug which should be fixed in newer versions of FreeBSD. 26 | 27 | * When using "preload_app true", with apps using background threads 28 | need to restart them in the after_fork hook because threads are never 29 | shared with child processes. Additionally, any synchronization 30 | primitives (Mutexes, Monitors, ConditionVariables) should be 31 | reinitialized in case they are held during fork time to avoid 32 | deadlocks. The core Ruby Logger class needlessly uses a MonitorMutex 33 | which can be disabled with a {monkey patch}[link:examples/logger_mp_safe.rb] 34 | 35 | == Known Issues (Old) 36 | 37 | * Under some versions of Ruby 1.8, it is necessary to call +srand+ in an 38 | after_fork hook to get correct random number generation. We have a builtin 39 | workaround for this starting with unicorn 3.6.1 40 | 41 | See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450 42 | 43 | * On Ruby 1.8 prior to Ruby 1.8.7-p248, *BSD platforms have a broken 44 | stdio that causes failure for file uploads larger than 112K. Upgrade 45 | your version of Ruby or continue using unicorn 1.x/3.4.x. 46 | 47 | * Under Ruby 1.9.1, methods like Array#shuffle and Array#sample will 48 | segfault if called after forking. Upgrade to Ruby 1.9.2 or call 49 | "Kernel.rand" in your after_fork hook to reinitialize the random 50 | number generator. 51 | 52 | See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/28655 53 | 54 | * Rails 2.3.2 bundles its own version of Rack. This may cause subtle 55 | bugs when simultaneously loaded with the system-wide Rack Rubygem 56 | which unicorn depends on. Upgrading to Rails 2.3.4 (or later) is 57 | strongly recommended for all Rails 2.3.x users for this (and security 58 | reasons). Rails 2.2.x series (or before) did not bundle Rack and are 59 | should be unnaffected. If there is any reason which forces your 60 | application to use Rails 2.3.2 and you have no other choice, then 61 | you may edit your unicorn gemspec and remove the Rack dependency. 62 | 63 | ref: https://yhbt.net/unicorn-public/20091014221552.GA30624@dcvr.yhbt.net/ 64 | Note: the workaround described in the article above only made 65 | the issue more subtle and we didn't notice them immediately. 66 | 67 | * WONTFIX: code reloading and restarts with Sinatra 0.3.x (and likely older 68 | versions) apps is broken. The workaround is to force production 69 | mode to disable code reloading as well as disabling "run" in your 70 | Sinatra application: 71 | set :env, :production 72 | set :run, false 73 | Since this is no longer an issue with Sinatra 0.9.x apps, this will not be 74 | fixed on our end. Since unicorn is itself the application launcher, the 75 | at_exit handler used in old Sinatra always caused Mongrel to be launched 76 | whenever a unicorn worker was about to exit. 77 | 78 | Also remember we're capable of replacing the running binary without dropping 79 | any connections regardless of framework :) 80 | -------------------------------------------------------------------------------- /ext/unicorn_http/epollexclusive.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is only intended for use inside a unicorn worker, nowhere else. 3 | * EPOLLEXCLUSIVE somewhat mitigates the thundering herd problem for 4 | * mostly idle processes since we can't use blocking accept4. 5 | * This is NOT intended for use with multi-threaded servers, nor 6 | * single-threaded multi-client ("C10K") servers or anything advanced 7 | * like that. This use of epoll is only appropriate for a primitive, 8 | * single-client, single-threaded servers like unicorn that need to 9 | * support SIGKILL timeouts and parent death detection. 10 | */ 11 | #if defined(HAVE_EPOLL_CREATE1) 12 | # include 13 | # include 14 | # include 15 | # include 16 | #endif /* __linux__ */ 17 | 18 | #if defined(EPOLLEXCLUSIVE) && defined(HAVE_EPOLL_CREATE1) 19 | # define USE_EPOLL (1) 20 | #else 21 | # define USE_EPOLL (0) 22 | #endif 23 | 24 | #if USE_EPOLL 25 | #if defined(HAVE_RB_IO_DESCRIPTOR) /* Ruby 3.1+ */ 26 | # define my_fileno(io) rb_io_descriptor(io) 27 | #else /* Ruby <3.1 */ 28 | static int my_fileno(VALUE io) 29 | { 30 | rb_io_t *fptr; 31 | GetOpenFile(io, fptr); 32 | rb_io_check_closed(fptr); 33 | return fptr->fd; 34 | } 35 | #endif /* Ruby <3.1 */ 36 | 37 | /* 38 | * :nodoc: 39 | * returns IO object if EPOLLEXCLUSIVE works and arms readers 40 | */ 41 | static VALUE prep_readers(VALUE cls, VALUE readers) 42 | { 43 | long i; 44 | int epfd = epoll_create1(EPOLL_CLOEXEC); 45 | VALUE epio; 46 | 47 | if (epfd < 0) rb_sys_fail("epoll_create1"); 48 | 49 | epio = rb_funcall(cls, rb_intern("for_fd"), 1, INT2NUM(epfd)); 50 | 51 | Check_Type(readers, T_ARRAY); 52 | for (i = 0; i < RARRAY_LEN(readers); i++) { 53 | int rc, fd; 54 | struct epoll_event e; 55 | VALUE io = rb_ary_entry(readers, i); 56 | 57 | e.data.u64 = i; /* the reason readers shouldn't change */ 58 | 59 | /* 60 | * I wanted to use EPOLLET here, but maintaining our own 61 | * equivalent of ep->rdllist in Ruby-space doesn't fit 62 | * our design at all (and the kernel already has it's own 63 | * code path for doing it). So let the kernel spend 64 | * cycles on maintaining level-triggering. 65 | */ 66 | e.events = EPOLLEXCLUSIVE | EPOLLIN; 67 | fd = my_fileno(rb_io_get_io(io)); 68 | rc = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &e); 69 | if (rc < 0) rb_sys_fail("epoll_ctl"); 70 | } 71 | return epio; 72 | } 73 | #endif /* USE_EPOLL */ 74 | 75 | #if USE_EPOLL 76 | struct ep_wait { 77 | struct epoll_event event; 78 | int epfd; 79 | int timeout_msec; 80 | }; 81 | 82 | static void *do_wait(void *ptr) /* runs w/o GVL */ 83 | { 84 | struct ep_wait *epw = ptr; 85 | /* 86 | * Linux delivers epoll events in the order received, and using 87 | * maxevents=1 ensures we pluck one item off ep->rdllist 88 | * at-a-time (c.f. fs/eventpoll.c in linux.git, it's quite 89 | * easy-to-understand for anybody familiar with Ruby C). 90 | */ 91 | return (void *)(long)epoll_wait(epw->epfd, &epw->event, 1, 92 | epw->timeout_msec); 93 | } 94 | 95 | /* :nodoc: */ 96 | /* readers must not change between prepare_readers and get_readers */ 97 | static VALUE 98 | get_readers(VALUE epio, VALUE ready, VALUE readers, VALUE timeout_msec) 99 | { 100 | struct ep_wait epw; 101 | long n; 102 | 103 | Check_Type(ready, T_ARRAY); 104 | Check_Type(readers, T_ARRAY); 105 | 106 | epw.epfd = my_fileno(epio); 107 | epw.timeout_msec = NUM2INT(timeout_msec); 108 | n = (long)rb_thread_call_without_gvl(do_wait, &epw, RUBY_UBF_IO, NULL); 109 | if (n < 0) { 110 | if (errno != EINTR) rb_sys_fail("epoll_wait"); 111 | } else if (n > 0) { /* maxevents is hardcoded to 1 */ 112 | VALUE obj = rb_ary_entry(readers, epw.event.data.u64); 113 | 114 | if (RTEST(obj)) 115 | rb_ary_push(ready, obj); 116 | } /* n == 0 : timeout */ 117 | return Qfalse; 118 | } 119 | #endif /* USE_EPOLL */ 120 | 121 | static void init_epollexclusive(VALUE mUnicorn) 122 | { 123 | #if USE_EPOLL 124 | VALUE cWaiter = rb_define_class_under(mUnicorn, "Waiter", rb_cIO); 125 | rb_define_singleton_method(cWaiter, "prep_readers", prep_readers, 1); 126 | rb_define_method(cWaiter, "get_readers", get_readers, 3); 127 | #endif 128 | } 129 | -------------------------------------------------------------------------------- /Sandbox: -------------------------------------------------------------------------------- 1 | = Tips for using unicorn with Sandbox installation tools 2 | 3 | Since unicorn includes executables and is usually used to start a Ruby 4 | process, there are certain caveats to using it with tools that sandbox 5 | RubyGems installations such as 6 | {Bundler}[https://bundler.io/] or 7 | {Isolate}[https://github.com/jbarnette/isolate]. 8 | 9 | == General deployment 10 | 11 | If you're sandboxing your unicorn installation and using Capistrano (or 12 | similar), it's required that you sandbox your RubyGems in a per-application 13 | shared directory that can be used between different revisions. 14 | 15 | unicorn will stash its original command-line at startup for the USR2 16 | upgrades, and cleaning up old revisions will cause revision-specific 17 | installations of unicorn to go missing and upgrades to fail. If you 18 | find yourself in this situation and can't afford downtime, you can 19 | override the existing unicorn executable path in the config file like 20 | this: 21 | 22 | Unicorn::HttpServer::START_CTX[0] = "/some/path/to/bin/unicorn" 23 | 24 | Then use HUP to reload, and then continue with the USR2+QUIT upgrade 25 | sequence. 26 | 27 | Environment variable pollution when exec-ing a new process (with USR2) 28 | is the primary issue with sandboxing tools such as Bundler and Isolate. 29 | 30 | == Bundler 31 | 32 | === Running 33 | 34 | If you're bundling unicorn, use "bundle exec unicorn" (or "bundle exec 35 | unicorn_rails") to start unicorn with the correct environment variables 36 | 37 | ref: https://yhbt.net/unicorn-public/9ECF07C4-5216-47BE-961D-AFC0F0C82060@internetfamo.us/ 38 | 39 | Otherwise (if you choose to not sandbox your unicorn installation), we 40 | expect the tips for Isolate (below) apply, too. 41 | 42 | === RUBYOPT pollution from SIGUSR2 upgrades 43 | 44 | This is no longer be an issue as of bundler 0.9.17 45 | 46 | ref: 47 | https://yhbt.net/unicorn-public/8FC34B23-5994-41CC-B5AF-7198EF06909E@tramchase.com/ 48 | 49 | === BUNDLE_GEMFILE for Capistrano users 50 | 51 | You may need to set or reset the BUNDLE_GEMFILE environment variable in 52 | the before_exec hook: 53 | 54 | before_exec do |server| 55 | ENV["BUNDLE_GEMFILE"] = "/path/to/app/current/Gemfile" 56 | end 57 | 58 | === Other ENV pollution issues 59 | 60 | If you're using an older Bundler version (0.9.x), you may need to set or 61 | reset GEM_HOME, GEM_PATH and PATH environment variables in the 62 | before_exec hook as illustrated by https://gist.github.com/534668 63 | 64 | === Ruby 2.0.0 close-on-exec and SIGUSR2 incompatibility 65 | 66 | Ruby 2.0.0 enforces FD_CLOEXEC on file descriptors by default. unicorn 67 | has been prepared for this behavior since unicorn 4.1.0, and bundler 68 | needs the "--keep-file-descriptors" option for "bundle exec": 69 | https://bundler.io/man/bundle-exec.1.html 70 | 71 | == Isolate 72 | 73 | === Running 74 | 75 | Installing "unicorn" as a system-wide Rubygem and using the 76 | isolate gem may cause issues if you're using any of the bundled 77 | application-level libraries in unicorn/app/* (for compatibility 78 | with CGI-based applications, Rails <= 2.2.2, or ExecCgi). 79 | For now workarounds include doing one of the following: 80 | 81 | 1. Isolating unicorn, setting GEM_HOME to your Isolate path, 82 | and running the isolated version of unicorn. You *must* set 83 | GEM_HOME before running your isolated unicorn install in this way. 84 | 85 | 2. Installing the same version of unicorn as a system-wide Rubygem 86 | *and* isolating unicorn as well. 87 | 88 | 3. Explicitly setting RUBYLIB or $LOAD_PATH to include any gem path 89 | where the unicorn gem is installed 90 | (e.g. /usr/lib/ruby/gems/3.0.0/gems/unicorn-VERSION/lib) 91 | 92 | === RUBYOPT pollution from SIGUSR2 upgrades 93 | 94 | If you are using Isolate, using Isolate 2.x is strongly recommended as 95 | environment modifications are idempotent. 96 | 97 | If you are stuck with 1.x versions of Isolate, it is recommended that 98 | you disable it with the before_exec hook prevent the PATH and 99 | RUBYOPT environment variable modifications from propagating between 100 | upgrades in your Unicorn config file: 101 | 102 | before_exec do |server| 103 | Isolate.disable 104 | end 105 | -------------------------------------------------------------------------------- /bin/unicorn: -------------------------------------------------------------------------------- 1 | #!/this/will/be/overwritten/or/wrapped/anyways/do/not/worry/ruby 2 | # -*- encoding: binary -*- 3 | # frozen_string_literal: false 4 | require 'unicorn/launcher' 5 | require 'optparse' 6 | 7 | ENV["RACK_ENV"] ||= "development" 8 | rackup_opts = Unicorn::Configurator::RACKUP 9 | options = rackup_opts[:options] 10 | set_no_default_middleware = true 11 | 12 | op = OptionParser.new("", 24, ' ') do |opts| 13 | cmd = File.basename($0) 14 | opts.banner = "Usage: #{cmd} " \ 15 | "[ruby options] [#{cmd} options] [rackup config file]" 16 | opts.separator "Ruby options:" 17 | 18 | lineno = 1 19 | opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line| 20 | eval line, TOPLEVEL_BINDING, "-e", lineno 21 | lineno += 1 22 | end 23 | 24 | opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do 25 | $DEBUG = true 26 | end 27 | 28 | opts.on("-w", "--warn", "turn warnings on for your script") do 29 | $-w = true 30 | end 31 | 32 | opts.on("-I", "--include PATH", 33 | "specify $LOAD_PATH (may be used more than once)") do |path| 34 | $LOAD_PATH.unshift(*path.split(':')) 35 | end 36 | 37 | opts.on("-r", "--require LIBRARY", 38 | "require the library, before executing your script") do |library| 39 | require library 40 | end 41 | 42 | opts.separator "#{cmd} options:" 43 | 44 | # some of these switches exist for rackup command-line compatibility, 45 | 46 | opts.on("-o", "--host HOST", 47 | "listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h| 48 | rackup_opts[:host] = h 49 | rackup_opts[:set_listener] = true 50 | end 51 | 52 | opts.on("-p", "--port PORT", Integer, 53 | "use PORT (default: #{Unicorn::Const::DEFAULT_PORT})") do |port| 54 | rackup_opts[:port] = port 55 | rackup_opts[:set_listener] = true 56 | end 57 | 58 | opts.on("-E", "--env RACK_ENV", 59 | "use RACK_ENV for defaults (default: development)") do |e| 60 | ENV["RACK_ENV"] = e 61 | end 62 | 63 | opts.on("-N", "--no-default-middleware", 64 | "do not load middleware implied by RACK_ENV") do |e| 65 | rackup_opts[:no_default_middleware] = true if set_no_default_middleware 66 | end 67 | 68 | opts.on("-D", "--daemonize", "run daemonized in the background") do |d| 69 | rackup_opts[:daemonize] = !!d 70 | end 71 | 72 | opts.on("-P", "--pid FILE", "DEPRECATED") do |f| 73 | warn %q{Use of --pid/-P is strongly discouraged} 74 | warn %q{Use the 'pid' directive in the Unicorn config file instead} 75 | options[:pid] = f 76 | end 77 | 78 | opts.on("-s", "--server SERVER", 79 | "this flag only exists for compatibility") do |s| 80 | warn "-s/--server only exists for compatibility with rackup" 81 | end 82 | 83 | # Unicorn-specific stuff 84 | opts.on("-l", "--listen {HOST:PORT|PATH}", 85 | "listen on HOST:PORT or PATH", 86 | "this may be specified multiple times", 87 | "(default: #{Unicorn::Const::DEFAULT_LISTEN})") do |address| 88 | options[:listeners] << address 89 | end 90 | 91 | opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f| 92 | options[:config_file] = f 93 | end 94 | 95 | # I'm avoiding Unicorn-specific config options on the command-line. 96 | # IMNSHO, config options on the command-line are redundant given 97 | # config files and make things unnecessarily complicated with multiple 98 | # places to look for a config option. 99 | 100 | opts.separator "Common options:" 101 | 102 | opts.on_tail("-h", "--help", "Show this message") do 103 | puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '') 104 | exit 105 | end 106 | 107 | opts.on_tail("-v", "--version", "Show version") do 108 | puts "#{cmd} v#{Unicorn::Const::UNICORN_VERSION}" 109 | exit 110 | end 111 | 112 | opts.parse! ARGV 113 | end 114 | 115 | set_no_default_middleware = false 116 | app = Unicorn.builder(ARGV[0] || 'config.ru', op) 117 | op = nil 118 | 119 | if $DEBUG 120 | require 'pp' 121 | pp({ 122 | :unicorn_options => options, 123 | :app => app, 124 | :daemonize => rackup_opts[:daemonize], 125 | }) 126 | end 127 | 128 | Unicorn::Launcher.daemonize!(options) if rackup_opts[:daemonize] 129 | Unicorn::HttpServer.new(app, options).start.join 130 | -------------------------------------------------------------------------------- /HACKING: -------------------------------------------------------------------------------- 1 | = Unicorn Hacker's Guide 2 | 3 | == Polyglot Infrastructure 4 | 5 | Like Mongrel, we use Ruby where it makes sense, and Ragel with C where 6 | it helps performance. All of the code that actually runs your Rack 7 | application is written Ruby, Ragel or C. 8 | 9 | Ragel may be dropped in favor of a picohttpparser-based one in the future. 10 | 11 | As far as tests and documentation goes, we're not afraid to embrace Unix 12 | and use traditional Unix tools where they make sense and get the job 13 | done. 14 | 15 | === Tests 16 | 17 | Tests are good, but slow tests make development slow, so we make tests 18 | faster (in parallel) with GNU make (instead of Rake) and avoiding 19 | RubyGems. 20 | 21 | New tests are written in Perl 5 and use TAP 22 | to ensure stability and immunity from Ruby incompatibilities. 23 | 24 | Users of GNU-based systems (such as GNU/Linux) usually have GNU make 25 | installed as "make" instead of "gmake". 26 | 27 | Running the entire test suite with 4 tests in parallel: 28 | 29 | gmake -j4 check 30 | 31 | Running just one unit test: 32 | 33 | gmake test/unit/test_http_parser.rb 34 | 35 | Running just one test case in a unit test: 36 | 37 | gmake test/unit/test_http_parser.rb--test_parse_simple.n 38 | 39 | === HttpServer 40 | 41 | We strive to write as little code as possible while still maintaining 42 | readability. However, readability and flexibility may be sacrificed for 43 | performance in hot code paths. For Ruby, less code generally means 44 | faster code. 45 | 46 | Memory allocation should be minimized as much as practically possible. 47 | Buffers for IO#readpartial are preallocated in the hot paths to avoid 48 | building up garbage. Hash assignments use frozen strings to avoid the 49 | duplication behind-the-scenes. 50 | 51 | We spend as little time as possible inside signal handlers and instead 52 | defer handling them for predictability and robustness. Most of the 53 | Unix-specific things are in the Unicorn::HttpServer class. Unix systems 54 | programming experience will come in handy (or be learned) here. 55 | 56 | === Documentation 57 | 58 | Please wrap documentation at 72 characters-per-line or less (long URLs 59 | are exempt) so it is comfortably readable from terminals. 60 | 61 | When referencing mailing list posts, use 62 | https://yhbt.net/unicorn-public/$MESSAGE_ID/ if possible 63 | since the Message-ID remains searchable even if a particular site 64 | becomes unavailable. 65 | 66 | === Ruby/C Compatibility 67 | 68 | We target C Ruby 2.5 and later. We need the Ruby 69 | implementation to support fork, exec, pipe, UNIX signals, access to 70 | integer file descriptors and ability to use unlinked files. 71 | 72 | All of our C code is OS-independent and should run on compilers 73 | supported by the versions of Ruby we target. 74 | 75 | === Ragel Compatibility 76 | 77 | We target the latest released version of Ragel in Debian and will update 78 | our code to keep up with new releases. Packaged tarballs and gems 79 | include the generated source code so they will remain usable if 80 | compatibility is broken. 81 | 82 | == Contributing 83 | 84 | Contributions are welcome in the form of patches, pull requests, code 85 | review, testing, documentation, user support or any other feedback is 86 | welcome. The mailing list is the central coordination point for all 87 | user and developer feedback and bug reports. 88 | 89 | === Submitting Patches 90 | 91 | Follow conventions already established in the code and do not exceed 80 92 | characters per line. 93 | 94 | Inline patches (from "git format-patch -M") to the mailing list are 95 | preferred because they allow code review and comments in the reply to 96 | the patch. 97 | 98 | We will adhere to mostly the same conventions for patch submissions as 99 | git itself. See the 100 | {SubmittingPatches}[https://git.kernel.org/cgit/git/git.git/tree/Documentation/SubmittingPatches] 101 | document 102 | distributed with git on on patch submission guidelines to follow. Just 103 | don't email the git mailing list or maintainer with Unicorn patches :) 104 | 105 | == Building a Gem 106 | 107 | You can build the Unicorn gem with the following command: 108 | 109 | gmake gem 110 | 111 | == Running Development Versions 112 | 113 | It is easy to install the contents of your git working directory: 114 | 115 | Via RubyGems 116 | 117 | gmake install-gem 118 | -------------------------------------------------------------------------------- /t/integration.ru: -------------------------------------------------------------------------------- 1 | #!ruby 2 | # frozen_string_literal: false 3 | # Copyright (C) unicorn hackers 4 | # License: GPL-3.0+ 5 | 6 | # this goes for t/integration.t We'll try to put as many tests 7 | # in here as possible to avoid startup overhead of Ruby. 8 | 9 | def early_hints(env, val) 10 | env['rack.early_hints'].call('link' => val) # val may be ary or string 11 | [ 200, {}, [ val.class.to_s ] ] 12 | end 13 | 14 | $orig_rack_200 = nil 15 | def tweak_status_code 16 | $orig_rack_200 = Rack::Utils::HTTP_STATUS_CODES[200] 17 | Rack::Utils::HTTP_STATUS_CODES[200] = "HI" 18 | [ 200, {}, [] ] 19 | end 20 | 21 | def restore_status_code 22 | $orig_rack_200 or return [ 500, {}, [] ] 23 | Rack::Utils::HTTP_STATUS_CODES[200] = $orig_rack_200 24 | [ 200, {}, [] ] 25 | end 26 | 27 | class WriteOnClose 28 | def each(&block) 29 | @callback = block 30 | end 31 | 32 | def close 33 | @callback.call "7\r\nGoodbye\r\n0\r\n\r\n" 34 | end 35 | end 36 | 37 | def write_on_close 38 | [ 200, { 'transfer-encoding' => 'chunked' }, WriteOnClose.new ] 39 | end 40 | 41 | def env_dump(env, dump_body = false) 42 | require 'json' 43 | h = {} 44 | env.each do |k,v| 45 | case v 46 | when String, Integer, true, false; h[k] = v 47 | else 48 | case k 49 | when 'rack.version', 'rack.after_reply'; h[k] = v 50 | when 'rack.input'; h[k] = v.class.to_s 51 | end 52 | end 53 | end 54 | h['unicorn_test.body'] = env['rack.input'].read if dump_body 55 | h.to_json 56 | end 57 | 58 | def rack_input_tests(env) 59 | return [ 100, {}, [] ] if /\A100-continue\z/i =~ env['HTTP_EXPECT'] 60 | cap = 16384 61 | require 'digest/md5' 62 | dig = Digest::MD5.new 63 | input = env['rack.input'] 64 | case env['PATH_INFO'] 65 | when '/rack_input/size_first'; input.size 66 | when '/rack_input/rewind_first'; input.rewind 67 | when '/rack_input'; # OK 68 | else 69 | abort "bad path: #{env['PATH_INFO']}" 70 | end 71 | if buf = input.read(rand(cap)) 72 | begin 73 | raise "#{buf.size} > #{cap}" if buf.size > cap 74 | dig.update(buf) 75 | end while input.read(rand(cap), buf) 76 | buf.clear # remove this call if Ruby ever gets escape analysis 77 | end 78 | h = { 'content-type' => 'text/plain' } 79 | if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i 80 | cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']] 81 | cmd5_bin = cmd5_b64.unpack('m')[0] 82 | if cmd5_bin != dig.digest 83 | h['content-length'] = cmd5_b64.size.to_s 84 | return [ 500, h, [ cmd5_b64 ] ] 85 | end 86 | end 87 | h['content-length'] = '32' 88 | [ 200, h, [ dig.hexdigest ] ] 89 | end 90 | 91 | $nr_aborts = 0 92 | run(lambda do |env| 93 | case env['REQUEST_METHOD'] 94 | when 'GET' 95 | case env['PATH_INFO'] 96 | when '/rack-2-newline-headers'; [ 200, { 'X-R2' => "a\nb\nc" }, [] ] 97 | when '/rack-3-array-headers'; [ 200, { 'x-r3' => %w(a b c) }, [] ] 98 | when '/nil-header-value'; [ 200, { 'X-Nil' => nil }, [] ] 99 | when '/unknown-status-pass-through'; [ '666 I AM THE BEAST', {}, [] ] 100 | when '/env_dump'; [ 200, {}, [ env_dump(env) ] ] 101 | when '/write_on_close'; write_on_close 102 | when '/pid'; [ 200, {}, [ "#$$\n" ] ] 103 | when '/early_hints_rack2'; early_hints(env, "r\n2") 104 | when '/early_hints_rack3'; early_hints(env, %w(r 3)) 105 | when '/broken_app'; raise RuntimeError, 'hello' 106 | when '/aborted'; $nr_aborts += 1; [ 200, {}, [] ] 107 | when '/nr_aborts'; [ 200, { 'nr-aborts' => "#$nr_aborts" }, [] ] 108 | when '/nil'; nil 109 | when '/read_fifo'; [ 200, {}, [ File.read(env['HTTP_READ_FIFO']) ] ] 110 | else '/'; [ 200, {}, [ env_dump(env) ] ] 111 | end # case PATH_INFO (GET) 112 | when 'POST' 113 | case env['PATH_INFO'] 114 | when '/tweak-status-code'; tweak_status_code 115 | when '/restore-status-code'; restore_status_code 116 | when '/env_dump'; [ 200, {}, [ env_dump(env, true) ] ] 117 | end # case PATH_INFO (POST) 118 | # ... 119 | when 'PUT' 120 | case env['PATH_INFO'] 121 | when %r{\A/rack_input}; rack_input_tests(env) 122 | when '/env_dump'; [ 200, {}, [ env_dump(env) ] ] 123 | end 124 | when 'OPTIONS' 125 | case env['REQUEST_URI'] 126 | when '*'; [ 200, {}, [ env_dump(env) ] ] 127 | end 128 | end # case REQUEST_METHOD 129 | end) # run 130 | -------------------------------------------------------------------------------- /test/unit/test_util.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | require './test/test_helper' 5 | require 'tempfile' 6 | 7 | class TestUtil < Test::Unit::TestCase 8 | 9 | EXPECT_FLAGS = File::WRONLY | File::APPEND 10 | def test_reopen_logs_noop 11 | tmp = Tempfile.new('') 12 | fp = File.open(tmp.path, 'ab') 13 | fp.sync = true 14 | ext = fp.external_encoding rescue nil 15 | int = fp.internal_encoding rescue nil 16 | before = fp.stat.inspect 17 | Unicorn::Util.reopen_logs 18 | assert_equal before, File.stat(fp.path).inspect 19 | assert_equal ext, (fp.external_encoding rescue nil) 20 | assert_equal int, (fp.internal_encoding rescue nil) 21 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 22 | tmp.close! 23 | fp.close 24 | end 25 | 26 | def test_reopen_logs_renamed 27 | tmp = Tempfile.new('') 28 | tmp_path = tmp.path.freeze 29 | fp = File.open(tmp_path, 'ab') 30 | fp.sync = true 31 | 32 | ext = fp.external_encoding rescue nil 33 | int = fp.internal_encoding rescue nil 34 | before = fp.stat.inspect 35 | to = Tempfile.new('') 36 | File.rename(tmp_path, to.path) 37 | assert ! File.exist?(tmp_path) 38 | Unicorn::Util.reopen_logs 39 | assert_equal tmp_path, tmp.path 40 | assert File.exist?(tmp_path) 41 | assert before != File.stat(tmp_path).inspect 42 | assert_equal fp.stat.inspect, File.stat(tmp_path).inspect 43 | assert_equal ext, (fp.external_encoding rescue nil) 44 | assert_equal int, (fp.internal_encoding rescue nil) 45 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 46 | assert fp.sync 47 | tmp.close! 48 | to.close! 49 | fp.close 50 | end 51 | 52 | def test_reopen_logs_renamed_with_encoding 53 | tmp = Tempfile.new('') 54 | tmp_path = tmp.path.dup.freeze 55 | Encoding.list.sample(5).each { |encoding| 56 | File.open(tmp_path, "a:#{encoding.to_s}") { |fp| 57 | fp.sync = true 58 | assert_equal encoding, fp.external_encoding 59 | assert_nil fp.internal_encoding 60 | File.unlink(tmp_path) 61 | assert ! File.exist?(tmp_path) 62 | Unicorn::Util.reopen_logs 63 | assert_equal tmp_path, fp.path 64 | assert File.exist?(tmp_path) 65 | assert_equal fp.stat.inspect, File.stat(tmp_path).inspect 66 | assert_equal encoding, fp.external_encoding 67 | assert_nil fp.internal_encoding 68 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 69 | assert fp.sync 70 | } 71 | } 72 | tmp.close! 73 | end 74 | 75 | def test_reopen_logs_renamed_with_internal_encoding 76 | tmp = Tempfile.new('') 77 | tmp_path = tmp.path.dup.freeze 78 | full = Encoding.list 79 | full.sample(2).each { |ext| 80 | full.sample(2).each { |int| 81 | next if ext == int 82 | File.open(tmp_path, "a:#{ext.to_s}:#{int.to_s}") { |fp| 83 | fp.sync = true 84 | assert_equal ext, fp.external_encoding 85 | 86 | if ext != Encoding::BINARY 87 | assert_equal int, fp.internal_encoding 88 | end 89 | 90 | File.unlink(tmp_path) 91 | assert ! File.exist?(tmp_path) 92 | Unicorn::Util.reopen_logs 93 | assert_equal tmp_path, fp.path 94 | assert File.exist?(tmp_path) 95 | assert_equal fp.stat.inspect, File.stat(tmp_path).inspect 96 | assert_equal ext, fp.external_encoding 97 | if ext != Encoding::BINARY 98 | assert_equal int, fp.internal_encoding 99 | end 100 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 101 | assert fp.sync 102 | } 103 | } 104 | } 105 | tmp.close! 106 | end 107 | 108 | def test_pipe 109 | r, w = Unicorn.pipe 110 | assert r 111 | assert w 112 | 113 | return if RUBY_PLATFORM !~ /linux/ 114 | 115 | begin 116 | f_getpipe_sz = 1032 117 | IO.pipe do |a, b| 118 | a_sz = a.fcntl(f_getpipe_sz) 119 | b.fcntl(f_getpipe_sz) 120 | assert_kind_of Integer, a_sz 121 | r_sz = r.fcntl(f_getpipe_sz) 122 | assert_equal Raindrops::PAGE_SIZE, r_sz 123 | assert_operator a_sz, :>=, r_sz 124 | end 125 | rescue Errno::EINVAL 126 | # Linux <= 2.6.34 127 | end 128 | ensure 129 | w.close 130 | r.close 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /examples/unicorn.conf.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: false 2 | # Sample verbose configuration file for Unicorn (not Rack) 3 | # 4 | # This configuration file documents many features of Unicorn 5 | # that may not be needed for some applications. See 6 | # https://yhbt.net/unicorn/examples/unicorn.conf.minimal.rb 7 | # for a much simpler configuration file. 8 | # 9 | # See https://yhbt.net/unicorn/Unicorn/Configurator.html for complete 10 | # documentation. 11 | 12 | # Use at least one worker per core if you're on a dedicated server, 13 | # more will usually help for _short_ waits on databases/caches. 14 | worker_processes 4 15 | 16 | # Since Unicorn is never exposed to outside clients, it does not need to 17 | # run on the standard HTTP port (80), there is no reason to start Unicorn 18 | # as root unless it's from system init scripts. 19 | # If running the master process as root and the workers as an unprivileged 20 | # user, do this to switch euid/egid in the workers (also chowns logs): 21 | # user "unprivileged_user", "unprivileged_group" 22 | 23 | # Help ensure your application will always spawn in the symlinked 24 | # "current" directory that Capistrano sets up. 25 | working_directory "/path/to/app/current" # available in 0.94.0+ 26 | 27 | # listen on both a Unix domain socket and a TCP port, 28 | # we use a shorter backlog for quicker failover when busy 29 | listen "/path/to/.unicorn.sock", :backlog => 64 30 | listen 8080, :tcp_nopush => true 31 | 32 | # nuke workers after 30 seconds instead of 60 seconds (the default) 33 | timeout 30 34 | 35 | # feel free to point this anywhere accessible on the filesystem 36 | pid "/path/to/app/shared/pids/unicorn.pid" 37 | 38 | # By default, the Unicorn logger will write to stderr. 39 | # Additionally, ome applications/frameworks log to stderr or stdout, 40 | # so prevent them from going to /dev/null when daemonized here: 41 | stderr_path "/path/to/app/shared/log/unicorn.stderr.log" 42 | stdout_path "/path/to/app/shared/log/unicorn.stdout.log" 43 | 44 | # combine Ruby 2.0.0+ with "preload_app true" for memory savings 45 | preload_app true 46 | 47 | # Enable this flag to have unicorn test client connections by writing the 48 | # beginning of the HTTP headers before calling the application. This 49 | # prevents calling the application for connections that have disconnected 50 | # while queued. This is only guaranteed to detect clients on the same 51 | # host unicorn runs on, and unlikely to detect disconnects even on a 52 | # fast LAN. 53 | check_client_connection false 54 | 55 | # local variable to guard against running a hook multiple times 56 | run_once = true 57 | 58 | before_fork do |server, worker| 59 | # the following is highly recomended for Rails + "preload_app true" 60 | # as there's no need for the master process to hold a connection 61 | defined?(ActiveRecord::Base) and 62 | ActiveRecord::Base.connection.disconnect! 63 | 64 | # Occasionally, it may be necessary to run non-idempotent code in the 65 | # master before forking. Keep in mind the above disconnect! example 66 | # is idempotent and does not need a guard. 67 | if run_once 68 | # do_something_once_here ... 69 | run_once = false # prevent from firing again 70 | end 71 | 72 | # The following is only recommended for memory/DB-constrained 73 | # installations. It is not needed if your system can house 74 | # twice as many worker_processes as you have configured. 75 | # 76 | # # This allows a new master process to incrementally 77 | # # phase out the old master process with SIGTTOU to avoid a 78 | # # thundering herd (especially in the "preload_app false" case) 79 | # # when doing a transparent upgrade. The last worker spawned 80 | # # will then kill off the old master process with a SIGQUIT. 81 | # old_pid = "#{server.config[:pid]}.oldbin" 82 | # if old_pid != server.pid 83 | # begin 84 | # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU 85 | # Process.kill(sig, File.read(old_pid).to_i) 86 | # rescue Errno::ENOENT, Errno::ESRCH 87 | # end 88 | # end 89 | # 90 | # Throttle the master from forking too quickly by sleeping. Due 91 | # to the implementation of standard Unix signal handlers, this 92 | # helps (but does not completely) prevent identical, repeated signals 93 | # from being lost when the receiving process is busy. 94 | # sleep 1 95 | end 96 | 97 | after_fork do |server, worker| 98 | # per-process listener ports for debugging/admin/migrations 99 | # addr = "127.0.0.1:#{9293 + worker.nr}" 100 | # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) 101 | 102 | # the following is *required* for Rails + "preload_app true", 103 | defined?(ActiveRecord::Base) and 104 | ActiveRecord::Base.establish_connection 105 | 106 | # if preload_app is true, then you may also want to check and 107 | # restart any other shared sockets/descriptors such as Memcached, 108 | # and Redis. TokyoCabinet file handles are safe to reuse 109 | # between any number of forked children (assuming your kernel 110 | # correctly implements pread()/pwrite() system calls) 111 | end 112 | -------------------------------------------------------------------------------- /lib/unicorn.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | require 'etc' 4 | require 'stringio' 5 | require 'raindrops' 6 | require 'io/wait' 7 | 8 | begin 9 | require 'rack' 10 | rescue LoadError 11 | warn 'rack not available, functionality reduced' 12 | end 13 | 14 | # :stopdoc: 15 | # Unicorn module containing all of the classes (include C extensions) for 16 | # running a Unicorn web server. It contains a minimalist HTTP server with just 17 | # enough functionality to service web application requests fast as possible. 18 | # :startdoc: 19 | 20 | # unicorn exposes very little of an user-visible API and most of its 21 | # internals are subject to change. unicorn is designed to host Rack 22 | # applications, so applications should be written against the Rack SPEC 23 | # and not unicorn internals. 24 | module Unicorn 25 | 26 | # Raised inside TeeInput when a client closes the socket inside the 27 | # application dispatch. This is always raised with an empty backtrace 28 | # since there is nothing in the application stack that is responsible 29 | # for client shutdowns/disconnects. This exception is visible to Rack 30 | # applications unless PrereadInput middleware is loaded. This 31 | # is a subclass of the standard EOFError class and applications should 32 | # not rescue it explicitly, but rescue EOFError instead. 33 | ClientShutdown = Class.new(EOFError) 34 | 35 | # :stopdoc: 36 | 37 | # This returns a lambda to pass in as the app, this does not "build" the 38 | # app (which we defer based on the outcome of "preload_app" in the 39 | # Unicorn config). The returned lambda will be called when it is 40 | # time to build the app. 41 | def self.builder(ru, op) 42 | # allow Configurator to parse cli switches embedded in the ru file 43 | op = Unicorn::Configurator::RACKUP.merge!(:file => ru, :optparse => op) 44 | if ru =~ /\.ru$/ && !defined?(Rack::Builder) 45 | abort "rack and Rack::Builder must be available for processing #{ru}" 46 | end 47 | 48 | # always called after config file parsing, may be called after forking 49 | lambda do |_, server| 50 | inner_app = case ru 51 | when /\.ru$/ 52 | raw = File.read(ru) 53 | raw.sub!(/^__END__\n.*/, '') 54 | eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru) 55 | else 56 | require ru 57 | Object.const_get(File.basename(ru, '.rb').capitalize) 58 | end 59 | 60 | if $DEBUG 61 | require 'pp' 62 | pp({ :inner_app => inner_app }) 63 | end 64 | 65 | return inner_app unless server.default_middleware 66 | 67 | middleware = { # order matters 68 | ContentLength: nil, 69 | CommonLogger: [ $stderr ], 70 | ShowExceptions: nil, 71 | Lint: nil, 72 | TempfileReaper: nil, 73 | } 74 | 75 | # return value, matches rackup defaults based on env 76 | # Unicorn does not support persistent connections, but Rainbows! 77 | # does. Users accustomed to the Rack::Server default 78 | # middlewares will need ContentLength middleware. 79 | case ENV["RACK_ENV"] 80 | when "development" 81 | when "deployment" 82 | middleware.delete(:ShowExceptions) 83 | middleware.delete(:Lint) 84 | else 85 | return inner_app 86 | end 87 | Rack::Builder.new do 88 | middleware.each do |m, args| 89 | use(Rack.const_get(m), *args) if Rack.const_defined?(m) 90 | end 91 | run inner_app 92 | end.to_app 93 | end 94 | end 95 | 96 | # returns an array of strings representing TCP listen socket addresses 97 | # and Unix domain socket paths. This is useful for use with 98 | # Raindrops::Middleware under Linux: https://yhbt.net/raindrops/ 99 | def self.listener_names 100 | Unicorn::HttpServer::LISTENERS.map do |io| 101 | Unicorn::SocketHelper.sock_name(io) 102 | end + Unicorn::HttpServer::NEW_LISTENERS 103 | end 104 | 105 | def self.log_error(logger, prefix, exc) 106 | message = exc.message 107 | message = message.dump if /[[:cntrl:]]/ =~ message 108 | logger.error "#{prefix}: #{message} (#{exc.class})" 109 | exc.backtrace.each { |line| logger.error(line) } 110 | end 111 | 112 | F_SETPIPE_SZ = 1031 if RUBY_PLATFORM =~ /linux/ 113 | 114 | def self.pipe # :nodoc: 115 | IO.pipe.each do |io| 116 | # shrink pipes to minimize impact on /proc/sys/fs/pipe-user-pages-soft 117 | # limits. 118 | if defined?(F_SETPIPE_SZ) 119 | begin 120 | io.fcntl(F_SETPIPE_SZ, Raindrops::PAGE_SIZE) 121 | rescue Errno::EINVAL 122 | # old kernel 123 | rescue Errno::EPERM 124 | # resizes fail if Linux is close to the pipe limit for the user 125 | # or if the user does not have permissions to resize 126 | end 127 | end 128 | end 129 | end 130 | # :startdoc: 131 | end 132 | # :enddoc: 133 | 134 | %w(const socket_helper stream_input tee_input http_request configurator 135 | tmpio util http_response worker http_server).each do |s| 136 | require_relative "unicorn/#{s}" 137 | end 138 | -------------------------------------------------------------------------------- /DESIGN: -------------------------------------------------------------------------------- 1 | == Design 2 | 3 | Unicorn was designed to support poorly-written codebases back in 2008. 4 | Its unfortunate popularity has only proliferated the existence of 5 | poorly-written code ever since... 6 | 7 | * Simplicity: Unicorn is a traditional UNIX prefork web server. 8 | No threads are used at all, this makes applications easier to debug 9 | and fix. When your application goes awry, a BOFH can just 10 | "kill -9" the runaway worker process without worrying about tearing 11 | all clients down, just one. Only UNIX-like systems supporting 12 | fork() and file descriptor inheritance are supported. 13 | 14 | * The Ragel+C HTTP parser is taken from Mongrel. 15 | 16 | * All HTTP parsing and I/O is done much like Mongrel: 17 | 1. read/parse HTTP request headers in full 18 | 2. call Rack application 19 | 3. write HTTP response back to the client 20 | 21 | * Like Mongrel, neither keepalive nor pipelining are supported. 22 | These aren't needed since Unicorn is only designed to serve 23 | fast, low-latency clients directly. Do one thing, do it well; 24 | let nginx handle slow clients. 25 | 26 | * Configuration is purely in Ruby and eval(). Ruby is less 27 | ambiguous than YAML and lets lambdas for 28 | before_fork/after_fork/before_exec hooks be defined inline. An 29 | optional, separate config_file may be used to modify supported 30 | configuration changes (and also gives you plenty of rope if you RTFS 31 | :>) 32 | 33 | * One master process spawns and reaps worker processes. The 34 | Rack application itself is called only within the worker process (but 35 | can be loaded within the master). A copy-on-write friendly garbage 36 | collector like the one found in mainline Ruby 2.0.0 and later 37 | can be used to minimize memory usage along with the "preload_app true" 38 | directive (see Unicorn::Configurator). 39 | 40 | * The number of worker processes should be scaled to the number of 41 | CPUs, memory or even spindles you have. If you have an existing 42 | Mongrel cluster on a single-threaded app, using the same amount of 43 | processes should work. Let a full-HTTP-request-buffering reverse 44 | proxy like nginx manage concurrency to thousands of slow clients for 45 | you. Unicorn scaling should only be concerned about limits of your 46 | backend system(s). 47 | 48 | * Load balancing between worker processes is done by the OS kernel. 49 | All workers share a common set of listener sockets and does 50 | non-blocking accept() on them. The kernel will decide which worker 51 | process to give a socket to and workers will sleep if there is 52 | nothing to accept(). 53 | 54 | * Since non-blocking accept() is used, there can be a thundering 55 | herd when an occasional client connects when application 56 | *is not busy*. The thundering herd problem should not affect 57 | applications that are running all the time since worker processes 58 | will only select()/accept() outside of the application dispatch. 59 | 60 | * Additionally, thundering herds are much smaller than with 61 | configurations using existing prefork servers. Process counts should 62 | only be scaled to backend resources, _never_ to the number of expected 63 | clients like is typical with blocking prefork servers. So while we've 64 | seen instances of popular prefork servers configured to run many 65 | hundreds of worker processes, Unicorn deployments are typically only 66 | 2-4 processes per-core. 67 | 68 | * On-demand scaling of worker processes never happens automatically. 69 | Again, Unicorn is concerned about scaling to backend limits and should 70 | never configured in a fashion where it could be waiting on slow 71 | clients. For extremely rare circumstances, we provide TTIN and TTOU 72 | signal handlers to increment/decrement your process counts without 73 | reloading. Think of it as driving a car with manual transmission: 74 | you have a lot more control if you know what you're doing. 75 | 76 | * Blocking I/O is used for clients. This allows a simpler code path 77 | to be followed within the Ruby interpreter and fewer syscalls. 78 | Applications that use threads continue to work if Unicorn 79 | is only serving LAN or localhost clients. 80 | 81 | * SIGKILL is used to terminate the timed-out workers from misbehaving apps 82 | as reliably as possible on a UNIX system. The default timeout is a 83 | generous 60 seconds (same default as in Mongrel). 84 | 85 | * The poor performance of select() on large FD sets is avoided 86 | as few file descriptors are used in each worker. 87 | There should be no gain from moving to highly scalable but 88 | unportable event notification solutions for watching few 89 | file descriptors. 90 | 91 | * If the master process dies unexpectedly for any reason, 92 | workers will notice within :timeout/2 seconds and follow 93 | the master to its death. 94 | 95 | * There is never any explicit real-time dependency or communication 96 | between the worker processes nor to the master process. 97 | Synchronization is handled entirely by the OS kernel and shared 98 | resources are never accessed by the worker when it is servicing 99 | a client. 100 | -------------------------------------------------------------------------------- /lib/unicorn/stream_input.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | # When processing uploads, unicorn may expose a StreamInput object under 5 | # "rack.input" of the Rack environment when 6 | # Unicorn::Configurator#rewindable_input is set to +false+ 7 | class Unicorn::StreamInput 8 | # The I/O chunk size (in +bytes+) for I/O operations where 9 | # the size cannot be user-specified when a method is called. 10 | # The default is 16 kilobytes. 11 | @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc: 12 | 13 | # Initializes a new StreamInput object. You normally do not have to call 14 | # this unless you are writing an HTTP server. 15 | def initialize(socket, request) # :nodoc: 16 | @chunked = request.content_length.nil? 17 | @socket = socket 18 | @parser = request 19 | @buf = request.buf 20 | @rbuf = '' 21 | @bytes_read = 0 22 | filter_body(@rbuf, @buf) unless @buf.empty? 23 | end 24 | 25 | # :call-seq: 26 | # ios.read([length [, buffer ]]) => string, buffer, or nil 27 | # 28 | # Reads at most length bytes from the I/O stream, or to the end of 29 | # file if length is omitted or is nil. length must be a non-negative 30 | # integer or nil. If the optional buffer argument is present, it 31 | # must reference a String, which will receive the data. 32 | # 33 | # At end of file, it returns nil or '' depend on length. 34 | # ios.read() and ios.read(nil) returns ''. 35 | # ios.read(length [, buffer]) returns nil. 36 | # 37 | # If the Content-Length of the HTTP request is known (as is the common 38 | # case for POST requests), then ios.read(length [, buffer]) will block 39 | # until the specified length is read (or it is the last chunk). 40 | # Otherwise, for uncommon "Transfer-Encoding: chunked" requests, 41 | # ios.read(length [, buffer]) will return immediately if there is 42 | # any data and only block when nothing is available (providing 43 | # IO#readpartial semantics). 44 | def read(length = nil, rv = '') 45 | if length 46 | if length <= @rbuf.size 47 | length < 0 and raise ArgumentError, "negative length #{length} given" 48 | rv.replace(@rbuf.slice!(0, length)) 49 | else 50 | to_read = length - @rbuf.size 51 | rv.replace(@rbuf.slice!(0, @rbuf.size)) 52 | until to_read == 0 || eof? || (rv.size > 0 && @chunked) 53 | filter_body(@rbuf, @socket.readpartial(to_read, @buf)) 54 | rv << @rbuf 55 | to_read -= @rbuf.size 56 | end 57 | @rbuf.clear 58 | end 59 | rv = nil if rv.empty? && length != 0 60 | else 61 | read_all(rv) 62 | end 63 | rv 64 | rescue EOFError 65 | return eof! 66 | end 67 | 68 | # :call-seq: 69 | # ios.gets => string or nil 70 | # 71 | # Reads the next ``line'' from the I/O stream; lines are separated 72 | # by the global record separator ($/, typically "\n"). A global 73 | # record separator of nil reads the entire unread contents of ios. 74 | # Returns nil if called at the end of file. 75 | # This takes zero arguments for strict Rack::Lint compatibility, 76 | # unlike IO#gets. 77 | def gets 78 | sep = $/ 79 | if sep.nil? 80 | read_all(rv = '') 81 | return rv.empty? ? nil : rv 82 | end 83 | re = /\A(.*?#{Regexp.escape(sep)})/ 84 | 85 | begin 86 | @rbuf.sub!(re, '') and return $1 87 | return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof? 88 | filter_body(once = '', @socket.readpartial(@@io_chunk_size, @buf)) 89 | @rbuf << once 90 | rescue EOFError 91 | return eof! 92 | end while true 93 | end 94 | 95 | # :call-seq: 96 | # ios.each { |line| block } => ios 97 | # 98 | # Executes the block for every ``line'' in *ios*, where lines are 99 | # separated by the global record separator ($/, typically "\n"). 100 | def each 101 | while line = gets 102 | yield line 103 | end 104 | 105 | self # Rack does not specify what the return value is here 106 | end 107 | 108 | private 109 | 110 | def eof? 111 | if @parser.body_eof? 112 | while @chunked && ! @parser.parse 113 | @buf << @socket.readpartial(@@io_chunk_size) 114 | end 115 | @socket = nil 116 | true 117 | else 118 | false 119 | end 120 | rescue EOFError 121 | return eof! 122 | end 123 | 124 | def filter_body(dst, src) 125 | rv = @parser.filter_body(dst, src) 126 | @bytes_read += dst.size 127 | rv 128 | end 129 | 130 | def read_all(dst) 131 | dst.replace(@rbuf) 132 | @socket or return 133 | until eof? 134 | filter_body(@rbuf, @socket.readpartial(@@io_chunk_size, @buf)) 135 | dst << @rbuf 136 | end 137 | rescue EOFError 138 | return eof! 139 | ensure 140 | @rbuf.clear 141 | end 142 | 143 | def eof! 144 | # in case client only did a premature shutdown(SHUT_WR) 145 | # we do support clients that shutdown(SHUT_WR) after the 146 | # _entire_ request has been sent, and those will not have 147 | # raised EOFError on us. 148 | @socket.shutdown if @socket 149 | ensure 150 | raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", [] 151 | end 152 | end 153 | -------------------------------------------------------------------------------- /ISSUES: -------------------------------------------------------------------------------- 1 | = Issues 2 | 3 | mailto:unicorn-public@yhbt.net is the best place to report bugs, 4 | submit patches and/or obtain support after you have searched the 5 | {email archives}[https://yhbt.net/unicorn-public/] and 6 | {documentation}[https://yhbt.net/unicorn/]. 7 | 8 | * No subscription will ever be required to email us 9 | * Cc: all participants in a thread or commit, as subscription is optional 10 | * Do not {top post}[http://catb.org/jargon/html/T/top-post.html] in replies 11 | * Quote as little as possible of the message you're replying to 12 | * Do not send HTML mail or images, 13 | they hurt reader privacy and will be flagged as spam 14 | * Anonymous and pseudonymous messages will ALWAYS be welcome 15 | * The email submission port (587) is enabled on the yhbt.net MX: 16 | https://yhbt.net/unicorn-public/20141004232241.GA23908@dcvr.yhbt.net/t/ 17 | 18 | We will never have a centralized or formal bug tracker. Instead we 19 | can interoperate with any bug tracker which can Cc: us plain-text to 20 | mailto:unicorn-public@yhbt.net This includes the Debian BTS 21 | at https://bugs.debian.org/unicorn and possibly others. 22 | 23 | unicorn is a server; it does not depend on graphics/audio. Nobody 24 | communicating with us will ever be expected to go through the trouble 25 | of setting up graphics nor audio support. 26 | 27 | If your issue is of a sensitive nature or you're just shy in public, 28 | use anonymity tools such as Tor or Mixmaster; and rely on the public 29 | mail archives for responses. Be sure to scrub sensitive log messages 30 | and such. 31 | 32 | If you don't get a response within a few days, we may have forgotten 33 | about it so feel free to ask again. 34 | 35 | The project does not and will never endorse nor promote commercial 36 | services (including support). The author of unicorn must never be 37 | allowed to profit off the damage it's done to the entire Ruby world. 38 | 39 | == Bugs in related projects 40 | 41 | unicorn is sometimes affected by bugs in its dependencies. Bugs 42 | triggered by unicorn in mainline Ruby, rack, GNU C library (glibc), 43 | or the Linux kernel will be reported upstream and fixed. 44 | 45 | For bugs in Ruby itself, we may forward bugs to 46 | https://bugs.ruby-lang.org/ and discuss+fix them on the ruby-core 47 | list at mailto:ruby-core@ruby-lang.org 48 | Subscription to post is required to ruby-core, unfortunately: 49 | mailto:ruby-core-request@ruby-lang.org?subject=subscribe 50 | Unofficial archives are available at: https://public-inbox.org/ruby-core/ 51 | 52 | For uncommon bugs in Rack, we may forward bugs to 53 | mailto:rack-devel@googlegroups.com and discuss there. 54 | Subscription (without any web UI or Google account) is possible via: 55 | mailto:rack-devel+subscribe@googlegroups.com 56 | Note: not everyone can use the proprietary bug tracker used by Rack, 57 | but their mailing list remains operational. 58 | Unofficial archives are available at: https://public-inbox.org/rack-devel/ 59 | 60 | Uncommon bugs we encounter in the Linux kernel should be Cc:-ed to the 61 | Linux kernel mailing list (LKML) at mailto:linux-kernel@vger.kernel.org 62 | and subsystem maintainers such as mailto:netdev@vger.kernel.org 63 | (for networking issues). It is expected practice to Cc: anybody 64 | involved with any problematic commits (including those in the 65 | Signed-off-by: and other trailer lines). No subscription is necessary, 66 | and the our mailing list follows the same conventions as LKML for 67 | interopability. Archives are available at https://lore.kernel.org/lkml/ 68 | There is a kernel.org Bugzilla instance, but it is ignored by most. 69 | 70 | Likewise for any rare glibc bugs we might encounter, we should Cc: 71 | mailto:libc-alpha@sourceware.org 72 | Archives are available at: https://inbox.sourceware.org/libc-alpha/ 73 | Keep in mind glibc upstream does use Bugzilla for tracking bugs: 74 | https://sourceware.org/bugzilla/ 75 | 76 | == Submitting Patches 77 | 78 | See the HACKING document (and additionally, the 79 | {SubmittingPatches}[https://git.kernel.org/cgit/git/git.git/tree/Documentation/SubmittingPatches] 80 | document distributed with git) on guidelines for patch submission. 81 | 82 | == Contact Info 83 | 84 | Mail is publicly-archived, SMTP subscription is discouraged to avoid 85 | servers being a single-point-of-failure, so Cc: all participants. 86 | 87 | The HTTP(S) archives have links to per-thread Atom feeds and downloadable 88 | mboxes. Read-only IMAP(S) folders, POP3, and NNTP(S) newsgroups are available. 89 | 90 | * https://yhbt.net/unicorn-public/ 91 | * http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/unicorn-public/ 92 | * imaps://;AUTH=ANONYMOUS@yhbt.net/inbox.comp.lang.ruby.unicorn.0 93 | * imap://;AUTH=ANONYMOUS@7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/inbox.comp.lang.ruby.unicorn.0 94 | * nntps://news.public-inbox.org/inbox.comp.lang.ruby.unicorn 95 | * nntp://news.gmane.io/gmane.comp.lang.ruby.unicorn.general 96 | * https://yhbt.net/unicorn-public/_/text/help/#pop3 97 | 98 | Full Atom feeds: 99 | * https://yhbt.net/unicorn-public/new.atom 100 | * http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/unicorn-public/new.atom 101 | 102 | We only accept plain-text mail: mailto:unicorn-public@yhbt.net 103 | -------------------------------------------------------------------------------- /lib/unicorn/tee_input.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | # Acts like tee(1) on an input input to provide a input-like stream 5 | # while providing rewindable semantics through a File/StringIO backing 6 | # store. On the first pass, the input is only read on demand so your 7 | # Rack application can use input notification (upload progress and 8 | # like). This should fully conform to the Rack::Lint::InputWrapper 9 | # specification on the public API. This class is intended to be a 10 | # strict interpretation of Rack::Lint::InputWrapper functionality and 11 | # will not support any deviations from it. 12 | # 13 | # When processing uploads, unicorn exposes a TeeInput object under 14 | # "rack.input" of the Rack environment by default. 15 | class Unicorn::TeeInput < Unicorn::StreamInput 16 | # The maximum size (in +bytes+) to buffer in memory before 17 | # resorting to a temporary file. Default is 112 kilobytes. 18 | @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc: 19 | 20 | # sets the maximum size of request bodies to buffer in memory, 21 | # amounts larger than this are buffered to the filesystem 22 | def self.client_body_buffer_size=(bytes) # :nodoc: 23 | @@client_body_buffer_size = bytes 24 | end 25 | 26 | # returns the maximum size of request bodies to buffer in memory, 27 | # amounts larger than this are buffered to the filesystem 28 | def self.client_body_buffer_size # :nodoc: 29 | @@client_body_buffer_size 30 | end 31 | 32 | # for Rack::TempfileReaper in rack 1.6+ 33 | def new_tmpio # :nodoc: 34 | tmpio = Unicorn::TmpIO.new 35 | (@parser.env['rack.tempfiles'] ||= []) << tmpio 36 | tmpio 37 | end 38 | 39 | # Initializes a new TeeInput object. You normally do not have to call 40 | # this unless you are writing an HTTP server. 41 | def initialize(socket, request) # :nodoc: 42 | @len = request.content_length 43 | super 44 | @tmp = @len && @len <= @@client_body_buffer_size ? 45 | StringIO.new("") : new_tmpio 46 | end 47 | 48 | # :call-seq: 49 | # ios.size => Integer 50 | # 51 | # Returns the size of the input. For requests with a Content-Length 52 | # header value, this will not read data off the socket and just return 53 | # the value of the Content-Length header as an Integer. 54 | # 55 | # For Transfer-Encoding:chunked requests, this requires consuming 56 | # all of the input stream before returning since there's no other 57 | # way to determine the size of the request body beforehand. 58 | # 59 | # This method is no longer part of the Rack specification as of 60 | # Rack 1.2, so its use is not recommended. This method only exists 61 | # for compatibility with Rack applications designed for Rack 1.1 and 62 | # earlier. Most applications should only need to call +read+ with a 63 | # specified +length+ in a loop until it returns +nil+. 64 | def size 65 | @len and return @len 66 | pos = @tmp.pos 67 | consume! 68 | @tmp.pos = pos 69 | @len = @tmp.size 70 | end 71 | 72 | # :call-seq: 73 | # ios.read([length [, buffer ]]) => string, buffer, or nil 74 | # 75 | # Reads at most length bytes from the I/O stream, or to the end of 76 | # file if length is omitted or is nil. length must be a non-negative 77 | # integer or nil. If the optional buffer argument is present, it 78 | # must reference a String, which will receive the data. 79 | # 80 | # At end of file, it returns nil or "" depend on length. 81 | # ios.read() and ios.read(nil) returns "". 82 | # ios.read(length [, buffer]) returns nil. 83 | # 84 | # If the Content-Length of the HTTP request is known (as is the common 85 | # case for POST requests), then ios.read(length [, buffer]) will block 86 | # until the specified length is read (or it is the last chunk). 87 | # Otherwise, for uncommon "Transfer-Encoding: chunked" requests, 88 | # ios.read(length [, buffer]) will return immediately if there is 89 | # any data and only block when nothing is available (providing 90 | # IO#readpartial semantics). 91 | def read(*args) 92 | @socket ? tee(super) : @tmp.read(*args) 93 | end 94 | 95 | # :call-seq: 96 | # ios.gets => string or nil 97 | # 98 | # Reads the next ``line'' from the I/O stream; lines are separated 99 | # by the global record separator ($/, typically "\n"). A global 100 | # record separator of nil reads the entire unread contents of ios. 101 | # Returns nil if called at the end of file. 102 | # This takes zero arguments for strict Rack::Lint compatibility, 103 | # unlike IO#gets. 104 | def gets 105 | @socket ? tee(super) : @tmp.gets 106 | end 107 | 108 | # :call-seq: 109 | # ios.rewind => 0 110 | # 111 | # Positions the *ios* pointer to the beginning of input, returns 112 | # the offset (zero) of the +ios+ pointer. Subsequent reads will 113 | # start from the beginning of the previously-buffered input. 114 | def rewind 115 | return 0 if 0 == @tmp.size 116 | consume! if @socket 117 | @tmp.rewind # Rack does not specify what the return value is here 118 | end 119 | 120 | private 121 | 122 | # consumes the stream of the socket 123 | def consume! 124 | junk = "" 125 | nil while read(@@io_chunk_size, junk) 126 | end 127 | 128 | def tee(buffer) 129 | @tmp.write(buffer) if buffer 130 | buffer 131 | end 132 | end 133 | -------------------------------------------------------------------------------- /t/my-tap-lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (c) 2009, 2010 Eric Wong 3 | # 4 | # TAP-producing shell library for POSIX-compliant Bourne shells We do 5 | # not _rely_ on Bourne Again features, though we will use "set -o 6 | # pipefail" from ksh93 or bash 3 if available 7 | # 8 | # Only generic, non-project/non-language-specific stuff goes here. We 9 | # only have POSIX dependencies for the core tests (without --verbose), 10 | # though we'll enable useful non-POSIX things if they're available. 11 | # 12 | # This test library is intentionally unforgiving, it does not support 13 | # skipping tests nor continuing after any failure. Any failures 14 | # immediately halt execution as do any references to undefined 15 | # variables. 16 | # 17 | # When --verbose is specified, we always prefix stdout/stderr 18 | # output with "#" to avoid confusing TAP consumers. Otherwise 19 | # the normal stdout/stderr streams are redirected to /dev/null 20 | 21 | # dup normal stdout(fd=1) and stderr (fd=2) to fd=3 and fd=4 respectively 22 | # normal TAP output goes to fd=3, nothing should go to fd=4 23 | exec 3>&1 4>&2 24 | 25 | # ensure a sane environment 26 | TZ=UTC LC_ALL=C LANG=C 27 | export LANG LC_ALL TZ 28 | unset CDPATH 29 | 30 | # pipefail is non-POSIX, but very useful in ksh93/bash 31 | ( set -o pipefail 2>/dev/null ) && set -o pipefail 32 | 33 | SED=${SED-sed} 34 | 35 | # Unlike other test frameworks, we are unforgiving and bail immediately 36 | # on any failures. We do this because we're lazy about error handling 37 | # and also because we believe anything broken should not be allowed to 38 | # propagate throughout the rest of the test 39 | set -e 40 | set -u 41 | 42 | # name of our test 43 | T=${0##*/} 44 | 45 | t_expect_nr=-1 46 | t_nr=0 47 | t_current= 48 | t_complete=false 49 | 50 | # list of files to remove unconditionally on exit 51 | T_RM_LIST= 52 | 53 | # list of files to remove only on successful exit 54 | T_OK_RM_LIST= 55 | 56 | # emit output to stdout, it'll be parsed by the TAP consumer 57 | # so it must be TAP-compliant output 58 | t_echo () { 59 | echo >&3 "$@" 60 | } 61 | 62 | # emits non-parsed information to stdout, it will be prefixed with a '#' 63 | # to not throw off TAP consumers 64 | t_info () { 65 | t_echo '#' "$@" 66 | } 67 | 68 | # exit with an error and print a diagnostic 69 | die () { 70 | echo >&2 "$@" 71 | exit 1 72 | } 73 | 74 | # our at_exit handler, it'll fire for all exits except SIGKILL (unavoidable) 75 | t_at_exit () { 76 | code=$? 77 | set +e 78 | if test $code -eq 0 79 | then 80 | $t_complete || { 81 | t_info "t_done not called" 82 | code=1 83 | } 84 | elif test -n "$t_current" 85 | then 86 | t_echo "not ok $t_nr - $t_current" 87 | fi 88 | if test $t_expect_nr -ne -1 89 | then 90 | test $t_expect_nr -eq $t_nr || { 91 | t_info "planned $t_expect_nr tests but ran $t_nr" 92 | test $code -ne 0 || code=1 93 | } 94 | fi 95 | $t_complete || { 96 | t_info "unexpected test failure" 97 | test $code -ne 0 || code=1 98 | } 99 | rm -f $T_RM_LIST 100 | test $code -eq 0 && rm -f $T_OK_RM_LIST 101 | set +x 102 | exec >&3 2>&4 103 | t_close_fds 104 | exit $code 105 | } 106 | 107 | # close test-specific extra file descriptors 108 | t_close_fds () { 109 | exec 3>&- 4>&- 110 | } 111 | 112 | # call this at the start of your test to specify the number of tests 113 | # you plan to run 114 | t_plan () { 115 | test "$1" -ge 1 || die "must plan at least one test" 116 | test $t_expect_nr -eq -1 || die "tried to plan twice in one test" 117 | t_expect_nr=$1 118 | shift 119 | t_echo 1..$t_expect_nr "#" "$@" 120 | trap t_at_exit EXIT 121 | } 122 | 123 | _t_checkup () { 124 | test $t_expect_nr -le 0 && die "no tests planned" 125 | test -n "$t_current" && t_echo "ok $t_nr - $t_current" 126 | true 127 | } 128 | 129 | # finalizes any previously test and starts a new one 130 | t_begin () { 131 | _t_checkup 132 | t_nr=$(( $t_nr + 1 )) 133 | t_current="$1" 134 | 135 | # just in case somebody wanted to cheat us: 136 | set -e 137 | } 138 | 139 | # finalizes the current test without starting a new one 140 | t_end () { 141 | _t_checkup 142 | t_current= 143 | } 144 | 145 | # run this to signify the end of your test 146 | t_done () { 147 | _t_checkup 148 | t_current= 149 | t_complete=true 150 | test $t_expect_nr -eq $t_nr || exit 1 151 | exit 0 152 | } 153 | 154 | # create and assign named-pipes to variable _names_ passed to this function 155 | t_fifos () { 156 | for _id in "$@" 157 | do 158 | _name=$_id 159 | _tmp=$(mktemp -t $T.$$.$_id.XXXXXXXX) 160 | eval "$_id=$_tmp" 161 | rm -f $_tmp 162 | mkfifo $_tmp 163 | T_RM_LIST="$T_RM_LIST $_tmp" 164 | done 165 | } 166 | 167 | t_verbose=false t_trace=false 168 | 169 | while test "$#" -ne 0 170 | do 171 | arg="$1" 172 | shift 173 | case $arg in 174 | -v|--verbose) t_verbose=true ;; 175 | --trace) t_trace=true t_verbose=true ;; 176 | *) die "Unknown option: $arg" ;; 177 | esac 178 | done 179 | 180 | # we always only setup stdout, nothing should end up in the "real" stderr 181 | if $t_verbose 182 | then 183 | if test x"$(which mktemp 2>/dev/null)" = x 184 | then 185 | die "mktemp(1) not available for --verbose" 186 | fi 187 | t_fifos t_stdout t_stderr 188 | 189 | ( 190 | # use a subshell so seds are not waitable 191 | $SED -e 's/^/#: /' < $t_stdout & 192 | $SED -e 's/^/#! /' < $t_stderr & 193 | ) & 194 | wait 195 | exec > $t_stdout 2> $t_stderr 196 | else 197 | exec > /dev/null 2> /dev/null 198 | fi 199 | 200 | $t_trace && set -x 201 | true 202 | -------------------------------------------------------------------------------- /test/unit/test_socket_helper.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | require './test/test_helper' 5 | require 'tempfile' 6 | 7 | class TestSocketHelper < Test::Unit::TestCase 8 | include Unicorn::SocketHelper 9 | attr_reader :logger 10 | GET_SLASH = "GET / HTTP/1.0\r\n\r\n".freeze 11 | 12 | def setup 13 | @log_tmp = Tempfile.new 'logger' 14 | @logger = Logger.new(@log_tmp.path) 15 | @test_addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' 16 | @test6_addr = ENV['UNICORN_TEST6_ADDR'] || '::1' 17 | GC.disable 18 | end 19 | 20 | def teardown 21 | GC.enable 22 | end 23 | 24 | def test_bind_listen_tcp 25 | port = unused_port @test_addr 26 | @tcp_listener_name = "#@test_addr:#{port}" 27 | @tcp_listener = bind_listen(@tcp_listener_name) 28 | assert Socket === @tcp_listener 29 | assert @tcp_listener.local_address.ip? 30 | assert_equal @tcp_listener_name, sock_name(@tcp_listener) 31 | end 32 | 33 | def test_bind_listen_options 34 | port = unused_port @test_addr 35 | tcp_listener_name = "#@test_addr:#{port}" 36 | tmp = Tempfile.new 'unix.sock' 37 | unix_listener_name = tmp.path 38 | File.unlink(tmp.path) 39 | [ { :backlog => 5 }, { :sndbuf => 4096 }, { :rcvbuf => 4096 }, 40 | { :backlog => 16, :rcvbuf => 4096, :sndbuf => 4096 } 41 | ].each do |opts| 42 | tcp_listener = bind_listen(tcp_listener_name, opts) 43 | assert tcp_listener.local_address.ip? 44 | tcp_listener.close 45 | unix_listener = bind_listen(unix_listener_name, opts) 46 | assert unix_listener.local_address.unix? 47 | unix_listener.close 48 | end 49 | end 50 | 51 | def test_bind_listen_unix 52 | old_umask = File.umask(0777) 53 | tmp = Tempfile.new 'unix.sock' 54 | @unix_listener_path = tmp.path 55 | File.unlink(@unix_listener_path) 56 | @unix_listener = bind_listen(@unix_listener_path) 57 | assert Socket === @unix_listener 58 | assert @unix_listener.local_address.unix? 59 | assert_equal @unix_listener_path, sock_name(@unix_listener) 60 | assert File.readable?(@unix_listener_path), "not readable" 61 | assert File.writable?(@unix_listener_path), "not writable" 62 | assert_equal 0777, File.umask 63 | assert_equal @unix_listener, bind_listen(@unix_listener) 64 | ensure 65 | File.umask(old_umask) 66 | end 67 | 68 | def test_bind_listen_unix_umask 69 | old_umask = File.umask(0777) 70 | tmp = Tempfile.new 'unix.sock' 71 | @unix_listener_path = tmp.path 72 | File.unlink(@unix_listener_path) 73 | @unix_listener = bind_listen(@unix_listener_path, :umask => 077) 74 | assert_equal @unix_listener_path, sock_name(@unix_listener) 75 | assert_equal 0140700, File.stat(@unix_listener_path).mode 76 | assert_equal 0777, File.umask 77 | ensure 78 | File.umask(old_umask) 79 | end 80 | 81 | def test_bind_listen_unix_rebind 82 | test_bind_listen_unix 83 | new_listener = nil 84 | assert_raises(Errno::EADDRINUSE) do 85 | new_listener = bind_listen(@unix_listener_path) 86 | end 87 | 88 | File.unlink(@unix_listener_path) 89 | new_listener = bind_listen(@unix_listener_path) 90 | 91 | assert new_listener.fileno != @unix_listener.fileno 92 | assert_equal sock_name(new_listener), sock_name(@unix_listener) 93 | assert_equal @unix_listener_path, sock_name(new_listener) 94 | pid = fork do 95 | begin 96 | client, _ = new_listener.accept 97 | client.syswrite('abcde') 98 | exit 0 99 | rescue => e 100 | warn "#{e.message} (#{e.class})" 101 | exit 1 102 | end 103 | end 104 | s = unix_socket(@unix_listener_path) 105 | IO.select([s]) 106 | assert_equal 'abcde', s.sysread(5) 107 | pid, status = Process.waitpid2(pid) 108 | assert status.success? 109 | end 110 | 111 | def test_tcp_defer_accept_default 112 | return unless defined?(TCP_DEFER_ACCEPT) 113 | port = unused_port @test_addr 114 | name = "#@test_addr:#{port}" 115 | sock = bind_listen(name) 116 | cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] 117 | assert cur >= 1 118 | end 119 | 120 | def test_tcp_defer_accept_disable 121 | return unless defined?(TCP_DEFER_ACCEPT) 122 | port = unused_port @test_addr 123 | name = "#@test_addr:#{port}" 124 | sock = bind_listen(name, :tcp_defer_accept => false) 125 | cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] 126 | assert_equal 0, cur 127 | end 128 | 129 | def test_tcp_defer_accept_nr 130 | return unless defined?(TCP_DEFER_ACCEPT) 131 | port = unused_port @test_addr 132 | name = "#@test_addr:#{port}" 133 | sock = bind_listen(name, :tcp_defer_accept => 60) 134 | cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] 135 | assert cur > 1 136 | end 137 | 138 | def test_ipv6only 139 | port = begin 140 | unused_port "#@test6_addr" 141 | rescue Errno::EINVAL 142 | return 143 | end 144 | sock = bind_listen "[#@test6_addr]:#{port}", :ipv6only => true 145 | cur = sock.getsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY).unpack('i')[0] 146 | assert_equal 1, cur 147 | rescue Errno::EAFNOSUPPORT 148 | end 149 | 150 | def test_reuseport 151 | return unless defined?(Socket::SO_REUSEPORT) 152 | port = unused_port @test_addr 153 | name = "#@test_addr:#{port}" 154 | sock = bind_listen(name, :reuseport => true) 155 | cur = sock.getsockopt(:SOL_SOCKET, :SO_REUSEPORT).int 156 | assert_operator cur, :>, 0 157 | rescue Errno::ENOPROTOOPT 158 | # kernel does not support SO_REUSEPORT (older Linux) 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /lib/unicorn/cgi_wrapper.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # frozen_string_literal: false 3 | 4 | # :enddoc: 5 | # This code is based on the original CGIWrapper from Mongrel 6 | # Copyright (c) 2005 Zed A. Shaw 7 | # Copyright (c) 2009 Eric Wong 8 | # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or 9 | # the GPLv2+ (GPLv3+ preferred) 10 | # 11 | # Additional work donated by contributors. See CONTRIBUTORS for more info. 12 | 13 | require 'cgi' 14 | 15 | module Unicorn; end 16 | 17 | # The beginning of a complete wrapper around Unicorn's internal HTTP 18 | # processing system but maintaining the original Ruby CGI module. Use 19 | # this only as a crutch to get existing CGI based systems working. It 20 | # should handle everything, but please notify us if you see special 21 | # warnings. This work is still very alpha so we need testers to help 22 | # work out the various corner cases. 23 | class Unicorn::CGIWrapper < ::CGI 24 | undef_method :env_table 25 | attr_reader :env_table 26 | attr_reader :body 27 | 28 | # these are stripped out of any keys passed to CGIWrapper.header function 29 | NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless 30 | CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this? 31 | CHARSET = 'charset'.freeze # this gets appended to Content-Type 32 | COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers 33 | STATUS = 'status'.freeze # stored as @status 34 | Status = 'Status'.freeze # code + human-readable text, Rails sets this 35 | 36 | # some of these are common strings, but this is the only module 37 | # using them and the reason they're not in Unicorn::Const 38 | SET_COOKIE = 'Set-Cookie'.freeze 39 | CONTENT_TYPE = 'Content-Type'.freeze 40 | CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH 41 | RACK_INPUT = 'rack.input'.freeze 42 | RACK_ERRORS = 'rack.errors'.freeze 43 | 44 | # this maps CGI header names to HTTP header names 45 | HEADER_MAP = { 46 | 'status' => Status, 47 | 'type' => CONTENT_TYPE, 48 | 'server' => 'Server'.freeze, 49 | 'language' => 'Content-Language'.freeze, 50 | 'expires' => 'Expires'.freeze, 51 | 'length' => CONTENT_LENGTH, 52 | } 53 | 54 | # Takes an a Rackable environment, plus any additional CGI.new 55 | # arguments These are used internally to create a wrapper around the 56 | # real CGI while maintaining Rack/Unicorn's view of the world. This 57 | # this will NOT deal well with large responses that take up a lot of 58 | # memory, but neither does the CGI nor the original CGIWrapper from 59 | # Mongrel... 60 | def initialize(rack_env, *args) 61 | @env_table = rack_env 62 | @status = nil 63 | @head = {} 64 | @headv = Hash.new { |hash,key| hash[key] = [] } 65 | @body = StringIO.new("") 66 | super(*args) 67 | end 68 | 69 | # finalizes the response in a way Rack applications would expect 70 | def rack_response 71 | # @head[CONTENT_LENGTH] ||= @body.size 72 | @headv[SET_COOKIE].concat(@output_cookies) if @output_cookies 73 | @headv.each_pair do |key,value| 74 | @head[key] ||= value.join("\n") unless value.empty? 75 | end 76 | 77 | # Capitalized "Status:", with human-readable status code (e.g. "200 OK") 78 | @status ||= @head.delete(Status) 79 | 80 | [ @status || 500, @head, [ @body.string ] ] 81 | end 82 | 83 | # The header is typically called to send back the header. In our case we 84 | # collect it into a hash for later usage. This can be called multiple 85 | # times to set different cookies. 86 | def header(options = "text/html") 87 | # if they pass in a string then just write the Content-Type 88 | if String === options 89 | @head[CONTENT_TYPE] ||= options 90 | else 91 | HEADER_MAP.each_pair do |from, to| 92 | from = options.delete(from) or next 93 | @head[to] = from.to_s 94 | end 95 | 96 | @head[CONTENT_TYPE] ||= "text/html" 97 | if charset = options.delete(CHARSET) 98 | @head[CONTENT_TYPE] << "; charset=#{charset}" 99 | end 100 | 101 | # lots of ways to set cookies 102 | if cookie = options.delete(COOKIE) 103 | set_cookies = @headv[SET_COOKIE] 104 | case cookie 105 | when Array 106 | cookie.each { |c| set_cookies << c.to_s } 107 | when Hash 108 | cookie.each_value { |c| set_cookies << c.to_s } 109 | else 110 | set_cookies << cookie.to_s 111 | end 112 | end 113 | @status ||= options.delete(STATUS) # all lower-case 114 | 115 | # drop the keys we don't want anymore 116 | options.delete(NPH) 117 | options.delete(CONNECTION) 118 | 119 | # finally, set the rest of the headers as-is, allowing duplicates 120 | options.each_pair { |k,v| @headv[k] << v } 121 | end 122 | 123 | # doing this fakes out the cgi library to think the headers are empty 124 | # we then do the real headers in the out function call later 125 | "" 126 | end 127 | 128 | # The dumb thing is people can call header or this or both and in 129 | # any order. So, we just reuse header and then finalize the 130 | # HttpResponse the right way. This will have no effect if called 131 | # the second time if the first "outputted" anything. 132 | def out(options = "text/html") 133 | header(options) 134 | @body.size == 0 or return 135 | @body << yield if block_given? 136 | end 137 | 138 | # Used to wrap the normal stdinput variable used inside CGI. 139 | def stdinput 140 | @env_table[RACK_INPUT] 141 | end 142 | 143 | # return a pointer to the StringIO body since it's STDOUT-like 144 | def stdoutput 145 | @body 146 | end 147 | 148 | end 149 | -------------------------------------------------------------------------------- /TUNING: -------------------------------------------------------------------------------- 1 | = Tuning unicorn 2 | 3 | unicorn performance is generally as good as a (mostly) Ruby web server 4 | can provide. Most often the performance bottleneck is in the web 5 | application running on Unicorn rather than Unicorn itself. 6 | 7 | == unicorn Configuration 8 | 9 | See Unicorn::Configurator for details on the config file format. 10 | +worker_processes+ is the most-commonly needed tuning parameter. 11 | 12 | === Unicorn::Configurator#worker_processes 13 | 14 | * worker_processes should be scaled to the number of processes your 15 | backend system(s) can support. DO NOT scale it to the number of 16 | external network clients your application expects to be serving. 17 | unicorn is NOT for serving slow clients, that is the job of nginx. 18 | 19 | * worker_processes should be *at* *least* the number of CPU cores on 20 | a dedicated server (unless you do not have enough memory). 21 | If your application has occasionally slow responses that are /not/ 22 | CPU-intensive, you may increase this to workaround those inefficiencies. 23 | 24 | * Under Ruby 2.2 or later, Etc.nprocessors may be used to determine 25 | the number of CPU cores present. 26 | 27 | * worker_processes may be increased for Unicorn::OobGC users to provide 28 | more consistent response times. 29 | 30 | * Never, ever, increase worker_processes to the point where the system 31 | runs out of physical memory and hits swap. Production servers should 32 | never see heavy swap activity. 33 | 34 | === Unicorn::Configurator#listen Options 35 | 36 | * Setting a very low value for the :backlog parameter in "listen" 37 | directives can allow failover to happen more quickly if your 38 | cluster is configured for it. 39 | 40 | * If you're doing extremely simple benchmarks and getting connection 41 | errors under high request rates, increasing your :backlog parameter 42 | above the already-generous default of 1024 can help avoid connection 43 | errors. Keep in mind this is not recommended for real traffic if 44 | you have another machine to failover to (see above). 45 | 46 | * :rcvbuf and :sndbuf parameters generally do not need to be set for TCP 47 | listeners under Linux 2.6 because auto-tuning is enabled. UNIX domain 48 | sockets do not have auto-tuning buffer sizes; so increasing those will 49 | allow syscalls and task switches to be saved for larger requests 50 | and responses. If your app only generates small responses or expects 51 | small requests, you may shrink the buffer sizes to save memory, too. 52 | 53 | * Having socket buffers too large can also be detrimental or have 54 | little effect. Huge buffers can put more pressure on the allocator 55 | and may also thrash CPU caches, cancelling out performance gains 56 | one would normally expect. 57 | 58 | * UNIX domain sockets are slightly faster than TCP sockets, but only 59 | work if nginx is on the same machine. 60 | 61 | == Other unicorn settings 62 | 63 | * Setting "preload_app true" can allow copy-on-write-friendly GC to 64 | be used to save memory. It will probably not work out of the box with 65 | applications that open sockets or perform random I/O on files. 66 | Databases like TokyoCabinet use concurrency-safe pread()/pwrite() 67 | functions for safe sharing of database file descriptors across 68 | processes. 69 | 70 | * On POSIX-compliant filesystems, it is safe for multiple threads or 71 | processes to append to one log file as long as all the processes are 72 | have them unbuffered (File#sync = true) or they are 73 | record(line)-buffered in userspace before any writes. 74 | 75 | == Kernel Parameters (Linux sysctl and sysfs) 76 | 77 | WARNING: Do not change system parameters unless you know what you're doing! 78 | 79 | * Transparent hugepages (THP) improves performance in many cases, 80 | but can also increase memory use when relying on a 81 | copy-on-write(CoW)-friendly GC (Ruby 2.0+) with "preload_app true". 82 | CoW operates at the page level, so writing to a huge page would 83 | trigger a 2 MB copy (x86-64), as opposed to a 4 KB copy on a 84 | regular (non-huge) page. 85 | 86 | Consider only allowing THP to be used when it is requested via the 87 | madvise(2) syscall: 88 | 89 | echo madvise >/sys/kernel/mm/transparent_hugepage/enabled 90 | 91 | Or disabling it system-wide, via "never". 92 | 93 | n.b. "page" in this context only applies to the OS kernel, 94 | Ruby GC implementations also use this term for the same concept 95 | in a way that is agnostic to the OS. 96 | 97 | * net.core.rmem_max and net.core.wmem_max can increase the allowed 98 | size of :rcvbuf and :sndbuf respectively. This is mostly only useful 99 | for UNIX domain sockets which do not have auto-tuning buffer sizes. 100 | 101 | * For load testing/benchmarking with UNIX domain sockets, you should 102 | consider increasing net.core.somaxconn or else nginx will start 103 | failing to connect under heavy load. You may also consider setting 104 | a higher :backlog to listen on as noted earlier. 105 | 106 | * If you're running out of local ports, consider lowering 107 | net.ipv4.tcp_fin_timeout to 20-30 (default: 60 seconds). Also 108 | consider widening the usable port range by changing 109 | net.ipv4.ip_local_port_range. 110 | 111 | * Setting net.ipv4.tcp_timestamps=1 will also allow setting 112 | net.ipv4.tcp_tw_reuse=1 and net.ipv4.tcp_tw_recycle=1, which along 113 | with the above settings can slow down port exhaustion. Not all 114 | networks are compatible with these settings, check with your friendly 115 | network administrator before changing these. 116 | 117 | * Increasing the MTU size can reduce framing overhead for larger 118 | transfers. One often-overlooked detail is that the loopback 119 | device (usually "lo") can have its MTU increased, too. 120 | -------------------------------------------------------------------------------- /SIGNALS: -------------------------------------------------------------------------------- 1 | == Signal handling 2 | 3 | In general, signals need only be sent to the master process. However, 4 | the signals Unicorn uses internally to communicate with the worker 5 | processes are documented here as well. With the exception of TTIN/TTOU, 6 | signal handling matches the behavior of {nginx}[http://nginx.org/] so it 7 | should be possible to easily share process management scripts between 8 | Unicorn and nginx. 9 | 10 | One example init script is distributed with unicorn: 11 | https://yhbt.net/unicorn/examples/init.sh 12 | 13 | === Master Process 14 | 15 | * HUP - reloads config file and gracefully restart all workers. 16 | If the "preload_app" directive is false (the default), then workers 17 | will also pick up any application code changes when restarted. If 18 | "preload_app" is true, then application code changes will have no 19 | effect; USR2 + QUIT (see below) must be used to load newer code in 20 | this case. When reloading the application, +Gem.refresh+ will 21 | be called so updated code for your application can pick up newly 22 | installed RubyGems. It is not recommended that you uninstall 23 | libraries your application depends on while Unicorn is running, 24 | as respawned workers may enter a spawn loop when they fail to 25 | load an uninstalled dependency. 26 | 27 | * INT/TERM - quick shutdown, kills all workers immediately 28 | 29 | * QUIT - graceful shutdown, waits for workers to finish their 30 | current request before finishing. 31 | 32 | * USR1 - reopen all logs owned by the master and all workers 33 | See Unicorn::Util.reopen_logs for what is considered a log. 34 | 35 | * USR2 - reexecute the running binary. A separate QUIT 36 | should be sent to the original process once the child is verified to 37 | be up and running. 38 | 39 | * WINCH - gracefully stops workers but keep the master running. 40 | This will only work for daemonized processes. 41 | 42 | * TTIN - increment the number of worker processes by one 43 | 44 | * TTOU - decrement the number of worker processes by one 45 | 46 | === Worker Processes 47 | 48 | Note: as of unicorn 4.8, the master uses a pipe to signal workers 49 | instead of kill(2) for most cases. Using signals still (and works and 50 | remains supported for external tools/libraries), however. 51 | 52 | Sending signals directly to the worker processes should not normally be 53 | needed. If the master process is running, any exited worker will be 54 | automatically respawned. 55 | 56 | * INT/TERM - Quick shutdown, immediately exit. 57 | Unless WINCH has been sent to the master (or the master is killed), 58 | the master process will respawn a worker to replace this one. 59 | Immediate shutdown is still triggered using kill(2) and not the 60 | internal pipe as of unicorn 4.8 61 | 62 | * QUIT - Gracefully exit after finishing the current request. 63 | Unless WINCH has been sent to the master (or the master is killed), 64 | the master process will respawn a worker to replace this one. 65 | 66 | * USR1 - Reopen all logs owned by the worker process. 67 | See Unicorn::Util.reopen_logs for what is considered a log. 68 | Log files are not reopened until it is done processing 69 | the current request, so multiple log lines for one request 70 | (as done by Rails) will not be split across multiple logs. 71 | 72 | It is NOT recommended to send the USR1 signal directly to workers via 73 | "killall -USR1 unicorn" if you are using user/group-switching support 74 | in your workers. You will encounter incorrect file permissions and 75 | workers will need to be respawned. Sending USR1 to the master process 76 | first will ensure logs have the correct permissions before the master 77 | forwards the USR1 signal to workers. 78 | 79 | === Procedure to replace a running unicorn executable 80 | 81 | You may replace a running instance of unicorn with a new one without 82 | losing any incoming connections. Doing so will reload all of your 83 | application code, Unicorn config, Ruby executable, and all libraries. 84 | The only things that will not change (due to OS limitations) are: 85 | 86 | 1. The path to the unicorn executable script. If you want to change to 87 | a different installation of Ruby, you can modify the shebang 88 | line to point to your alternative interpreter. 89 | 90 | The procedure is exactly like that of nginx: 91 | 92 | 1. Send USR2 to the master process 93 | 94 | 2. Check your process manager or pid files to see if a new master spawned 95 | successfully. If you're using a pid file, the old process will have 96 | ".oldbin" appended to its path. You should have two master instances 97 | of unicorn running now, both of which will have workers servicing 98 | requests. Your process tree should look something like this: 99 | 100 | unicorn master (old) 101 | \_ unicorn worker[0] 102 | \_ unicorn worker[1] 103 | \_ unicorn worker[2] 104 | \_ unicorn worker[3] 105 | \_ unicorn master 106 | \_ unicorn worker[0] 107 | \_ unicorn worker[1] 108 | \_ unicorn worker[2] 109 | \_ unicorn worker[3] 110 | 111 | 3. You can now send WINCH to the old master process so only the new workers 112 | serve requests. If your unicorn process is bound to an interactive 113 | terminal (not daemonized), you can skip this step. Step 5 will be more 114 | difficult but you can also skip it if your process is not daemonized. 115 | 116 | 4. You should now ensure that everything is running correctly with the 117 | new workers as the old workers die off. 118 | 119 | 5. If everything seems ok, then send QUIT to the old master. You're done! 120 | 121 | If something is broken, then send HUP to the old master to reload 122 | the config and restart its workers. Then send QUIT to the new master 123 | process. 124 | --------------------------------------------------------------------------------