Ticket #30: mongrel_patch_20080507.diff
| File mongrel_patch_20080507.diff, 42.0 kB (added by cmdrclueless, 4 months ago) |
|---|
-
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 7 require 'test/testhelp' 8 9 include Mongrel 10 11 class 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 18 end 19 20 21 class 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 96 end 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 7 require 'test/testhelp' 8 9 class 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 42 end 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 7 require 'test/testhelp' 8 9 include Mongrel 10 11 class 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 107 end -
lib/mongrel/configurator.rb
old new 136 136 opts[:throttle] ||= 0 137 137 opts[:timeout] ||= 60 138 138 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 144 155 @listener_name = "#{opts[:host]}:#{opts[:port]}" 145 156 @listeners[@listener_name] = @listener 146 157 … … 372 383 373 384 unless RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ 374 385 # graceful shutdown 375 trap("TERM") { Mongrel.log(:notice, " TERM signal received."); stop }386 trap("TERM") { Mongrel.log(:notice, "pid #{$$}: TERM signal received."); stop } 376 387 # 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 } 378 389 # restart 379 trap("USR2") { Mongrel.log(:notice, " USR2 signal received."); stop(true) }390 trap("USR2") { Mongrel.log(:notice, "pid #{$$}: USR2 signal received."); stop(true) } 380 391 381 392 Mongrel.log(:notice, "Signals ready. TERM => stop. USR2 => restart. INT => stop (no restart).") 382 393 else … … 387 398 end 388 399 389 400 end 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>) 1 3 # Ruby 2 4 require 'socket' 3 5 require 'tempfile' … … 42 44 # Thrown at a thread when it is timed out. 43 45 class TimeoutError < Exception; end 44 46 47 class UriChangeEvent < Exception; end 48 45 49 # A Hash with one extra parameter for the HTTP body, used internally. 46 50 class HttpParams < Hash 47 51 attr_accessor :http_body 48 52 end 49 53 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: 54 56 # 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 56 63 # 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 78 65 attr_accessor :logger 66 attr_accessor :classifier 67 attr_reader :acceptor 68 attr_reader :domain 69 attr_reader :host 70 attr_reader :port 79 71 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) 98 73 @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) 106 75 end 107 76 108 77 # Does the majority of the IO processing. It has been written in Ruby using … … 117 86 request = nil 118 87 data = client.readpartial(Const::CHUNK_SIZE) 119 88 nparsed = 0 120 89 121 90 # Assumption: nparsed will always be less since data will get filled with more 122 91 # after each parsing. If it doesn't get more then there was a problem 123 92 # with the read operation on the client socket. Effect is to stop processing when the … … 185 154 end 186 155 end 187 156 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) 189 159 client.close rescue nil 190 160 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") 193 163 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4 194 164 client.write(Const::ERROR_400_RESPONSE) 195 rescue Errno::EMFILE 165 rescue Errno::EMFILE => e 166 Mongrel.log(:debug, e.message) 196 167 reap_dead_workers('too many files') 197 168 rescue Object => e 198 Mongrel.log(:error, " #{Time.now.httpdate}:Read error: #{e.inspect}")169 Mongrel.log(:error, "Read error: #{e.inspect}") 199 170 Mongrel.log(:error, e.backtrace.join("\n")) 200 171 ensure 201 172 begin 202 173 client.close 203 rescue IOError 174 rescue IOError => e 175 Mongrel.log(:debug, e.message) 204 176 # Already closed 205 177 rescue Object => e 206 Mongrel.log(:error, " #{Time.now.httpdate}:Client error: #{e.inspect}")178 Mongrel.log(:error, "Client error: #{e.inspect}") 207 179 Mongrel.log(:error, e.backtrace.join("\n")) 208 180 end 209 181 request.body.delete if request and request.body.class == Tempfile 210 182 end 211 183 end 212 184 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 213 312 # Used internally to kill off any worker threads that have taken too long 214 313 # to complete processing. Only called if there are too many processors 215 314 # currently servicing. It returns the count of workers still active 216 315 # after the reap is done. It only runs if there are workers to reap. 316 # 217 317 def reap_dead_workers(reason='unknown') 318 218 319 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}" 221 323 mark = Time.now 222 324 @workers.list.each do |worker| 223 325 worker[:started_on] = Time.now if not worker[:started_on] 224 326 225 327 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.") 227 329 worker.raise(TimeoutError.new(error_msg)) 228 330 end 229 331 end 332 230 333 end 231 334 232 return@workers.list.length335 @workers.list.length 233 336 end 234 337 235 338 # Performs a wait on all the currently running threads and kills any that take 236 339 # too long. It waits by @timeout seconds, which can be set in .initialize or 237 340 # via mongrel_rails. The @throttle setting does extend this waiting period by 238 341 # that much longer. 342 # 239 343 def graceful_shutdown 240 344 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.") 242 346 sleep @timeout / 10 243 347 end 244 348 end 245 349 246 def configure_socket_options247 case RUBY_PLATFORM248 when /linux/249 # 9 is currently TCP_DEFER_ACCEPT250 $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.4254 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_arg257 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 end260 end261 end262 263 350 # Runs the thing. It returns the thread used so you can "join" it. You can also 264 351 # access the HttpServer::acceptor attribute to get the thread later. 352 # 265 353 def run 266 354 BasicSocket.do_not_reverse_lookup=true 267 355 … … 279 367 280 368 num_workers = @workers.list.length 281 369 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.") 283 371 client.close rescue nil 284 372 reap_dead_workers("max processors") 285 373 else … … 301 389 # client closed the socket even before accept 302 390 client.close rescue nil 303 391 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}.") 305 393 Mongrel.log(:error, e.backtrace.join("\n")) 306 394 end 307 395 end 308 396 graceful_shutdown 309 397 ensure 310 398 @socket.close 311 # Mongrel.log(:error, "#{Time.now.httpdate}: Closed socket.")312 399 end 313 400 end 314 401 315 return@acceptor402 @acceptor 316 403 end 404 end 317 405 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") 331 482 end 332 handler.listener = self 483 484 @acceptor 333 485 end 334 486 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] 340 515 end 341 516 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 346 531 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 349 537 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 350 546 end 351 547 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 352 702 end 353 703 end 354 704 … … 357 707 358 708 $LOAD_PATH.unshift 'projects/mongrel_experimental/lib/' 359 709 Mongrel::Gems.require 'mongrel_experimental', ">=#{Mongrel::Const::MONGREL_VERSION}" 710 711 # vim:expandtab:shiftwidth=2:tabstop=2 -
bin/mongrel_rails
old new 1 1 # Copyright (c) 2005 Zed A. Shaw
