unicorn 5.0.1 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. checksums.yaml +5 -5
  2. data/.manifest +11 -5
  3. data/.olddoc.yml +16 -6
  4. data/Application_Timeouts +4 -4
  5. data/CONTRIBUTORS +6 -2
  6. data/Documentation/.gitignore +1 -3
  7. data/Documentation/unicorn.1 +222 -0
  8. data/Documentation/unicorn_rails.1 +207 -0
  9. data/FAQ +1 -1
  10. data/GIT-VERSION-FILE +1 -1
  11. data/GIT-VERSION-GEN +1 -1
  12. data/GNUmakefile +118 -58
  13. data/HACKING +2 -10
  14. data/ISSUES +40 -35
  15. data/KNOWN_ISSUES +2 -2
  16. data/LATEST +23 -28
  17. data/LICENSE +2 -2
  18. data/Links +13 -11
  19. data/NEWS +612 -0
  20. data/README +30 -29
  21. data/SIGNALS +1 -1
  22. data/Sandbox +8 -7
  23. data/TODO +0 -2
  24. data/TUNING +19 -1
  25. data/archive/slrnpull.conf +1 -1
  26. data/bin/unicorn +3 -1
  27. data/bin/unicorn_rails +2 -2
  28. data/examples/big_app_gc.rb +1 -1
  29. data/examples/init.sh +36 -8
  30. data/examples/logrotate.conf +17 -2
  31. data/examples/nginx.conf +4 -3
  32. data/examples/unicorn.conf.minimal.rb +2 -2
  33. data/examples/unicorn.conf.rb +2 -2
  34. data/examples/unicorn@.service +14 -0
  35. data/ext/unicorn_http/c_util.h +5 -13
  36. data/ext/unicorn_http/common_field_optimization.h +22 -5
  37. data/ext/unicorn_http/epollexclusive.h +124 -0
  38. data/ext/unicorn_http/ext_help.h +0 -44
  39. data/ext/unicorn_http/extconf.rb +32 -6
  40. data/ext/unicorn_http/global_variables.h +2 -2
  41. data/ext/unicorn_http/httpdate.c +2 -1
  42. data/ext/unicorn_http/unicorn_http.c +853 -498
  43. data/ext/unicorn_http/unicorn_http.rl +86 -30
  44. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  45. data/lib/unicorn/configurator.rb +93 -13
  46. data/lib/unicorn/http_request.rb +101 -11
  47. data/lib/unicorn/http_response.rb +8 -4
  48. data/lib/unicorn/http_server.rb +141 -72
  49. data/lib/unicorn/launcher.rb +1 -1
  50. data/lib/unicorn/oob_gc.rb +6 -6
  51. data/lib/unicorn/select_waiter.rb +6 -0
  52. data/lib/unicorn/socket_helper.rb +23 -7
  53. data/lib/unicorn/stream_input.rb +5 -4
  54. data/lib/unicorn/tee_input.rb +8 -10
  55. data/lib/unicorn/tmpio.rb +8 -2
  56. data/lib/unicorn/util.rb +3 -3
  57. data/lib/unicorn/version.rb +1 -1
  58. data/lib/unicorn/worker.rb +33 -8
  59. data/lib/unicorn.rb +55 -29
  60. data/man/man1/unicorn.1 +120 -118
  61. data/man/man1/unicorn_rails.1 +106 -107
  62. data/t/GNUmakefile +3 -72
  63. data/t/README +4 -4
  64. data/t/t0011-active-unix-socket.sh +1 -1
  65. data/t/t0012-reload-empty-config.sh +2 -1
  66. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  67. data/t/t0301.ru +13 -0
  68. data/t/test-lib.sh +4 -3
  69. data/test/benchmark/README +14 -4
  70. data/test/benchmark/ddstream.ru +50 -0
  71. data/test/benchmark/readinput.ru +40 -0
  72. data/test/benchmark/uconnect.perl +66 -0
  73. data/test/exec/test_exec.rb +26 -24
  74. data/test/test_helper.rb +38 -30
  75. data/test/unit/test_ccc.rb +91 -0
  76. data/test/unit/test_droplet.rb +1 -1
  77. data/test/unit/test_http_parser.rb +46 -16
  78. data/test/unit/test_http_parser_ng.rb +81 -0
  79. data/test/unit/test_request.rb +10 -10
  80. data/test/unit/test_server.rb +86 -12
  81. data/test/unit/test_signals.rb +8 -8
  82. data/test/unit/test_socket_helper.rb +13 -9
  83. data/test/unit/test_upload.rb +9 -14
  84. data/test/unit/test_util.rb +31 -5
  85. data/test/unit/test_waiter.rb +34 -0
  86. data/unicorn.gemspec +21 -22
  87. metadata +21 -28
  88. data/Documentation/GNUmakefile +0 -30
  89. data/Documentation/unicorn.1.txt +0 -188
  90. data/Documentation/unicorn_rails.1.txt +0 -175
  91. data/t/hijack.ru +0 -43
  92. data/t/t0200-rack-hijack.sh +0 -30
