unicorn 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
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