├── archive ├── .gitignore └── slrnpull.conf ├── Documentation └── .gitignore ├── t ├── .gitignore ├── pid.ru ├── env.ru ├── GNUmakefile ├── listener_names.ru ├── t0014.ru ├── t0013.ru ├── fails-rack-lint.ru ├── detach.ru ├── broken-app.ru ├── t0301.ru ├── client_body_buffer_size.ru ├── reopen-logs.ru ├── heartbeat-timeout.ru ├── t0300-no-default-middleware.sh ├── oob_gc.ru ├── oob_gc_path.ru ├── 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 ├── t0009-broken-app.sh ├── t9001-oob_gc.sh ├── bin │ └── unused_listen ├── README ├── reload-bad-config.t ├── heartbeat-timeout.t ├── winch_ttin.t ├── t0012-reload-empty-config.sh ├── t0008-back_out_of_upgrade.sh ├── t9002-oob_gc-path.sh ├── working_directory.t ├── client_body_buffer_size.t ├── test-lib.sh ├── integration.ru ├── active-unix-socket.t └── 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_ccc.rb │ └── test_util.rb ├── lib ├── unicorn │ ├── write_splat.rb │ ├── 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 │ └── worker.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 ├── HACKING ├── Sandbox ├── bin └── unicorn ├── 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 | * Documentation improvements 2 | 3 | * improve test suite 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.gemspec diff=ruby 2 | *.rb diff=ruby 3 | *.ru diff=ruby 4 | Rakefile diff=ruby 5 | bin/* diff=ruby 6 | -------------------------------------------------------------------------------- /t/pid.ru: -------------------------------------------------------------------------------- 1 | use Rack::ContentLength 2 | use Rack::ContentType, "text/plain" 3 | run lambda { |env| [ 200, {}, [ "#$$\n" ] ] } 4 | -------------------------------------------------------------------------------- /t/env.ru: -------------------------------------------------------------------------------- 1 | use Rack::ContentLength 2 | use Rack::ContentType, "text/plain" 3 | run lambda { |env| [ 200, {}, [ env.inspect << "\n" ] ] } 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/big_app_gc.rb: -------------------------------------------------------------------------------- 1 | # see {Unicorn::OobGC}[https://yhbt.net/unicorn/Unicorn/OobGC.html] 2 | # Unicorn::OobGC was broken in Unicorn v3.3.1 - v3.6.1 and fixed in v3.6.2 3 | -------------------------------------------------------------------------------- /t/listener_names.ru: -------------------------------------------------------------------------------- 1 | use Rack::ContentLength 2 | use Rack::ContentType, "text/plain" 3 | names = Unicorn.listener_names.inspect # rely on preload_app=true 4 | run(lambda { |_| [ 200, {}, [ names ] ] }) 5 | -------------------------------------------------------------------------------- /test/benchmark/stack.ru: -------------------------------------------------------------------------------- 1 | run(lambda { |env| 2 | body = "#{caller.size}\n" 3 | h = { 4 | "Content-Length" => body.size.to_s, 5 | "Content-Type" => "text/plain", 6 | } 7 | [ 200, h, [ body ] ] 8 | }) 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/unicorn/write_splat.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # compatibility module for Ruby <= 2.4, remove when we go Ruby 2.5+ 3 | module Unicorn::WriteSplat # :nodoc: 4 | def write(*arg) # :nodoc: 5 | super(arg.join('')) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /t/t0014.ru: -------------------------------------------------------------------------------- 1 | #\ -E none 2 | use Rack::ContentLength 3 | use Rack::ContentType, 'text/plain' 4 | app = lambda do |env| 5 | case env['rack.input'] 6 | when Unicorn::TeeInput 7 | [ 200, {}, %w(OK) ] 8 | else 9 | [ 500, {}, %w(NO) ] 10 | end 11 | end 12 | run app 13 | -------------------------------------------------------------------------------- /t/t0013.ru: -------------------------------------------------------------------------------- 1 | #\ -E none 2 | use Rack::ContentLength 3 | use Rack::ContentType, 'text/plain' 4 | app = lambda do |env| 5 | case env['rack.input'] 6 | when Unicorn::StreamInput 7 | [ 200, {}, %w(OK) ] 8 | else 9 | [ 500, {}, %w(NO) ] 10 | end 11 | end 12 | run app 13 | -------------------------------------------------------------------------------- /lib/unicorn/select_waiter.rb: -------------------------------------------------------------------------------- 1 | # fallback for non-Linux and Linux <4.5 systems w/o EPOLLEXCLUSIVE 2 | class Unicorn::SelectWaiter # :nodoc: 3 | def get_readers(ready, readers, timeout) # :nodoc: 4 | ret = IO.select(readers, nil, nil, timeout) and ready.replace(ret[0]) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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/fails-rack-lint.ru: -------------------------------------------------------------------------------- 1 | # This rack app returns an invalid status code, which will cause 2 | # Rack::Lint to throw an exception if it is present. This 3 | # is used to check whether Rack::Lint is in the stack or not. 4 | 5 | run lambda {|env| return [42, {}, ["Rack::Lint wasn't there if you see this"]]} 6 | -------------------------------------------------------------------------------- /t/detach.ru: -------------------------------------------------------------------------------- 1 | use Rack::ContentType, "text/plain" 2 | fifo_path = ENV["TEST_FIFO"] or abort "TEST_FIFO not set" 3 | run lambda { |env| 4 | pid = fork do 5 | File.open(fifo_path, "wb") do |fp| 6 | fp.write "HIHI" 7 | end 8 | end 9 | Process.detach(pid) 10 | [ 200, {}, [ pid.to_s ] ] 11 | } 12 | -------------------------------------------------------------------------------- /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/broken-app.ru: -------------------------------------------------------------------------------- 1 | # we do not want Rack::Lint or anything to protect us 2 | use Rack::ContentLength 3 | use Rack::ContentType, "text/plain" 4 | map "/" do 5 | run lambda { |env| [ 200, {}, [ "OK\n" ] ] } 6 | end 7 | map "/raise" do 8 | run lambda { |env| raise "BAD" } 9 | end 10 | map "/nil" do 11 | run lambda { |env| nil } 12 | end 13 | -------------------------------------------------------------------------------- /t/t0301.ru: -------------------------------------------------------------------------------- 1 | #\-N --debug 2 | run(lambda do |env| 3 | case env['PATH_INFO'] 4 | when '/vars' 5 | b = "debug=#{$DEBUG.inspect}\n" \ 6 | "lint=#{caller.grep(%r{rack/lint\.rb})[0].split(':')[0]}\n" 7 | end 8 | h = { 9 | 'content-length' => b.size.to_s, 10 | 'content-type' => 'text/plain', 11 | } 12 | [ 200, h, [ b ] ] 13 | end) 14 | -------------------------------------------------------------------------------- /t/client_body_buffer_size.ru: -------------------------------------------------------------------------------- 1 | #\ -E none 2 | app = lambda do |env| 3 | input = env['rack.input'] 4 | case env["PATH_INFO"] 5 | when "/tmp_class" 6 | body = input.instance_variable_get(:@tmp).class.name 7 | when "/input_class" 8 | body = input.class.name 9 | else 10 | return [ 500, {}, [] ] 11 | end 12 | [ 200, {}, [ body ] ] 13 | end 14 | run app 15 | -------------------------------------------------------------------------------- /t/reopen-logs.ru: -------------------------------------------------------------------------------- 1 | use Rack::ContentLength 2 | use Rack::ContentType, "text/plain" 3 | run lambda { |env| 4 | 5 | # our File objects for stderr/stdout should always have #path 6 | # and be sync=true 7 | ok = $stderr.sync && 8 | $stdout.sync && 9 | String === $stderr.path && 10 | String === $stdout.path 11 | 12 | [ 200, {}, [ "#{ok}\n" ] ] 13 | } 14 | -------------------------------------------------------------------------------- /.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/heartbeat-timeout.ru: -------------------------------------------------------------------------------- 1 | use Rack::ContentLength 2 | headers = { 'content-type' => 'text/plain' } 3 | run lambda { |env| 4 | case env['PATH_INFO'] 5 | when "/block-forever" 6 | Process.kill(:STOP, $$) 7 | sleep # in case STOP signal is not received in time 8 | [ 500, headers, [ "Should never get here\n" ] ] 9 | else 10 | [ 200, headers, [ "#$$" ] ] 11 | end 12 | } 13 | -------------------------------------------------------------------------------- /test/aggregate.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby -n 2 | # -*- encoding: binary -*- 3 | 4 | BEGIN { $tests = $assertions = $failures = $errors = 0 } 5 | 6 | $_ =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ or next 7 | $tests += $1.to_i 8 | $assertions += $2.to_i 9 | $failures += $3.to_i 10 | $errors += $4.to_i 11 | 12 | END { 13 | printf("\n%d tests, %d assertions, %d failures, %d errors\n", 14 | $tests, $assertions, $failures, $errors) 15 | } 16 | -------------------------------------------------------------------------------- /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/oob_gc.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | require 'unicorn/oob_gc' 3 | use Rack::ContentLength 4 | use Rack::ContentType, "text/plain" 5 | use Unicorn::OobGC 6 | $gc_started = false 7 | 8 | # Mock GC.start 9 | def GC.start 10 | ObjectSpace.each_object(Kgio::Socket) do |x| 11 | x.closed? or abort "not closed #{x}" 12 | end 13 | $gc_started = true 14 | end 15 | run lambda { |env| 16 | if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"] 17 | $gc_started = false 18 | end 19 | [ 200, {}, [ "#$gc_started\n" ] ] 20 | } 21 | -------------------------------------------------------------------------------- /t/oob_gc_path.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | require 'unicorn/oob_gc' 3 | use Rack::ContentLength 4 | use Rack::ContentType, "text/plain" 5 | use Unicorn::OobGC, 5, /BAD/ 6 | $gc_started = false 7 | 8 | # Mock GC.start 9 | def GC.start 10 | ObjectSpace.each_object(Kgio::Socket) do |x| 11 | x.closed? or abort "not closed #{x}" 12 | end 13 | $gc_started = true 14 | end 15 | run lambda { |env| 16 | if "/gc_reset" == env["PATH_INFO"] && "POST" == env["REQUEST_METHOD"] 17 | $gc_started = false 18 | end 19 | [ 200, {}, [ "#$gc_started\n" ] ] 20 | } 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 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # optional rake-compiler support in case somebody needs to cross compile 2 | begin 3 | mk = "ext/unicorn_http/Makefile" 4 | if File.readable?(mk) 5 | warn "run 'gmake -C ext/unicorn_http clean' and\n" \ 6 | "remove #{mk} before using rake-compiler" 7 | elsif ENV['VERSION'] 8 | unless File.readable?("ext/unicorn_http/unicorn_http.c") 9 | abort "run 'gmake ragel' or 'make ragel' to generate the Ragel source" 10 | end 11 | spec = Gem::Specification.load('unicorn.gemspec') 12 | require 'rake/extensiontask' 13 | Rake::ExtensionTask.new('unicorn_http', spec) 14 | end 15 | rescue LoadError 16 | end 17 | -------------------------------------------------------------------------------- /examples/unicorn.conf.minimal.rb: -------------------------------------------------------------------------------- 1 | # Minimal sample configuration file for Unicorn (not Rack) when used 2 | # with daemonization (unicorn -D) started in your working directory. 3 | # 4 | # See https://yhbt.net/unicorn/Unicorn/Configurator.html for complete 5 | # documentation. 6 | # See also https://yhbt.net/unicorn/examples/unicorn.conf.rb for 7 | # a more verbose configuration using more features. 8 | 9 | listen 2007 # by default Unicorn listens on port 8080 10 | worker_processes 2 # this should be >= nr_cpus 11 | pid "/path/to/app/shared/pids/unicorn.pid" 12 | stderr_path "/path/to/app/shared/log/unicorn.log" 13 | stdout_path "/path/to/app/shared/log/unicorn.log" 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | # 3 | # Example application that echoes read data back to the HTTP client. 4 | # This emulates the old echo protocol people used to run. 5 | # 6 | # An example of using this in a client would be to run: 7 | # curl --no-buffer -T- http://host:port/ 8 | # 9 | # Then type random stuff in your terminal to watch it get echoed back! 10 | 11 | class EchoBody < Struct.new(:input) 12 | 13 | def each(&block) 14 | while buf = input.read(4096) 15 | yield buf 16 | end 17 | self 18 | end 19 | 20 | end 21 | 22 | run lambda { |env| 23 | /\A100-continue\z/i =~ env['HTTP_EXPECT'] and return [100, {}, []] 24 | [ 200, { 'Content-Type' => 'application/octet-stream' }, 25 | EchoBody.new(env['rack.input']) ] 26 | } 27 | -------------------------------------------------------------------------------- /lib/unicorn/const.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | 3 | module Unicorn::Const # :nodoc: 4 | # default TCP listen host address (0.0.0.0, all interfaces) 5 | DEFAULT_HOST = "0.0.0.0" 6 | 7 | # default TCP listen port (8080) 8 | DEFAULT_PORT = 8080 9 | 10 | # default TCP listen address and port (0.0.0.0:8080) 11 | DEFAULT_LISTEN = "#{DEFAULT_HOST}:#{DEFAULT_PORT}" 12 | 13 | # The basic request body size we'll try to read at once (16 kilobytes). 14 | CHUNK_SIZE = 16 * 1024 15 | 16 | # Maximum request body size before it is moved out of memory and into a 17 | # temporary file for reading (112 kilobytes). This is the default 18 | # value of client_body_buffer_size. 19 | MAX_BODY = 1024 * 112 20 | end 21 | require_relative 'version' 22 | -------------------------------------------------------------------------------- /t/preread_input.ru: -------------------------------------------------------------------------------- 1 | #\-E none 2 | require 'digest/md5' 3 | require 'unicorn/preread_input' 4 | use Unicorn::PrereadInput 5 | nr = 0 6 | run lambda { |env| 7 | $stderr.write "app dispatch: #{nr += 1}\n" 8 | input = env["rack.input"] 9 | dig = Digest::MD5.new 10 | if buf = input.read(16384) 11 | begin 12 | dig.update(buf) 13 | end while input.read(16384, buf) 14 | buf.clear # remove this call if Ruby ever gets escape analysis 15 | end 16 | if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i 17 | cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']] 18 | cmd5_bin = cmd5_b64.unpack('m')[0] 19 | return [500, {}, [ cmd5_b64 ] ] if cmd5_bin != dig.digest 20 | end 21 | [ 200, {}, [ dig.hexdigest ] ] 22 | } 23 | -------------------------------------------------------------------------------- /lib/unicorn/preread_input.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | 3 | module Unicorn 4 | # This middleware is used to ensure input is buffered to memory 5 | # or disk (depending on size) before the application is dispatched 6 | # by entirely consuming it (from TeeInput) beforehand. 7 | # 8 | # Usage (in config.ru): 9 | # 10 | # require 'unicorn/preread_input' 11 | # if defined?(Unicorn) 12 | # use Unicorn::PrereadInput 13 | # end 14 | # run YourApp.new 15 | class PrereadInput 16 | 17 | # :stopdoc: 18 | def initialize(app) 19 | @app = app 20 | end 21 | 22 | def call(env) 23 | buf = "" 24 | input = env["rack.input"] 25 | if input.respond_to?(:rewind) 26 | true while input.read(16384, buf) 27 | input.rewind 28 | end 29 | @app.call(env) 30 | end 31 | # :startdoc: 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/benchmark/dd.ru: -------------------------------------------------------------------------------- 1 | # This benchmark is the simplest test of the I/O facilities in 2 | # unicorn. It is meant to return a fixed-sized blob to test 3 | # the performance of things in Unicorn, _NOT_ the app. 4 | # 5 | # Adjusting this benchmark is done via the "bs" (byte size) and "count" 6 | # environment variables. "count" designates the count of elements of 7 | # "bs" length in the Rack response body. The defaults are bs=4096, count=1 8 | # to return one 4096-byte chunk. 9 | bs = ENV['bs'] ? ENV['bs'].to_i : 4096 10 | count = ENV['count'] ? ENV['count'].to_i : 1 11 | slice = (' ' * bs).freeze 12 | body = (1..count).map { slice }.freeze 13 | hdr = { 14 | 'Content-Length' => (bs * count).to_s.freeze, 15 | 'Content-Type' => 'text/plain'.freeze 16 | }.freeze 17 | response = [ 200, hdr, body ].freeze 18 | run(lambda { |env| response }) 19 | -------------------------------------------------------------------------------- /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/unit/test_droplet.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'unicorn' 3 | 4 | class TestDroplet < Test::Unit::TestCase 5 | def test_create_many_droplets 6 | now = Time.now.to_i 7 | (0..1024).each do |i| 8 | droplet = Unicorn::Worker.new(i) 9 | assert droplet.respond_to?(:tick) 10 | assert_equal 0, droplet.tick 11 | assert_equal(now, droplet.tick = now) 12 | assert_equal now, droplet.tick 13 | assert_equal(0, droplet.tick = 0) 14 | assert_equal 0, droplet.tick 15 | end 16 | end 17 | 18 | def test_shared_process 19 | droplet = Unicorn::Worker.new(0) 20 | _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) }) 21 | assert status.success?, status.inspect 22 | assert_equal 1, droplet.tick 23 | 24 | _, status = Process.waitpid2(fork { droplet.tick += 1; exit!(0) }) 25 | assert status.success?, status.inspect 26 | assert_equal 2, droplet.tick 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /examples/logger_mp_safe.rb: -------------------------------------------------------------------------------- 1 | # Multi-Processing-safe monkey patch for Logger 2 | # 3 | # This monkey patch fixes the case where "preload_app true" is used and 4 | # the application spawns a background thread upon being loaded. 5 | # 6 | # This removes all lock from the Logger code and solely relies on the 7 | # underlying filesystem to handle write(2) system calls atomically when 8 | # O_APPEND is used. This is safe in the presence of both multiple 9 | # threads (native or green) and multiple processes when writing to 10 | # a filesystem with POSIX O_APPEND semantics. 11 | # 12 | # It should be noted that the original locking on Logger could _never_ be 13 | # considered reliable on non-POSIX filesystems with multiple processes, 14 | # either, so nothing is lost in that case. 15 | 16 | require 'logger' 17 | class Logger::LogDevice 18 | def write(message) 19 | @dev.syswrite(message) 20 | end 21 | 22 | def close 23 | @dev.close 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/unicorn/tmpio.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # :stopdoc: 3 | require 'tmpdir' 4 | 5 | # some versions of Ruby had a broken Tempfile which didn't work 6 | # well with unlinked files. This one is much shorter, easier 7 | # to understand, and slightly faster. 8 | class Unicorn::TmpIO < File 9 | 10 | # creates and returns a new File object. The File is unlinked 11 | # immediately, switched to binary mode, and userspace output 12 | # buffering is disabled 13 | def self.new 14 | path = nil 15 | 16 | # workaround File#path being tainted: 17 | # https://bugs.ruby-lang.org/issues/14485 18 | fp = begin 19 | path = "#{Dir::tmpdir}/#{rand}" 20 | super(path, RDWR|CREAT|EXCL, 0600) 21 | rescue Errno::EEXIST 22 | retry 23 | end 24 | 25 | unlink(path) 26 | fp.binmode 27 | fp.sync = true 28 | fp 29 | end 30 | 31 | # pretend we're Tempfile for Rack::TempfileReaper 32 | alias close! close 33 | end 34 | -------------------------------------------------------------------------------- /.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 | 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 GPLv2+ (GPLv3+ preferred) 9 | # Additional work donated by contributors. See CONTRIBUTORS for more info. 10 | require 'unicorn/cgi_wrapper' 11 | require 'dispatcher' 12 | 13 | module Unicorn; module App; end; end 14 | 15 | # Implements a handler that can run Rails. 16 | class Unicorn::App::OldRails 17 | 18 | autoload :Static, "unicorn/app/old_rails/static" 19 | 20 | def call(env) 21 | cgi = Unicorn::CGIWrapper.new(env) 22 | begin 23 | Dispatcher.dispatch(cgi, 24 | ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, 25 | cgi.body) 26 | rescue => e 27 | err = env['rack.errors'] 28 | err.write("#{e} #{e.message}\n") 29 | e.backtrace.each { |line| err.write("#{line}\n") } 30 | end 31 | cgi.out # finalize the response 32 | cgi.rack_response 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /ext/unicorn_http/extconf.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | require 'mkmf' 3 | 4 | have_func("rb_hash_clear", "ruby.h") or abort 'Ruby 2.0+ required' 5 | 6 | message('checking if String#-@ (str_uminus) dedupes... ') 7 | begin 8 | a = -(%w(t e s t).join) 9 | b = -(%w(t e s t).join) 10 | if a.equal?(b) 11 | $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=1 ' 12 | message("yes\n") 13 | else 14 | $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 ' 15 | message("no, needs Ruby 2.5+\n") 16 | end 17 | rescue NoMethodError 18 | $CPPFLAGS += ' -DSTR_UMINUS_DEDUPE=0 ' 19 | message("no, String#-@ not available\n") 20 | end 21 | 22 | message('checking if Hash#[]= (rb_hash_aset) dedupes... ') 23 | h = {} 24 | x = {} 25 | r = rand.to_s 26 | h[%W(#{r}).join('')] = :foo 27 | x[%W(#{r}).join('')] = :foo 28 | if x.keys[0].equal?(h.keys[0]) 29 | $CPPFLAGS += ' -DHASH_ASET_DEDUPE=1 ' 30 | message("yes\n") 31 | else 32 | $CPPFLAGS += ' -DHASH_ASET_DEDUPE=0 ' 33 | message("no, needs Ruby 2.6+\n") 34 | end 35 | 36 | if have_func('epoll_create1', %w(sys/epoll.h)) 37 | have_func('rb_io_descriptor') # Ruby 3.1+ 38 | end 39 | create_makefile("unicorn_http") 40 | -------------------------------------------------------------------------------- /test/unit/test_waiter.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'unicorn' 3 | require 'unicorn/select_waiter' 4 | class TestSelectWaiter < Test::Unit::TestCase 5 | 6 | def test_select_timeout # n.b. this is level-triggered 7 | sw = Unicorn::SelectWaiter.new 8 | IO.pipe do |r,w| 9 | sw.get_readers(ready = [], [r], 0) 10 | assert_equal [], ready 11 | w.syswrite '.' 12 | sw.get_readers(ready, [r], 1000) 13 | assert_equal [r], ready 14 | sw.get_readers(ready, [r], 0) 15 | assert_equal [r], ready 16 | end 17 | end 18 | 19 | def test_linux # ugh, also level-triggered, unlikely to change 20 | IO.pipe do |r,w| 21 | wtr = Unicorn::Waiter.prep_readers([r]) 22 | wtr.get_readers(ready = [], [r], 0) 23 | assert_equal [], ready 24 | w.syswrite '.' 25 | wtr.get_readers(ready = [], [r], 1000) 26 | assert_equal [r], ready 27 | wtr.get_readers(ready = [], [r], 1000) 28 | assert_equal [r], ready, 'still ready (level-triggered :<)' 29 | assert_nil wtr.close 30 | end 31 | rescue SystemCallError => e 32 | warn "#{e.message} (#{e.class})" 33 | end if Unicorn.const_defined?(:Waiter) 34 | end 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 $u_conf = "$tmpdir/u.conf.rb"; 8 | my $out_log = "$tmpdir/out.log"; 9 | open my $fh, '>', $u_conf; 10 | print $fh < $srv } ); 17 | my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 18 | is($bdy, "true\n", 'logs opened'); 19 | 20 | rename($err_log, "$err_log.rot"); 21 | rename($out_log, "$out_log.rot"); 22 | 23 | $auto_reap->do_kill('USR1'); 24 | 25 | my $tries = 1000; 26 | while (!-f $err_log && --$tries) { sleep 0.01 }; 27 | while (!-f $out_log && --$tries) { sleep 0.01 }; 28 | 29 | ok(-f $out_log, 'stdout_path recreated after USR1'); 30 | ok(-f $err_log, 'stderr_path recreated after USR1'); 31 | 32 | ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 33 | is($bdy, "true\n", 'logs reopened with sync==true'); 34 | 35 | $auto_reap->join('QUIT'); 36 | is($?, 0, 'no error on exit'); 37 | check_stderr; 38 | undef $tmpdir; 39 | done_testing; 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/t0009-broken-app.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | 4 | t_plan 9 "graceful handling of broken apps" 5 | 6 | t_begin "setup and start" && { 7 | unicorn_setup 8 | unicorn -E none -D broken-app.ru -c $unicorn_config 9 | unicorn_wait_start 10 | } 11 | 12 | t_begin "normal response is alright" && { 13 | test xOK = x"$(curl -sSf http://$listen/)" 14 | } 15 | 16 | t_begin "app raised exception" && { 17 | curl -sSf http://$listen/raise 2> $tmp || : 18 | grep -F 500 $tmp 19 | > $tmp 20 | } 21 | 22 | t_begin "app exception logged and backtrace not swallowed" && { 23 | grep -F 'app error' $r_err 24 | grep -A1 -F 'app error' $r_err | tail -1 | grep broken-app.ru: 25 | dbgcat r_err 26 | > $r_err 27 | } 28 | 29 | t_begin "trigger bad response" && { 30 | curl -sSf http://$listen/nil 2> $tmp || : 31 | grep -F 500 $tmp 32 | > $tmp 33 | } 34 | 35 | t_begin "app exception logged" && { 36 | grep -F 'app error' $r_err 37 | > $r_err 38 | } 39 | 40 | t_begin "normal responses alright afterwards" && { 41 | > $tmp 42 | curl -sSf http://$listen/ >> $tmp & 43 | curl -sSf http://$listen/ >> $tmp & 44 | curl -sSf http://$listen/ >> $tmp & 45 | curl -sSf http://$listen/ >> $tmp & 46 | wait 47 | test xOK = x$(sort < $tmp | uniq) 48 | } 49 | 50 | t_begin "teardown" && { 51 | kill $unicorn_pid 52 | } 53 | 54 | t_begin "check stderr" && check_stderr 55 | 56 | t_done 57 | -------------------------------------------------------------------------------- /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 | # This app is intended to test large HTTP requests with or without 2 | # a fully-buffering reverse proxy such as nginx. Without a fully-buffering 3 | # reverse proxy, unicorn will be unresponsive when client count exceeds 4 | # worker_processes. 5 | 6 | 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 | my $u_conf = "$tmpdir/u.conf.rb"; 10 | 11 | open my $fh, '>', $ru; 12 | print $fh <<'EOM'; 13 | use Rack::ContentLength 14 | use Rack::ContentType, 'text/plain' 15 | config = ru = "hello world\n" # check for config variable conflicts, too 16 | run lambda { |env| [ 200, {}, [ ru.to_s ] ] } 17 | EOM 18 | close $fh; 19 | 20 | open $fh, '>', $u_conf; 21 | print $fh < $srv }); 28 | my ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 29 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid at start'); 30 | is($bdy, "hello world\n", 'body matches expected'); 31 | 32 | open $fh, '>>', $ru; 33 | say $fh '....this better be a syntax error in any version of ruby...'; 34 | close $fh; 35 | 36 | $ar->do_kill('HUP'); # reload 37 | my @l; 38 | for (1..1000) { 39 | @l = grep(/(?:done|error) reloading/, slurp($err_log)) and 40 | last; 41 | sleep 0.011; 42 | } 43 | diag slurp($err_log) if $ENV{V}; 44 | ok(grep(/error reloading/, @l), 'got error reloading'); 45 | open $fh, '>', $err_log; 46 | close $fh; 47 | 48 | ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 49 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid afte reload'); 50 | is($bdy, "hello world\n", 'body matches expected after reload'); 51 | 52 | check_stderr; 53 | undef $tmpdir; # quiet t/lib.perl END{} 54 | done_testing; 55 | -------------------------------------------------------------------------------- /.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 | # This app is intended to test large HTTP responses with or without 2 | # a fully-buffering reverse proxy such as nginx. Without a fully-buffering 3 | # reverse proxy, unicorn will be unresponsive when client count exceeds 4 | # worker_processes. 5 | # 6 | # To demonstrate how bad unicorn is at slowly reading clients: 7 | # 8 | # # in one terminal, start unicorn with one worker: 9 | # unicorn -E none -l 127.0.0.1:8080 test/benchmark/ddstream.ru 10 | # 11 | # # in a different terminal, start more slow curl processes than 12 | # # unicorn workers and watch time outputs 13 | # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null & 14 | # curl --limit-rate 8K --trace-time -vsN http://127.0.0.1:8080/ >/dev/null & 15 | # wait 16 | # 17 | # The last client won't see a response until the first one is done reading 18 | # 19 | # nginx note: do not change the default "proxy_buffering" behavior. 20 | # Setting "proxy_buffering off" prevents nginx from protecting unicorn. 21 | 22 | # totally standalone rack app to stream a giant response 23 | class BigResponse 24 | def initialize(bs, count) 25 | @buf = "#{bs.to_s(16)}\r\n#{' ' * bs}\r\n" 26 | @count = count 27 | @res = [ 200, 28 | { 'Transfer-Encoding' => -'chunked', 'Content-Type' => 'text/plain' }, 29 | self 30 | ] 31 | end 32 | 33 | # rack response body iterator 34 | def each 35 | (1..@count).each { yield @buf } 36 | yield -"0\r\n\r\n" 37 | end 38 | 39 | # rack app entry endpoint 40 | def call(_env) 41 | @res 42 | end 43 | end 44 | 45 | # default to a giant (128M) response because kernel socket buffers 46 | # can be ridiculously large on some systems 47 | bs = ENV['bs'] ? ENV['bs'].to_i : 65536 48 | count = ENV['count'] ? ENV['count'].to_i : 2048 49 | warn "serving response with bs=#{bs} count=#{count} (#{bs*count} bytes)" 50 | run BigResponse.new(bs, count) 51 | -------------------------------------------------------------------------------- /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 | open my $fh, '>', $u_conf; 10 | print $fh < $srv }); 19 | 20 | my ($status, $hdr, $wpid) = do_req($srv, 'GET /pid HTTP/1.0'); 21 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds'); 22 | like($wpid, qr/\A[0-9]+\z/, 'worker is running'); 23 | 24 | my $t0 = clock_gettime(CLOCK_MONOTONIC); 25 | my $c = tcp_start($srv, 'GET /block-forever HTTP/1.0'); 26 | vec(my $rvec = '', fileno($c), 1) = 1; 27 | is(select($rvec, undef, undef, 6), 1, 'got readiness'); 28 | $c->blocking(0); 29 | is(sysread($c, my $buf, 128), 0, 'got EOF response'); 30 | my $elapsed = clock_gettime(CLOCK_MONOTONIC) - $t0; 31 | ok($elapsed > 3, 'timeout took >3s'); 32 | 33 | my @timeout_err = slurp($err_log); 34 | truncate($err_log, 0); 35 | is(grep(/timeout \(\d+s > 3s\), killing/, @timeout_err), 1, 36 | 'noted timeout error') or diag explain(\@timeout_err); 37 | 38 | # did it respawn? 39 | ($status, $hdr, my $new_pid) = do_req($srv, 'GET /pid HTTP/1.0'); 40 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'PID request succeeds'); 41 | isnt($new_pid, $wpid, 'spawned new worker'); 42 | 43 | diag 'SIGSTOP for 4 seconds...'; 44 | $ar->do_kill('STOP'); 45 | sleep 4; 46 | $ar->do_kill('CONT'); 47 | for my $i (1..2) { 48 | ($status, $hdr, my $spid) = do_req($srv, 'GET /pid HTTP/1.0'); 49 | like($status, qr!\AHTTP/1\.[01] 200\b!, 50 | "PID request succeeds #$i after STOP+CONT"); 51 | is($new_pid, $spid, "worker pid unchanged after STOP+CONT #$i"); 52 | if ($i == 1) { 53 | diag 'sleeping 2s to ensure timeout is not delayed'; 54 | sleep 2; 55 | } 56 | } 57 | 58 | $ar->join('TERM'); 59 | check_stderr; 60 | undef $tmpdir; 61 | 62 | done_testing; 63 | -------------------------------------------------------------------------------- /unicorn.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | manifest = File.exist?('.manifest') ? 3 | IO.readlines('.manifest').map!(&:chomp!) : `git ls-files`.split("\n") 4 | 5 | # don't bother with tests that fork, not worth our time to get working 6 | # with `gem check -t` ... (of course we care for them when testing with 7 | # GNU make when they can run in parallel) 8 | test_files = manifest.grep(%r{\Atest/unit/test_.*\.rb\z}).map do |f| 9 | File.readlines(f).grep(/\bfork\b/).empty? ? f : nil 10 | end.compact 11 | 12 | Gem::Specification.new do |s| 13 | s.name = %q{unicorn} 14 | s.version = (ENV['VERSION'] || '6.1.0').dup 15 | s.authors = ['unicorn hackers'] 16 | s.summary = 'Rack HTTP server for fast clients and Unix' 17 | s.description = File.read('README').split("\n\n")[1] 18 | s.email = %q{unicorn-public@yhbt.net} 19 | s.executables = %w(unicorn unicorn_rails) 20 | s.extensions = %w(ext/unicorn_http/extconf.rb) 21 | s.extra_rdoc_files = IO.readlines('.document').map!(&:chomp!).keep_if do |f| 22 | File.exist?(f) 23 | end 24 | s.files = manifest 25 | s.homepage = 'https://yhbt.net/unicorn/' 26 | s.test_files = test_files 27 | 28 | # 2.0.0 is the minimum supported version. We don't specify 29 | # a maximum version to make it easier to test pre-releases, 30 | # but we do warn users if they install unicorn on an untested 31 | # version in extconf.rb 32 | s.required_ruby_version = ">= 2.0.0" 33 | 34 | # We do not have a hard dependency on rack, it's possible to load 35 | # things which respond to #call. HTTP status lines in responses 36 | # won't have descriptive text, only the numeric status. 37 | s.add_development_dependency(%q) 38 | 39 | s.add_dependency(%q, '~> 2.6') 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_conf = "$tmpdir/u.conf.rb"; 8 | my $u_sock = "$tmpdir/u.sock"; 9 | my $fifo = "$tmpdir/fifo"; 10 | mkfifo($fifo, 0666) or die "mkfifo($fifo): $!"; 11 | 12 | open my $fh, '>', $u_conf; 13 | print $fh <join; 25 | is($?, 0, 'daemonized properly'); 26 | open $fh, '<', "$tmpdir/pid"; 27 | chomp(my $pid = <$fh>); 28 | ok(kill(0, $pid), 'daemonized PID works'); 29 | my $quit = sub { kill('QUIT', $pid) if $pid; $pid = undef }; 30 | END { $quit->() }; 31 | 32 | open $fh, '<', $fifo; 33 | my $worker_nr = <$fh>; 34 | close $fh; 35 | is($worker_nr, '0', 'initial worker spawned'); 36 | 37 | my ($status, $hdr, $worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0'); 38 | like($status, qr/ 200\b/, 'got 200 response'); 39 | like($worker_pid, qr/\A[0-9]+\n\z/s, 'PID in response'); 40 | chomp $worker_pid; 41 | ok(kill(0, $worker_pid), 'worker_pid is valid'); 42 | 43 | ok(kill('WINCH', $pid), 'SIGWINCH can be sent'); 44 | 45 | my $tries = 1000; 46 | while (CORE::kill(0, $worker_pid) && --$tries) { sleep 0.01 } 47 | ok(!CORE::kill(0, $worker_pid), 'worker not running'); 48 | 49 | ok(kill('TTIN', $pid), 'SIGTTIN to restart worker'); 50 | 51 | open $fh, '<', $fifo; 52 | $worker_nr = <$fh>; 53 | close $fh; 54 | is($worker_nr, '0', 'worker restarted'); 55 | 56 | ($status, $hdr, my $new_worker_pid) = do_req($u_sock, 'GET /pid HTTP/1.0'); 57 | like($status, qr/ 200\b/, 'got 200 response'); 58 | like($new_worker_pid, qr/\A[0-9]+\n\z/, 'got new worker PID'); 59 | chomp $new_worker_pid; 60 | ok(kill(0, $new_worker_pid), 'got a valid worker PID'); 61 | isnt($worker_pid, $new_worker_pid, 'worker PID changed'); 62 | 63 | $quit->(); 64 | 65 | check_stderr; 66 | undef $tmpdir; 67 | done_testing; 68 | -------------------------------------------------------------------------------- /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 | 3 | # :enddoc: 4 | $stdout.sync = $stderr.sync = true 5 | $stdin.binmode 6 | $stdout.binmode 7 | $stderr.binmode 8 | 9 | require 'unicorn' 10 | 11 | module Unicorn::Launcher 12 | 13 | # We don't do a lot of standard daemonization stuff: 14 | # * umask is whatever was set by the parent process at startup 15 | # and can be set in config.ru and config_file, so making it 16 | # 0000 and potentially exposing sensitive log data can be bad 17 | # policy. 18 | # * don't bother to chdir("/") here since unicorn is designed to 19 | # run inside APP_ROOT. Unicorn will also re-chdir() to 20 | # the directory it was started in when being re-executed 21 | # to pickup code changes if the original deployment directory 22 | # is a symlink or otherwise got replaced. 23 | def self.daemonize!(options) 24 | cfg = Unicorn::Configurator 25 | $stdin.reopen("/dev/null") 26 | 27 | # We only start a new process group if we're not being reexecuted 28 | # and inheriting file descriptors from our parent 29 | unless ENV['UNICORN_FD'] 30 | # grandparent - reads pipe, exits when master is ready 31 | # \_ parent - exits immediately ASAP 32 | # \_ unicorn master - writes to pipe when ready 33 | 34 | rd, wr = Unicorn.pipe 35 | grandparent = $$ 36 | if fork 37 | wr.close # grandparent does not write 38 | else 39 | rd.close # unicorn master does not read 40 | Process.setsid 41 | exit if fork # parent dies now 42 | end 43 | 44 | if grandparent == $$ 45 | # this will block until HttpServer#join runs (or it dies) 46 | master_pid = (rd.readpartial(16) rescue nil).to_i 47 | unless master_pid > 1 48 | warn "master failed to start, check stderr log for details" 49 | exit!(1) 50 | end 51 | exit 0 52 | else # unicorn master process 53 | options[:ready_pipe] = wr 54 | end 55 | end 56 | # $stderr/$stderr can/will be redirected separately in the Unicorn config 57 | cfg::DEFAULTS[:stderr_path] ||= "/dev/null" 58 | cfg::DEFAULTS[:stdout_path] ||= "/dev/null" 59 | cfg::RACKUP[:daemonized] = true 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /lib/unicorn/app/old_rails/static.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # :enddoc: 3 | # This code is based on the original Rails handler in Mongrel 4 | # Copyright (c) 2005 Zed A. Shaw 5 | # Copyright (c) 2009 Eric Wong 6 | # You can redistribute it and/or modify it under the same terms as Ruby 1.8 or 7 | # the GPLv3 8 | 9 | # Static file handler for Rails < 2.3. This handler is only provided 10 | # as a convenience for developers. Performance-minded deployments should 11 | # use nginx (or similar) for serving static files. 12 | # 13 | # This supports page caching directly and will try to resolve a 14 | # request in the following order: 15 | # 16 | # * If the requested exact PATH_INFO exists as a file then serve it. 17 | # * If it exists at PATH_INFO+rest_operator+".html" exists 18 | # then serve that. 19 | # 20 | # This means that if you are using page caching it will actually work 21 | # with Unicorn and you should see a decent speed boost (but not as 22 | # fast as if you use a static server like nginx). 23 | class Unicorn::App::OldRails::Static < Struct.new(:app, :root, :file_server) 24 | FILE_METHODS = { 'GET' => true, 'HEAD' => true } 25 | 26 | # avoid allocating new strings for hash lookups 27 | REQUEST_METHOD = 'REQUEST_METHOD' 28 | REQUEST_URI = 'REQUEST_URI' 29 | PATH_INFO = 'PATH_INFO' 30 | 31 | def initialize(app) 32 | self.app = app 33 | self.root = "#{::RAILS_ROOT}/public" 34 | self.file_server = ::Rack::File.new(root) 35 | end 36 | 37 | def call(env) 38 | # short circuit this ASAP if serving non-file methods 39 | FILE_METHODS.include?(env[REQUEST_METHOD]) or return app.call(env) 40 | 41 | # first try the path as-is 42 | path_info = env[PATH_INFO].chomp("/") 43 | if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}") 44 | # File exists as-is so serve it up 45 | env[PATH_INFO] = path_info 46 | return file_server.call(env) 47 | end 48 | 49 | # then try the cached version: 50 | path_info << ActionController::Base.page_cache_extension 51 | 52 | if File.file?("#{root}/#{::Rack::Utils.unescape(path_info)}") 53 | env[PATH_INFO] = path_info 54 | return file_server.call(env) 55 | end 56 | 57 | app.call(env) # call OldRails 58 | end 59 | end if defined?(Unicorn::App::OldRails) 60 | -------------------------------------------------------------------------------- /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/t0008-back_out_of_upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . ./test-lib.sh 3 | t_plan 13 "backout of USR2 upgrade" 4 | 5 | worker_wait_start () { 6 | test xSTART = x"$(cat $fifo)" 7 | unicorn_pid=$(cat $pid) 8 | } 9 | 10 | t_begin "setup and start" && { 11 | unicorn_setup 12 | rm -f $pid.oldbin 13 | 14 | cat >> $unicorn_config </dev/null 56 | do 57 | i=$(( $i + 1 )) 58 | test $i -lt 600 || die "timed out" 59 | sleep 1 60 | done 61 | } 62 | 63 | t_begin "capture pid of new worker" && { 64 | new_worker_pid=$(curl -sSf http://$listen/) 65 | } 66 | 67 | t_begin "reload old master process" && { 68 | kill -HUP $orig_master_pid 69 | worker_wait_start 70 | } 71 | 72 | t_begin "gracefully kill new master and ensure it dies" && { 73 | kill -QUIT $new_master_pid 74 | i=0 75 | while kill -0 $new_worker_pid 2>/dev/null 76 | do 77 | i=$(( $i + 1 )) 78 | test $i -lt 600 || die "timed out" 79 | sleep 1 80 | done 81 | } 82 | 83 | t_begin "ensure $pid.oldbin does not exist" && { 84 | i=0 85 | while test -s $pid.oldbin 86 | do 87 | i=$(( $i + 1 )) 88 | test $i -lt 600 || die "timed out" 89 | sleep 1 90 | done 91 | while ! test -s $pid 92 | do 93 | i=$(( $i + 1 )) 94 | test $i -lt 600 || die "timed out" 95 | sleep 1 96 | done 97 | } 98 | 99 | t_begin "ensure $pid is correct" && { 100 | cur_master_pid=$(cat $pid) 101 | test $orig_master_pid -eq $cur_master_pid 102 | } 103 | 104 | t_begin "killing succeeds" && { 105 | kill $orig_master_pid 106 | } 107 | 108 | dbgcat r_err 109 | 110 | t_done 111 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | open my $fh, '>', $u_conf; 9 | print $fh <', $ru; 25 | print $fh <join; # will daemonize 32 | chomp($daemon_pid = slurp($pid_file)); 33 | 34 | my ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0'); 35 | is($bdy, "1\n", 'got expected $master_ppid'); 36 | 37 | stop_daemon; 38 | check_stderr; 39 | 40 | if ('test without CLI switches in config.ru') { 41 | truncate $err_log, 0; 42 | open $fh, '>', $ru; 43 | print $fh $common_ru; 44 | close $fh; 45 | 46 | unicorn('-D', '-l', $u_sock, '-c', $u_conf)->join; # will daemonize 47 | chomp($daemon_pid = slurp($pid_file)); 48 | 49 | ($status, $hdr, $bdy) = do_req($u_sock, 'GET / HTTP/1.0'); 50 | is($bdy, "1\n", 'got expected $master_ppid'); 51 | 52 | stop_daemon; 53 | check_stderr; 54 | } 55 | 56 | if ('ensures broken working_directory (missing config.ru) is OK') { 57 | truncate $err_log, 0; 58 | unlink $ru; 59 | 60 | my $auto_reap = unicorn('-c', $u_conf); 61 | $auto_reap->join; 62 | isnt($?, 0, 'exited with error due to missing config.ru'); 63 | 64 | like(slurp($err_log), qr/rackup file \Q(config.ru)\E not readable/, 65 | 'noted unreadability of config.ru in stderr'); 66 | } 67 | 68 | if ('fooapp.rb (not config.ru) works with working_directory') { 69 | truncate $err_log, 0; 70 | my $fooapp = "$tmpdir/alt/fooapp.rb"; 71 | open $fh, '>', $fooapp; 72 | print $fh < 'text/plain', 'content-length' => b.bytesize.to_s } 77 | [ 200, h, [ b ] ] 78 | end 79 | end 80 | EOM 81 | close $fh; 82 | my $srv = tcp_server; 83 | my $auto_reap = unicorn(qw(-c), $u_conf, qw(-I. fooapp.rb), 84 | { -C => '/', 3 => $srv }); 85 | ($status, $hdr, $bdy) = do_req($srv, 'GET / HTTP/1.0'); 86 | is($bdy, "dir=$tmpdir/alt", 87 | 'fooapp.rb (w/o config.ru) w/ working_directory'); 88 | $auto_reap->join('TERM'); 89 | is($?, 0, 'fooapp.rb process exited'); 90 | check_stderr; 91 | } 92 | 93 | undef $tmpdir; 94 | done_testing; 95 | -------------------------------------------------------------------------------- /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 | open my $conf_fh, '>', $u_conf; 8 | $conf_fh->autoflush(1); 9 | print $conf_fh < $srv }); 16 | my ($c, $status, $hdr); 17 | my $mem_class = 'StringIO'; 18 | my $fs_class = 'Unicorn::TmpIO'; 19 | 20 | $c = tcp_start($srv, "PUT /input_class HTTP/1.0\r\nContent-Length: 0"); 21 | ($status, $hdr) = slurp_hdr($c); 22 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 23 | is(readline($c), $mem_class, 'zero-byte file is StringIO'); 24 | 25 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1"); 26 | print $c '.'; 27 | ($status, $hdr) = slurp_hdr($c); 28 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 29 | is(readline($c), $fs_class, '1 byte file is filesystem-backed'); 30 | 31 | 32 | my $fifo = "$tmpdir/fifo"; 33 | POSIX::mkfifo($fifo, 0600) or die "mkfifo: $!"; 34 | seek($conf_fh, 0, SEEK_SET); 35 | truncate($conf_fh, 0); 36 | print $conf_fh <do_kill('HUP'); 40 | open my $fifo_fh, '<', $fifo; 41 | like(my $wpid = readline($fifo_fh), qr/\Apid=\d+\z/a , 42 | 'reloaded w/ default client_body_buffer_size'); 43 | 44 | 45 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: 1"); 46 | ($status, $hdr) = slurp_hdr($c); 47 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 48 | is(readline($c), $mem_class, 'class for a 1 byte file is memory-backed'); 49 | 50 | 51 | my $one_meg = 1024 ** 2; 52 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg"); 53 | ($status, $hdr) = slurp_hdr($c); 54 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 55 | is(readline($c), $fs_class, '1 megabyte file is FS-backed'); 56 | 57 | # reload with bigger client_body_buffer_size 58 | say $conf_fh "client_body_buffer_size $one_meg"; 59 | $ar->do_kill('HUP'); 60 | open $fifo_fh, '<', $fifo; 61 | like($wpid = readline($fifo_fh), qr/\Apid=\d+\z/a , 62 | 'reloaded w/ bigger client_body_buffer_size'); 63 | 64 | 65 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $one_meg"); 66 | ($status, $hdr) = slurp_hdr($c); 67 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 68 | is(readline($c), $mem_class, '1 megabyte file is now memory-backed'); 69 | 70 | my $too_big = $one_meg + 1; 71 | $c = tcp_start($srv, "PUT /tmp_class HTTP/1.0\r\nContent-Length: $too_big"); 72 | ($status, $hdr) = slurp_hdr($c); 73 | like($status, qr!\AHTTP/1\.[01] 200\b!, 'status line valid'); 74 | is(readline($c), $fs_class, '1 megabyte + 1 byte file is FS-backed'); 75 | 76 | 77 | undef $ar; 78 | check_stderr; 79 | undef $tmpdir; 80 | done_testing; 81 | -------------------------------------------------------------------------------- /test/unit/test_ccc.rb: -------------------------------------------------------------------------------- 1 | require 'socket' 2 | require 'unicorn' 3 | require 'io/wait' 4 | require 'tempfile' 5 | require 'test/unit' 6 | require './test/test_helper' 7 | 8 | class TestCccTCPI < Test::Unit::TestCase 9 | def test_ccc_tcpi 10 | start_pid = $$ 11 | host = '127.0.0.1' 12 | srv = TCPServer.new(host, 0) 13 | port = srv.addr[1] 14 | err = Tempfile.new('unicorn_ccc') 15 | rd, wr = IO.pipe 16 | sleep_pipe = IO.pipe 17 | pid = fork do 18 | sleep_pipe[1].close 19 | reqs = 0 20 | rd.close 21 | worker_pid = nil 22 | app = lambda do |env| 23 | worker_pid ||= begin 24 | at_exit { wr.write(reqs.to_s) if worker_pid == $$ } 25 | $$ 26 | end 27 | reqs += 1 28 | 29 | # will wake up when writer closes 30 | sleep_pipe[0].read if env['PATH_INFO'] == '/sleep' 31 | 32 | [ 200, {'content-length'=>'0', 'content-type'=>'text/plain'}, [] ] 33 | end 34 | ENV['UNICORN_FD'] = srv.fileno.to_s 35 | opts = { 36 | listeners: [ "#{host}:#{port}" ], 37 | stderr_path: err.path, 38 | check_client_connection: true, 39 | } 40 | uni = Unicorn::HttpServer.new(app, opts) 41 | uni.start.join 42 | end 43 | wr.close 44 | 45 | # make sure the server is running, at least 46 | client = tcp_socket(host, port) 47 | client.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") 48 | assert client.wait(10), 'never got response from server' 49 | res = client.read 50 | assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first response' 51 | assert_match %r{\r\n\r\n\z}, res, 'got end of response, server is ready' 52 | client.close 53 | 54 | # start a slow request... 55 | sleeper = tcp_socket(host, port) 56 | sleeper.write("GET /sleep HTTP/1.1\r\nHost: example.com\r\n\r\n") 57 | 58 | # and a bunch of aborted ones 59 | nr = 100 60 | nr.times do |i| 61 | client = tcp_socket(host, port) 62 | client.write("GET /collections/#{rand(10000)} HTTP/1.1\r\n" \ 63 | "Host: example.com\r\n\r\n") 64 | client.close 65 | end 66 | sleep_pipe[1].close # wake up the reader in the worker 67 | res = sleeper.read 68 | assert_match %r{\AHTTP/1\.1 200}, res, 'got part of first sleeper response' 69 | assert_match %r{\r\n\r\n\z}, res, 'got end of sleeper response' 70 | sleeper.close 71 | kpid = pid 72 | pid = nil 73 | Process.kill(:QUIT, kpid) 74 | _, status = Process.waitpid2(kpid) 75 | assert status.success? 76 | reqs = rd.read.to_i 77 | warn "server got #{reqs} requests with #{nr} CCC aborted\n" if $DEBUG 78 | assert_operator reqs, :<, nr 79 | assert_operator reqs, :>=, 2, 'first 2 requests got through, at least' 80 | ensure 81 | return if start_pid != $$ 82 | srv.close if srv 83 | if pid 84 | Process.kill(:QUIT, pid) 85 | _, status = Process.waitpid2(pid) 86 | assert status.success? 87 | end 88 | err.close! if err 89 | rd.close if rd 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /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 $snake_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 | 3 | require 'fcntl' 4 | module Unicorn::Util # :nodoc: 5 | 6 | # :stopdoc: 7 | def self.is_log?(fp) 8 | append_flags = File::WRONLY | File::APPEND 9 | 10 | ! fp.closed? && 11 | fp.stat.file? && 12 | fp.sync && 13 | (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags 14 | rescue IOError, Errno::EBADF 15 | false 16 | end 17 | 18 | def self.chown_logs(uid, gid) 19 | ObjectSpace.each_object(File) do |fp| 20 | fp.chown(uid, gid) if is_log?(fp) 21 | end 22 | end 23 | # :startdoc: 24 | 25 | # This reopens ALL logfiles in the process that have been rotated 26 | # using logrotate(8) (without copytruncate) or similar tools. 27 | # A +File+ object is considered for reopening if it is: 28 | # 1) opened with the O_APPEND and O_WRONLY flags 29 | # 2) the current open file handle does not match its original open path 30 | # 3) unbuffered (as far as userspace buffering goes, not O_SYNC) 31 | # Returns the number of files reopened 32 | # 33 | # In Unicorn 3.5.x and earlier, files must be opened with an absolute 34 | # path to be considered a log file. 35 | def self.reopen_logs 36 | to_reopen = [] 37 | nr = 0 38 | ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp } 39 | 40 | to_reopen.each do |fp| 41 | orig_st = begin 42 | fp.stat 43 | rescue IOError, Errno::EBADF # race 44 | next 45 | end 46 | 47 | begin 48 | b = File.stat(fp.path) 49 | next if orig_st.ino == b.ino && orig_st.dev == b.dev 50 | rescue Errno::ENOENT 51 | end 52 | 53 | begin 54 | # stdin, stdout, stderr are special. The following dance should 55 | # guarantee there is no window where `fp' is unwritable in MRI 56 | # (or any correct Ruby implementation). 57 | # 58 | # Fwiw, GVL has zero bearing here. This is tricky because of 59 | # the unavoidable existence of stdio FILE * pointers for 60 | # std{in,out,err} in all programs which may use the standard C library 61 | if fp.fileno <= 2 62 | # We do not want to hit fclose(3)->dup(2) window for std{in,out,err} 63 | # MRI will use freopen(3) here internally on std{in,out,err} 64 | fp.reopen(fp.path, "a") 65 | else 66 | # We should not need this workaround, Ruby can be fixed: 67 | # https://bugs.ruby-lang.org/issues/9036 68 | # MRI will not call call fclose(3) or freopen(3) here 69 | # since there's no associated std{in,out,err} FILE * pointer 70 | # This should atomically use dup3(2) (or dup2(2)) syscall 71 | File.open(fp.path, "a") { |tmpfp| fp.reopen(tmpfp) } 72 | end 73 | 74 | fp.sync = true 75 | fp.flush # IO#sync=true may not implicitly flush 76 | new_st = fp.stat 77 | 78 | # this should only happen in the master: 79 | if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid 80 | fp.chown(orig_st.uid, orig_st.gid) 81 | end 82 | 83 | nr += 1 84 | rescue IOError, Errno::EBADF 85 | # not much we can do... 86 | end 87 | end 88 | nr 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /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 and converts dashes 39 | * to underscores for HTTP headers. Locale-agnostic. 40 | */ 41 | static void snake_upcase_char(char *c) 42 | { 43 | if (*c >= 'a' && *c <= 'z') 44 | *c &= ~0x20; 45 | else if (*c == '-') 46 | *c = '_'; 47 | } 48 | 49 | /* Downcases a single ASCII character. Locale-agnostic. */ 50 | static void downcase_char(char *c) 51 | { 52 | if (*c >= 'A' && *c <= 'Z') 53 | *c |= 0x20; 54 | } 55 | 56 | static int hexchar2int(int xdigit) 57 | { 58 | if (xdigit >= 'A' && xdigit <= 'F') 59 | return xdigit - 'A' + 10; 60 | if (xdigit >= 'a' && xdigit <= 'f') 61 | return xdigit - 'a' + 10; 62 | 63 | /* Ragel already does runtime range checking for us in Unicorn: */ 64 | assert(xdigit >= '0' && xdigit <= '9' && "invalid digit character"); 65 | 66 | return xdigit - '0'; 67 | } 68 | 69 | /* 70 | * multiplies +i+ by +base+ and increments the result by the parsed 71 | * integer value of +xdigit+. +xdigit+ is a character byte 72 | * representing a number the range of 0..(base-1) 73 | * returns the new value of +i+ on success 74 | * returns -1 on errors (including overflow) 75 | */ 76 | static off_t step_incr(off_t i, int xdigit, const int base) 77 | { 78 | static const off_t max = UH_OFF_T_MAX; 79 | const off_t next_max = (max - (max % base)) / base; 80 | off_t offset = hexchar2int(xdigit); 81 | 82 | if (offset > (base - 1)) 83 | return -1; 84 | if (i > next_max) 85 | return -1; 86 | i *= base; 87 | 88 | if ((offset > (base - 1)) || ((max - i) < offset)) 89 | return -1; 90 | 91 | return i + offset; 92 | } 93 | 94 | /* 95 | * parses a non-negative length according to base-10 and 96 | * returns it as an off_t value. Returns -1 on errors 97 | * (including overflow). 98 | */ 99 | static off_t parse_length(const char *value, size_t length) 100 | { 101 | off_t rv; 102 | 103 | for (rv = 0; length-- && rv >= 0; ++value) { 104 | if (*value >= '0' && *value <= '9') 105 | rv = step_incr(rv, *value, 10); 106 | else 107 | return -1; 108 | } 109 | 110 | return rv; 111 | } 112 | 113 | #define CONST_MEM_EQ(const_p, buf, len) \ 114 | ((sizeof(const_p) - 1) == len && !memcmp(const_p, buf, sizeof(const_p) - 1)) 115 | 116 | #endif /* UH_util_h */ 117 | -------------------------------------------------------------------------------- /lib/unicorn/oob_gc.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | 3 | # Strongly consider https://github.com/tmm1/gctools if using Ruby 2.1+ 4 | # It is built on new APIs in Ruby 2.1, so it is more intelligent than 5 | # this historical implementation. 6 | # 7 | # Users on Ruby 2.0 (not 2.1+) may also want to check out 8 | # lib/middleware/unicorn_oobgc.rb from the Discourse project 9 | # (https://github.com/discourse/discourse) 10 | # 11 | # The following information is only for historical versions of Ruby. 12 | # 13 | # Runs GC after requests, after closing the client socket and 14 | # before attempting to accept more connections. 15 | # 16 | # This shouldn't hurt overall performance as long as the server cluster 17 | # is at <50% CPU capacity, and improves the performance of most memory 18 | # intensive requests. This serves to improve _client-visible_ 19 | # performance (possibly at the cost of overall performance). 20 | # 21 | # Increasing the number of +worker_processes+ may be necessary to 22 | # improve average client response times because some of your workers 23 | # will be busy doing GC and unable to service clients. Think of 24 | # using more workers with this module as a poor man's concurrent GC. 25 | # 26 | # We'll call GC after each request is been written out to the socket, so 27 | # the client never sees the extra GC hit it. 28 | # 29 | # This middleware is _only_ effective for applications that use a lot 30 | # of memory, and will hurt simpler apps/endpoints that can process 31 | # multiple requests before incurring GC. 32 | # 33 | # This middleware is only designed to work with unicorn, as it harms 34 | # performance with keepalive-enabled servers. 35 | # 36 | # Example (in config.ru): 37 | # 38 | # require 'unicorn/oob_gc' 39 | # 40 | # # GC ever two requests that hit /expensive/foo or /more_expensive/foo 41 | # # in your app. By default, this will GC once every 5 requests 42 | # # for all endpoints in your app 43 | # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)} 44 | # 45 | # Feedback from users of early implementations of this module: 46 | # * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/ 47 | # * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/ 48 | 49 | module Unicorn::OobGC 50 | 51 | # this pretends to be Rack middleware because it used to be 52 | # But we need to hook into unicorn internals so we need to close 53 | # the socket before clearing the request env. 54 | # 55 | # +interval+ is the number of requests matching the +path+ regular 56 | # expression before invoking GC. 57 | def self.new(app, interval = 5, path = %r{\A/}) 58 | @@nr = interval 59 | self.const_set :OOBGC_PATH, path 60 | self.const_set :OOBGC_INTERVAL, interval 61 | ObjectSpace.each_object(Unicorn::HttpServer) do |s| 62 | s.extend(self) 63 | end 64 | app # pretend to be Rack middleware since it was in the past 65 | end 66 | 67 | #:stopdoc: 68 | def process_client(client) 69 | super(client) # Unicorn::HttpServer#process_client 70 | env = instance_variable_get(:@request).env 71 | if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0) 72 | @@nr = OOBGC_INTERVAL 73 | env.clear 74 | disabled = GC.enable 75 | GC.start 76 | GC.disable if disabled 77 | end 78 | end 79 | 80 | # :startdoc: 81 | end 82 | -------------------------------------------------------------------------------- /lib/unicorn/http_response.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | # :enddoc: 3 | # Writes a Rack response to your client using the HTTP/1.1 specification. 4 | # You use it by simply doing: 5 | # 6 | # status, headers, body = rack_app.call(env) 7 | # http_response_write(socket, status, headers, body) 8 | # 9 | # Most header correctness (including Content-Length and Content-Type) 10 | # is the job of Rack, with the exception of the "Date" and "Status" header. 11 | module Unicorn::HttpResponse 12 | 13 | STATUS_CODES = defined?(Rack::Utils::HTTP_STATUS_CODES) ? 14 | Rack::Utils::HTTP_STATUS_CODES : {} 15 | STATUS_WITH_NO_ENTITY_BODY = defined?( 16 | Rack::Utils::STATUS_WITH_NO_ENTITY_BODY) ? 17 | Rack::Utils::STATUS_WITH_NO_ENTITY_BODY : begin 18 | warn 'Rack::Utils::STATUS_WITH_NO_ENTITY_BODY missing' 19 | {} 20 | end 21 | 22 | # internal API, code will always be common-enough-for-even-old-Rack 23 | def err_response(code, response_start_sent) 24 | "#{response_start_sent ? '' : 'HTTP/1.1 '}" \ 25 | "#{code} #{STATUS_CODES[code]}\r\n\r\n" 26 | end 27 | 28 | def append_header(buf, key, value) 29 | case value 30 | when Array # Rack 3 31 | value.each { |v| buf << "#{key}: #{v}\r\n" } 32 | when /\n/ # Rack 2 33 | # avoiding blank, key-only cookies with /\n+/ 34 | value.split(/\n+/).each { |v| buf << "#{key}: #{v}\r\n" } 35 | else 36 | buf << "#{key}: #{value}\r\n" 37 | end 38 | end 39 | 40 | # writes the rack_response to socket as an HTTP response 41 | def http_response_write(socket, status, headers, body, 42 | req = Unicorn::HttpRequest.new) 43 | hijack = nil 44 | do_chunk = false 45 | if headers 46 | code = status.to_i 47 | msg = STATUS_CODES[code] 48 | start = req.response_start_sent ? ''.freeze : 'HTTP/1.1 '.freeze 49 | term = STATUS_WITH_NO_ENTITY_BODY.include?(code) || false 50 | buf = "#{start}#{msg ? %Q(#{code} #{msg}) : status}\r\n" \ 51 | "Date: #{httpdate}\r\n" \ 52 | "Connection: close\r\n" 53 | headers.each do |key, value| 54 | case key 55 | when %r{\A(?:Date|Connection)\z}i 56 | next 57 | when %r{\AContent-Length\z}i 58 | append_header(buf, key, value) 59 | term = true 60 | when %r{\ATransfer-Encoding\z}i 61 | append_header(buf, key, value) 62 | term = true if /\bchunked\b/i === value # value may be Array :x 63 | when "rack.hijack" 64 | # This should only be hit under Rack >= 1.5, as this was an illegal 65 | # key in Rack < 1.5 66 | hijack = value 67 | else 68 | append_header(buf, key, value) 69 | end 70 | end 71 | if !hijack && !term && req.chunkable_response? 72 | do_chunk = true 73 | buf << "Transfer-Encoding: chunked\r\n".freeze 74 | end 75 | socket.write(buf << "\r\n".freeze) 76 | end 77 | 78 | if hijack 79 | req.hijacked! 80 | hijack.call(socket) 81 | elsif do_chunk 82 | begin 83 | body.each do |b| 84 | socket.write("#{b.bytesize.to_s(16)}\r\n", b, "\r\n".freeze) 85 | end 86 | ensure 87 | socket.write("0\r\n\r\n".freeze) 88 | end 89 | else 90 | body.each { |chunk| socket.write(chunk) } 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /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/integration.ru: -------------------------------------------------------------------------------- 1 | #!ruby 2 | # Copyright (C) unicorn hackers 3 | # License: GPL-3.0+ 4 | 5 | # this goes for t/integration.t We'll try to put as many tests 6 | # in here as possible to avoid startup overhead of Ruby. 7 | 8 | def early_hints(env, val) 9 | env['rack.early_hints'].call('link' => val) # val may be ary or string 10 | [ 200, {}, [ val.class.to_s ] ] 11 | end 12 | 13 | $orig_rack_200 = nil 14 | def tweak_status_code 15 | $orig_rack_200 = Rack::Utils::HTTP_STATUS_CODES[200] 16 | Rack::Utils::HTTP_STATUS_CODES[200] = "HI" 17 | [ 200, {}, [] ] 18 | end 19 | 20 | def restore_status_code 21 | $orig_rack_200 or return [ 500, {}, [] ] 22 | Rack::Utils::HTTP_STATUS_CODES[200] = $orig_rack_200 23 | [ 200, {}, [] ] 24 | end 25 | 26 | class WriteOnClose 27 | def each(&block) 28 | @callback = block 29 | end 30 | 31 | def close 32 | @callback.call "7\r\nGoodbye\r\n0\r\n\r\n" 33 | end 34 | end 35 | 36 | def write_on_close 37 | [ 200, { 'transfer-encoding' => 'chunked' }, WriteOnClose.new ] 38 | end 39 | 40 | def env_dump(env) 41 | require 'json' 42 | h = {} 43 | env.each do |k,v| 44 | case v 45 | when String, Integer, true, false; h[k] = v 46 | else 47 | case k 48 | when 'rack.version', 'rack.after_reply'; h[k] = v 49 | end 50 | end 51 | end 52 | h.to_json 53 | end 54 | 55 | def rack_input_tests(env) 56 | return [ 100, {}, [] ] if /\A100-continue\z/i =~ env['HTTP_EXPECT'] 57 | cap = 16384 58 | require 'digest/md5' 59 | dig = Digest::MD5.new 60 | input = env['rack.input'] 61 | case env['PATH_INFO'] 62 | when '/rack_input/size_first'; input.size 63 | when '/rack_input/rewind_first'; input.rewind 64 | when '/rack_input'; # OK 65 | else 66 | abort "bad path: #{env['PATH_INFO']}" 67 | end 68 | if buf = input.read(rand(cap)) 69 | begin 70 | raise "#{buf.size} > #{cap}" if buf.size > cap 71 | dig.update(buf) 72 | end while input.read(rand(cap), buf) 73 | buf.clear # remove this call if Ruby ever gets escape analysis 74 | end 75 | h = { 'content-type' => 'text/plain' } 76 | if env['HTTP_TRAILER'] =~ /\bContent-MD5\b/i 77 | cmd5_b64 = env['HTTP_CONTENT_MD5'] or return [500, {}, ['No Content-MD5']] 78 | cmd5_bin = cmd5_b64.unpack('m')[0] 79 | if cmd5_bin != dig.digest 80 | h['content-length'] = cmd5_b64.size.to_s 81 | return [ 500, h, [ cmd5_b64 ] ] 82 | end 83 | end 84 | h['content-length'] = '32' 85 | [ 200, h, [ dig.hexdigest ] ] 86 | end 87 | 88 | run(lambda do |env| 89 | case env['REQUEST_METHOD'] 90 | when 'GET' 91 | case env['PATH_INFO'] 92 | when '/rack-2-newline-headers'; [ 200, { 'X-R2' => "a\nb\nc" }, [] ] 93 | when '/rack-3-array-headers'; [ 200, { 'x-r3' => %w(a b c) }, [] ] 94 | when '/nil-header-value'; [ 200, { 'X-Nil' => nil }, [] ] 95 | when '/unknown-status-pass-through'; [ '666 I AM THE BEAST', {}, [] ] 96 | when '/env_dump'; [ 200, {}, [ env_dump(env) ] ] 97 | when '/write_on_close'; write_on_close 98 | when '/pid'; [ 200, {}, [ "#$$\n" ] ] 99 | when '/early_hints_rack2'; early_hints(env, "r\n2") 100 | when '/early_hints_rack3'; early_hints(env, %w(r 3)) 101 | when '/broken_app'; raise RuntimeError, 'hello' 102 | else '/'; [ 200, {}, [ env_dump(env) ] ] 103 | end # case PATH_INFO (GET) 104 | when 'POST' 105 | case env['PATH_INFO'] 106 | when '/tweak-status-code'; tweak_status_code 107 | when '/restore-status-code'; restore_status_code 108 | end # case PATH_INFO (POST) 109 | # ... 110 | when 'PUT' 111 | case env['PATH_INFO'] 112 | when %r{\A/rack_input}; rack_input_tests(env) 113 | end 114 | end # case REQUEST_METHOD 115 | end) # run 116 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | open my $fh, '>', "$tmpdir/u1.conf.rb"; 15 | print $fh <', "$tmpdir/u2.conf.rb"; 23 | print $fh <', "$tmpdir/u3.conf.rb"; 31 | print $fh <join; 47 | is($?, 0, 'daemonized 1st process'); 48 | chomp($to_kill{u1} = slurp("$tmpdir/u.pid")); 49 | like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file'); 50 | 51 | chomp(my $worker_pid = readline(unix_start($u1, 'GET /pid'))); 52 | like($worker_pid, qr/\A\d+\z/s, 'captured worker pid'); 53 | ok(kill(0, $worker_pid), 'worker is kill-able'); 54 | 55 | 56 | # 2nd process conflicts on PID 57 | unicorn('-c', "$tmpdir/u2.conf.rb", @uarg)->join; 58 | isnt($?, 0, 'conflicting PID file fails to start'); 59 | 60 | chomp(my $pidf = slurp("$tmpdir/u.pid")); 61 | is($pidf, $to_kill{u1}, 'pid file contents unchanged after start failure'); 62 | 63 | chomp(my $pid2 = readline(unix_start($u1, 'GET /pid'))); 64 | is($worker_pid, $pid2, 'worker PID unchanged'); 65 | 66 | 67 | # 3rd process conflicts on socket 68 | unicorn('-c', "$tmpdir/u3.conf.rb", @uarg)->join; 69 | isnt($?, 0, 'conflicting UNIX socket fails to start'); 70 | 71 | chomp($pid2 = readline(unix_start($u1, 'GET /pid'))); 72 | is($worker_pid, $pid2, 'worker PID still unchanged'); 73 | 74 | chomp($pidf = slurp("$tmpdir/u.pid")); 75 | is($pidf, $to_kill{u1}, 'pid file contents unchanged after 2nd start failure'); 76 | 77 | { # teardown initial process via SIGKILL 78 | ok(kill('KILL', delete $to_kill{u1}), 'SIGKILL initial daemon'); 79 | close $p1; 80 | vec(my $rvec = '', fileno($p0), 1) = 1; 81 | is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP'); 82 | is(my $undef = <$p0>, undef, 'process closed pipe writer at exit'); 83 | ok(-f "$tmpdir/u.pid", 'pid file stayed after SIGKILL'); 84 | ok(-S $u1, 'socket stayed after SIGKILL'); 85 | is(IO::Socket::UNIX->new(Peer => $u1, Type => SOCK_STREAM), undef, 86 | 'fail to connect to u1'); 87 | for (1..50) { # wait for init process to reap worker 88 | kill(0, $worker_pid) or last; 89 | sleep 0.011; 90 | } 91 | ok(!kill(0, $worker_pid), 'worker gone after parent dies'); 92 | } 93 | 94 | # restart the first instance 95 | { 96 | pipe($p0, $p1); 97 | fcntl($p1, POSIX::F_SETFD, 0); 98 | unicorn('-c', "$tmpdir/u1.conf.rb", @uarg)->join; 99 | is($?, 0, 'daemonized 1st process'); 100 | chomp($to_kill{u1} = slurp("$tmpdir/u.pid")); 101 | like($to_kill{u1}, qr/\A\d+\z/s, 'read pid file'); 102 | 103 | chomp($pid2 = readline(unix_start($u1, 'GET /pid'))); 104 | like($pid2, qr/\A\d+\z/, 'worker running'); 105 | 106 | ok(kill('TERM', delete $to_kill{u1}), 'SIGTERM restarted daemon'); 107 | close $p1; 108 | vec(my $rvec = '', fileno($p0), 1) = 1; 109 | is(select($rvec, undef, undef, 5), 1, 'timeout for pipe HUP'); 110 | is(my $undef = <$p0>, undef, 'process closed pipe writer at exit'); 111 | ok(!-f "$tmpdir/u.pid", 'pid file gone after SIGTERM'); 112 | ok(-S $u1, 'socket stays after SIGTERM'); 113 | } 114 | 115 | check_stderr; 116 | undef $tmpdir; 117 | done_testing; 118 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | As far as tests and documentation goes, we're not afraid to embrace Unix 10 | and use traditional Unix tools where they make sense and get the job 11 | done. 12 | 13 | === Tests 14 | 15 | Tests are good, but slow tests make development slow, so we make tests 16 | faster (in parallel) with GNU make (instead of Rake) and avoiding 17 | RubyGems. 18 | 19 | Users of GNU-based systems (such as GNU/Linux) usually have GNU make 20 | installed as "make" instead of "gmake". 21 | 22 | Running the entire test suite with 4 tests in parallel: 23 | 24 | gmake -j4 check 25 | 26 | Running just one unit test: 27 | 28 | gmake test/unit/test_http_parser.rb 29 | 30 | Running just one test case in a unit test: 31 | 32 | gmake test/unit/test_http_parser.rb--test_parse_simple.n 33 | 34 | === HttpServer 35 | 36 | We strive to write as little code as possible while still maintaining 37 | readability. However, readability and flexibility may be sacrificed for 38 | performance in hot code paths. For Ruby, less code generally means 39 | faster code. 40 | 41 | Memory allocation should be minimized as much as practically possible. 42 | Buffers for IO#readpartial are preallocated in the hot paths to avoid 43 | building up garbage. Hash assignments use frozen strings to avoid the 44 | duplication behind-the-scenes. 45 | 46 | We spend as little time as possible inside signal handlers and instead 47 | defer handling them for predictability and robustness. Most of the 48 | Unix-specific things are in the Unicorn::HttpServer class. Unix systems 49 | programming experience will come in handy (or be learned) here. 50 | 51 | === Documentation 52 | 53 | Please wrap documentation at 72 characters-per-line or less (long URLs 54 | are exempt) so it is comfortably readable from terminals. 55 | 56 | When referencing mailing list posts, use 57 | https://yhbt.net/unicorn-public/$MESSAGE_ID/ if possible 58 | since the Message-ID remains searchable even if a particular site 59 | becomes unavailable. 60 | 61 | === Ruby/C Compatibility 62 | 63 | We target C Ruby 2.0 and later. We need the Ruby 64 | implementation to support fork, exec, pipe, UNIX signals, access to 65 | integer file descriptors and ability to use unlinked files. 66 | 67 | All of our C code is OS-independent and should run on compilers 68 | supported by the versions of Ruby we target. 69 | 70 | === Ragel Compatibility 71 | 72 | We target the latest released version of Ragel and will update our code 73 | to keep up with new releases. Packaged tarballs and gems include the 74 | generated source code so they will remain usable if compatibility is 75 | broken. 76 | 77 | == Contributing 78 | 79 | Contributions are welcome in the form of patches, pull requests, code 80 | review, testing, documentation, user support or any other feedback is 81 | welcome. The mailing list is the central coordination point for all 82 | user and developer feedback and bug reports. 83 | 84 | === Submitting Patches 85 | 86 | Follow conventions already established in the code and do not exceed 80 87 | characters per line. 88 | 89 | Inline patches (from "git format-patch -M") to the mailing list are 90 | preferred because they allow code review and comments in the reply to 91 | the patch. 92 | 93 | We will adhere to mostly the same conventions for patch submissions as 94 | git itself. See the 95 | {SubmittingPatches}[https://git.kernel.org/cgit/git/git.git/tree/Documentation/SubmittingPatches] 96 | document 97 | distributed with git on on patch submission guidelines to follow. Just 98 | don't email the git mailing list or maintainer with Unicorn patches :) 99 | 100 | == Building a Gem 101 | 102 | You can build the Unicorn gem with the following command: 103 | 104 | gmake gem 105 | 106 | == Running Development Versions 107 | 108 | It is easy to install the contents of your git working directory: 109 | 110 | Via RubyGems 111 | 112 | gmake install-gem 113 | -------------------------------------------------------------------------------- /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 | require 'unicorn/launcher' 4 | require 'optparse' 5 | 6 | ENV["RACK_ENV"] ||= "development" 7 | rackup_opts = Unicorn::Configurator::RACKUP 8 | options = rackup_opts[:options] 9 | set_no_default_middleware = true 10 | 11 | op = OptionParser.new("", 24, ' ') do |opts| 12 | cmd = File.basename($0) 13 | opts.banner = "Usage: #{cmd} " \ 14 | "[ruby options] [#{cmd} options] [rackup config file]" 15 | opts.separator "Ruby options:" 16 | 17 | lineno = 1 18 | opts.on("-e", "--eval LINE", "evaluate a LINE of code") do |line| 19 | eval line, TOPLEVEL_BINDING, "-e", lineno 20 | lineno += 1 21 | end 22 | 23 | opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") do 24 | $DEBUG = true 25 | end 26 | 27 | opts.on("-w", "--warn", "turn warnings on for your script") do 28 | $-w = true 29 | end 30 | 31 | opts.on("-I", "--include PATH", 32 | "specify $LOAD_PATH (may be used more than once)") do |path| 33 | $LOAD_PATH.unshift(*path.split(':')) 34 | end 35 | 36 | opts.on("-r", "--require LIBRARY", 37 | "require the library, before executing your script") do |library| 38 | require library 39 | end 40 | 41 | opts.separator "#{cmd} options:" 42 | 43 | # some of these switches exist for rackup command-line compatibility, 44 | 45 | opts.on("-o", "--host HOST", 46 | "listen on HOST (default: #{Unicorn::Const::DEFAULT_HOST})") do |h| 47 | rackup_opts[:host] = h 48 | rackup_opts[:set_listener] = true 49 | end 50 | 51 | opts.on("-p", "--port PORT", Integer, 52 | "use PORT (default: #{Unicorn::Const::DEFAULT_PORT})") do |port| 53 | rackup_opts[:port] = port 54 | rackup_opts[:set_listener] = true 55 | end 56 | 57 | opts.on("-E", "--env RACK_ENV", 58 | "use RACK_ENV for defaults (default: development)") do |e| 59 | ENV["RACK_ENV"] = e 60 | end 61 | 62 | opts.on("-N", "--no-default-middleware", 63 | "do not load middleware implied by RACK_ENV") do |e| 64 | rackup_opts[:no_default_middleware] = true if set_no_default_middleware 65 | end 66 | 67 | opts.on("-D", "--daemonize", "run daemonized in the background") do |d| 68 | rackup_opts[:daemonize] = !!d 69 | end 70 | 71 | opts.on("-P", "--pid FILE", "DEPRECATED") do |f| 72 | warn %q{Use of --pid/-P is strongly discouraged} 73 | warn %q{Use the 'pid' directive in the Unicorn config file instead} 74 | options[:pid] = f 75 | end 76 | 77 | opts.on("-s", "--server SERVER", 78 | "this flag only exists for compatibility") do |s| 79 | warn "-s/--server only exists for compatibility with rackup" 80 | end 81 | 82 | # Unicorn-specific stuff 83 | opts.on("-l", "--listen {HOST:PORT|PATH}", 84 | "listen on HOST:PORT or PATH", 85 | "this may be specified multiple times", 86 | "(default: #{Unicorn::Const::DEFAULT_LISTEN})") do |address| 87 | options[:listeners] << address 88 | end 89 | 90 | opts.on("-c", "--config-file FILE", "Unicorn-specific config file") do |f| 91 | options[:config_file] = f 92 | end 93 | 94 | # I'm avoiding Unicorn-specific config options on the command-line. 95 | # IMNSHO, config options on the command-line are redundant given 96 | # config files and make things unnecessarily complicated with multiple 97 | # places to look for a config option. 98 | 99 | opts.separator "Common options:" 100 | 101 | opts.on_tail("-h", "--help", "Show this message") do 102 | puts opts.to_s.gsub(/^.*DEPRECATED.*$/s, '') 103 | exit 104 | end 105 | 106 | opts.on_tail("-v", "--version", "Show version") do 107 | puts "#{cmd} v#{Unicorn::Const::UNICORN_VERSION}" 108 | exit 109 | end 110 | 111 | opts.parse! ARGV 112 | end 113 | 114 | set_no_default_middleware = false 115 | app = Unicorn.builder(ARGV[0] || 'config.ru', op) 116 | op = nil 117 | 118 | if $DEBUG 119 | require 'pp' 120 | pp({ 121 | :unicorn_options => options, 122 | :app => app, 123 | :daemonize => rackup_opts[:daemonize], 124 | }) 125 | end 126 | 127 | Unicorn::Launcher.daemonize!(options) if rackup_opts[:daemonize] 128 | Unicorn::HttpServer.new(app, options).start.join 129 | -------------------------------------------------------------------------------- /test/unit/test_util.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | 3 | require './test/test_helper' 4 | require 'tempfile' 5 | 6 | class TestUtil < Test::Unit::TestCase 7 | 8 | EXPECT_FLAGS = File::WRONLY | File::APPEND 9 | def test_reopen_logs_noop 10 | tmp = Tempfile.new('') 11 | fp = File.open(tmp.path, 'ab') 12 | fp.sync = true 13 | ext = fp.external_encoding rescue nil 14 | int = fp.internal_encoding rescue nil 15 | before = fp.stat.inspect 16 | Unicorn::Util.reopen_logs 17 | assert_equal before, File.stat(fp.path).inspect 18 | assert_equal ext, (fp.external_encoding rescue nil) 19 | assert_equal int, (fp.internal_encoding rescue nil) 20 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 21 | tmp.close! 22 | fp.close 23 | end 24 | 25 | def test_reopen_logs_renamed 26 | tmp = Tempfile.new('') 27 | tmp_path = tmp.path.freeze 28 | fp = File.open(tmp_path, 'ab') 29 | fp.sync = true 30 | 31 | ext = fp.external_encoding rescue nil 32 | int = fp.internal_encoding rescue nil 33 | before = fp.stat.inspect 34 | to = Tempfile.new('') 35 | File.rename(tmp_path, to.path) 36 | assert ! File.exist?(tmp_path) 37 | Unicorn::Util.reopen_logs 38 | assert_equal tmp_path, tmp.path 39 | assert File.exist?(tmp_path) 40 | assert before != File.stat(tmp_path).inspect 41 | assert_equal fp.stat.inspect, File.stat(tmp_path).inspect 42 | assert_equal ext, (fp.external_encoding rescue nil) 43 | assert_equal int, (fp.internal_encoding rescue nil) 44 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 45 | assert fp.sync 46 | tmp.close! 47 | to.close! 48 | fp.close 49 | end 50 | 51 | def test_reopen_logs_renamed_with_encoding 52 | tmp = Tempfile.new('') 53 | tmp_path = tmp.path.dup.freeze 54 | Encoding.list.sample(5).each { |encoding| 55 | File.open(tmp_path, "a:#{encoding.to_s}") { |fp| 56 | fp.sync = true 57 | assert_equal encoding, fp.external_encoding 58 | assert_nil fp.internal_encoding 59 | File.unlink(tmp_path) 60 | assert ! File.exist?(tmp_path) 61 | Unicorn::Util.reopen_logs 62 | assert_equal tmp_path, fp.path 63 | assert File.exist?(tmp_path) 64 | assert_equal fp.stat.inspect, File.stat(tmp_path).inspect 65 | assert_equal encoding, fp.external_encoding 66 | assert_nil fp.internal_encoding 67 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 68 | assert fp.sync 69 | } 70 | } 71 | tmp.close! 72 | end 73 | 74 | def test_reopen_logs_renamed_with_internal_encoding 75 | tmp = Tempfile.new('') 76 | tmp_path = tmp.path.dup.freeze 77 | full = Encoding.list 78 | full.sample(2).each { |ext| 79 | full.sample(2).each { |int| 80 | next if ext == int 81 | File.open(tmp_path, "a:#{ext.to_s}:#{int.to_s}") { |fp| 82 | fp.sync = true 83 | assert_equal ext, fp.external_encoding 84 | 85 | if ext != Encoding::BINARY 86 | assert_equal int, fp.internal_encoding 87 | end 88 | 89 | File.unlink(tmp_path) 90 | assert ! File.exist?(tmp_path) 91 | Unicorn::Util.reopen_logs 92 | assert_equal tmp_path, fp.path 93 | assert File.exist?(tmp_path) 94 | assert_equal fp.stat.inspect, File.stat(tmp_path).inspect 95 | assert_equal ext, fp.external_encoding 96 | if ext != Encoding::BINARY 97 | assert_equal int, fp.internal_encoding 98 | end 99 | assert_equal(EXPECT_FLAGS, EXPECT_FLAGS & fp.fcntl(Fcntl::F_GETFL)) 100 | assert fp.sync 101 | } 102 | } 103 | } 104 | tmp.close! 105 | end 106 | 107 | def test_pipe 108 | r, w = Unicorn.pipe 109 | assert r 110 | assert w 111 | 112 | return if RUBY_PLATFORM !~ /linux/ 113 | 114 | begin 115 | f_getpipe_sz = 1032 116 | IO.pipe do |a, b| 117 | a_sz = a.fcntl(f_getpipe_sz) 118 | b.fcntl(f_getpipe_sz) 119 | assert_kind_of Integer, a_sz 120 | r_sz = r.fcntl(f_getpipe_sz) 121 | assert_equal Raindrops::PAGE_SIZE, r_sz 122 | assert_operator a_sz, :>=, r_sz 123 | end 124 | rescue Errno::EINVAL 125 | # Linux <= 2.6.34 126 | end 127 | ensure 128 | w.close 129 | r.close 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /examples/unicorn.conf.rb: -------------------------------------------------------------------------------- 1 | # Sample verbose configuration file for Unicorn (not Rack) 2 | # 3 | # This configuration file documents many features of Unicorn 4 | # that may not be needed for some applications. See 5 | # https://yhbt.net/unicorn/examples/unicorn.conf.minimal.rb 6 | # for a much simpler configuration file. 7 | # 8 | # See https://yhbt.net/unicorn/Unicorn/Configurator.html for complete 9 | # documentation. 10 | 11 | # Use at least one worker per core if you're on a dedicated server, 12 | # more will usually help for _short_ waits on databases/caches. 13 | worker_processes 4 14 | 15 | # Since Unicorn is never exposed to outside clients, it does not need to 16 | # run on the standard HTTP port (80), there is no reason to start Unicorn 17 | # as root unless it's from system init scripts. 18 | # If running the master process as root and the workers as an unprivileged 19 | # user, do this to switch euid/egid in the workers (also chowns logs): 20 | # user "unprivileged_user", "unprivileged_group" 21 | 22 | # Help ensure your application will always spawn in the symlinked 23 | # "current" directory that Capistrano sets up. 24 | working_directory "/path/to/app/current" # available in 0.94.0+ 25 | 26 | # listen on both a Unix domain socket and a TCP port, 27 | # we use a shorter backlog for quicker failover when busy 28 | listen "/path/to/.unicorn.sock", :backlog => 64 29 | listen 8080, :tcp_nopush => true 30 | 31 | # nuke workers after 30 seconds instead of 60 seconds (the default) 32 | timeout 30 33 | 34 | # feel free to point this anywhere accessible on the filesystem 35 | pid "/path/to/app/shared/pids/unicorn.pid" 36 | 37 | # By default, the Unicorn logger will write to stderr. 38 | # Additionally, ome applications/frameworks log to stderr or stdout, 39 | # so prevent them from going to /dev/null when daemonized here: 40 | stderr_path "/path/to/app/shared/log/unicorn.stderr.log" 41 | stdout_path "/path/to/app/shared/log/unicorn.stdout.log" 42 | 43 | # combine Ruby 2.0.0+ with "preload_app true" for memory savings 44 | preload_app true 45 | 46 | # Enable this flag to have unicorn test client connections by writing the 47 | # beginning of the HTTP headers before calling the application. This 48 | # prevents calling the application for connections that have disconnected 49 | # while queued. This is only guaranteed to detect clients on the same 50 | # host unicorn runs on, and unlikely to detect disconnects even on a 51 | # fast LAN. 52 | check_client_connection false 53 | 54 | # local variable to guard against running a hook multiple times 55 | run_once = true 56 | 57 | before_fork do |server, worker| 58 | # the following is highly recomended for Rails + "preload_app true" 59 | # as there's no need for the master process to hold a connection 60 | defined?(ActiveRecord::Base) and 61 | ActiveRecord::Base.connection.disconnect! 62 | 63 | # Occasionally, it may be necessary to run non-idempotent code in the 64 | # master before forking. Keep in mind the above disconnect! example 65 | # is idempotent and does not need a guard. 66 | if run_once 67 | # do_something_once_here ... 68 | run_once = false # prevent from firing again 69 | end 70 | 71 | # The following is only recommended for memory/DB-constrained 72 | # installations. It is not needed if your system can house 73 | # twice as many worker_processes as you have configured. 74 | # 75 | # # This allows a new master process to incrementally 76 | # # phase out the old master process with SIGTTOU to avoid a 77 | # # thundering herd (especially in the "preload_app false" case) 78 | # # when doing a transparent upgrade. The last worker spawned 79 | # # will then kill off the old master process with a SIGQUIT. 80 | # old_pid = "#{server.config[:pid]}.oldbin" 81 | # if old_pid != server.pid 82 | # begin 83 | # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU 84 | # Process.kill(sig, File.read(old_pid).to_i) 85 | # rescue Errno::ENOENT, Errno::ESRCH 86 | # end 87 | # end 88 | # 89 | # Throttle the master from forking too quickly by sleeping. Due 90 | # to the implementation of standard Unix signal handlers, this 91 | # helps (but does not completely) prevent identical, repeated signals 92 | # from being lost when the receiving process is busy. 93 | # sleep 1 94 | end 95 | 96 | after_fork do |server, worker| 97 | # per-process listener ports for debugging/admin/migrations 98 | # addr = "127.0.0.1:#{9293 + worker.nr}" 99 | # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true) 100 | 101 | # the following is *required* for Rails + "preload_app true", 102 | defined?(ActiveRecord::Base) and 103 | ActiveRecord::Base.establish_connection 104 | 105 | # if preload_app is true, then you may also want to check and 106 | # restart any other shared sockets/descriptors such as Memcached, 107 | # and Redis. TokyoCabinet file handles are safe to reuse 108 | # between any number of forked children (assuming your kernel 109 | # correctly implements pread()/pwrite() system calls) 110 | end 111 | -------------------------------------------------------------------------------- /lib/unicorn/stream_input.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | 3 | # When processing uploads, unicorn may expose a StreamInput object under 4 | # "rack.input" of the Rack environment when 5 | # Unicorn::Configurator#rewindable_input is set to +false+ 6 | class Unicorn::StreamInput 7 | # The I/O chunk size (in +bytes+) for I/O operations where 8 | # the size cannot be user-specified when a method is called. 9 | # The default is 16 kilobytes. 10 | @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc: 11 | 12 | # Initializes a new StreamInput object. You normally do not have to call 13 | # this unless you are writing an HTTP server. 14 | def initialize(socket, request) # :nodoc: 15 | @chunked = request.content_length.nil? 16 | @socket = socket 17 | @parser = request 18 | @buf = request.buf 19 | @rbuf = '' 20 | @bytes_read = 0 21 | filter_body(@rbuf, @buf) unless @buf.empty? 22 | end 23 | 24 | # :call-seq: 25 | # ios.read([length [, buffer ]]) => string, buffer, or nil 26 | # 27 | # Reads at most length bytes from the I/O stream, or to the end of 28 | # file if length is omitted or is nil. length must be a non-negative 29 | # integer or nil. If the optional buffer argument is present, it 30 | # must reference a String, which will receive the data. 31 | # 32 | # At end of file, it returns nil or '' depend on length. 33 | # ios.read() and ios.read(nil) returns ''. 34 | # ios.read(length [, buffer]) returns nil. 35 | # 36 | # If the Content-Length of the HTTP request is known (as is the common 37 | # case for POST requests), then ios.read(length [, buffer]) will block 38 | # until the specified length is read (or it is the last chunk). 39 | # Otherwise, for uncommon "Transfer-Encoding: chunked" requests, 40 | # ios.read(length [, buffer]) will return immediately if there is 41 | # any data and only block when nothing is available (providing 42 | # IO#readpartial semantics). 43 | def read(length = nil, rv = '') 44 | if length 45 | if length <= @rbuf.size 46 | length < 0 and raise ArgumentError, "negative length #{length} given" 47 | rv.replace(@rbuf.slice!(0, length)) 48 | else 49 | to_read = length - @rbuf.size 50 | rv.replace(@rbuf.slice!(0, @rbuf.size)) 51 | until to_read == 0 || eof? || (rv.size > 0 && @chunked) 52 | @socket.kgio_read(to_read, @buf) or eof! 53 | filter_body(@rbuf, @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 | end 65 | 66 | # :call-seq: 67 | # ios.gets => string or nil 68 | # 69 | # Reads the next ``line'' from the I/O stream; lines are separated 70 | # by the global record separator ($/, typically "\n"). A global 71 | # record separator of nil reads the entire unread contents of ios. 72 | # Returns nil if called at the end of file. 73 | # This takes zero arguments for strict Rack::Lint compatibility, 74 | # unlike IO#gets. 75 | def gets 76 | sep = $/ 77 | if sep.nil? 78 | read_all(rv = '') 79 | return rv.empty? ? nil : rv 80 | end 81 | re = /\A(.*?#{Regexp.escape(sep)})/ 82 | 83 | begin 84 | @rbuf.sub!(re, '') and return $1 85 | return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof? 86 | @socket.kgio_read(@@io_chunk_size, @buf) or eof! 87 | filter_body(once = '', @buf) 88 | @rbuf << once 89 | end while true 90 | end 91 | 92 | # :call-seq: 93 | # ios.each { |line| block } => ios 94 | # 95 | # Executes the block for every ``line'' in *ios*, where lines are 96 | # separated by the global record separator ($/, typically "\n"). 97 | def each 98 | while line = gets 99 | yield line 100 | end 101 | 102 | self # Rack does not specify what the return value is here 103 | end 104 | 105 | private 106 | 107 | def eof? 108 | if @parser.body_eof? 109 | while @chunked && ! @parser.parse 110 | once = @socket.kgio_read(@@io_chunk_size) or eof! 111 | @buf << once 112 | end 113 | @socket = nil 114 | true 115 | else 116 | false 117 | end 118 | end 119 | 120 | def filter_body(dst, src) 121 | rv = @parser.filter_body(dst, src) 122 | @bytes_read += dst.size 123 | rv 124 | end 125 | 126 | def read_all(dst) 127 | dst.replace(@rbuf) 128 | @socket or return 129 | until eof? 130 | @socket.kgio_read(@@io_chunk_size, @buf) or eof! 131 | filter_body(@rbuf, @buf) 132 | dst << @rbuf 133 | end 134 | ensure 135 | @rbuf.clear 136 | end 137 | 138 | def eof! 139 | # in case client only did a premature shutdown(SHUT_WR) 140 | # we do support clients that shutdown(SHUT_WR) after the 141 | # _entire_ request has been sent, and those will not have 142 | # raised EOFError on us. 143 | @socket.shutdown if @socket 144 | ensure 145 | raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", [] 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /lib/unicorn.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | require 'etc' 3 | require 'stringio' 4 | require 'kgio' 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 | Kgio::Pipe.new.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 | -------------------------------------------------------------------------------- /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 | 3 | # Acts like tee(1) on an input input to provide a input-like stream 4 | # while providing rewindable semantics through a File/StringIO backing 5 | # store. On the first pass, the input is only read on demand so your 6 | # Rack application can use input notification (upload progress and 7 | # like). This should fully conform to the Rack::Lint::InputWrapper 8 | # specification on the public API. This class is intended to be a 9 | # strict interpretation of Rack::Lint::InputWrapper functionality and 10 | # will not support any deviations from it. 11 | # 12 | # When processing uploads, unicorn exposes a TeeInput object under 13 | # "rack.input" of the Rack environment by default. 14 | class Unicorn::TeeInput < Unicorn::StreamInput 15 | # The maximum size (in +bytes+) to buffer in memory before 16 | # resorting to a temporary file. Default is 112 kilobytes. 17 | @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc: 18 | 19 | # sets the maximum size of request bodies to buffer in memory, 20 | # amounts larger than this are buffered to the filesystem 21 | def self.client_body_buffer_size=(bytes) # :nodoc: 22 | @@client_body_buffer_size = bytes 23 | end 24 | 25 | # returns the maximum size of request bodies to buffer in memory, 26 | # amounts larger than this are buffered to the filesystem 27 | def self.client_body_buffer_size # :nodoc: 28 | @@client_body_buffer_size 29 | end 30 | 31 | # for Rack::TempfileReaper in rack 1.6+ 32 | def new_tmpio # :nodoc: 33 | tmpio = Unicorn::TmpIO.new 34 | (@parser.env['rack.tempfiles'] ||= []) << tmpio 35 | tmpio 36 | end 37 | 38 | # Initializes a new TeeInput object. You normally do not have to call 39 | # this unless you are writing an HTTP server. 40 | def initialize(socket, request) # :nodoc: 41 | @len = request.content_length 42 | super 43 | @tmp = @len && @len <= @@client_body_buffer_size ? 44 | StringIO.new("") : new_tmpio 45 | end 46 | 47 | # :call-seq: 48 | # ios.size => Integer 49 | # 50 | # Returns the size of the input. For requests with a Content-Length 51 | # header value, this will not read data off the socket and just return 52 | # the value of the Content-Length header as an Integer. 53 | # 54 | # For Transfer-Encoding:chunked requests, this requires consuming 55 | # all of the input stream before returning since there's no other 56 | # way to determine the size of the request body beforehand. 57 | # 58 | # This method is no longer part of the Rack specification as of 59 | # Rack 1.2, so its use is not recommended. This method only exists 60 | # for compatibility with Rack applications designed for Rack 1.1 and 61 | # earlier. Most applications should only need to call +read+ with a 62 | # specified +length+ in a loop until it returns +nil+. 63 | def size 64 | @len and return @len 65 | pos = @tmp.pos 66 | consume! 67 | @tmp.pos = pos 68 | @len = @tmp.size 69 | end 70 | 71 | # :call-seq: 72 | # ios.read([length [, buffer ]]) => string, buffer, or nil 73 | # 74 | # Reads at most length bytes from the I/O stream, or to the end of 75 | # file if length is omitted or is nil. length must be a non-negative 76 | # integer or nil. If the optional buffer argument is present, it 77 | # must reference a String, which will receive the data. 78 | # 79 | # At end of file, it returns nil or "" depend on length. 80 | # ios.read() and ios.read(nil) returns "". 81 | # ios.read(length [, buffer]) returns nil. 82 | # 83 | # If the Content-Length of the HTTP request is known (as is the common 84 | # case for POST requests), then ios.read(length [, buffer]) will block 85 | # until the specified length is read (or it is the last chunk). 86 | # Otherwise, for uncommon "Transfer-Encoding: chunked" requests, 87 | # ios.read(length [, buffer]) will return immediately if there is 88 | # any data and only block when nothing is available (providing 89 | # IO#readpartial semantics). 90 | def read(*args) 91 | @socket ? tee(super) : @tmp.read(*args) 92 | end 93 | 94 | # :call-seq: 95 | # ios.gets => string or nil 96 | # 97 | # Reads the next ``line'' from the I/O stream; lines are separated 98 | # by the global record separator ($/, typically "\n"). A global 99 | # record separator of nil reads the entire unread contents of ios. 100 | # Returns nil if called at the end of file. 101 | # This takes zero arguments for strict Rack::Lint compatibility, 102 | # unlike IO#gets. 103 | def gets 104 | @socket ? tee(super) : @tmp.gets 105 | end 106 | 107 | # :call-seq: 108 | # ios.rewind => 0 109 | # 110 | # Positions the *ios* pointer to the beginning of input, returns 111 | # the offset (zero) of the +ios+ pointer. Subsequent reads will 112 | # start from the beginning of the previously-buffered input. 113 | def rewind 114 | return 0 if 0 == @tmp.size 115 | consume! if @socket 116 | @tmp.rewind # Rack does not specify what the return value is here 117 | end 118 | 119 | private 120 | 121 | # consumes the stream of the socket 122 | def consume! 123 | junk = "" 124 | nil while read(@@io_chunk_size, junk) 125 | end 126 | 127 | def tee(buffer) 128 | @tmp.write(buffer) if buffer 129 | buffer 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/unicorn/cgi_wrapper.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | 3 | # :enddoc: 4 | # This code is based on the original CGIWrapper from 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 GPLv2+ (GPLv3+ preferred) 9 | # 10 | # Additional work donated by contributors. See CONTRIBUTORS for more info. 11 | 12 | require 'cgi' 13 | 14 | module Unicorn; end 15 | 16 | # The beginning of a complete wrapper around Unicorn's internal HTTP 17 | # processing system but maintaining the original Ruby CGI module. Use 18 | # this only as a crutch to get existing CGI based systems working. It 19 | # should handle everything, but please notify us if you see special 20 | # warnings. This work is still very alpha so we need testers to help 21 | # work out the various corner cases. 22 | class Unicorn::CGIWrapper < ::CGI 23 | undef_method :env_table 24 | attr_reader :env_table 25 | attr_reader :body 26 | 27 | # these are stripped out of any keys passed to CGIWrapper.header function 28 | NPH = 'nph'.freeze # Completely ignored, Unicorn outputs the date regardless 29 | CONNECTION = 'connection'.freeze # Completely ignored. Why is CGI doing this? 30 | CHARSET = 'charset'.freeze # this gets appended to Content-Type 31 | COOKIE = 'cookie'.freeze # maps (Hash,Array,String) to "Set-Cookie" headers 32 | STATUS = 'status'.freeze # stored as @status 33 | Status = 'Status'.freeze # code + human-readable text, Rails sets this 34 | 35 | # some of these are common strings, but this is the only module 36 | # using them and the reason they're not in Unicorn::Const 37 | SET_COOKIE = 'Set-Cookie'.freeze 38 | CONTENT_TYPE = 'Content-Type'.freeze 39 | CONTENT_LENGTH = 'Content-Length'.freeze # this is NOT Const::CONTENT_LENGTH 40 | RACK_INPUT = 'rack.input'.freeze 41 | RACK_ERRORS = 'rack.errors'.freeze 42 | 43 | # this maps CGI header names to HTTP header names 44 | HEADER_MAP = { 45 | 'status' => Status, 46 | 'type' => CONTENT_TYPE, 47 | 'server' => 'Server'.freeze, 48 | 'language' => 'Content-Language'.freeze, 49 | 'expires' => 'Expires'.freeze, 50 | 'length' => CONTENT_LENGTH, 51 | } 52 | 53 | # Takes an a Rackable environment, plus any additional CGI.new 54 | # arguments These are used internally to create a wrapper around the 55 | # real CGI while maintaining Rack/Unicorn's view of the world. This 56 | # this will NOT deal well with large responses that take up a lot of 57 | # memory, but neither does the CGI nor the original CGIWrapper from 58 | # Mongrel... 59 | def initialize(rack_env, *args) 60 | @env_table = rack_env 61 | @status = nil 62 | @head = {} 63 | @headv = Hash.new { |hash,key| hash[key] = [] } 64 | @body = StringIO.new("") 65 | super(*args) 66 | end 67 | 68 | # finalizes the response in a way Rack applications would expect 69 | def rack_response 70 | # @head[CONTENT_LENGTH] ||= @body.size 71 | @headv[SET_COOKIE].concat(@output_cookies) if @output_cookies 72 | @headv.each_pair do |key,value| 73 | @head[key] ||= value.join("\n") unless value.empty? 74 | end 75 | 76 | # Capitalized "Status:", with human-readable status code (e.g. "200 OK") 77 | @status ||= @head.delete(Status) 78 | 79 | [ @status || 500, @head, [ @body.string ] ] 80 | end 81 | 82 | # The header is typically called to send back the header. In our case we 83 | # collect it into a hash for later usage. This can be called multiple 84 | # times to set different cookies. 85 | def header(options = "text/html") 86 | # if they pass in a string then just write the Content-Type 87 | if String === options 88 | @head[CONTENT_TYPE] ||= options 89 | else 90 | HEADER_MAP.each_pair do |from, to| 91 | from = options.delete(from) or next 92 | @head[to] = from.to_s 93 | end 94 | 95 | @head[CONTENT_TYPE] ||= "text/html" 96 | if charset = options.delete(CHARSET) 97 | @head[CONTENT_TYPE] << "; charset=#{charset}" 98 | end 99 | 100 | # lots of ways to set cookies 101 | if cookie = options.delete(COOKIE) 102 | set_cookies = @headv[SET_COOKIE] 103 | case cookie 104 | when Array 105 | cookie.each { |c| set_cookies << c.to_s } 106 | when Hash 107 | cookie.each_value { |c| set_cookies << c.to_s } 108 | else 109 | set_cookies << cookie.to_s 110 | end 111 | end 112 | @status ||= options.delete(STATUS) # all lower-case 113 | 114 | # drop the keys we don't want anymore 115 | options.delete(NPH) 116 | options.delete(CONNECTION) 117 | 118 | # finally, set the rest of the headers as-is, allowing duplicates 119 | options.each_pair { |k,v| @headv[k] << v } 120 | end 121 | 122 | # doing this fakes out the cgi library to think the headers are empty 123 | # we then do the real headers in the out function call later 124 | "" 125 | end 126 | 127 | # The dumb thing is people can call header or this or both and in 128 | # any order. So, we just reuse header and then finalize the 129 | # HttpResponse the right way. This will have no effect if called 130 | # the second time if the first "outputted" anything. 131 | def out(options = "text/html") 132 | header(options) 133 | @body.size == 0 or return 134 | @body << yield if block_given? 135 | end 136 | 137 | # Used to wrap the normal stdinput variable used inside CGI. 138 | def stdinput 139 | @env_table[RACK_INPUT] 140 | end 141 | 142 | # return a pointer to the StringIO body since it's STDOUT-like 143 | def stdoutput 144 | @body 145 | end 146 | 147 | end 148 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/unicorn/worker.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: binary -*- 2 | require "raindrops" 3 | 4 | # This class and its members can be considered a stable interface 5 | # and will not change in a backwards-incompatible fashion between 6 | # releases of unicorn. Knowledge of this class is generally not 7 | # not needed for most users of unicorn. 8 | # 9 | # Some users may want to access it in the before_fork/after_fork hooks. 10 | # See the Unicorn::Configurator RDoc for examples. 11 | class Unicorn::Worker 12 | # :stopdoc: 13 | attr_accessor :nr, :switched 14 | attr_reader :to_io # IO.select-compatible 15 | attr_reader :master 16 | 17 | PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE 18 | DROPS = [] 19 | 20 | def initialize(nr, pipe=nil) 21 | drop_index = nr / PER_DROP 22 | @raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP) 23 | @offset = nr % PER_DROP 24 | @raindrop[@offset] = 0 25 | @nr = nr 26 | @switched = false 27 | @to_io, @master = pipe || Unicorn.pipe 28 | end 29 | 30 | def atfork_child # :nodoc: 31 | # we _must_ close in child, parent just holds this open to signal 32 | @master = @master.close 33 | end 34 | 35 | # master fakes SIGQUIT using this 36 | def quit # :nodoc: 37 | @master = @master.close if @master 38 | end 39 | 40 | # parent does not read 41 | def atfork_parent # :nodoc: 42 | @to_io = @to_io.close 43 | end 44 | 45 | # call a signal handler immediately without triggering EINTR 46 | # We do not use the more obvious Process.kill(sig, $$) here since 47 | # that signal delivery may be deferred. We want to avoid signal delivery 48 | # while the Rack app.call is running because some database drivers 49 | # (e.g. ruby-pg) may cancel pending requests. 50 | def fake_sig(sig) # :nodoc: 51 | old_cb = trap(sig, "IGNORE") 52 | old_cb.call 53 | ensure 54 | trap(sig, old_cb) 55 | end 56 | 57 | # master sends fake signals to children 58 | def soft_kill(sig) # :nodoc: 59 | case sig 60 | when Integer 61 | signum = sig 62 | else 63 | signum = Signal.list[sig.to_s] or 64 | raise ArgumentError, "BUG: bad signal: #{sig.inspect}" 65 | end 66 | # writing and reading 4 bytes on a pipe is atomic on all POSIX platforms 67 | # Do not care in the odd case the buffer is full, here. 68 | @master.kgio_trywrite([signum].pack('l')) 69 | rescue Errno::EPIPE 70 | # worker will be reaped soon 71 | end 72 | 73 | # this only runs when the Rack app.call is not running 74 | # act like a listener 75 | def kgio_tryaccept # :nodoc: 76 | case buf = @to_io.kgio_tryread(4) 77 | when String 78 | # unpack the buffer and trigger the signal handler 79 | signum = buf.unpack('l') 80 | fake_sig(signum[0]) 81 | # keep looping, more signals may be queued 82 | when nil # EOF: master died, but we are at a safe place to exit 83 | fake_sig(:QUIT) 84 | when :wait_readable # keep waiting 85 | return false 86 | end while true # loop, as multiple signals may be sent 87 | end 88 | 89 | # worker objects may be compared to just plain Integers 90 | def ==(other_nr) # :nodoc: 91 | @nr == other_nr 92 | end 93 | 94 | # called in the worker process 95 | def tick=(value) # :nodoc: 96 | @raindrop[@offset] = value 97 | end 98 | 99 | # called in the master process 100 | def tick # :nodoc: 101 | @raindrop[@offset] 102 | end 103 | 104 | # called in both the master (reaping worker) and worker (SIGQUIT handler) 105 | def close # :nodoc: 106 | @master.close if @master 107 | @to_io.close if @to_io 108 | end 109 | 110 | # :startdoc: 111 | 112 | # In most cases, you should be using the Unicorn::Configurator#user 113 | # directive instead. This method should only be used if you need 114 | # fine-grained control of exactly when you want to change permissions 115 | # in your after_fork or after_worker_ready hooks, or if you want to 116 | # use the chroot support. 117 | # 118 | # Changes the worker process to the specified +user+ and +group+, 119 | # and chroots to the current working directory if +chroot+ is set. 120 | # This is only intended to be called from within the worker 121 | # process from the +after_fork+ hook. This should be called in 122 | # the +after_fork+ hook after any privileged functions need to be 123 | # run (e.g. to set per-worker CPU affinity, niceness, etc) 124 | # 125 | # +group+ can be specified as a string, or as an array of two 126 | # strings. If an array of two strings is given, the first string 127 | # is used as the primary group of the process, and the second is 128 | # used as the group of the log files. 129 | # 130 | # Any and all errors raised within this method will be propagated 131 | # directly back to the caller (usually the +after_fork+ hook. 132 | # These errors commonly include ArgumentError for specifying an 133 | # invalid user/group and Errno::EPERM for insufficient privileges. 134 | # 135 | # chroot support is only available in unicorn 5.3.0+ 136 | # user and group switching appeared in unicorn 0.94.0 (2009-11-05) 137 | def user(user, group = nil, chroot = false) 138 | # we do not protect the caller, checking Process.euid == 0 is 139 | # insufficient because modern systems have fine-grained 140 | # capabilities. Let the caller handle any and all errors. 141 | uid = Etc.getpwnam(user).uid 142 | 143 | if group 144 | if group.is_a?(Array) 145 | group, log_group = group 146 | log_gid = Etc.getgrnam(log_group).gid 147 | end 148 | gid = Etc.getgrnam(group).gid 149 | log_gid ||= gid 150 | end 151 | 152 | Unicorn::Util.chown_logs(uid, log_gid) 153 | if gid && Process.egid != gid 154 | Process.initgroups(user, gid) 155 | Process::GID.change_privilege(gid) 156 | end 157 | if chroot 158 | chroot = Dir.pwd if chroot == true 159 | Dir.chroot(chroot) 160 | Dir.chdir('/') 161 | end 162 | Process.euid != uid and Process::UID.change_privilege(uid) 163 | @switched = true 164 | end 165 | end 166 | --------------------------------------------------------------------------------