@@ -6,7 +6,7 @@
6
6
  # forked worker children.
7
7
  #
8
8
  # Users do not need to know the internals of this class, but reading the
9
- # {source}[http://bogomips.org/unicorn.git/tree/lib/unicorn/http_server.rb]
9
+ # {source}[https://yhbt.net/unicorn.git/tree/lib/unicorn/http_server.rb]
10
10
  # is education for programmers wishing to learn how unicorn works.
11
11
  # See Unicorn::Configurator for information on how to configure unicorn.
12
12
  class Unicorn::HttpServer
@@ -14,7 +14,9 @@ class Unicorn::HttpServer
14
14
  attr_accessor :app, :timeout, :worker_processes,
15
15
  :before_fork, :after_fork, :before_exec,
16
16
  :listener_opts, :preload_app,
17
- :orig_app, :config, :ready_pipe, :user
17
+ :orig_app, :config, :ready_pipe, :user,
18
+ :default_middleware, :early_hints
19
+ attr_writer :after_worker_exit, :after_worker_ready, :worker_exec
18
20
 
19
21
  attr_reader :pid, :logger
20
22
  include Unicorn::SocketHelper
@@ -36,7 +38,7 @@ class Unicorn::HttpServer
36
38
  # or even different installations of the same applications without
37
39
  # downtime. Keys of this constant Hash are described as follows:
38
40
  #
39
- # * 0 - the path to the unicorn/unicorn_rails executable
41
+ # * 0 - the path to the unicorn executable
40
42
  # * :argv - a deep copy of the ARGV array the executable originally saw
41
43
  # * :cwd - the working directory of the application, this is where
42
44
  # you originally started Unicorn.
@@ -45,7 +47,7 @@ class Unicorn::HttpServer
45
47
  # you can set the following in your Unicorn config file, HUP and then
46
48
  # continue with the traditional USR2 + QUIT upgrade steps:
47
49
  #
