unicorn 3.6.0 → 4.0.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 (58) hide show
  1. data/.document +1 -0
  2. data/.manifest +13 -0
  3. data/ChangeLog +783 -1
  4. data/DESIGN +0 -8
  5. data/Documentation/GNUmakefile +1 -1
  6. data/GIT-VERSION-FILE +1 -1
  7. data/GIT-VERSION-GEN +1 -1
  8. data/GNUmakefile +2 -2
  9. data/HACKING +11 -0
  10. data/KNOWN_ISSUES +2 -2
  11. data/LATEST +24 -24
  12. data/Links +53 -0
  13. data/NEWS +66 -0
  14. data/PHILOSOPHY +49 -49
  15. data/Sandbox +13 -4
  16. data/TODO +0 -2
  17. data/TUNING +31 -9
  18. data/bin/unicorn +2 -1
  19. data/bin/unicorn_rails +2 -1
  20. data/examples/big_app_gc.rb +2 -33
  21. data/examples/nginx.conf +17 -4
  22. data/ext/unicorn_http/ext_help.h +16 -0
  23. data/ext/unicorn_http/extconf.rb +1 -0
  24. data/ext/unicorn_http/global_variables.h +9 -3
  25. data/ext/unicorn_http/unicorn_http.c +357 -259
  26. data/ext/unicorn_http/unicorn_http.rl +148 -50
  27. data/lib/unicorn/configurator.rb +36 -8
  28. data/lib/unicorn/const.rb +5 -3
  29. data/lib/unicorn/http_request.rb +1 -3
  30. data/lib/unicorn/http_server.rb +82 -95
  31. data/lib/unicorn/oob_gc.rb +61 -50
  32. data/lib/unicorn/socket_helper.rb +23 -8
  33. data/lib/unicorn/worker.rb +45 -4
  34. data/lib/unicorn.rb +8 -6
  35. data/script/isolate_for_tests +4 -2
  36. data/t/broken-app.ru +12 -0
  37. data/t/heartbeat-timeout.ru +12 -0
  38. data/t/oob_gc.ru +21 -0
  39. data/t/oob_gc_path.ru +21 -0
  40. data/t/t0001-reload-bad-config.sh +1 -0
  41. data/t/t0002-parser-error.sh +64 -1
  42. data/t/t0004-heartbeat-timeout.sh +69 -0
  43. data/t/t0009-broken-app.sh +56 -0
  44. data/t/t0019-max_header_len.sh +49 -0
  45. data/t/t0020-at_exit-handler.sh +49 -0
  46. data/t/t9001-oob_gc.sh +47 -0
  47. data/t/t9002-oob_gc-path.sh +75 -0
  48. data/test/benchmark/stack.ru +8 -0
  49. data/test/unit/test_droplet.rb +28 -0
  50. data/test/unit/test_http_parser.rb +60 -4
  51. data/test/unit/test_http_parser_ng.rb +54 -0
  52. data/test/unit/test_response.rb +1 -1
  53. data/test/unit/test_server.rb +1 -1
  54. data/test/unit/test_signals.rb +1 -1
  55. data/test/unit/test_socket_helper.rb +8 -0
  56. data/test/unit/test_upload.rb +1 -1
  57. data/unicorn.gemspec +3 -2
  58. metadata +44 -16
@@ -23,9 +23,6 @@ class Unicorn::HttpServer
23
23
  # backwards compatibility with 1.x
24
24
  Worker = Unicorn::Worker
25
25
 
26
- # prevents IO objects in here from being GC-ed
27
- IO_PURGATORY = []
28
-
29
26
  # all bound listener sockets
30
27
  LISTENERS = []
31
28
 
@@ -100,7 +97,7 @@ class Unicorn::HttpServer
100
97
  self.reexec_pid = 0
101
98
  options = options.dup
102
99
  @ready_pipe = options.delete(:ready_pipe)
103
- self.init_listeners = options[:listeners] ? options[:listeners].dup : []
100
+ @init_listeners = options[:listeners] ? options[:listeners].dup : []
104
101
  options[:use_defaults] = true
