Ticket #30: mongrel_patch_stable_1-1.20080624-rev_1028.patch

File mongrel_patch_stable_1-1.20080624-rev_1028.patch, 32.7 kB (added by cmdrclueless, 4 months ago)

This is a patch against revision 1028 for 1.1 stable

  • branches/stable_1-1/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 = 9998 
     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 
  • branches/stable_1-1/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', 9998, 1) 
     14    end 
     15    @server.run 
     16    @client = Net::HTTP.new('127.0.0.1', 9998) 
     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 
  • branches/stable_1-1/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', 3501, 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 
  • branches/stable_1-1/lib/mongrel/configurator.rb

    old new  
    136136      ops[:throttle] ||= 0 
    137137      ops[:timeout] ||= 60 
    138138 
    139       @listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:throttle].to_i, ops[:timeout].to_i) 
     139      @listener = Mongrel::HttpServer.new(ops[:host], ops[:port].to_i, ops[:num_processors].to_i, ops[:throttle].to_i, ops[:timeout].to_i) unless ops.has_key?(:unixmaster) and ops[:unixmaster] 
     140      @listener = Mongrel::UnixDispatchServer.new(ops[:host], ops[:port].to_i, ops[:min_children].to_i, ops[:max_childen].to_i) if ops.has_key?(:unixmaster) and ops[:unixmaster] 
    140141      @listener_name = "#{ops[:host]}:#{ops[:port]}" 
    141142      @listeners[@listener_name] = @listener 
    142143 
     
    386387 
    387388  end 
    388389end 
     390# vim:shiftwidth=2:tabstop=2:expandtab:ft=ruby 
  • branches/stable_1-1/lib/mongrel.rb

    old new  
    4040  # Thrown at a thread when it is timed out. 
    4141  class TimeoutError < Exception; end 
    4242 
     43  class UriChangeEvent < Exception; end 
     44 
    4345  # A Hash with one extra parameter for the HTTP body, used internally. 
    4446  class HttpParams < Hash 
    4547    attr_accessor :http_body 
    4648  end 
    4749 
     50  class Server 
     51    attr_accessor :classifier 
     52    attr_reader   :acceptor 
     53    attr_reader   :domain 
     54    attr_reader   :host 
     55    attr_reader   :port 
    4856 
    49   # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier 
    50   # make up the majority of how the server functions.  It's a very simple class that just 
    51   # has a thread accepting connections and a simple HttpServer.process_client function 
    52   # to do the heavy lifting with the IO and Ruby.   
    53   # 
    54   # You use it by doing the following: 
    55   # 
    56   #   server = HttpServer.new("0.0.0.0", 3000) 
    57   #   server.register("/stuff", MyNiftyHandler.new) 
    58   #   server.run.join 
    59   # 
    60   # The last line can be just server.run if you don't want to join the thread used. 
    61   # If you don't though Ruby will mysteriously just exit on you. 
    62   # 
    63   # Ruby's thread implementation is "interesting" to say the least.  Experiments with 
    64   # *many* different types of IO processing simply cannot make a dent in it.  Future 
    65   # releases of Mongrel will find other creative ways to make threads faster, but don't 
    66   # hold your breath until Ruby 1.9 is actually finally useful. 
    67   class HttpServer 
    68     attr_reader :acceptor 
    69     attr_reader :workers 
    70     attr_reader :classifier 
    71     attr_reader :host 
    72     attr_reader :port 
    73     attr_reader :throttle 
    74     attr_reader :timeout 
    75     attr_reader :num_processors 
    76  
    7757    # Creates a working server on host:port (strange things happen if port isn't a Number). 
    7858    # Use HttpServer::run to start the server and HttpServer.acceptor.join to  
    7959    # join the thread that's processing incoming requests on the socket. 
     
    8767    # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between  
    8868    # socket.accept calls in order to give the server a cheap throttle time.  It defaults to 0 and 
    8969    # actually if it is 0 then the sleep is not done at all. 
    90     def initialize(host, port, num_processors=950, throttle=0, timeout=60) 
    91        
    92       tries = 0 
    93       @socket = TCPServer.new(host, port)  
    94        
     70    def initialize 
    9571      @classifier = URIClassifier.new 
    96       @host = host 
    97       @port = port 
    98       @workers = ThreadGroup.new 
    99       @throttle = throttle / 100.0 
    100       @num_processors = num_processors 
    101       @timeout = timeout 
    10272    end 
    10373 
    10474    # Does the majority of the IO processing.  It has been written in Ruby using 
     
    204174      end 
    205175    end 
    206176 
     177    def read_dead_workers(reason=nil) 
     178      raise RuntimeError, "This method must be overridden in a derived class" 
     179    end 
     180 
     181    def configure_socket_options 
     182      case RUBY_PLATFORM 
     183      when /linux/ 
     184        # 9 is currently TCP_DEFER_ACCEPT 
     185        $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1] 
     186        $tcp_cork_opts = [Socket::SOL_TCP, 3, 1] 
     187      when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ 
     188        # Do nothing, just closing a bug when freebsd <= 5.4 
     189      when /freebsd/ 
     190        # Use the HTTP accept filter if available. 
     191        # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg 
     192        unless `/sbin/sysctl -nq net.inet.accf.http`.empty? 
     193          $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')] 
     194        end 
     195      end 
     196    end 
     197     
     198    # Runs the thing.  It returns the thread used so you can "join" it.  You can also 
     199    # access the HttpServer::acceptor attribute to get the thread later. 
     200    def run 
     201      raise RuntimeError, "This method must be overridden in a derived class" 
     202    end 
     203 
     204    # Simply registers a handler with the internal URIClassifier.  When the URI is 
     205    # found in the prefix of a request then your handler's HttpHandler::process method 
     206    # is called.  See Mongrel::URIClassifier#register for more information. 
     207    # 
     208    # If you set in_front=true then the passed in handler will be put in the front of the list 
     209    # for that particular URI. Otherwise it's placed at the end of the list. 
     210    def register(uri, handler, in_front=false) 
     211      begin 
     212        @classifier.register(uri, [handler]) 
     213      rescue URIClassifier::RegistrationError 
     214        handlers = @classifier.resolve(uri)[2] 
     215        method_name = in_front ? 'unshift' : 'push' 
     216        handlers.send(method_name, handler) 
     217      end 
     218      handler.listener = self 
     219    end 
     220 
     221    # Removes any handlers registered at the given URI.  See Mongrel::URIClassifier#unregister 
     222    # for more information.  Remember this removes them *all* so the entire 
     223    # processing chain goes away. 
     224    def unregister(uri) 
     225      @classifier.unregister(uri) 
     226    end 
     227 
     228    # Stops the acceptor thread and then causes the worker threads to finish 
     229    # off the request queue before finally exiting. 
     230    def stop(synchronous=false) 
     231      unless @acceptor.nil? 
     232        @acceptor.raise(StopServer.new) 
     233 
     234        if synchronous 
     235          sleep(0.5) while @acceptor.alive? 
     236        end 
     237      end 
     238    end 
     239  end 
     240 
     241  # This is the main driver of Mongrel, while the Mongrel::HttpParser and Mongrel::URIClassifier 
     242  # make up the majority of how the server functions.  It's a very simple class that just 
     243  # has a thread accepting connections and a simple HttpServer.process_client function 
     244  # to do the heavy lifting with the IO and Ruby.   
     245  # 
     246  # You use it by doing the following: 
     247  # 
     248  #   server = HttpServer.new("0.0.0.0", 3000) 
     249  #   server.register("/stuff", MyNiftyHandler.new) 
     250  #   server.run.join 
     251  # 
     252  # The last line can be just server.run if you don't want to join the thread used. 
     253  # If you don't though Ruby will mysteriously just exit on you. 
     254  # 
     255  # Ruby's thread implementation is "interesting" to say the least.  Experiments with 
     256  # *many* different types of IO processing simply cannot make a dent in it.  Future 
     257  # releases of Mongrel will find other creative ways to make threads faster, but don't 
     258  # hold your breath until Ruby 1.9 is actually finally useful. 
     259  class HttpServer < Server 
     260    attr_reader :workers 
     261    attr_reader :throttle 
     262    attr_reader :timeout 
     263    attr_reader :num_processors 
     264 
     265    # Creates a working server on host:port (strange things happen if port isn't a Number). 
     266    # Use HttpServer::run to start the server and HttpServer.acceptor.join to  
     267    # join the thread that's processing incoming requests on the socket. 
     268    # 
     269    # The num_processors optional argument is the maximum number of concurrent 
     270    # processors to accept, anything over this is closed immediately to maintain 
     271    # server processing performance.  This may seem mean but it is the most efficient 
     272    # way to deal with overload.  Other schemes involve still parsing the client's request 
     273    # which defeats the point of an overload handling system. 
     274    #  
     275    # The throttle parameter is a sleep timeout (in hundredths of a second) that is placed between  
     276    # socket.accept calls in order to give the server a cheap throttle time.  It defaults to 0 and 
     277    # actually if it is 0 then the sleep is not done at all. 
     278    def initialize(host, port, num_processors=950, throttle=0, timeout=60) 
     279      @host = host 
     280      @port = port 
     281      @domain = "tcp" 
     282      super() 
     283 
     284      @socket = TCPServer.new(host, port)  
     285      @workers = ThreadGroup.new 
     286      @throttle = throttle / 100.0 
     287      @num_processors = num_processors 
     288      @timeout = timeout 
     289    end 
     290 
    207291    # Used internally to kill off any worker threads that have taken too long 
    208292    # to complete processing.  Only called if there are too many processors 
    209293    # currently servicing.  It returns the count of workers still active 
     
    236320        sleep @timeout / 10 
    237321      end 
    238322    end 
    239  
    240     def configure_socket_options 
    241       case RUBY_PLATFORM 
    242       when /linux/ 
    243         # 9 is currently TCP_DEFER_ACCEPT 
    244         $tcp_defer_accept_opts = [Socket::SOL_TCP, 9, 1] 
    245         $tcp_cork_opts = [Socket::SOL_TCP, 3, 1] 
    246       when /freebsd(([1-4]\..{1,2})|5\.[0-4])/ 
    247         # Do nothing, just closing a bug when freebsd <= 5.4 
    248       when /freebsd/ 
    249         # Use the HTTP accept filter if available. 
    250         # The struct made by pack() is defined in /usr/include/sys/socket.h as accept_filter_arg 
    251         unless `/sbin/sysctl -nq net.inet.accf.http`.empty? 
    252           $tcp_defer_accept_opts = [Socket::SOL_SOCKET, Socket::SO_ACCEPTFILTER, ['httpready', nil].pack('a16a240')] 
    253         end 
    254       end 
    255     end 
    256323     
    257324    # Runs the thing.  It returns the thread used so you can "join" it.  You can also 
    258325    # access the HttpServer::acceptor attribute to get the thread later. 
     
    310377 
    311378      return @acceptor 
    312379    end 
     380  end 
    313381 
    314     # Simply registers a handler with the internal URIClassifier.  When the URI is 
    315     # found in the prefix of a request then your handler's HttpHandler::process method 
    316     # is called.  See Mongrel::URIClassifier#register for more information. 
    317     # 
    318     # If you set in_front=true then the passed in handler will be put in the front of the list 
    319     # for that particular URI. Otherwise it's placed at the end of the list. 
    320     def register(uri, handler, in_front=false) 
    321       begin 
    322         @classifier.register(uri, [handler]) 
    323       rescue URIClassifier::RegistrationError 
    324         handlers = @classifier.resolve(uri)[2] 
    325         method_name = in_front ? 'unshift' : 'push' 
    326         handlers.send(method_name, handler) 
     382  class UnixDispatchServer < Server 
     383    attr_reader :min_children 
     384    attr_reader :num_children 
     385    attr_reader :max_children 
     386 
     387    def initialize(host, port, min_children=5, max_children=nil, log=nil, log_level=:debug) 
     388      @host     = host 
     389      @port     = port 
     390      @domain   = 'unix' 
     391      super() 
     392 
     393      @serversock= TCPServer.new(@host,@port) 
     394 
     395      @terminate = false 
     396      @children  = Hash.new 
     397      @busy      = Hash.new 
     398 
     399      @min_children = min_children 
     400      @max_children = max_children 
     401 
     402      @close_on_fork = [@serversock] 
     403    end 
     404 
     405    def reap_dead_workers(reason=nil) 
     406    end # do nothing, not useful for the unix dispatch server 
     407 
     408    def runchild(server) 
     409      BasicSocket.do_not_reverse_lookup = true 
     410 
     411      configure_socket_options 
     412 
     413      @acceptor = Thread.new do 
     414        begin 
     415          while not @terminate 
     416            begin 
     417              server.write("READY #{$$}\n") 
     418              server.flush 
     419 
     420              client = server.recv_io(TCPSocket) # read the client's file descriptor from the server! 
     421              unless client.is_a?(TCPSocket) 
     422                next 
     423              end 
     424              if client.respond_to?(:setsockopt)  and defined?($tcp_cork_opts) and $tcp_cork_opts 
     425                client.setsockopt(*$tcp_cork_opts) rescue nil 
     426              end 
     427 
     428              process_client(client) 
     429            rescue StopServer 
     430              @terminate = true 
     431            rescue Errno::ECONNABORTED 
     432              client.close rescue nil 
     433            rescue Errno::EPIPE 
     434              # server broke connection 
     435              @terminate = true 
     436            rescue SignalException => e 
     437              # child signaled to terminate 
     438              @terminate = true 
     439            rescue Object => e 
     440              @terminte = true 
     441              STDERR.puts "Child #{$$} Unhandled listen loop exception #{e.inspect}." 
     442              STDERR.puts e.backtrace.join("\n") 
     443            end 
     444          end 
     445        ensure 
     446          server.write("CLOSED #{$$}\n") rescue nil 
     447          server.close rescue nil 
     448        end 
    327449      end 
    328       handler.listener = self 
     450      return @acceptor 
    329451    end 
    330452 
    331     # Removes any handlers registered at the given URI.  See Mongrel::URIClassifier#unregister 
    332     # for more information.  Remember this removes them *all* so the entire 
    333     # processing chain goes away. 
    334     def unregister(uri) 
    335       @classifier.unregister(uri) 
     453    def start_child 
     454      cio,sio = UNIXSocket::socketpair 
     455      @close_on_fork << sio 
     456 
     457      pid = fork 
     458      if pid.nil? # child 
     459        begin 
     460          @close_on_fork.each { |io| io.close rescue nil } 
     461          unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ 
     462            trap("INT")  { @terminate = true; stop } 
     463            trap("HUP")  { @terminate = true; stop } 
     464            trap("TERM") { @terminate = true; stop } 
     465          end 
     466          begin 
     467            runchild(cio).join 
     468          rescue StopServer 
     469            nil # no need to log this, it's just going to terminate the child. 
     470          end 
     471          Kernel.exit!(0) 
     472        rescue Object => e 
     473          STDERR.puts "Child #{$$} Unhandled listen loop exception #{e.inspect}." 
     474          STDERR.puts e.backtrace.join("\n") 
     475        end 
     476        Kernel.exit!(1) 
     477      end 
     478 
     479      cio.close rescue nil 
     480      [pid,sio] 
    336481    end 
    337482 
    338     # Stops the acceptor thread and then causes the worker threads to finish 
    339     # off the request queue before finally exiting. 
    340     def stop(synchronous=false) 
    341       @acceptor.raise(StopServer.new) 
     483    def evict_child(pid) 
     484      begin 
     485        Process.kill("TERM", pid)  
     486        Process.waitpid(pid) 
     487      rescue Errno::ESRCH,Errno::ECHILD => e 
     488        nil # ignore 
     489      rescue StopServer 
     490        # be sure to pass the stop up the call chain until it gets to the toplevel handler! 
     491        raise 
     492      rescue Object => e 
     493        STDERR.puts "Server #{$$} Unhandled exception #{e.inspect}." 
     494        STDERR.puts :error, e.backtrace.join("\n") 
     495      end 
    342496 
    343       if synchronous 
    344         sleep(0.5) while @acceptor.alive? 
     497      if @children.has_key?(pid) 
     498        io = @children.delete(pid) 
     499        if io.respond_to?(:close) 
     500          io.close rescue nil 
     501        end 
    345502      end 
     503 
     504      if @busy.has_key?(pid) 
     505        io = @busy.delete(pid) 
     506        if io.respond_to?(:close) 
     507          io.close rescue nil 
     508        end 
     509      end 
    346510    end 
     511  
     512    def run 
     513      BasicSocket.do_not_reverse_lookup = true 
     514      configure_socket_options 
    347515 
     516      if defined?($tcp_defer_accept_opts) and $tcp_defer_accept_opts 
     517          @serversock.setsockopt(*$tcp_defer_accept_opts) rescue nil 
     518      end 
     519 
     520      @acceptor = Thread.new do 
     521 
     522        begin 
     523          while not @terminate 
     524            begin 
     525              @children.each_key do |pid| 
     526                begin 
     527                  do_evict = false 
     528                  do_evict = true if (@children[pid].closed? or Process.waitpid(pid, Process::WNOHANG) == pid) 
     529                rescue Errno::ECHILD 
     530                  do_evict = true 
     531                rescue StopServer 
     532                  raise # pass it up the chain, don't ignore it. 
     533                rescue Object => e 
     534                  STDERR.puts "Server #{$$} Unhandled exception #{e.inspect}." 
     535                  STDERR.puts e.backtrace.join("\n") 
     536                end 
     537 
     538                evict_child(pid) if do_evict 
     539              end 
     540 
     541              if @children.length < @min_children 
     542                (@children.length .. @min_children).each do 
     543                  pid, socket = start_child 
     544                  @children[pid] = socket 
     545                  @busy[pid]     = true 
     546                end 
     547              end 
     548 
     549              readfds = [ @serversock ] + @children.values  
     550              r,w,e  = Kernel.select(readfds, nil, nil, 60) 
     551 
     552              # check to see if anyone wants to talk to us. If no one is talking 
     553              # and we have more than the necessary number of children then signal 
     554              # one to terminate. 
     555              # 
     556              evict_child(@children.keys.first) if (r.nil? or r.empty?) and @children.length > @min_children 
     557              next if r.nil? 
     558 
     559              # OK, someone is talking. Find out who and process the client. 
     560              # Delay the processing of the TCP server socket until all the children 
     561              # are finished. This allows a client to become available before processing 
     562              # the HTTP connection. 
     563              # 
     564              flag_http = false 
     565              r.each do |io| 
     566                if io == @serversock # a new connection from apache or the outside world 
     567                  flag_http = true  # defer 
     568                else                # one of the children 
     569                  begin 
     570                    msg = io.readline 
     571                    msg.chomp! 
     572                    if msg =~ /^READY\s+(\d+)$/ 
     573                      pid = $1.to_i 
     574                      if @busy[pid].respond_to?(:close) 
     575                        @busy[pid].close rescue nil 
     576                      end 
     577                      @busy[pid] = false 
     578                    elsif msg =~ /CLOSED\s+(\d+)/ 
     579                      evict_child($1.to_i) 
     580                    end 
     581                  rescue EOFError 
     582                    @children.each_key do |pid| 
     583                      evict_child(pid) if @children[pid] == io 
     584                    end 
     585                  end 
     586                end 
     587              end 
     588 
     589              next unless flag_http 
     590 
     591              client = nil 
     592              begin 
     593                client = @serversock.accept 
     594              rescue StandardError => e 
     595                STDERR.puts "Server #{$$} An error occurred accepting a new client connection, a restart may be necessary." 
     596                STDERR.puts "Server #{$$} #{e.message}" 
     597              end 
     598 
     599              next if client.nil? 
     600 
     601              @busy.each_pair do |pid,busy| 
     602                unless busy 
     603                  io = @children[pid] 
     604                  io.send_io(client) 
     605                  io.flush 
     606 
     607                  @busy[pid] = client 
     608                  client = nil 
     609                  break 
     610                end 
     611              end 
     612 
     613              if client and not @max_children.nil? and @max_children == @children.length 
     614                STDERR.puts "Server #{$$} Maximum number of child processes exceeded, request aborted" 
     615                client.close rescue nil 
     616                client = nil 
     617              end 
     618 
     619              next unless client 
     620 
     621              # spin up a new one 
     622              # 
     623              pid, socket = start_child 
     624              @children[pid] = socket 
     625              socket.readline 
     626              socket.send_io(client) 
     627              socket.flush 
     628              @busy[pid] = client 
     629 
     630            rescue StopServer 
     631              @terminate = true 
     632            rescue UriChangeEvent 
     633              @children.keys.each { |pid| evict_child(pid) } 
     634            rescue Object => e 
     635              STDERR.puts "Server #{$$} Unhandled listen loop exception #{e.inspect}." 
     636              STDERR.puts e.backtrace.join("\n") 
     637            end 
     638          end 
     639        ensure 
     640          @serversock.close  rescue nil 
     641          @children.each_key { |pid| evict_child(pid) } 
     642        end 
     643      end # end thread 
     644 
     645      return @acceptor 
     646    end 
     647 
     648    def register(uri, handler, in_front=false) 
     649      super(uri,handler,in_front) 
     650      @acceptor.raise(UriChangeEvent.new) if not @acceptor.nil? and @acceptor.alive? 
     651    end 
     652 
     653    def unregister(uri) 
     654      super(uri) 
     655      @acceptor.raise(UriChangeEvent.new) if not @acceptor.nil? and @acceptor.alive? 
     656    end 
    348657  end 
    349658end 
    350659 
     
    353662 
    354663$LOAD_PATH.unshift 'projects/mongrel_experimental/lib/' 
    355664Mongrel::Gems.require 'mongrel_experimental', ">=#{Mongrel::Const::MONGREL_VERSION}" 
     665 
     666# vim:expandtab:shiftwidth=2:tabstop=2 
  • branches/stable_1-1/bin/mongrel_rails

    old new  
    11# Copyright (c) 2005 Zed A. Shaw 
     2# Copyright (c) 2008 Raritan Computer, Inc (Brian Weaver <brian.weaver@raritan.com>) 
    23# You can redistribute it and/or modify it under the same terms as Ruby. 
    34# 
    45# Additional work donated by contributors.  See http://mongrel.rubyforge.org/attributions.html 
     
    2728        ['-p', '--port PORT', "Which port to bind to", :@port, 3000], 
    2829        ['-a', '--address ADDR', "Address to bind to", :@address, "0.0.0.0"], 
    2930        ['-l', '--log FILE', "Where to write log messages", :@log_file, "log/mongrel.log"], 
     31        ['-L', '--log-level (debug|notice|warn|info|error)', "The logging level", :@log_level, 'debug'], 
    3032        ['-P', '--pid FILE', "Where to write the PID", :@pid_file, "log/mongrel.pid"], 
    3133        ['-n', '--num-processors INT', "Number of processors active before clients denied", :@num_processors, 1024], 
    3234        ['-o', '--timeout TIME', "Time to wait (in seconds) before killing a stalled thread", :@timeout, 60], 
     
    3840        ['-C', '--config PATH', "Use a config file", :@config_file, nil], 
    3941        ['-S', '--script PATH', "Load the given file as an extra config script", :@config_script, nil], 
    4042        ['-G', '--generate PATH', "Generate a config file for use with -C", :@generate, nil], 
     43        ['-U', '--unixmaster', "Run the rails as a master-slave server", :@unixmaster, false], 
     44        ['-N', '--children COUNT', "The default number of unix slaves to start", :@min_children, 5], 
     45        ['-M', '--max-children COUNT', "The maximum number of unix slaves to start", :@max_children, nil], 
    4146        ['', '--user USER', "User to run as", :@user, nil], 
    4247        ['', '--group GROUP', "Group to run as", :@group, nil], 
    4348        ['', '--prefix PATH', "URL prefix for Rails app", :@prefix, nil] 
     
    6974      valid_user? @user if @user 
    7075      valid_group? @group if @group 
    7176 
     77      @log_level = @log_level.to_sym unless @log_level.nil? or @log_level.is_a?(Symbol) 
     78 
    7279      return @valid 
    7380    end 
    7481 
     
    9299          daemonize 
    93100          log "Daemonized, any open files are closed.  Look at #{defaults[:pid_file]} and #{defaults[:log_file]} for info." 
    94101          log "Settings loaded from #{@config_file} (they override command line)." if @config_file 
     102 
     103          write_pid_file 
    95104        end 
    96105 
    97106        log "Starting Mongrel listening at #{defaults[:host]}:#{defaults[:port]}" 
     
    128137      config.run 
    129138      config.log "Mongrel #{Mongrel::Const::MONGREL_VERSION} available at #{@address}:#{@port}" 
    130139 
    131       if config.defaults[:daemon] 
    132         config.write_pid_file 
    133       else 
     140      unless config.defaults[:daemon] 
    134141        config.log "Use CTRL-C to stop."  
    135142      end 
    136143 
     
    180187 
    181188    def config_keys 
    182189      @config_keys ||= 
    183         %w(address host port cwd log_file pid_file environment docroot mime_map daemon debug includes config_script num_processors timeout throttle user group prefix
     190        %w(address host port cwd log_file pid_file environment docroot mime_map daemon debug includes config_script num_processors timeout throttle user group prefix unixmaster min_children max_children
    184191    end 
    185192 
    186193    def settings 
     
    281288if not Mongrel::Command::Registry.instance.run ARGV 
    282289  exit 1 
    283290end 
     291 
     292# vim:shiftwidth=2:tabstop=2:expandtab:ft=ruby