Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 693

Show
Ignore:
Timestamp:
09/29/05 18:37:37
Author:
fumanchu
Message:

Phew. Tracked down all possible occurrences of KeyboardInterrupt? and trapped them appropriately. There's still a problem in test_states when using the CherryPyHTTPServer (it hangs during KeyboardInterrupt? testing), but all other servers pass. Maybe *this* fixes #321. ;)

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cphttpserver.py

    r691 r693  
    3131import cherrypy 
    3232from cherrypy import _cputil, _cphttptools 
     33 
    3334 
    3435class CherryHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 
     
    136137 
    137138 
    138 class CherryHTTPServer(BaseHTTPServer.HTTPServer): 
     139class CherryHTTPServer(SocketServer.TCPServer): 
     140    # Subclass TCPServer (instead of BaseHTTPServer.HTTPServer), because 
     141    # getfqdn call was timing out on localhost when calling gethostbyaddr. 
    139142     
    140143    ready = False 
     144    interrupt = None 
     145     
     146    allow_reuse_address = True 
    141147     
    142148    def __init__(self): 
     
    167173        self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 
    168174         
    169         BaseHTTPServer.HTTPServer.__init__(self, server_address, CherryHTTPRequestHandler) 
     175        SocketServer.TCPServer.__init__(self, server_address, CherryHTTPRequestHandler) 
    170176     
    171177    def server_activate(self): 
    172178        """Override server_activate to set timeout on our listener socket""" 
    173179        self.socket.settimeout(1) 
    174         BaseHTTPServer.HTTPServer.server_activate(self) 
    175      
    176     def server_bind(self): 
    177         # Removed getfqdn call because it was timing out 
    178         # on localhost when calling gethostbyaddr 
    179         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    180         self.socket.bind(self.server_address) 
     180        SocketServer.TCPServer.server_activate(self) 
    181181     
    182182    def get_request(self): 
     
    192192     
    193193    def handle_request(self): 
    194         """Override handle_request to trap timeout exception.""" 
     194        """Handle one request, possibly blocking.""" 
     195        # Overridden to trap socket.timeout and KeyboardInterrupt/SystemExit. 
    195196        try: 
    196             BaseHTTPServer.HTTPServer.handle_request(self) 
     197            request, client_address = self.get_request() 
     198        except socket.error: 
     199            return 
    197200        except socket.timeout: 
    198201            # The only reason for the timeout is so we can notice keyboard 
    199202            # interrupts on Win32, which don't interrupt accept() by default 
    200203            return 1 
     204         
     205        if self.verify_request(request, client_address): 
     206            try: 
     207                self.process_request(request, client_address) 
     208            except (KeyboardInterrupt, SystemExit): 
     209                self.close_request(request) 
     210                raise 
     211            except: 
     212                self.handle_error(request, client_address) 
     213                self.close_request(request) 
     214     
     215    def handle_error(self, request, client_address): 
     216        errorBody = _cputil.formatExc() 
     217        cherrypy.log(errorBody) 
    201218     
    202219    def serve_forever(self): 
    203220        """Override serve_forever to handle shutdown.""" 
    204         self.__running = 1 
    205221        self.ready = True 
    206         while self.__running
     222        while self.ready
    207223            self.handle_request() 
     224            if self.interrupt: 
     225                i = self.interrupt 
     226                self.interrupt = None 
     227                raise i 
    208228    start = serve_forever 
    209229     
    210230    def shutdown(self): 
    211         self.__running = 0 
     231        self.ready = False 
    212232        # Close the socket 
    213233        self.server_close() 
     
    219239class ServerThread(threading.Thread): 
    220240     
    221     def __init__(self, RequestHandlerClass, requestQueue): 
     241    def __init__(self, RequestHandlerClass, requestQueue, server): 
     242        self.server = server 
    222243        self.ready = False 
    223244        threading.Thread.__init__(self) 
     
    226247     
    227248    def run(self): 
    228         self.ready = True 
    229         while 1: 
    230             request, client_address = self._requestQueue.get() 
    231             if (request, client_address) == _SHUTDOWNREQUEST: 
    232                 return 
    233             if self.verify_request(request, client_address): 
    234                 try: 
    235                     self.process_request(request, client_address) 
    236                 except: 
    237                     self.handle_error(request, client_address) 
     249        try: 
     250            self.ready = True 
     251            while 1: 
     252                request, client_address = self._requestQueue.get() 
     253                if (request, client_address) == _SHUTDOWNREQUEST: 
     254                    return 
     255                if self.verify_request(request, client_address): 
     256                    try: 
     257                        self.process_request(request, client_address) 
     258                    except (KeyboardInterrupt, SystemExit): 
     259                        self.close_request(request) 
     260                        raise 
     261                    except: 
     262                        self.handle_error(request, client_address) 
     263                        self.close_request(request) 
     264                else: 
    238265                    self.close_request(request) 
    239             else
    240                 self.close_request(request) 
     266        except (KeyboardInterrupt, SystemExit), exc
     267            self.server.interrupt = exc 
    241268     
    242269    def verify_request(self, request, client_address): 
     
    271298    allow_reuse_address = 1 
    272299    ready = False 
     300    interrupt = None 
    273301     
    274302    def __init__(self): 
     
    297325     
    298326    def createThread(self): 
    299         return self._ThreadClass(self._RequestHandlerClass, self._requestQueue
     327        return self._ThreadClass(self._RequestHandlerClass, self._requestQueue, self
    300328     
    301329    def server_activate(self): 
     
    304332        SocketServer.TCPServer.server_activate(self) 
    305333     
    306     def server_bind(self): 
    307         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    308         self.socket.bind(self.server_address) 
    309      
    310334    def shutdown(self): 
    311335        """Gracefully shutdown a server that is serve_forever()ing.""" 
    312         self.__running = 0 
     336        self.ready = False 
    313337        # Close the socket so restarts work. 
    314338        self.server_close() 
     
    333357                worker.start() 
    334358         
    335         self.__running = 1 
    336          
    337359        for worker in self._workerThreads: 
    338360            while not worker.ready: 
    339361                time.sleep(.1) 
     362         
    340363        self.ready = True 
    341          
    342         while self.__running: 
     364        while self.ready: 
     365            if self.interrupt: 
     366                i = self.interrupt 
     367                self.interrupt = None 
     368                raise i 
    343369            if not self.handle_request(): 
    344370                break 
  • trunk/cherrypy/_cphttptools.py

    r682 r693  
    285285            finally: 
    286286                applyFilters('onEndResource') 
     287        except (KeyboardInterrupt, SystemExit): 
     288            raise 
    287289        except: 
    288290            handleError(sys.exc_info()) 
     
    436438            finalize() 
    437439            return 
     440        except (KeyboardInterrupt, SystemExit): 
     441            raise 
    438442        except: 
    439443            # Fall through to the second error handler 
    440444            pass 
     445    except (KeyboardInterrupt, SystemExit): 
     446        raise 
    441447    except: 
    442448        # Fall through to the second error handler 
  • trunk/cherrypy/_cpwsgi.py

    r665 r693  
    110110                                environ['wsgi.url_scheme'], 
    111111                                ) 
     112    except (KeyboardInterrupt, SystemExit): 
     113        raise 
    112114    except: 
    113115        tb = _cputil.formatExc() 
     
    131133            chunk = str(chunk) 
    132134            yield chunk 
     135    except (KeyboardInterrupt, SystemExit): 
     136        raise 
    133137    except: 
    134138        tb = _cputil.formatExc() 
  • trunk/cherrypy/_cpwsgiserver.py

    r691 r693  
    103103        self.addr = addr 
    104104        self.server = server 
    105         self.environ = None 
     105        self.environ = {} 
    106106        self.ready = False 
    107107        self.started_response = False 
    108         self.status = None 
    109         self.outheaders = None 
    110         self.outheaderkeys = None 
     108        self.status = "" 
     109        self.outheaders = [] 
     110        self.outheaderkeys = [] 
    111111        self.rfile = self.socket.makefile("r", self.server.bufsize) 
    112112        if self.server.config: 
     
    212212     
    213213    def terminate(self): 
    214         if self.ready and not self.sent_headers
     214        if self.ready and not self.sent_headers and not self.server.interrupt
    215215            self.sent_headers = True 
    216216            self.send_headers() 
     
    230230     
    231231    def run(self): 
    232         self.ready = True 
    233         while self.server._running: 
    234             request = self.server.requests.get() 
    235             if request is _SHUTDOWNREQUEST: 
    236                 return 
    237              
    238             try: 
     232        try: 
     233            self.ready = True 
     234            while True: 
     235                request = self.server.requests.get() 
     236                if request is _SHUTDOWNREQUEST: 
     237                    return 
     238                 
    239239                try: 
    240                     request.parse_request() 
    241                     if request.ready: 
    242                         response = self.server.wsgi_app(request.environ, 
    243                                                         request.start_response) 
    244                         for line in response: 
    245                             request.write(line) 
    246                 except socket.error, e: 
    247                     errno = e.args[0] 
    248                     if errno in (32, 104, 10053, 10054): 
    249                         # Client probably closed the connection before the 
    250                         # response was sent. 
    251                         pass 
    252                     else: 
    253                         raise 
    254                 except MaxSizeExceeded: 
    255                     str = "Request Entity Too Large" 
    256                     proto = request.environ.get("SERVER_PROTOCOL", "HTTP/1.0") 
    257                     request.wfile.write("%s 413 %s\r\n" % (proto, str)) 
    258                     request.wfile.write("Content-Length: %s\r\n\r\n" % len(str)) 
    259                     request.wfile.write(str) 
    260                     request.wfile.flush() 
    261                 except: 
    262                     traceback.print_exc() 
    263             finally: 
    264                 request.terminate() 
     240                    try: 
     241                        request.parse_request() 
     242                        if request.ready: 
     243                            response = self.server.wsgi_app(request.environ, 
     244                                                            request.start_response) 
     245                            for line in response: 
     246                                request.write(line) 
     247                    except socket.error, e: 
     248                        errno = e.args[0] 
     249                        if errno in (32, 104, 10053, 10054): 
     250                            # Client probably closed the connection before the 
     251                            # response was sent. 
     252                            pass 
     253                        else: 
     254                            raise 
     255                    except MaxSizeExceeded: 
     256                        str = "Request Entity Too Large" 
     257                        proto = request.environ.get("SERVER_PROTOCOL", "HTTP/1.0") 
     258                        request.wfile.write("%s 413 %s\r\n" % (proto, str)) 
     259                        request.wfile.write("Content-Length: %s\r\n\r\n" % len(str)) 
     260                        request.wfile.write(str) 
     261                        request.wfile.flush() 
     262                    except (KeyboardInterrupt, SystemExit), exc: 
     263                        self.server.interrupt = exc 
     264                    except: 
     265                        traceback.print_exc() 
     266                finally: 
     267                    request.terminate() 
     268        except (KeyboardInterrupt, SystemExit), exc: 
     269            self.server.interrupt = exc 
    265270 
    266271 
     
    269274    version = "CherryPy/2.1.0-rc1" 
    270275    ready = False 
     276    interrupt = None 
    271277     
    272278    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, 
     
    304310        self.socket.listen(5) 
    305311         
    306         self._running = True 
    307          
    308312        # Create worker threads 
    309313        for i in xrange(self.numthreads): 
     
    314318            while not worker.ready: 
    315319                time.sleep(.1) 
     320         
    316321        self.ready = True 
    317          
    318         while self._running: 
     322        while self.ready: 
    319323            self.tick() 
    320      
    321     def stop(self): 
    322         """Gracefully shutdown a server that is serving forever.""" 
    323         self._running = False 
    324         self.socket.close() 
    325          
    326         # Must shut down threads here so the code that calls 
    327         # this method can know when all threads are stopped. 
    328         for worker in self._workerThreads: 
    329             self.requests.put(_SHUTDOWNREQUEST) 
    330          
    331         # Don't join currentThread (when stop is called inside a request). 
    332         current = threading.currentThread() 
    333         for worker in self._workerThreads: 
    334             if worker is not current: 
    335                 worker.join() 
    336          
    337         self._workerThreads = [] 
     324            if self.interrupt: 
     325                i = self.interrupt 
     326                self.interrupt = None 
     327                raise i 
    338328     
    339329    def tick(self): 
     
    351341            # accept() by default 
    352342            return 
     343     
     344    def stop(self): 
     345        """Gracefully shutdown a server that is serving forever.""" 
     346        self.ready = False 
     347        self.socket.close() 
     348         
     349        # Must shut down threads here so the code that calls 
     350        # this method can know when all threads are stopped. 
     351        for worker in self._workerThreads: 
     352            self.requests.put(_SHUTDOWNREQUEST) 
     353         
     354        # Don't join currentThread (when stop is called inside a request). 
     355        current = threading.currentThread() 
     356        for worker in self._workerThreads: 
     357            if worker is not current: 
     358                worker.join() 
     359         
     360        self._workerThreads = [] 
  • trunk/cherrypy/server.py

    r691 r693  
    158158        # This should block until the http server stops. 
    159159        cherrypy._httpserver.start() 
    160     except (KeyboardInterrupt, SystemExit)
     160    except KeyboardInterrupt
    161161        cherrypy.log("<Ctrl-C> hit: shutting down server", "HTTP") 
     162        stop() 
     163    except SystemExit: 
     164        cherrypy.log("SystemExit raised: shutting down server", "HTTP") 
    162165        stop() 
    163166 
     
    271274 
    272275def wait_for_free_port(host, port): 
     276    """Wait for the specified port to become free (drop requests).""" 
    273277    for trial in xrange(50): 
    274278        try: 
     
    284288 
    285289def wait_for_occupied_port(host, port): 
     290    """Wait for the specified port to become active (receive requests).""" 
    286291    for trial in xrange(50): 
    287292        try: 
  • trunk/cherrypy/test/helper.py

    r691 r693  
    156156        from cherrypy._cputil import getErrorPage 
    157157        esc = re.escape 
     158        # This will never contain a traceback: 
    158159        page = esc(getErrorPage(status, message=message)) 
    159160         
     
    164165        m = re.match(page, self.body, re.DOTALL) 
    165166        if not m: 
    166             self._handlewebError('Error page does not match'
     167            self._handlewebError('Error page does not match\n' + page
    167168            return 
    168169         
  • trunk/cherrypy/test/test_core.py

    r692 r693  
    224224         
    225225        if m == "LINK": 
    226             cherrypy.response.status = 405 
     226            raise cherrypy.HTTPError(405) 
    227227        else: 
    228             cherrypy.response.status = 501 
     228            raise cherrypy.HTTPError(501) 
    229229     
    230230    def parameterized(self, data): 
     
    606606        # Test that all defined HTTP methods work. 
    607607        for m in defined_http_methods: 
    608             h = [] 
    609608            self.getPage("/method/", method=m) 
    610609             
  • trunk/cherrypy/test/test_states.py

    r691 r693  
    5252import helper 
    5353 
     54 
    5455class ServerStateTests(helper.CPWebCase): 
    5556     
     
    7879        self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    7980     
    80     def test_1_KeyboardInterrupts(self): 
     81    def test_1_KeyboardInterrupt(self): 
    8182        if self.serverClass: 
    8283            # Raise a keyboard interrupt in the HTTP server's main thread. 
     84            helper.startServer(self.serverClass) 
     85            cherrypy._httpserver.interrupt = KeyboardInterrupt 
     86            # Give the server time to shut down. 
     87            while cherrypy._appserver_state != 0: 
     88                time.sleep(.1) 
     89            self.assertEqual(cherrypy._httpserver, None) 
     90            self.assertEqual(cherrypy._appserver_state, 0) 
     91            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    8392             
    84             def raiser(x=None): 
    85                 raise KeyboardInterrupt 
    86              
     93            # Raise a keyboard interrupt in a page handler; on multithreaded 
     94            # servers, this should occur in one of the worker threads. 
     95            # This should raise a BadStatusLine error, since the worker 
     96            # thread will just die without writing a response. 
     97            from httplib import BadStatusLine 
    8798            helper.startServer(self.serverClass) 
    88              
    89             name = cherrypy._httpserver.__class__.__name__ 
    90             if name == "WSGIServer": 
    91                 cherrypy._httpserver.tick = raiser 
    92             elif name in ("CherryHTTPServer", "PooledThreadServer"): 
    93                 cherrypy._httpserver.handle_request = raiser 
    94             else: 
    95                 raise ValueError("Unknown HTTP server: %s" % name) 
    96              
     99            self.assertRaises(BadStatusLine, self.getPage, "/ctrlc") 
     100            # Give the server time to shut down. 
     101            while cherrypy._appserver_state != 0: 
     102                print ".", 
     103                time.sleep(.1) 
     104            self.assertEqual(cherrypy._httpserver, None) 
     105            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
     106     
     107    def test_2_Restart(self): 
     108        # Test server start 
     109        helper.startServer(self.serverClass) 
     110        self.getPage("/") 
     111        self.assertBody("Hello World") 
     112         
     113        # Test server restart 
     114        cherrypy.server.restart() 
     115        self.assertEqual(cherrypy._appserver_state, 1) 
     116        self.getPage("/") 
     117        self.assertBody("Hello World") 
     118         
     119        # Now that we've restarted, test a KeyboardInterrupt (ticket 321). 
     120        if self.serverClass: 
     121            cherrypy._httpserver.interrupt = KeyboardInterrupt 
    97122            # Give the server time to shut down. 
    98123            while cherrypy._appserver_state != 0: 
     
    102127            # Once the server has stopped, we should get a NotReady error again. 
    103128            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    104      
    105     def test_2_Restart(self): 
    106         # Test server start 
    107         import cherrypy 
    108          
    109         helper.startServer(self.serverClass) 
    110         self.getPage("/") 
    111         self.assertBody("Hello World") 
    112          
    113         # Test server restart 
    114         cherrypy.server.restart() 
    115          
    116         self.assertEqual(cherrypy._appserver_state, 1) 
    117         self.getPage("/") 
    118         self.assertBody("Hello World") 
    119129 
    120130 

Hosted by WebFaction

Log in as guest/cpguest to create tickets