105
102
  self.config = Unicorn::Configurator.new(options)
106
103
  self.listener_opts = {}
@@ -121,35 +118,7 @@ class Unicorn::HttpServer
121
118
 
122
119
  # Runs the thing. Returns self so you can run join on it
123
120
  def start
124
- BasicSocket.do_not_reverse_lookup = true
125
-
126
- # inherit sockets from parents, they need to be plain Socket objects
127
- # before they become Kgio::UNIXServer or Kgio::TCPServer
128
- inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
129
- io = Socket.for_fd(fd.to_i)
130
- set_server_sockopt(io, listener_opts[sock_name(io)])
131
- IO_PURGATORY << io
132
- logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
133
- server_cast(io)
134
- end
135
-
136
- config_listeners = config[:listeners].dup
137
- LISTENERS.replace(inherited)
138
-
139
- # we start out with generic Socket objects that get cast to either
140
- # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
141
- # objects share the same OS-level file descriptor as the higher-level
142
- # *Server objects; we need to prevent Socket objects from being
143
- # garbage-collected
144
- config_listeners -= listener_names
145
- if config_listeners.empty? && LISTENERS.empty?
146
- config_listeners << Unicorn::Const::DEFAULT_LISTEN
147
- init_listeners << Unicorn::Const::DEFAULT_LISTEN
148
- START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
149
- end
150
- config_listeners.each { |addr| listen(addr) }
151
- raise ArgumentError, "no listeners" if LISTENERS.empty?
152
-
121
+ inherit_listeners!
153
122
  # this pipe is used to wake us up from select(2) in #join when signals
154
123
  # are trapped. See trap_deferred.
155
124
  init_self_pipe!
@@ -163,7 +132,7 @@ class Unicorn::HttpServer
163
132
 
164
133
  self.master_pid = $$
165
134
  build_app! if preload_app
166
- maintain_worker_count
135
+ spawn_missing_workers
167
136
  self
168
137
  end
169
138
 
@@ -284,8 +253,7 @@ class Unicorn::HttpServer
284
253
  logger.info "master process ready" # test_exec.rb relies on this message
285
254
  if @ready_pipe
286
255
  @ready_pipe.syswrite($$.to_s)
287
- @ready_pipe.close rescue nil
288
- @ready_pipe = nil
256
+ @ready_pipe = @ready_pipe.close rescue nil
289
257
  end
290
258
  begin
291
259
  reap_all_workers
@@ -336,10 +304,8 @@ class Unicorn::HttpServer
336
304
  reexec
337
305
  end
338
306
  end
339
- rescue Errno::EINTR
340
307
  rescue => e
341
- logger.error "Unhandled master loop exception #{e.inspect}."
342
- logger.error e.backtrace.join("\n")
308
+ Unicorn.log_error(@logger, "master loop error", e)
343
309
  end while true
344
310
  stop # gracefully shutdown all workers on our way out
345
311
  logger.info "master complete"
@@ -406,7 +372,7 @@ class Unicorn::HttpServer
406
372
  self.pid = pid.chomp('.oldbin') if pid
407
373
  proc_name 'master'
408
374
  else
409
- worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
375
+ worker = WORKERS.delete(wpid) and worker.close rescue nil
410
376
  m = "reaped #{status.inspect} worker=#{worker.nr rescue 'unknown'}"
411
377
  status.success? ? logger.info(m) : logger.error(m)
412
378
  end
@@ -463,22 +429,17 @@ class Unicorn::HttpServer
463
429
  proc_name 'master (old)'
464
430
  end
465
431
 
466
- # forcibly terminate all workers that haven't checked in in timeout
467
- # seconds. The timeout is implemented using an unlinked File
468
- # shared between the parent process and each worker. The worker
469
- # runs File#chmod to modify the ctime of the File. If the ctime
470
- # is stale for >timeout seconds, then we'll kill the corresponding
471
- # worker.
432
+ # forcibly terminate all workers that haven't checked in in timeout seconds. The timeout is implemented using an unlinked File
472
433
  def murder_lazy_workers
