Ticket #30: mongrel_patch_20080507.diff

File mongrel_patch_20080507.diff, 42.0 kB (added by cmdrclueless, 4 months ago)

Here is an updated patch which includes adapted tests for the unix dispatch server.

  • test/test_ws_unix.rb

    old new  
     1# Copyright (c) 2005 Zed A. Shaw  
     2# You can redistribute it and/or modify it under the same terms as Ruby. 
     3# 
     4# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html  
     5# for more information. 
     6 
     7require 'test/testhelp' 
     8 
     9include Mongrel 
     10 
     11class TestHandler < Mongrel::HttpHandler 
     12  attr_reader :ran_test 
     13 
     14  def process(request, response) 
     15    @ran_test = true 
     16    response.socket.write("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nhello!\n") 
     17  end 
     18end 
     19 
     20 
     21class WebServerUnixTest < Test::Unit::TestCase 
     22 
     23  def setup 
     24    @port = process_based_port 
     25    @valid_request = "GET / HTTP/1.1\r\nHost: www.zedshaw.com\r\nContent-Type: text/plain\r\n\r\n" 
     26     
     27    redirect_test_io do 
     28      # We set num_processors=1 so that we can test the reaping code 
     29      @server = UnixDispatchServer.new("127.0.0.1", @port, 1) 
     30    end 
     31     
     32    @tester = TestHandler.new 
     33    @server.register("/test", @tester) 
     34    redirect_test_io do 
     35      @server.run  
     36    end 
     37  end 
     38 
     39  def teardown 
     40    redirect_test_io do 
     41      @server.stop(true) 
     42    end 
     43  end 
     44 
     45  def do_test(string, chunk, close_after=nil, shutdown_delay=0) 
     46    # Do not use instance variables here, because it needs to be thread safe 
     47    socket = TCPSocket.new("127.0.0.1", @port); 
     48    request = StringIO.new(string) 
     49    chunks_out = 0 
     50 
     51    while data = request.read(chunk) 
     52      chunks_out += socket.write(data) 
     53      socket.flush 
     54      sleep 0.2 
     55      if close_after and chunks_out > close_after 
     56        socket.close 
     57        sleep 1 
     58      end 
     59    end 
     60    sleep(shutdown_delay) 
     61    socket.write(" ") # Some platforms only raise the exception on attempted write 
     62    socket.flush 
     63  end 
     64 
     65  def test_trickle_attack 
     66    do_test(@valid_request, 3) 
     67  end 
     68 
     69  def test_close_client 
     70    assert_raises IOError do 
     71      do_test(@valid_request, 10, 20) 
     72    end 
     73  end 
     74 
     75  def test_bad_client 
     76    redirect_test_io do 
     77      do_test("GET /test HTTP/BAD", 3) 
     78    end 
     79  end 
     80 
     81  def test_header_is_too_long 
     82    redirect_test_io do 
     83      long = "GET /test HTTP/1.1\r\n" + ("X-Big: stuff\r\n" * 15000) + "\r\n" 
     84      assert_raises Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EINVAL, IOError do 
     85        do_test(long, long.length/2, 10) 
     86      end 
     87    end 
     88  end 
     89 
     90  def test_file_streamed_request 
     91    body = "a" * (Mongrel::Const::MAX_BODY * 2) 
     92    long = "GET /test HTTP/1.1\r\nContent-length: #{body.length}\r\n\r\n" + body 
     93    do_test(long, Mongrel::Const::CHUNK_SIZE * 2 -400) 
     94  end 
     95 
     96end 
     97 
  • test/test_redirect_handler_unix.rb

    old new  
     1# Copyright (c) 2005 Zed A. Shaw  
     2# You can redistribute it and/or modify it under the same terms as Ruby. 
     3# 
     4# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html  
     5# for more information. 
     6 
     7require 'test/testhelp' 
     8 
     9class RedirectHandlerUnixTest < Test::Unit::TestCase 
     10 
     11  def setup 
     12    redirect_test_io do 
     13      @server = Mongrel::UnixDispatchServer.new('127.0.0.1', process_based_port,1) 
     14    end 
     15    @server.run 
     16    @client = Net::HTTP.new('127.0.0.1', process_based_port) 
     17  end 
     18 
     19  def teardown 
     20    @server.stop(true) 
     21  end 
     22 
     23  def test_simple_redirect 
     24    tester = Mongrel::RedirectHandler.new('/yo') 
     25    @server.register("/test", tester) 
     26 
     27    sleep(1) 
     28    res = @client.request_get('/test') 
     29    assert res != nil, "Didn't get a response" 
     30    assert_equal ['/yo'], res.get_fields('Location') 
     31  end 
     32 
     33  def test_rewrite 
     34    tester = Mongrel::RedirectHandler.new(/(\w+)/, '+\1+') 
     35    @server.register("/test", tester) 
     36 
     37    sleep(1) 
     38    res = @client.request_get('/test/something') 
     39    assert_equal ['/+test+/+something+'], res.get_fields('Location') 
     40  end 
     41 
     42end 
     43 
     44 
  • test/test_conditional_unix.rb

    old new  
     1# Copyright (c) 2005 Zed A. Shaw  
     2# You can redistribute it and/or modify it under the same terms as Ruby. 
     3# 
     4# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html  
     5# for more information. 
     6 
     7require 'test/testhelp' 
     8 
     9include Mongrel 
     10 
     11class ConditionalResponseUnixTest < Test::Unit::TestCase 
     12  def setup 
     13    @server = UnixDispatchServer.new('127.0.0.1', process_based_port, 1) 
     14    @server.register('/', Mongrel::DirHandler.new('.')) 
     15    @server.run 
     16     
     17    @http = Net::HTTP.new(@server.host, @server.port) 
     18 
     19    # get the ETag and Last-Modified headers 
     20    @path = '/README' 
     21    res = @http.start { |http| http.get(@path) } 
     22    assert_not_nil @etag = res['ETag'] 
     23    assert_not_nil @last_modified = res['Last-Modified'] 
     24    assert_not_nil @content_length = res['Content-Length'] 
     25  end 
     26 
     27  def teardown 
     28    @server.stop(true) 
     29  end 
     30 
     31  # status should be 304 Not Modified when If-None-Match is the matching ETag 
     32  def test_not_modified_via_if_none_match 
     33    assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag 
     34  end 
     35 
     36  # status should be 304 Not Modified when If-Modified-Since is the matching Last-Modified date 
     37  def test_not_modified_via_if_modified_since 
     38    assert_status_for_get_and_head Net::HTTPNotModified, 'If-Modified-Since' => @last_modified 
     39  end 
     40 
     41  # status should be 304 Not Modified when If-None-Match is the matching ETag 
     42  # and If-Modified-Since is the matching Last-Modified date 
     43  def test_not_modified_via_if_none_match_and_if_modified_since 
     44    assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => @last_modified 
     45  end 
     46 
     47  # status should be 200 OK when If-None-Match is invalid 
     48  def test_invalid_if_none_match 
     49    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid' 
     50    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => 'invalid', 'If-Modified-Since' => @last_modified 
     51  end 
     52 
     53  # status should be 200 OK when If-Modified-Since is invalid 
     54  def test_invalid_if_modified_since 
     55    assert_status_for_get_and_head Net::HTTPOK,                           'If-Modified-Since' => 'invalid' 
     56    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => 'invalid' 
     57  end 
     58 
     59  # status should be 304 Not Modified when If-Modified-Since is greater than the Last-Modified header, but less than the system time 
     60  def test_if_modified_since_greater_than_last_modified 
     61    sleep 2 
     62    last_modified_plus_1 = (Time.httpdate(@last_modified) + 1).httpdate 
     63    assert_status_for_get_and_head Net::HTTPNotModified,                           'If-Modified-Since' => last_modified_plus_1 
     64    assert_status_for_get_and_head Net::HTTPNotModified, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_plus_1 
     65  end 
     66 
     67  # status should be 200 OK when If-Modified-Since is less than the Last-Modified header 
     68  def test_if_modified_since_less_than_last_modified 
     69    last_modified_minus_1 = (Time.httpdate(@last_modified) - 1).httpdate 
     70    assert_status_for_get_and_head Net::HTTPOK,                           'If-Modified-Since' => last_modified_minus_1 
     71    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => last_modified_minus_1 
     72  end 
     73 
     74  # status should be 200 OK when If-Modified-Since is a date in the future 
     75  def test_future_if_modified_since 
     76    the_future = Time.at(2**31-1).httpdate 
     77    assert_status_for_get_and_head Net::HTTPOK,                           'If-Modified-Since' => the_future 
     78    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => @etag, 'If-Modified-Since' => the_future 
     79  end 
     80 
     81  # status should be 200 OK when If-None-Match is a wildcard 
     82  def test_wildcard_match 
     83    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*' 
     84    assert_status_for_get_and_head Net::HTTPOK, 'If-None-Match' => '*', 'If-Modified-Since' => @last_modified 
     85  end 
     86 
     87  private 
     88 
     89    # assert the response status is correct for GET and HEAD 
     90    def assert_status_for_get_and_head(response_class, headers = {}) 
     91      %w{ get head }.each do |method| 
     92        res = @http.send(method, @path, headers) 
     93        assert_kind_of response_class, res 
     94        assert_equal @etag, res['ETag'] 
     95        case response_class.to_s 
     96          when 'Net::HTTPNotModified' then 
     97            assert_nil res['Last-Modified'] 
     98            assert_nil res['Content-Length'] 
     99          when 'Net::HTTPOK' then 
     100            assert_equal @last_modified, res['Last-Modified'] 
     101            assert_equal @content_length, res['Content-Length'] 
     102          else 
     103            fail "Incorrect response class: #{response_class}" 
     104        end 
     105      end 
     106    end 
     107end 
  • lib/mongrel/configurator.rb

    old new  
    136136      opts[:throttle] ||= 0 
    137137      opts[:timeout] ||= 60 
    138138 
    139       @listener = Mongrel::HttpServer.new( 
    140       opts[:host], opts[:port].to_i, opts[:num_processors].to_i,  
    141       opts[:throttle].to_i, opts[:timeout].to_i,  
    142       opts[:log], opts[:log_level] 
    143       ) 
     139      if opts.has_key?(:unixmaster) and opts[:unixmaster] 
     140        @listener = Mongrel::UnixDispatchServer.new(opts[:host], 
     141                                                    opts[:port], 
     142                                                    opts[:min_children], 
     143                                                    opts[:max_children], 
     144                                                    opts[:log_file], 
     145                                                    opts[:log_level]) 
     146      else 
     147        @listener = Mongrel::HttpServer.new(opts[:host], 
     148                                            opts[:port].to_i, 
     149                                            opts[:num_processors].to_i, 
     150                                            opts[:throttle].to_i, 
     151                                            opts[:timeout].to_i, 
     152                                            opts[:log_file], 
     153                                            opts[:log_level]) 
     154      end 
    144155      @listener_name = "#{opts[:host]}:#{opts[:port]}" 
    145156      @listeners[@listener_name] = @listener 
    146157 
     
    372383 
    373384      unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ 
    374385        # graceful shutdown 
    375         trap("TERM") { Mongrel.log(:notice, "TERM signal received."); stop } 
     386        trap("TERM") { Mongrel.log(:notice, "pid #{$$}: TERM signal received."); stop } 
    376387        # debug mode 
    377         trap("USR1") { Mongrel.log(:notice, "USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"); $mongrel_debug_client = !$mongrel_debug_client } 
     388        trap("USR1") { Mongrel.log(:notice, "pid #{$$}: USR1 received, toggling $mongrel_debug_client to #{!$mongrel_debug_client}"); $mongrel_debug_client = !$mongrel_debug_client } 
    378389        # restart 
    379         trap("USR2") { Mongrel.log(:notice, "USR2 signal received."); stop(true) } 
     390        trap("USR2") { Mongrel.log(:notice, "pid #{$$}: USR2 signal received."); stop(true) } 
    380391 
    381392        Mongrel.log(:notice, "Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).") 
    382393      else 
     
    387398  end 
    388399 
    389400end 
     401# vim:shiftwidth=2:tabstop=2:expandtab:ft=ruby 
  • lib/mongrel.rb

    old new  
     1# Copyright (c) 2005 Zed A. Shaw 
     2# Copyright (c) 2008 Raritan Computer, Inc (Brian Weaver <brian.weaver@raritan.com>) 
    13# Ruby 
    24require 'socket' 
    35require 'tempfile' 
     
    4244  # Thrown at a thread when it is timed out. 
    4345  class TimeoutError < Exception; end 
    4446 
     47  class UriChangeEvent < Exception; end 
     48 
    4549  # A Hash with one extra parameter for the HTTP body, used internally. 
    4650  class HttpParams < Hash 
    4751    attr_accessor :http_body 
    4852  end 
    4953 
    50   # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier 
    51   # make up the majority of how the server functions.  It's a very simple class that just 
    52   # has a thread accepting connections and a simple HttpServer.process_client function 
    53   # to do the heavy lifting with the IO and Ruby.   
     54  # The base class for all server instances. The class encapsulates the basic information 
     55  # that should be common to all server instances. This includes: 
    5456  # 
    55   # You use it by doing the following: 
     57  #  * logging instance 
     58  #  * client request acceptor thread 
     59  #  * URI classifier for request routing 
     60  #  * socket domain (AF_UNIX, AF_INET, etc) 
     61  #  * host 
     62  #  * port 
    5663  # 
    57   #   server = 2("0.0.0.0", 3000) 
    58   #   server.register("/stuff", MyNiftyHandler.new) 
    59   #   server.run.join 
    60   # 
    61   # The last line can be just server.run if you don't want to join the thread used. 
    62   # If you don't though Ruby will mysteriously just exit on you. 
    63   # 
    64   # Ruby's thread implementation is "interesting" to say the least.  Experiments with 
    65   # *many* different types of IO processing simply cannot make a dent in it.  Future 
    66   # releases of Mongrel will find other creative ways to make threads faster, but don't 
    67   # hold your breath until Ruby 1.9 is actually finally useful. 
    68   class HttpServer 
    69     attr_reader :acceptor 
    70     attr_reader :workers 
    71     attr_reader :classifier 
    72     attr_reader :host 
    73     attr_reader :port 
    74     attr_reader :throttle 
    75     attr_reader :timeout 
    76     attr_reader :num_processors 
    77  
     64  class Server 
    7865    attr_accessor :logger 
     66    attr_accessor :classifier 
     67    attr_reader   :acceptor 
     68    attr_reader   :domain 
     69    attr_reader   :host 
     70    attr_reader   :port 
    7971 
    80     # Creates a working server on host:port (strange things happen if port isn't a Number). 
    81     # Use HttpServer::run to start the server and HttpServer.acceptor.join to  
    82     # join the thread that's processing incoming requests on the socket. 
    83     # 
    84     # The num_processors optional argument is the maximum number of concurrent 
    85     # processors to accept, anything over this is closed immediately to maintain 
    86     # server processing performance.  This may seem mean but it is the most efficient 
    87     # way to deal with overload.  Other schemes involve still parsing the client's request 
    88     # which defeats the point of an overload handling system. 
    89     #  
    90     # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between  
    91     # socket.accept calls in order to give the server a cheap throttle time.  It defaults to 0 and 
    92     # actually if it is 0 then the sleep is not done at all. 
    93     def initialize(host, port, num_processors=950, throttle=0, timeout=60, log=nil, log_level=:debug) 
    94        
    95       tries = 0 
    96       @socket = TCPServer.new(host, port)  
    97        
     72    def initialize(log=nil, log_level=:debug) 
    9873      @classifier = URIClassifier.new 
    99       @host = host 
    100       @port = port 
    101       @workers = ThreadGroup.new 
    102       @throttle = throttle 
    103       @num_processors = num_processors 
    104       @timeout = timeout 
    105       @logger = Mongrel::Log.new(log || "log/mongrel-#{host}-#{port}.log", log_level) 
     74      @logger = Mongrel::Log.new(log || "log/mongrel-#{domain}-#{host}-#{port}.log", log_level) 
    10675    end 
    10776     
    10877    # Does the majority of the IO processing.  It has been written in Ruby using 
     
    11786        request = nil 
    11887        data = client.readpartial(Const::CHUNK_SIZE) 
    11988        nparsed = 0 
    120  
     89       
    12190        # Assumption: nparsed will always be less since data will get filled with more 
    12291        # after each parsing.  If it doesn't get more then there was a problem 
    12392        # with the read operation on the client socket.  Effect is to stop processing when the 
     
    185154            end 
    186155          end 
    187156        end 
    188       rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF 
     157      rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF => e 
     158        Mongrel.log(:debug, e.message) 
    189159        client.close rescue nil 
    190160      rescue HttpParserError => e 
    191         Mongrel.log(:error, "#{Time.now.httpdate}: HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}") 
    192         Mongrel.log(:error, "#{Time.now.httpdate}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n") 
     161        Mongrel.log(:error, "HTTP parse error, malformed request (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #{e.inspect}") 
     162        Mongrel.log(:error, "REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n") 
    193163        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4 
    194164        client.write(Const::ERROR_400_RESPONSE) 
    195       rescue Errno::EMFILE 
     165      rescue Errno::EMFILE => e 
     166        Mongrel.log(:debug, e.message) 
    196167        reap_dead_workers('too many files') 
    197168      rescue Object => e 
    198         Mongrel.log(:error, "#{Time.now.httpdate}: Read error: #{e.inspect}") 
     169        Mongrel.log(:error, "Read error: #{e.inspect}") 
    199170        Mongrel.log(:error, e.backtrace.join("\n")) 
    200171      ensure 
    201172        begin 
    202173          client.close 
    203         rescue IOError 
     174        rescue IOError => e 
     175          Mongrel.log(:debug, e.message) 
    204176          # Already closed 
    205177        rescue Object => e 
    206           Mongrel.log(:error, "#{Time.now.httpdate}: Client error: #{e.inspect}") 
     178          Mongrel.log(:error, "Client error: #{e.inspect}") 
    207179          Mongrel.log(:error, e.backtrace.join("\n")) 
    208180        end 
    209181        request.body.delete if request and request.body.class == Tempfile 
    210182      end 
    211183    end 
    212184 
     185    def reap_dead_workers(reason=nil) 
     186      raise RuntimeError, "This method must be overridden in a derived class" 
     187    end 
     188 
     189    def configure_socket_options 
     190      case RUBY_PLATFORM 
     191      when /linux/ 
     192        # 9 is currently TCP_DEFER_ACCEPT 
     193        $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1] 
     194        $tcp_cork_opts = [Socket::SOL_TCP, 3, 1] 
     195      when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ 
     196        # Do nothing, just closing a bug when freebsd <= 5.4 
     197      when /freebsd/ 
     198        # Use the HTTP accept filter if available. 
     199        # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg 
     200        unless `/sbin/sysctl -nq net.inet.accf.http`.empty? 
     201          $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')] 
     202        end 
     203      end 
     204    end 
     205 
     206    # Runs the thing.  It returns the thread used so you can "join" it.  You can also 
     207    # access the HttpServer::acceptor attribute to get the thread later. 
     208    def run 
     209      raise RuntimeError, "This method must be overridden in a derived class" 
     210    end 
     211 
     212    # Simply registers a handler with the internal URIClassifier.  When the URI is 
     213    # found in the prefix of a request then your handler's HttpHandler::process method 
     214    # is called.  See Mongrel::URIClassifier#register for more information. 
     215    # 
     216    # If you set in_front=true then the passed in handler will be put in the front of the list 
     217    # for that particular URI. Otherwise it's placed at the end of the list. 
     218    def register(uri, handler, in_front=false) 
     219      begin 
     220        @classifier.register(uri, [handler]) 
     221      rescue URIClassifier::RegistrationError 
     222        handlers = @classifier.resolve(uri)[2] 
     223        method_name = in_front ? 'unshift' : 'push' 
     224        handlers.send(method_name, handler) 
     225      end 
     226      handler.listener = self 
     227    end 
     228 
     229    # Removes any handlers registered at the given URI.  See Mongrel::URIClassifier#unregister 
     230    # for more information.  Remember this removes them *all* so the entire 
     231    # processing chain goes away. 
     232    def unregister(uri) 
     233      @classifier.unregister(uri) 
     234    end 
     235 
     236    def stop(synchronous=false) 
     237      #Mongrel.log(:debug, "stop invoked for #{self.inspect}") 
     238      Mongrel.log(:debug, "stop invoked for #{$$}") 
     239      # 
     240      # check for a nil acceptor, guard against stupid errors 
     241      if @acceptor.nil? 
     242        Mongrel.log(:error, "unexpected condition, the acceptor thread was nil in the stop method") 
     243        Mongrel.log(:error, Exception.new.backtrace.join("\n")) 
     244      else 
     245        @acceptor.raise(StopServer.new) 
     246 
     247        if synchronous 
     248          sleep(0.5) while @acceptor.alive? 
     249        end 
     250      end 
     251    end 
     252 
     253  end 
     254 
     255  # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier 
     256  # make up the majority of how the server functions.  It's a very simple class that just 
     257  # has a thread accepting connections and a simple HttpServer.process_client function 
     258  # to do the heavy lifting with the IO and Ruby.   
     259  # 
     260  # You use it by doing the following: 
     261  # 
     262  #   server = 2("0.0.0.0", 3000) 
     263  #   server.register("/stuff", MyNiftyHandler.new) 
     264  #   server.run.join 
     265  # 
     266  # The last line can be just server.run if you don't want to join the thread used. 
     267  # If you don't though Ruby will mysteriously just exit on you. 
     268  # 
     269  # Ruby's thread implementation is "interesting" to say the least.  Experiments with 
     270  # *many* different types of IO processing simply cannot make a dent in it.  Future 
     271  # releases of Mongrel will find other creative ways to make threads faster, but don't 
     272  # hold your breath until Ruby 1.9 is actually finally useful. 
     273  #  
     274  # Functionality moved to base class by Brian Weaver (cmdrclueless at gmail.com) 2008-05-06 
     275  # 
     276  class HttpServer < Server 
     277    attr_reader :workers 
     278    attr_reader :throttle 
     279    attr_reader :timeout 
     280    attr_reader :num_processors 
     281 
     282    # Creates a working server on host:port (strange things happen if port isn't a Number). 
     283    # Use HttpServer::run to start the server and HttpServer.acceptor.join to  
     284    # join the thread that's processing incoming requests on the socket. 
     285    # 
     286    # The num_processors optional argument is the maximum number of concurrent 
     287    # processors to accept, anything over this is closed immediately to maintain 
     288    # server processing performance.  This may seem mean but it is the most efficient 
     289    # way to deal with overload.  Other schemes involve still parsing the client's request 
     290    # which defeats the point of an overload handling system. 
     291    #  
     292    # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between  
     293    # socket.accept calls in order to give the server a cheap throttle time.  It defaults to 0 and 
     294    # actually if it is 0 then the sleep is not done at all. 
     295    # 
     296    def initialize(host, port, num_processors=950, throttle=0, timeout=60, log=nil, log_level=:debug) 
     297       
     298      @host   = host 
     299      @port   = port 
     300      @domain = "tcp" 
     301 
     302      super(log, log_level) 
     303 
     304      @socket   = TCPServer.new(host, port)  
     305      @workers  = ThreadGroup.new 
     306      @throttle = throttle 
     307      @timeout  = timeout 
     308      @num_processors = num_processors 
     309 
     310    end 
     311     
    213312    # Used internally to kill off any worker threads that have taken too long 
    214313    # to complete processing.  Only called if there are too many processors 
    215314    # currently servicing.  It returns the count of workers still active 
    216315    # after the reap is done.  It only runs if there are workers to reap. 
     316    # 
    217317    def reap_dead_workers(reason='unknown') 
     318 
    218319      if @workers.list.length > 0 
    219         Mongrel.log(:error, "#{Time.now.httpdate}: Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'") 
    220         error_msg = "#{Time.now.httpdate}: Mongrel timed out this thread: #{reason}" 
     320 
     321        Mongrel.log(:error, "Reaping #{@workers.list.length} threads for slow workers because of '#{reason}'") 
     322        error_msg = "Mongrel timed out this thread: #{reason}" 
    221323        mark = Time.now 
    222324        @workers.list.each do |worker| 
    223325          worker[:started_on] = Time.now if not worker[:started_on] 
    224326 
    225327          if mark - worker[:started_on] > @timeout + @throttle 
    226             Mongrel.log(:error, "#{Time.now.httpdate}: Thread #{worker.inspect} is too old, killing.") 
     328            Mongrel.log(:error, "Thread #{worker.inspect} is too old, killing.") 
    227329            worker.raise(TimeoutError.new(error_msg)) 
    228330          end 
    229331        end 
     332 
    230333      end 
    231334 
    232       return @workers.list.length 
     335      @workers.list.length 
    233336    end 
    234337 
    235338    # Performs a wait on all the currently running threads and kills any that take 
    236339    # too long.  It waits by @timeout seconds, which can be set in .initialize or 
    237340    # via mongrel_rails. The @throttle setting does extend this waiting period by 
    238341    # that much longer. 
     342    # 
    239343    def graceful_shutdown 
    240344      while reap_dead_workers("shutdown") > 0 
    241         Mongrel.log(:error, "#{Time.now.httpdate}: Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds.") 
     345        Mongrel.log(:error, "Waiting for #{@workers.list.length} requests to finish, could take #{@timeout + @throttle} seconds.") 
    242346        sleep @timeout / 10 
    243347      end 
    244348    end 
    245349 
    246     def configure_socket_options 
    247       case RUBY_PLATFORM 
    248       when /linux/ 
    249         # 9 is currently TCP_DEFER_ACCEPT 
    250         $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1] 
    251         $tcp_cork_opts = [Socket::SOL_TCP, 3, 1] 
    252       when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ 
    253         # Do nothing, just closing a bug when freebsd <= 5.4 
    254       when /freebsd/ 
    255         # Use the HTTP accept filter if available. 
    256         # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg 
    257         unless `/sbin/sysctl -nq net.inet.accf.http`.empty? 
    258           $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')] 
    259         end 
    260       end 
    261     end 
    262      
    263350    # Runs the thing.  It returns the thread used so you can "join" it.  You can also 
    264351    # access the HttpServer::acceptor attribute to get the thread later. 
     352    # 
    265353    def run 
    266354      BasicSocket.do_not_reverse_lookup=true 
    267355 
     
    279367 
    280368              num_workers = @workers.list.length 
    281369              if num_workers >= @num_processors 
    282                 Mongrel.log(:error, "#{Time.now.httpdate}: Server overloaded with #{num_workers} processors (#@num_processors max). Dropping connection.") 
     370                Mongrel.log(:error, "Server overloaded with #{num_workers} processors (#@num_processors max). Dropping connection.") 
    283371                client.close rescue nil 
    284372                reap_dead_workers("max processors") 
    285373              else 
     
    301389              # client closed the socket even before accept 
    302390              client.close rescue nil 
    303391            rescue Object => e 
    304               Mongrel.log(:error, "#{Time.now.httpdate}: Unhandled listen loop exception #{e.inspect}.") 
     392              Mongrel.log(:error, "Unhandled listen loop exception #{e.inspect}.") 
    305393              Mongrel.log(:error, e.backtrace.join("\n")) 
    306394            end 
    307395          end 
    308396          graceful_shutdown 
    309397        ensure 
    310398          @socket.close 
    311           # Mongrel.log(:error, "#{Time.now.httpdate}: Closed socket.") 
    312399        end 
    313400      end 
    314401 
    315       return @acceptor 
     402      @acceptor 
    316403    end 
     404  end 
    317405 
    318     # Simply registers a handler with the internal URIClassifier.  When the URI is 
    319     # found in the prefix of a request then your handler's HttpHandler::process method 
    320     # is called.  See Mongrel::URIClassifier#register for more information. 
    321     # 
    322     # If you set in_front=true then the passed in handler will be put in the front of the list 
    323     # for that particular URI. Otherwise it's placed at the end of the list. 
    324     def register(uri, handler, in_front=false) 
    325       begin 
    326         @classifier.register(uri, [handler]) 
    327       rescue URIClassifier::RegistrationError 
    328         handlers = @classifier.resolve(uri)[2] 
    329         method_name = in_front ? 'unshift' : 'push' 
    330         handlers.send(method_name, handler) 
     406  class UnixDispatchServer < Server 
     407    attr_reader :min_children 
     408    attr_reader :num_children 
     409    attr_reader :max_children 
     410 
     411    def initialize(host, port, min_children=5, max_children=nil, log=nil, log_level=:debug) 
     412      @host     = host 
     413      @port     = port 
     414      @domain   = 'unix' 
     415      super(log, log_level) 
     416 
     417      @tcpsocket = TCPServer.new(@host,@port) 
     418 
     419      @terminate = false 
     420      @children  = Hash.new 
     421      @busy      = Hash.new 
     422 
     423      @min_children = min_children 
     424      @max_children = max_children 
     425 
     426      @close_on_fork = [@tcpsocket] 
     427    end 
     428 
     429    def reap_dead_workers(reason=nil) 
     430      # do nothing, not useful for the unix dispatch server 
     431    end 
     432 
     433    def runchild(server) 
     434      BasicSocket.do_not_reverse_lookup = true 
     435      configure_socket_options 
     436 
     437      Mongrel.log(:notice, "New child server started, process #{$$}") 
     438      @acceptor = Thread.new do 
     439        begin 
     440          while not @terminate 
     441            begin 
     442              server.write("READY #{$$}\n") 
     443              server.flush 
     444              Mongrel.log(:debug, "Child #{$$} wrote \"READY #{$$}\"") 
     445 
     446              client = server.recv_io(TCPSocket) # read the client's file descriptor from the server! 
     447              unless client.is_a?(TCPSocket) 
     448                Mongrel.log(:error, "Child #{$$} doesn't know how to handle object #{client.class}: #{client.inspect}") 
     449                next 
     450              end 
     451              if client.respond_to?(:setsockopt)  and defined?($tcp_cork_opts) and $tcp_cork_opts 
     452                client.setsockopt(*$tcp_cork_opts) rescue nil 
     453              end 
     454 
     455              process_client(client) 
     456            rescue StopServer 
     457              Mongrel.log(:debug, "Child #{$$} received StopServer command") 
     458              @terminate = true 
     459            rescue Errno::ECONNABORTED 
     460              Mongrel.log(:debug, "Child #{$$} connection aborted") 
     461              client.close rescue nil 
     462            rescue Errno::EPIPE 
     463              # server broke connection 
     464              Mongrel.log(:debug, "Child #{$$} broken pipe") 
     465              @terminate = true 
     466            rescue SignalException => e 
     467              # child signaled to terminate 
     468              @terminate = true 
     469              Mongrel.log(:debug, "Child #{$$} Termination signal received, #{e.signm}") 
     470            rescue Object => e 
     471              @terminte = true 
     472              Mongrel.log(:error, "Child #{$$} Unhandled listen loop exception #{e.inspect}.") 
     473              Mongrel.log(:error, e.backtrace.join("\n")) 
     474            end 
     475          end 
     476        ensure 
     477          server.write("CLOSED #{$$}\n") rescue nil 
     478          server.close rescue nil 
     479          Mongrel.log(:debug, "Child #{$$} wrote \"CLOSED #{$$}\"") 
     480        end 
     481        Mongrel.log(:notice, "Child #{$$} stopped") 
    331482      end 
    332       handler.listener = self 
     483 
     484      @acceptor 
    333485    end 
    334486 
    335     # Removes any handlers registered at the given URI.  See Mongrel::URIClassifier#unregister 
    336     # for more information.  Remember this removes them *all* so the entire 
    337     # processing chain goes away. 
    338     def unregister(uri) 
    339       @classifier.unregister(uri) 
     487    def start_child 
     488      cio,sio = UNIXSocket::socketpair 
     489      @close_on_fork << sio 
     490 
     491      Mongrel.log(:debug, "Server #{$$} starting new child handler") 
     492      pid = fork 
     493      if pid.nil? # child 
     494        begin 
     495          @close_on_fork.each { |io| io.close rescue nil } 
     496          unless unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ 
     497            trap("INT")  { Mongrel.log("Child #{$$} received SIGINT"); stop } 
     498            trap("HUP")  { Mongrel.log("Child #{$$} received SIGHUP"); stop } 
     499            trap("TERM") { Mongrel.log("Child #{$$} received SIGTERM"); stop } 
     500          end 
     501          runchild(cio).join 
     502          Kernel.exit! 0 
     503        #rescue StopServer 
     504        #  Mongrel.log("Child #{$$} rescued from StopServer error, exiting!") 
     505        #  Kernel.exit! 0 
     506        rescue Object => e 
     507          Mongrel.log(:error, "Child #{$$} Unhandled listen loop exception #{e.inspect}.") 
     508          Mongrel.log(:error, e.backtrace.join("\n")) 
     509        end 
     510        Kernel.exit! 1 
     511      end 
     512 
     513      cio.close rescue nil 
     514      [pid,sio] 
    340515    end 
    341516 
    342     # Stops the acceptor thread and then causes the worker threads to finish 
    343     # off the request queue before finally exiting. 
    344     def stop(synchronous=false) 
    345       @acceptor.raise(StopServer.new) 
     517    def evict_child(pid) 
     518      begin 
     519        Process.kill("TERM", pid)  
     520        Mongrel.log(:debug, "Sent TERM to process #{pid}, waiting ...") 
     521        Process.waitpid(pid) 
     522      rescue Errno::ESRCH,Errno::ECHILD => e 
     523        Mongrel.log(:debug, "receied fault ESRCH or ECHILD #{e.insepct}.") 
     524      rescue StopServer 
     525        # be sure to pass the stop up the call chain until it gets to the toplevel handler! 
     526        raise 
     527      rescue Object => e 
     528        Mongrel.log(:error, "Server #{$$} Unhandled exception #{e.inspect}.") 
     529        Mongrel.log(:error, e.backtrace.join("\n")) 
     530      end 
    346531 
    347       if synchronous 
    348         sleep(0.5) while @acceptor.alive? 
     532      if @children.has_key?(pid) 
     533        io = @children.delete(pid) 
     534        if io.respond_to?(:close) 
     535          io.close rescue nil 
     536        end 
    349537      end 
     538 
     539      if @busy.has_key?(pid) 
     540        io = @busy.delete(pid) 
     541        if io.respond_to?(:close) 
     542          io.close rescue nil 
     543        end 
     544      end 
     545 
    350546    end 
    351547 
     548    def run 
     549      BasicSocket.do_not_reverse_lookup = true 
     550      configure_socket_options 
     551 
     552      if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts 
     553          @tcpsocket.setsockopt(*$tcp_defer_accept_opts) rescue nil 
     554      end 
     555 
     556      @acceptor = Thread.new do 
     557 
     558        begin 
     559          Mongrel.log("Server #{$$} ready to accept connections") 
     560          while not @terminate 
     561            begin 
     562              @children.each_key do |pid| 
     563                begin 
     564                  do_evict = false 
     565                  do_evict = true if (@children[pid].closed? or Process.waitpid(pid, Process::WNOHANG) == pid) 
     566                rescue Errno::ECHILD 
     567                  Mongrel.log(:debug, "Server #{$$} waitpid returned ECHILD, the server has no children") 
     568                  do_evict = true 
     569                rescue StopServer 
     570                  raise # pass it up the chain, don't ignore it. 
     571                rescue Object => e 
     572                  Mongrel.log(:error, "Server #{$$} Unhandled exception #{e.inspect}.") 
     573                  Mongrel.log(:error, e.backtrace.join("\n")) 
     574                end 
     575 
     576                evict_child(pid) if do_evict 
     577              end 
     578 
     579              if @children.length < @min_children 
     580                (@children.length .. @min_children).each do 
     581                  pid, socket = start_child 
     582                  @children[pid] = socket 
     583                  @busy[pid]     = true 
     584                end 
     585              end 
     586 
     587              readfds = [ @tcpsocket ] + @children.values  
     588              r,w,e  = Kernel.select(readfds, nil, nil, 60) 
     589 
     590              # check to see if anyone wants to talk to us. If no one is talking 
     591              # and we have more than the necessary number of children then signal 
     592              # one to terminate. 
     593              # 
     594              evict_child(@children.keys.first) if (r.nil? or r.empty?) and @children.length > @min_children 
     595              next if r.nil? 
     596 
     597              # OK, someone is talking. Find out who and process the client. 
     598              # Delay the processing of the TCP server socket until all the children 
     599              # are finished. This allows a client to become available before processing 
     600              # the HTTP connection. 
     601              # 
     602              flag_http = false 
     603              r.each do |io| 
     604                if io == @tcpsocket # a new connection from apache or the outside world 
     605                  flag_http = true  # defer 
     606                else                # one of the children 
     607                  begin 
     608                    msg = io.readline 
     609                    msg.chomp! 
     610                    Mongrel.log(:debug, "Server #{$$} received message \"#{msg}\"") 
     611                    if msg =~ /^READY\s+(\d+)$/ 
     612                      pid = $1.to_i 
     613                      if @busy[pid].respond_to?(:close) 
     614                        @busy[pid].close rescue nil 
     615                      end 
     616                      @busy[pid] = false 
     617                    elsif msg =~ /CLOSED\s+(\d+)/ 
     618                      evict_child($1.to_i) 
     619                    end 
     620                  rescue EOFError => ex 
     621                    Mongrel.log(:error,ex.message) 
     622                    @children.each_key do |pid| 
     623                      evict_child(pid) if @children[pid] == io 
     624                    end 
     625                  end 
     626                end 
     627              end 
     628 
     629              next unless flag_http 
     630 
     631              client = nil 
     632              begin 
     633                client = @tcpsocket.accept 
     634              rescue StandardError => e 
     635                Mongrel.log(:error, "Server #{$$} An error occurred accepting a new client connection, a restart may be necessary.") 
     636                Mongrel.log(:error, "Server #{$$} #{e.message}") 
     637                Mongrel.log(:info, e.backtrace.join("\n")) 
     638              end 
     639 
     640              next if client.nil? 
     641 
     642              @busy.each_pair do |pid,busy| 
     643                unless busy 
     644                  io = @children[pid] 
     645                  io.send_io(client) 
     646                  io.flush 
     647 
     648                  @busy[pid] = client 
     649                  client = nil 
     650                  break 
     651                end 
     652              end 
     653 
     654              if client and not @max_children.nil? and @max_children == @children.length 
     655                Mongrel.log(:error, "Server #{$$} Maximum number of child processes exceeded, request aborted") 
     656                client.close rescue nil 
     657                client = nil 
     658              end 
     659 
     660              next unless client 
     661 
     662              Mongrel.log(:debug, "Server #{$$} No avaialble child found, spinning up a new child") 
     663 
     664              # spin up a new one 
     665              # 
     666              pid, socket = start_child 
     667              @children[pid] = socket 
     668              socket.readline 
     669              socket.send_io(client) 
     670              socket.flush 
     671              @busy[pid] = client 
     672 
     673            rescue StopServer 
     674              @terminate = true 
     675            rescue UriChangeEvent 
     676              Mongrel.log("URI Handler Changed, respawing children") 
     677              @children.keys.each { |pid| evict_child(pid) } 
     678            rescue Object => e 
     679              Mongrel.log(:error, "Server #{$$} Unhandled listen loop exception #{e.inspect}.") 
     680              Mongrel.log(:error, e.backtrace.join("\n")) 
     681            end 
     682          end 
     683        ensure 
     684          @tcpsocket.close  rescue nil 
     685          @children.each_key { |pid| evict_child(pid) } 
     686        end 
     687      end # end thread 
     688 
     689      @acceptor 
     690    end 
     691 
     692    def register(uri, handler, in_front=false) 
     693      super(uri,handler,in_front) 
     694      @acceptor.raise(UriChangeEvent.new) if not @acceptor.nil? and @acceptor.alive? 
     695    end 
     696 
     697    def unregister(uri) 
     698      super(uri) 
     699      @acceptor.raise(UriChangeEvent.new) if not @acceptor.nil? and @acceptor.alive? 
     700    end 
     701 
    352702  end 
    353703end 
    354704 
     
    357707 
    358708$LOAD_PATH.unshift 'projects/mongrel_experimental/lib/' 
    359709Mongrel::Gems.require 'mongrel_experimental', ">=#{Mongrel::Const::MONGREL_VERSION}" 
     710 
     711# vim:expandtab:shiftwidth=2:tabstop=2 
  • bin/mongrel_rails

    old new  
    11# Copyright (c) 2005 Zed A. Shaw