48
- # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.2.0/bin/unicorn"
50
+ # Unicorn::HttpServer::START_CTX[0] = "/home/bofh/2.3.0/bin/unicorn"
49
51
  START_CTX = {
50
52
  :argv => ARGV.map(&:dup),
51
53
  0 => $0.dup,
@@ -67,8 +69,8 @@ class Unicorn::HttpServer
67
69
  # incoming requests on the socket.
68
70
  def initialize(app, options = {})
69
71
  @app = app
70
- @request = Unicorn::HttpRequest.new
71
72
  @reexec_pid = 0
73
+ @default_middleware = true
72
74
  options = options.dup
73
75
  @ready_pipe = options.delete(:ready_pipe)
74
76
  @init_listeners = options[:listeners] ? options[:listeners].dup : []
@@ -81,13 +83,14 @@ class Unicorn::HttpServer
81
83
  # * The master process never closes or reinitializes this once
82
84
  # initialized. Signal handlers in the master process will write to
83
85
  # it to wake up the master from IO.select in exactly the same manner
84
- # djb describes in http://cr.yp.to/docs/selfpipe.html
86
+ # djb describes in https://cr.yp.to/docs/selfpipe.html
85
87
  #
86
88
  # * The workers immediately close the pipe they inherit. See the
87
89
  # Unicorn::Worker class for the pipe workers use.
88
90
  @self_pipe = []
89
91
  @workers = {} # hash maps PIDs to Workers
90
92
  @sig_queue = [] # signal queue used for self-piping
93
+ @pid = nil
91
94
 
92
95
  # we try inheriting listeners first, so we bind them later.
93
96
  # we don't write the pid file until we've bound listeners in case
@@ -104,6 +107,14 @@ class Unicorn::HttpServer
104
107
  # list of signals we care about and trap in master.
105
108
  @queue_sigs = [
106
109
  :WINCH, :QUIT, :INT, :TERM, :USR1, :USR2, :HUP, :TTIN, :TTOU ]
110
+
111
+ @worker_data = if worker_data = ENV['UNICORN_WORKER']
112
+ worker_data = worker_data.split(',').map!(&:to_i)
113
+ worker_data[1] = worker_data.slice!(1..2).map do |i|
114
+ Kgio::Pipe.for_fd(i)
115
+ end
116
+ worker_data
117
+ end
107
118
  end
108
119
 
109
120
  # Runs the thing. Returns self so you can run join on it
@@ -112,7 +123,7 @@ class Unicorn::HttpServer
112
123
  # this pipe is used to wake us up from select(2) in #join when signals
113
124
  # are trapped. See trap_deferred.
114
125
  @self_pipe.replace(Unicorn.pipe)
115
- @master_pid = $$
126
+ @master_pid = @worker_data ? Process.ppid : $$
116
127
 
117
128
  # setup signal handlers before writing pid file in case people get
118
129
  # trigger happy and send signals as soon as the pid file exists.
@@ -138,7 +149,7 @@ class Unicorn::HttpServer
138
149
  def listeners=(listeners)
139
150
  cur_names, dead_names = [], []
140
151
  listener_names.each do |name|
141
- if ?/ == name[0]
152
+ if name.start_with?('/')
142
153
  # mark unlinked sockets as dead so we can rebind them
143
154
  (File.socket?(name) ? cur_names : dead_names) << name
144
155
  else
@@ -370,7 +381,7 @@ class Unicorn::HttpServer
370
381
 
371
382
  # wait for a signal hander to wake us up and then consume the pipe
372
383
  def master_sleep(sec)
373
- @self_pipe[0].kgio_wait_readable(sec) or return
384
+ @self_pipe[0].wait(sec) or return
374
385
  # 11 bytes is the maximum string length which can be embedded within
375
386
  # the Ruby itself and not require a separate malloc (on 32-bit MRI 1.9+).
376
387
  # Most reads are only one byte here and uncommon, so it's not worth a
@@ -395,8 +406,7 @@ class Unicorn::HttpServer
395
406
  proc_name 'master'
396
407
  else
397
408
  worker = @workers.delete(wpid) and worker.close rescue nil
398
- m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
399
- status.success? ? logger.info(m) : logger.error(m)
409
+ @after_worker_exit.call(self, worker, status)
400
410
  end
401
411
  rescue Errno::ECHILD
402
412
  break
@@ -430,11 +440,7 @@ class Unicorn::HttpServer
430
440
  end
431
441
 
432
442
  @reexec_pid = fork do
433
- listener_fds = {}
434
- LISTENERS.each do |sock|
435
- sock.close_on_exec = false
436
- listener_fds[sock.fileno] = sock
437
- end
443
+ listener_fds = listener_sockets
438
444
  ENV['UNICORN_FD'] = listener_fds.keys.join(',')
439
445
  Dir.chdir(START_CTX[:cwd])
440
446
  cmd = [ START_CTX[0] ].concat(START_CTX[:argv])
@@ -442,12 +448,7 @@ class Unicorn::HttpServer
442
448
  # avoid leaking FDs we don't know about, but let before_exec
443
449
  # unset FD_CLOEXEC, if anything else in the app eventually
444
450
  # relies on FD inheritence.
445
- (3..1024).each do |io|
446
- next if listener_fds.include?(io)
447
- io = IO.for_fd(io) rescue next
448
- io.autoclose = false
449
- io.close_on_exec = true
450
- end
451
+ close_sockets_on_exec(listener_fds)
451
452
 
452
453
  # exec(command, hash) works in at least 1.9.1+, but will only be
453
454
  # required in 1.9.4/2.0.0 at earliest.
@@ -459,6 +460,40 @@ class Unicorn::HttpServer
459
460
  proc_name 'master (old)'
460
461
  end
461
462
 
463
+ def worker_spawn(worker)
464
+ listener_fds = listener_sockets
465
+ env = {}
466
+ env['UNICORN_FD'] = listener_fds.keys.join(',')
467
+
468
+ listener_fds[worker.to_io.fileno] = worker.to_io
469
+ listener_fds[worker.master.fileno] = worker.master
470
+
471
+ worker_info = [worker.nr, worker.to_io.fileno, worker.master.fileno]
472
+ env['UNICORN_WORKER'] = worker_info.join(',')
473
+
474
+ close_sockets_on_exec(listener_fds)
475
+
476
+ Process.spawn(env, START_CTX[0], *START_CTX[:argv], listener_fds)
477
+ end
478
+
479
+ def listener_sockets
480
+ listener_fds = {}
481
+ LISTENERS.each do |sock|
482
+ sock.close_on_exec = false
483
+ listener_fds[sock.fileno] = sock
484
+ end
485
+ listener_fds
486
+ end
487
+
488
+ def close_sockets_on_exec(sockets)
489
+ (3..1024).each do |io|
490
+ next if sockets.include?(io)
491
+ io = IO.for_fd(io) rescue next
492
+ io.autoclose = false
493
+ io.close_on_exec = true
494
+ end
495
+ end
496
+
462
497
  # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
463
498
  def murder_lazy_workers
464
499
  next_sleep = @timeout - 1
@@ -486,32 +521,39 @@ class Unicorn::HttpServer
486
521
  Unicorn::Configurator::RACKUP.clear
487
522
  @ready_pipe = @init_listeners = @before_exec = @before_fork = nil
488
523
 
489
- # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/36450
490
- srand # remove in unicorn 6
491
-
492
524
  # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
493
525
  # dying workers can recycle pids
494
526
  OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
495
527
  end
496
528
 
497
529
  def spawn_missing_workers
530
+ if @worker_data
531
+ worker = Unicorn::Worker.new(*@worker_data)
532
+ after_fork_internal
533
+ worker_loop(worker)
534
+ exit
535
+ end
536
+
498
537
  worker_nr = -1
499
538
  until (worker_nr += 1) == @worker_processes
500
539
  @workers.value?(worker_nr) and next
501
540
  worker = Unicorn::Worker.new(worker_nr)
502
541
  before_fork.call(self, worker)
503
- if pid = fork
504
- @workers[pid] = worker
505
- worker.atfork_parent
506
- else
542
+
543
+ pid = @worker_exec ? worker_spawn(worker) : fork
544
+
545
+ unless pid
507
546
  after_fork_internal
508
547
  worker_loop(worker)
509
548
  exit
510
549
  end
550
+
551
+ @workers[pid] = worker
552
+ worker.atfork_parent
511
553
  end
512
- rescue => e
513
- @logger.error(e) rescue nil
514
- exit!
554
+ rescue => e
555
+ @logger.error(e) rescue nil
556
+ exit!
515
557
  end
516
558
 
517
559
  def maintain_worker_count
@@ -542,13 +584,32 @@ class Unicorn::HttpServer
542
584
  client.kgio_trywrite(err_response(code, @request.response_start_sent))
543
585
  end
544
586
  client.close
545
- rescue
587
+ rescue
588
+ end
589
+
590
+ def e103_response_write(client, headers)
591
+ response = if @request.response_start_sent
592
+ "103 Early Hints\r\n"
593
+ else
594
+ "HTTP/1.1 103 Early Hints\r\n"
595
+ end
596
+
597
+ headers.each_pair do |k, vs|
598
+ next if !vs || vs.empty?
599
+ values = vs.to_s.split("\n".freeze)
600
+ values.each do |v|
601
+ response << "#{k}: #{v}\r\n"
602
+ end
603
+ end
604
+ response << "\r\n".freeze
605
+ response << "HTTP/1.1 ".freeze if @request.response_start_sent
606
+ client.write(response)
546
607
  end
547
608
 
548
609
  def e100_response_write(client, env)
549
610
  # We use String#freeze to avoid allocations under Ruby 2.1+
550
611
  # Not many users hit this code path, so it's better to reduce the
551
- # constant table sizes even for 1.9.3-2.0 users who'll hit extra
612
+ # constant table sizes even for Ruby 2.0 users who'll hit extra
552
613
  # allocations here.
553
614
  client.write(@request.response_start_sent ?
554
615
  "100 Continue\r\n\r\nHTTP/1.1 ".freeze :
@@ -559,7 +620,18 @@ class Unicorn::HttpServer
559
620
  # once a client is accepted, it is processed in its entirety here
560
621
  # in 3 easy steps: read request, call app, write app response
561
622
  def process_client(client)
562
- status, headers, body = @app.call(env = @request.read(client))
623
+ @request = Unicorn::HttpRequest.new
624
+ env = @request.read(client)
625
+
626
+ if early_hints
627
+ env["rack.early_hints"] = lambda do |headers|
628
+ e103_response_write(client, headers)
629
+ end
630
+ end
631
+
632
+ env["rack.after_reply"] = []
633
+
634
+ status, headers, body = @app.call(env)
563
635
 
564
636
  begin
565
637
  return if @request.hijacked?
@@ -570,8 +642,7 @@ class Unicorn::HttpServer
570
642
  return if @request.hijacked?
571
643
  end
572
644
  @request.headers? or headers = nil
573
- http_response_write(client, status, headers, body,
574
- @request.response_start_sent)
645
+ http_response_write(client, status, headers, body, @request)
575
646
  ensure
576
647
  body.respond_to?(:close) and body.close
577
648
  end
@@ -582,6 +653,8 @@ class Unicorn::HttpServer
582
653
  end
583
654
  rescue => e
584
655
  handle_error(client, e)
656
+ ensure
657
+ env["rack.after_reply"].each(&:call) if env
585
658
  end
586
659
 
587
660
  def nuke_listeners!(readers)
@@ -612,7 +685,6 @@ class Unicorn::HttpServer
612
685
  LISTENERS.each { |sock| sock.close_on_exec = true }
613
686
 
614
687
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
615
- self.timeout /= 2.0 # halve it for select()
616
688
  @config = nil
617
689
  build_app! unless preload_app
618
690
  @after_fork = @listener_opts = @orig_app = nil
@@ -626,58 +698,55 @@ class Unicorn::HttpServer
626
698
  logger.info "worker=#{worker_nr} reopening logs..."
627
699
  Unicorn::Util.reopen_logs
628
700
  logger.info "worker=#{worker_nr} done reopening logs"
629
- rescue => e
630
- logger.error(e) rescue nil
631
- exit!(77) # EX_NOPERM in sysexits.h
701
+ false
702
+ rescue => e
703
+ logger.error(e) rescue nil
704
+ exit!(77) # EX_NOPERM in sysexits.h
705
+ end
706
+
707
+ def prep_readers(readers)
708
+ wtr = Unicorn::Waiter.prep_readers(readers)
709
+ @timeout *= 500 # to milliseconds for epoll, but halved
710
+ wtr
711
+ rescue
712
+ require_relative 'select_waiter'
713
+ @timeout /= 2.0 # halved for IO.select
714
+ Unicorn::SelectWaiter.new
632
715
  end
633
716
 
634
717
  # runs inside each forked worker, this sits around and waits
635
718
  # for connections and doesn't die until the parent dies (or is
636
719
  # given a INT, QUIT, or TERM signal)
637
720
  def worker_loop(worker)
638
- ppid = @master_pid
639
721
  readers = init_worker_process(worker)
640
- nr = 0 # this becomes negative if we need to reopen logs
722
+ waiter = prep_readers(readers)
723
+ reopen = false
641
724
 
642
725
  # this only works immediately if the master sent us the signal
643
726
  # (which is the normal case)
644
- trap(:USR1) { nr = -65536 }
727
+ trap(:USR1) { reopen = true }
645
728
 
646
729
  ready = readers.dup
647
- @logger.info "worker=#{worker.nr} ready"
730
+ @after_worker_ready.call(self, worker)
648
731
 
649
732
  begin
650
- nr < 0 and reopen_worker_logs(worker.nr)
651
- nr = 0
733
+ reopen = reopen_worker_logs(worker.nr) if reopen
652
734
  worker.tick = time_now.to_i
653
- tmp = ready.dup
654
- while sock = tmp.shift
735
+ while sock = ready.shift
655
736
  # Unicorn::Worker#kgio_tryaccept is not like accept(2) at all,
656
737
  # but that will return false
657
738
  if client = sock.kgio_tryaccept
658
739
  process_client(client)
659
- nr += 1
660
740
  worker.tick = time_now.to_i
661
741
  end
662
- break if nr < 0
742
+ break if reopen
663
743
  end
664
744
 
665
- # make the following bet: if we accepted clients this round,
666
- # we're probably reasonably busy, so avoid calling select()
667
- # and do a speculative non-blocking accept() on ready listeners
668
- # before we sleep again in select().
669
- unless nr == 0
670
- tmp = ready.dup
671
- redo
672
- end
673
-
674
- ppid == Process.ppid or return
675
-
676
- # timeout used so we can detect parent death:
745
+ # timeout so we can .tick and keep parent from SIGKILL-ing us
677
746
  worker.tick = time_now.to_i
678
- ret = IO.select(readers, nil, nil, @timeout) and ready = ret[0]
747
+ waiter.get_readers(ready, readers, @timeout)
679
748
  rescue => e
680
- redo if nr < 0 && readers[0]
749
+ redo if reopen && readers[0]
681
750
  Unicorn.log_error(@logger, "listen loop error", e) if readers[0]
682
751
  end while readers[0]
683
752
  end
@@ -714,11 +783,11 @@ class Unicorn::HttpServer
714
783
  wpid <= 0 and return
715
784
  Process.kill(0, wpid)
716
785
  wpid
717
- rescue Errno::EPERM
718
- logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
719
- nil
720
- rescue Errno::ESRCH, Errno::ENOENT
721
- # don't unlink stale pid files, racy without non-portable locking...
786
+ rescue Errno::EPERM
787
+ logger.info "pid=#{path} possibly stale, got EPERM signalling PID:#{wpid}"
788
+ nil
789
+ rescue Errno::ESRCH, Errno::ENOENT
790
+ # don't unlink stale pid files, racy without non-portable locking...
722
791
  end
723
792
 
724
793
  def load_config!
@@ -744,12 +813,12 @@ class Unicorn::HttpServer
744
813
  end
745
814
 
746
815
  def build_app!
747
- if app.respond_to?(:arity) && app.arity == 0
816
+ if app.respond_to?(:arity) && (app.arity == 0 || app.arity == 2)
748
817
  if defined?(Gem) && Gem.respond_to?(:refresh)
749
818
  logger.info "Refreshing Gem list"
750
819
  Gem.refresh
751
820
  end
752
- self.app = app.call
821
+ self.app = app.arity == 0 ? app.call : app.call(nil, self)
753
822
  end
754
823
  end
755
824
 
@@ -31,7 +31,7 @@ module Unicorn::Launcher
31
31
  # \_ parent - exits immediately ASAP
32
32
  # \_ unicorn master - writes to pipe when ready
33
33
 
34
- rd, wr = IO.pipe
34
+ rd, wr = Unicorn.pipe
35
35
  grandparent = $$
36
36
  if fork
37
37
  wr.close # grandparent does not write
@@ -43,8 +43,9 @@
43
43
  # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
44
44
  #
45
45
  # Feedback from users of early implementations of this module:
46
- # * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
47
- # * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
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
+
48
49
  module Unicorn::OobGC
49
50
 
50
51
  # this pretends to be Rack middleware because it used to be
@@ -59,18 +60,17 @@ module Unicorn::OobGC
59
60
  self.const_set :OOBGC_INTERVAL, interval
60
61
  ObjectSpace.each_object(Unicorn::HttpServer) do |s|
61
62
  s.extend(self)
62
- self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
63
63
  end
64
64
  app # pretend to be Rack middleware since it was in the past
65
65
  end
66
66
 
67
67
  #:stopdoc:
68
- PATH_INFO = "PATH_INFO"
69
68
  def process_client(client)
70
69
  super(client) # Unicorn::HttpServer#process_client
71
- if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
70
+ env = instance_variable_get(:@request).env
71
+ if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
72
72
  @@nr = OOBGC_INTERVAL
73
- OOBGC_ENV.clear
73
+ env.clear
74
74
  disabled = GC.enable
75
75
  GC.start
76
76
  GC.disable if disabled
@@ -0,0 +1,6 @@
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
@@ -3,6 +3,18 @@
3
3
  require 'socket'
4
4
 
5
5
  module Unicorn
6
+
7
+ # Instead of using a generic Kgio::Socket for everything,
8
+ # tag TCP sockets so we can use TCP_INFO under Linux without
9
+ # incurring extra syscalls for Unix domain sockets.
10
+ # TODO: remove these when we remove kgio
11
+ TCPClient = Class.new(Kgio::Socket) # :nodoc:
12
+ class TCPSrv < Kgio::TCPServer # :nodoc:
13
+ def kgio_tryaccept # :nodoc:
14
+ super(TCPClient)
15
+ end
16
+ end
17
+
6
18
  module SocketHelper
7
19
 
8
20
  # internal interface
@@ -63,12 +75,16 @@ module Unicorn
63
75
  elsif respond_to?(:accf_arg)
64
76
  name = opt[:accept_filter]
65
77
  name = DEFAULTS[:accept_filter] if name.nil?
78
+ sock.listen(opt[:backlog])
79
+ got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
80
+ arg = accf_arg(name)
66
81
  begin
67
- sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, accf_arg(name))
82
+ sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
68
83
  rescue => e
69
84
  logger.error("#{sock_name(sock)} " \
70
85
  "failed to set accept_filter=#{name} (#{e.inspect})")
71
- end
86
+ logger.error("perhaps accf_http(9) needs to be loaded".freeze)
87
+ end if arg != got
72
88
  end
73
89
  end
74
90
 
@@ -85,8 +101,8 @@ module Unicorn
85
101
  log_buffer_sizes(sock, " after: ")
86
102
  end
87
103
  sock.listen(opt[:backlog])
88
- rescue => e
89
- Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
104
+ rescue => e
105
+ Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
90
106
  end
91
107
 
92
108
  def log_buffer_sizes(sock, pfx = '')
@@ -101,7 +117,7 @@ module Unicorn
101
117
  def bind_listen(address = '0.0.0.0:8080', opt = {})
102
118
  return address unless String === address
103
119
 
104
- sock = if address[0] == ?/
120
+ sock = if address.start_with?('/')
105
121
  if File.exist?(address)
106
122
  if File.socket?(address)
107
123
  begin
@@ -148,7 +164,7 @@ module Unicorn
148
164
  end
149
165
  sock.bind(Socket.pack_sockaddr_in(port, addr))
150
166
  sock.autoclose = false
151
- Kgio::TCPServer.for_fd(sock.fileno)
167
+ TCPSrv.for_fd(sock.fileno)
152
168
  end
153
169
 
154
170
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
@@ -185,7 +201,7 @@ module Unicorn
185
201
  def server_cast(sock)
186
202
  begin
187
203
  Socket.unpack_sockaddr_in(sock.getsockname)
188
- Kgio::TCPServer.for_fd(sock.fileno)
204
+ TCPSrv.for_fd(sock.fileno)
189
205
  rescue ArgumentError
190
206
  Kgio::UNIXServer.for_fd(sock.fileno)
191
207
  end
@@ -1,16 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # When processing uploads, Unicorn may expose a StreamInput object under
4
- # "rack.input" of the (future) Rack (2.x) environment.
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+
5
6
  class Unicorn::StreamInput
6
7
  # The I/O chunk size (in +bytes+) for I/O operations where
7
8
  # the size cannot be user-specified when a method is called.
8
9
  # The default is 16 kilobytes.
9
- @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
10
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
10
11
 
11
12
  # Initializes a new StreamInput object. You normally do not have to call
12
13
  # this unless you are writing an HTTP server.
13
- def initialize(socket, request)
14
+ def initialize(socket, request) # :nodoc:
14
15
  @chunked = request.content_length.nil?
15
16
  @socket = socket
16
17
  @parser = request
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # acts like tee(1) on an input input to provide a input-like stream
3
+ # Acts like tee(1) on an input input to provide a input-like stream
4
4
  # while providing rewindable semantics through a File/StringIO backing
5
5
  # store. On the first pass, the input is only read on demand so your
6
6
  # Rack application can use input notification (upload progress and
@@ -9,22 +9,22 @@
9
9
  # strict interpretation of Rack::Lint::InputWrapper functionality and
10
10
  # will not support any deviations from it.
11
11
  #
12
- # When processing uploads, Unicorn exposes a TeeInput object under
13
- # "rack.input" of the Rack environment.
12
+ # When processing uploads, unicorn exposes a TeeInput object under
13
+ # "rack.input" of the Rack environment by default.
14
14
  class Unicorn::TeeInput < Unicorn::StreamInput
15
15
  # The maximum size (in +bytes+) to buffer in memory before
16
16
  # resorting to a temporary file. Default is 112 kilobytes.
17
- @@client_body_buffer_size = Unicorn::Const::MAX_BODY
17
+ @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
18
18
 
19
19
  # sets the maximum size of request bodies to buffer in memory,
20
20
  # amounts larger than this are buffered to the filesystem
21
- def self.client_body_buffer_size=(bytes)
21
+ def self.client_body_buffer_size=(bytes) # :nodoc:
22
22
  @@client_body_buffer_size = bytes
23
23
  end
24
24
 
25
25
  # returns the maximum size of request bodies to buffer in memory,
26
26
  # amounts larger than this are buffered to the filesystem
27
- def self.client_body_buffer_size
27
+ def self.client_body_buffer_size # :nodoc:
28
28
  @@client_body_buffer_size
29
29
  end
30
30
 
@@ -37,7 +37,7 @@ class Unicorn::TeeInput < Unicorn::StreamInput
37
37
 
38
38
  # Initializes a new TeeInput object. You normally do not have to call
39
39
  # this unless you are writing an HTTP server.
40
- def initialize(socket, request)
40
+ def initialize(socket, request) # :nodoc:
41
41
  @len = request.content_length
42
42
  super
43
43
  @tmp = @len && @len <= @@client_body_buffer_size ?
@@ -125,9 +125,7 @@ private
125
125
  end
126
126
 
127
127
  def tee(buffer)
128
- if buffer && buffer.size > 0
129
- @tmp.write(buffer)
130
- end
128
+ @tmp.write(buffer) if buffer
131
129
  buffer
132
130
  end
133
131
  end
data/lib/unicorn/tmpio.rb CHANGED
@@ -11,12 +11,18 @@ class Unicorn::TmpIO < File
11
11
  # immediately, switched to binary mode, and userspace output
12
12
  # buffering is disabled
13
13
  def self.new
14
+ path = nil
15
+
16
+ # workaround File#path being tainted:
17
+ # https://bugs.ruby-lang.org/issues/14485
14
18
  fp = begin
15
- super("#{Dir::tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
19
+ path = "#{Dir::tmpdir}/#{rand}"
20
+ super(path, RDWR|CREAT|EXCL, 0600)
16
21
  rescue Errno::EEXIST
17
22
  retry
18
23
  end
19
- unlink(fp.path)
24
+
25
+ unlink(path)
20
26
  fp.binmode
21
27
  fp.sync = true
22
28
  fp
data/lib/unicorn/util.rb CHANGED
@@ -11,8 +11,8 @@ module Unicorn::Util # :nodoc:
11
11
  fp.stat.file? &&
12
12
  fp.sync &&
13
13
  (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
14
- rescue IOError, Errno::EBADF
15
- false
14
+ rescue IOError, Errno::EBADF
15
+ false
16
16
  end
17
17
 
18
18
  def self.chown_logs(uid, gid)
@@ -64,7 +64,7 @@ module Unicorn::Util # :nodoc:
64
64
  fp.reopen(fp.path, "a")
65
65
  else
66
66
  # We should not need this workaround, Ruby can be fixed:
67
- # http://bugs.ruby-lang.org/issues/9036
67
+ # https://bugs.ruby-lang.org/issues/9036
68
68
  # MRI will not call call fclose(3) or freopen(3) here
69
69
  # since there's no associated std{in,out,err} FILE * pointer
70
70
  # This should atomically use dup3(2) (or dup2(2)) syscall
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '5.0.1'
1
+ Unicorn::Const::UNICORN_VERSION = '6.1.0'