473
434
  t = @timeout
474
435
  next_sleep = 1
436
+ now = Time.now.to_i
475
437
  WORKERS.dup.each_pair do |wpid, worker|
476
- stat = worker.tmp.stat
477
- # skip workers that disable fchmod or have never fchmod-ed
478
- stat.mode == 0100600 and next
479
- diff = Time.now - stat.ctime
480
- if diff <= t
481
- tmp = t - diff
438
+ tick = worker.tick
439
+ 0 == tick and next # skip workers that are sleeping
440
+ diff = now - tick
441
+ tmp = t - diff
442
+ if tmp >= 0
482
443
  next_sleep < tmp and next_sleep = tmp
483
444
  next
484
445
  end
@@ -491,24 +452,33 @@ class Unicorn::HttpServer
491
452
 
492
453
  def after_fork_internal
493
454
  @ready_pipe.close if @ready_pipe
494
- self.ready_pipe = nil # XXX Rainbows! compat, change for Unicorn 4.x
495
- tmp = srand # http://redmine.ruby-lang.org/issues/4338
455
+ Unicorn::Configurator::RACKUP.clear
456
+ @ready_pipe = @init_listeners = @config = @before_exec = @before_fork = nil
457
+
458
+ srand # http://redmine.ruby-lang.org/issues/4338
496
459
 
497
460
  # The OpenSSL PRNG is seeded with only the pid, and apps with frequently
498
461
  # dying workers can recycle pids
499
- OpenSSL::Random.seed(tmp.to_s) if defined?(OpenSSL::Random)
462
+ OpenSSL::Random.seed(rand.to_s) if defined?(OpenSSL::Random)
500
463
  end
501
464
 
502
465
  def spawn_missing_workers
503
- (0...worker_processes).each do |worker_nr|
504
- WORKERS.values.include?(worker_nr) and next
505
- worker = Worker.new(worker_nr, Unicorn::TmpIO.new)
466
+ worker_nr = -1
467
+ until (worker_nr += 1) == @worker_processes
468
+ WORKERS.value?(worker_nr) and next
469
+ worker = Worker.new(worker_nr)
506
470
  before_fork.call(self, worker)
507
- WORKERS[fork {
471
+ if pid = fork
472
+ WORKERS[pid] = worker
473
+ else
508
474
  after_fork_internal
509
475
  worker_loop(worker)
510
- }] = worker
476
+ exit
477
+ end
511
478
  end
479
+ rescue => e
480
+ @logger.error(e) rescue nil
481
+ exit!
512
482
  end
513
483
 
514
484
  def maintain_worker_count
@@ -527,11 +497,14 @@ class Unicorn::HttpServer
527
497
  msg = case e
528
498
  when EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
529
499
  Unicorn::Const::ERROR_500_RESPONSE
500
+ when Unicorn::RequestURITooLongError
501
+ Unicorn::Const::ERROR_414_RESPONSE
502
+ when Unicorn::RequestEntityTooLargeError
503
+ Unicorn::Const::ERROR_413_RESPONSE
530
504
  when Unicorn::HttpParserError # try to tell the client they're bad
531
505
  Unicorn::Const::ERROR_400_RESPONSE
532
506
  else
533
- logger.error "Read error: #{e.inspect}"
534
- logger.error e.backtrace.join("\n")
507
+ Unicorn.log_error(@logger, "app error", e)
535
508
  Unicorn::Const::ERROR_500_RESPONSE
536
509
  end
537
510
  client.kgio_trywrite(msg)
@@ -567,10 +540,8 @@ class Unicorn::HttpServer
567
540
  proc_name "worker[#{worker.nr}]"
568
541
  START_CTX.clear
569
542
  init_self_pipe!
570
- WORKERS.values.each { |other| other.tmp.close rescue nil }
571
543
  WORKERS.clear
572
544
  LISTENERS.each { |sock| sock.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
573
- worker.tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
574
545
  after_fork.call(self, worker) # can drop perms
575
546
  worker.user(*user) if user.kind_of?(Array) && ! worker.switched
576
547
  self.timeout /= 2.0 # halve it for select()
@@ -594,35 +565,25 @@ class Unicorn::HttpServer
594
565
  ppid = master_pid
595
566
  init_worker_process(worker)
596
567
  nr = 0 # this becomes negative if we need to reopen logs
597
- alive = worker.tmp # tmp is our lifeline to the master process
598
- ready = LISTENERS
568
+ l = LISTENERS.dup
569
+ ready = l.dup
599
570
 
600
571
  # closing anything we IO.select on will raise EBADF
601
572
  trap(:USR1) { nr = -65536; SELF_PIPE[0].close rescue nil }
602
- trap(:QUIT) { alive = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
573
+ trap(:QUIT) { worker = nil; LISTENERS.each { |s| s.close rescue nil }.clear }
603
574
  [:TERM, :INT].each { |sig| trap(sig) { exit!(0) } } # instant shutdown
604
575
  logger.info "worker=#{worker.nr} ready"
605
- m = 0
606
576
 
607
577
  begin
608
578
  nr < 0 and reopen_worker_logs(worker.nr)
609
579
  nr = 0
610
580
 
611
- # we're a goner in timeout seconds anyways if alive.chmod
612
- # breaks, so don't trap the exception. Using fchmod() since
613
- # futimes() is not available in base Ruby and I very strongly
614
- # prefer temporary files to be unlinked for security,
615
- # performance and reliability reasons, so utime is out. No-op
616
- # changes with chmod doesn't update ctime on all filesystems; so
617
- # we change our counter each and every time (after process_client
618
- # and before IO.select).
619
- alive.chmod(m = 0 == m ? 1 : 0)
620
-
621
- ready.each do |sock|
581
+ worker.tick = Time.now.to_i
582
+ while sock = ready.shift
622
583
  if client = sock.kgio_tryaccept
623
584
  process_client(client)
624
585
  nr += 1
625
- alive.chmod(m = 0 == m ? 1 : 0)
586
+ worker.tick = Time.now.to_i
626
587
  end
627
588
  break if nr < 0
628
589
  end
@@ -631,23 +592,21 @@ class Unicorn::HttpServer
631
592
  # we're probably reasonably busy, so avoid calling select()
632
593
  # and do a speculative non-blocking accept() on ready listeners
633
594
  # before we sleep again in select().
634
- redo unless nr == 0 # (nr < 0) => reopen logs
595
+ unless nr == 0 # (nr < 0) => reopen logs (unlikely)
596
+ ready = l.dup
597
+ redo
598
+ end
635
599
 
636
600
  ppid == Process.ppid or return
637
- alive.chmod(m = 0 == m ? 1 : 0)
638
601
 
639
602
  # timeout used so we can detect parent death:
640
- ret = IO.select(LISTENERS, nil, SELF_PIPE, timeout) and ready = ret[0]
641
- rescue Errno::EINTR
642
- ready = LISTENERS
603
+ worker.tick = Time.now.to_i
604
+ ret = IO.select(l, nil, SELF_PIPE, @timeout) and ready = ret[0]
643
605
  rescue Errno::EBADF
644
606
  nr < 0 or return
645
607
  rescue => e
646
- if alive
647
- logger.error "Unhandled listen loop exception #{e.inspect}."
648
- logger.error e.backtrace.join("\n")
649
- end
650
- end while alive
608
+ Unicorn.log_error(@logger, "listen loop error", e) if worker
609
+ end while worker
651
610
  end
652
611
 
653
612
  # delivers a signal to a worker and fails gracefully if the worker
@@ -655,7 +614,7 @@ class Unicorn::HttpServer
655
614
  def kill_worker(signal, wpid)
656
615
  Process.kill(signal, wpid)
657
616
  rescue Errno::ESRCH
658
- worker = WORKERS.delete(wpid) and worker.tmp.close rescue nil
617
+ worker = WORKERS.delete(wpid) and worker.close rescue nil
659
618
  end
660
619
 
661
620
  # delivers a signal to each worker
@@ -685,7 +644,7 @@ class Unicorn::HttpServer
685
644
  def load_config!
686
645
  loaded_app = app
687
646
  logger.info "reloading config_file=#{config.config_file}"
688
- config[:listeners].replace(init_listeners)
647
+ config[:listeners].replace(@init_listeners)
689
648
  config.reload
690
649
  config.commit!(self)
691
650
  kill_each_worker(:QUIT)
@@ -694,8 +653,8 @@ class Unicorn::HttpServer
694
653
  build_app! if preload_app
695
654
  logger.info "done reloading config_file=#{config.config_file}"
696
655
  rescue StandardError, LoadError, SyntaxError => e
697
- logger.error "error reloading config_file=#{config.config_file}: " \
698
- "#{e.class} #{e.message} #{e.backtrace}"
656
+ Unicorn.log_error(@logger,
657
+ "error reloading config_file=#{config.config_file}", e)
699
658
  self.app = loaded_app
700
659
  end
701
660
 
@@ -729,5 +688,33 @@ class Unicorn::HttpServer
729
688
  SELF_PIPE.replace(Kgio::Pipe.new)
730
689
  SELF_PIPE.each { |io| io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) }
731
690
  end
732
- end
733
691
 
692
+ def inherit_listeners!
693
+ # inherit sockets from parents, they need to be plain Socket objects
694
+ # before they become Kgio::UNIXServer or Kgio::TCPServer
695
+ inherited = ENV['UNICORN_FD'].to_s.split(/,/).map do |fd|
696
+ io = Socket.for_fd(fd.to_i)
697
+ set_server_sockopt(io, listener_opts[sock_name(io)])
698
+ IO_PURGATORY << io
699
+ logger.info "inherited addr=#{sock_name(io)} fd=#{fd}"
700
+ server_cast(io)
701
+ end
702
+
703
+ config_listeners = config[:listeners].dup
704
+ LISTENERS.replace(inherited)
705
+
706
+ # we start out with generic Socket objects that get cast to either
707
+ # Kgio::TCPServer or Kgio::UNIXServer objects; but since the Socket
708
+ # objects share the same OS-level file descriptor as the higher-level
709
+ # *Server objects; we need to prevent Socket objects from being
710
+ # garbage-collected
711
+ config_listeners -= listener_names
712
+ if config_listeners.empty? && LISTENERS.empty?
713
+ config_listeners << Unicorn::Const::DEFAULT_LISTEN
714
+ @init_listeners << Unicorn::Const::DEFAULT_LISTEN
715
+ START_CTX[:argv] << "-l#{Unicorn::Const::DEFAULT_LISTEN}"
716
+ end
717
+ config_listeners.each { |addr| listen(addr) }
718
+ raise ArgumentError, "no listeners" if LISTENERS.empty?
719
+ end
720
+ end
@@ -1,58 +1,69 @@
1
1
  # -*- encoding: binary -*-
2
- module Unicorn
3
2
 
4
- # Run GC after every request, after closing the client socket and
5
- # before attempting to accept more connections.
6
- #
7
- # This shouldn't hurt overall performance as long as the server cluster
8
- # is at <50% CPU capacity, and improves the performance of most memory
9
- # intensive requests. This serves to improve _client-visible_
10
- # performance (possibly at the cost of overall performance).
11
- #
12
- # We'll call GC after each request is been written out to the socket, so
13
- # the client never sees the extra GC hit it.
14
- #
15
- # This middleware is _only_ effective for applications that use a lot
16
- # of memory, and will hurt simpler apps/endpoints that can process
17
- # multiple requests before incurring GC.
18
- #
19
- # This middleware is only designed to work with Unicorn, as it harms
20
- # keepalive performance.
21
- #
22
- # Example (in config.ru):
23
- #
24
- # require 'unicorn/oob_gc'
25
- #
26
- # # GC ever two requests that hit /expensive/foo or /more_expensive/foo
27
- # # in your app. By default, this will GC once every 5 requests
28
- # # for all endpoints in your app
29
- # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
30
- class OobGC < Struct.new(:app, :interval, :path, :nr, :env, :body)
31
-
32
- def initialize(app, interval = 5, path = %r{\A/})
33
- super(app, interval, path, interval)
34
- end
35
-
36
- def call(env)
37
- status, headers, self.body = app.call(self.env = env)
38
- [ status, headers, self ]
39
- end
3
+ # Runs GC after requests, after closing the client socket and
4
+ # before attempting to accept more connections.
5
+ #
6
+ # This shouldn't hurt overall performance as long as the server cluster
7
+ # is at <50% CPU capacity, and improves the performance of most memory
8
+ # intensive requests. This serves to improve _client-visible_
9
+ # performance (possibly at the cost of overall performance).
10
+ #
11
+ # Increasing the number of +worker_processes+ may be necessary to
12
+ # improve average client response times because some of your workers
13
+ # will be busy doing GC and unable to service clients. Think of
14
+ # using more workers with this module as a poor man's concurrent GC.
15
+ #
16
+ # We'll call GC after each request is been written out to the socket, so
17
+ # the client never sees the extra GC hit it.
18
+ #
19
+ # This middleware is _only_ effective for applications that use a lot
20
+ # of memory, and will hurt simpler apps/endpoints that can process
21
+ # multiple requests before incurring GC.
22
+ #
23
+ # This middleware is only designed to work with unicorn, as it harms
24
+ # performance with keepalive-enabled servers.
25
+ #
26
+ # Example (in config.ru):
27
+ #
28
+ # require 'unicorn/oob_gc'
29
+ #
30
+ # # GC ever two requests that hit /expensive/foo or /more_expensive/foo
31
+ # # in your app. By default, this will GC once every 5 requests
32
+ # # for all endpoints in your app
33
+ # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
34
+ #
35
+ # Feedback from users of early implementations of this module:
36
+ # * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
37
+ # * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
38
+ module Unicorn::OobGC
40
39
 
41
- def each
42
- body.each { |x| yield x }
40
+ # this pretends to be Rack middleware because it used to be
41
+ # But we need to hook into unicorn internals so we need to close
42
+ # the socket before clearing the request env.
43
+ #
44
+ # +interval+ is the number of requests matching the +path+ regular
45
+ # expression before invoking GC.
46
+ def self.new(app, interval = 5, path = %r{\A/})
47
+ @@nr = interval
48
+ self.const_set :OOBGC_PATH, path
49
+ self.const_set :OOBGC_INTERVAL, interval
50
+ ObjectSpace.each_object(Unicorn::HttpServer) do |s|
51
+ s.extend(self)
52
+ self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
43
53
  end
54
+ app # pretend to be Rack middleware since it was in the past
55
+ end
44
56
 
45
- # in Unicorn, this is closed _after_ the client socket
46
- def close
47
- body.close if body.respond_to?(:close)
48
-
49
- if path =~ env['PATH_INFO'] && ((self.nr -= 1) <= 0)
50
- self.nr = interval
51
- self.body = nil
52
- env.clear
53
- GC.start
54
- end
57
+ #:stopdoc:
58
+ PATH_INFO = "PATH_INFO"
59
+ def process_client(client)
60
+ super(client) # Unicorn::HttpServer#process_client
61
+ if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
62
+ @@nr = OOBGC_INTERVAL
63
+ OOBGC_ENV.clear
64
+ GC.start
55
65
  end
56
-
57
66
  end
67
+
68
+ # :startdoc:
58
69
  end
@@ -4,9 +4,12 @@ require 'socket'
4
4
 
5
5
  module Unicorn
6
6
  module SocketHelper
7
+ # :stopdoc:
7
8
  include Socket::Constants
8
9
 
9
- # :stopdoc:
10
+ # prevents IO objects in here from being GC-ed
11
+ IO_PURGATORY = []
12
+
10
13
  # internal interface, only used by Rainbows!/Zbatery
11
14
  DEFAULTS = {
12
15
  # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
@@ -24,9 +27,9 @@ module Unicorn
24
27
  # same default value as Mongrel
25
28
  :backlog => 1024,
26
29
 
27
- # since we don't do keepalive, we'll always flush-on-close and
28
- # this saves packets for everyone.
29
- :tcp_nopush => true,
30
+ # favor latency over bandwidth savings
31
+ :tcp_nopush => false,
32
+ :tcp_nodelay => true,
30
33
  }
31
34
  #:startdoc:
32
35
 
@@ -98,8 +101,7 @@ module Unicorn
98
101
  end
99
102
  sock.listen(opt[:backlog])
100
103
  rescue => e
101
- logger.error "error setting socket options: #{e.inspect}"
102
- logger.error e.backtrace.join("\n")
104
+ Unicorn.log_error(logger, message, e)
103
105
  end
104
106
 
105
107
  def log_buffer_sizes(sock, pfx = '')
@@ -136,8 +138,9 @@ module Unicorn
136
138
  ensure
137
139
  File.umask(old_umask)
138
140
  end
139
- elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address ||
140
- /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
141
+ elsif /\A\[([a-fA-F0-9:]+)\]:(\d+)\z/ =~ address
142
+ new_ipv6_server($1, $2.to_i, opt)
143
+ elsif /\A(\d+\.\d+\.\d+\.\d+):(\d+)\z/ =~ address
141
144
  Kgio::TCPServer.new($1, $2.to_i)
142
145
  else
143
146
  raise ArgumentError, "Don't know how to bind: #{address}"
@@ -146,6 +149,18 @@ module Unicorn
146
149
  sock
147
150
  end
148
151
 
152
+ def new_ipv6_server(addr, port, opt)
153
+ opt.key?(:ipv6only) or return Kgio::TCPServer.new(addr, port)
154
+ defined?(IPV6_V6ONLY) or
155
+ abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
156
+ sock = Socket.new(AF_INET6, SOCK_STREAM, 0)
157
+ sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
158
+ sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
159
+ sock.bind(Socket.pack_sockaddr_in(port, addr))
160
+ IO_PURGATORY << sock
161
+ Kgio::TCPServer.for_fd(sock.fileno)
162
+ end
163
+
149
164
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
150
165
  def tcp_name(sock)
151
166
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
@@ -1,4 +1,5 @@
1
1
  # -*- encoding: binary -*-
2
+ require "raindrops"
2
3
 
3
4
  # This class and its members can be considered a stable interface
4
5
  # and will not change in a backwards-incompatible fashion between
@@ -7,13 +8,53 @@
7
8
  #
8
9
  # Some users may want to access it in the before_fork/after_fork hooks.
9
10
  # See the Unicorn::Configurator RDoc for examples.
10
- class Unicorn::Worker < Struct.new(:nr, :tmp, :switched)
11
+ class Unicorn::Worker
12
+ # :stopdoc:
13
+ attr_accessor :nr, :switched
14
+ attr_writer :tmp
15
+
16
+ PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
17
+ DROPS = []
18
+
19
+ def initialize(nr)
20
+ drop_index = nr / PER_DROP
21
+ @raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
22
+ @offset = nr % PER_DROP
23
+ @raindrop[@offset] = 0
24
+ @nr = nr
25
+ @tmp = @switched = false
26
+ end
11
27
 
12
28
  # worker objects may be compared to just plain Integers
13
29
  def ==(other_nr) # :nodoc:
14
- nr == other_nr
30
+ @nr == other_nr
31
+ end
32
+
33
+ # called in the worker process
34
+ def tick=(value) # :nodoc:
35
+ @raindrop[@offset] = value
36
+ end
37
+
38
+ # called in the master process
39
+ def tick # :nodoc:
40
+ @raindrop[@offset]
15
41
  end
16
42
 
43
+ # only exists for compatibility
44
+ def tmp # :nodoc:
45
+ @tmp ||= begin
46
+ tmp = Unicorn::TmpIO.new
47
+ tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
48
+ tmp
49
+ end
50
+ end
51
+
52
+ def close # :nodoc:
53
+ @tmp.close if @tmp
54
+ end
55
+
56
+ # :startdoc:
57
+
17
58
  # In most cases, you should be using the Unicorn::Configurator#user
18
59
  # directive instead. This method should only be used if you need
19
60
  # fine-grained control of exactly when you want to change permissions
@@ -36,12 +77,12 @@ class Unicorn::Worker < Struct.new(:nr, :tmp, :switched)
36
77
  uid = Etc.getpwnam(user).uid
37
78
  gid = Etc.getgrnam(group).gid if group
38
79
  Unicorn::Util.chown_logs(uid, gid)
39
- tmp.chown(uid, gid)
80
+ @tmp.chown(uid, gid) if @tmp
40
81
  if gid && Process.egid != gid
41
82
  Process.initgroups(user, gid)
42
83
  Process::GID.change_privilege(gid)
43
84
  end
44
85
  Process.euid != uid and Process::UID.change_privilege(uid)
45
- self.switched = true
86
+ @switched = true
46
87
  end
47
88
  end
data/lib/unicorn.rb CHANGED
@@ -26,17 +26,14 @@ module Unicorn
26
26
  end
27
27
 
28
28
  # :stopdoc:
29
- def self.run(app, options = {})
30
- Unicorn::HttpServer.new(app, options).start.join
31
- end
32
29
 
33
30
  # This returns a lambda to pass in as the app, this does not "build" the
34
31
  # app (which we defer based on the outcome of "preload_app" in the
35
32
  # Unicorn config). The returned lambda will be called when it is
36
33
  # time to build the app.
37
- def self.builder(ru, opts)
34
+ def self.builder(ru, op)
38
35
  # allow Configurator to parse cli switches embedded in the ru file
39
- Unicorn::Configurator::RACKUP.update(:file => ru, :optparse => opts)
36
+ op = Unicorn::Configurator::RACKUP.merge!(:file => ru, :optparse => op)
40
37
 
41
38
  # always called after config file parsing, may be called after forking
42
39
  lambda do ||
@@ -44,7 +41,7 @@ module Unicorn
44
41
  when /\.ru$/
45
42
  raw = File.read(ru)
46
43
  raw.sub!(/^__END__\n.*/, '')
47
- eval("Rack::Builder.new {(#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
44
+ eval("Rack::Builder.new {(\n#{raw}\n)}.to_app", TOPLEVEL_BINDING, ru)
48
45
  else
49
46
  require ru
50
47
  Object.const_get(File.basename(ru, '.rb').capitalize)
@@ -80,6 +77,11 @@ module Unicorn
80
77
  Unicorn::SocketHelper.sock_name(io)
81
78
  end
82
79
  end
80
+
81
+ def self.log_error(logger, message, exc)
82
+ logger.error "#{message}: #{exc.message} (#{exc.class})"
83
+ exc.backtrace.each { |line| logger.error(line) }
84
+ end
83
85
  # :startdoc:
84
86
  end
85
87
  # :enddoc:
@@ -17,8 +17,9 @@ opts = {
17
17
  pid = fork do
18
18
  Isolate.now!(opts) do
19
19
  gem 'sqlite3-ruby', '1.2.5'
20
- gem 'kgio', '2.3.3'
21
- gem 'rack', '1.2.2'
20
+ gem 'raindrops', '0.6.1'
21
+ gem 'kgio', '2.5.0'
22
+ gem 'rack', '1.3.0'
22
23
  end
23
24
  end
24
25
  _, status = Process.waitpid2(pid)
@@ -35,6 +36,7 @@ File.rename("#{dst}.#$$", dst)
35
36
  opts[:path] = "tmp/isolate/rails-#{rails_ver}"
36
37
  pid = fork do
37
38
  Isolate.now!(opts) do
39
+ gem 'rake', '0.8.7'
38
40
  gem 'rails', rails_ver
39
41
  end
40
42
  end
data/t/broken-app.ru ADDED
@@ -0,0 +1,12 @@
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