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

Changeset 691

Show
Ignore:
Timestamp:
09/28/05 16:53:32
Author:
fumanchu
Message:

More server state tests and fixes:

1. The built-in HTTP servers all failed on restart() because they weren't closing the socket on server.stop.
2. HTTP server start() and stop() methods now do more to make sure the server is truly in a started or stopped state before they return.
3. server.py does a lot more checking and waiting to make sure the HTTP server is truly started or stopped, before allowing the main thread to proceed. Some time.sleep() calls have been replaced with indefinite waiting.

Files:

Legend:

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

    r685 r691  
    138138class CherryHTTPServer(BaseHTTPServer.HTTPServer): 
    139139     
     140    ready = False 
     141     
    140142    def __init__(self): 
    141143        # Set protocol_version 
     
    173175     
    174176    def server_bind(self): 
    175         # Removed getfqdn call because it was timing out on localhost when calling gethostbyaddr 
     177        # Removed getfqdn call because it was timing out 
     178        # on localhost when calling gethostbyaddr 
    176179        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    177180        self.socket.bind(self.server_address) 
    178181     
    179182    def get_request(self): 
    180         # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode 
    181         #  results in request sockets that are also set in nonblocking mode. Since that doesn't play 
    182         #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set 
    183         #  the request socket to blocking 
     183        # With Python 2.3 it seems that an accept socket in timeout 
     184        # (nonblocking) mode results in request sockets that are also set 
     185        # in nonblocking mode. Since that doesn't play well with makefile() 
     186        # (where wfile and rfile are set in SocketServer.py) we explicitly 
     187        # set the request socket to blocking 
    184188         
    185189        request, client_address = self.socket.accept() 
     
    195199            # interrupts on Win32, which don't interrupt accept() by default 
    196200            return 1 
    197 ##        except (KeyboardInterrupt, SystemExit): 
    198 ##            cherrypy.log("<Ctrl-C> hit: shutting down http server", "HTTP") 
    199 ##            self.shutdown() 
    200201     
    201202    def serve_forever(self): 
    202203        """Override serve_forever to handle shutdown.""" 
    203204        self.__running = 1 
     205        self.ready = True 
    204206        while self.__running: 
    205207            self.handle_request() 
     
    208210    def shutdown(self): 
    209211        self.__running = 0 
     212        # Close the socket 
     213        self.server_close() 
    210214    stop = shutdown 
    211215 
     
    216220     
    217221    def __init__(self, RequestHandlerClass, requestQueue): 
     222        self.ready = False 
    218223        threading.Thread.__init__(self) 
    219224        self._RequestHandlerClass = RequestHandlerClass 
     
    221226     
    222227    def run(self): 
     228        self.ready = True 
    223229        while 1: 
    224230            request, client_address = self._requestQueue.get() 
     
    264270     
    265271    allow_reuse_address = 1 
     272    ready = False 
    266273     
    267274    def __init__(self): 
     
    276283        self.request_queue_size = cherrypy.config.get('server.socketQueueSize') 
    277284         
    278         # I know it says "do not override", but I have to in order to implement SSL support ! 
     285        # I know it says "do not override", 
     286        # but I have to in order to implement SSL support ! 
    279287        SocketServer.BaseServer.__init__(self, server_address, CherryHTTPRequestHandler) 
    280288        self.socket = socket.socket(self.address_family, self.socket_type) 
     
    303311        """Gracefully shutdown a server that is serve_forever()ing.""" 
    304312        self.__running = 0 
     313        # Close the socket so restarts work. 
     314        self.server_close() 
    305315         
    306316        # Must shut down threads here so the code that calls 
     
    310320        current = threading.currentThread() 
    311321        for worker in self._workerThreads: 
    312             if worker is not current
     322            if worker is not current and worker.isAlive
    313323                worker.join() 
    314324        self._workerThreads = [] 
     
    322332            for worker in self._workerThreads: 
    323333                worker.start() 
     334         
    324335        self.__running = 1 
     336         
     337        for worker in self._workerThreads: 
     338            while not worker.ready: 
     339                time.sleep(.1) 
     340        self.ready = True 
     341         
    325342        while self.__running: 
    326343            if not self.handle_request(): 
     
    335352        try: 
    336353            request, client_address = self.get_request() 
    337 ##        except (KeyboardInterrupt, SystemExit): 
    338 ##            cherrypy.log("<Ctrl-C> hit: shutting down", "HTTP") 
    339 ##            return 0 
    340354        except socket.error, e: 
    341355            return 1 
     
    344358     
    345359    def get_request(self): 
    346         # With Python 2.3 it seems that an accept socket in timeout (nonblocking) mode 
    347         #  results in request sockets that are also set in nonblocking mode. Since that doesn't play 
    348         #  well with makefile() (where wfile and rfile are set in SocketServer.py) we explicitly set 
    349         #  the request socket to blocking 
    350  
     360        # With Python 2.3 it seems that an accept socket in timeout 
     361        # (nonblocking) mode results in request sockets that are also set 
     362        # in nonblocking mode. Since that doesn't play well with makefile() 
     363        # (where wfile and rfile are set in SocketServer.py) we explicitly 
     364        # set the request socket to blocking 
     365         
    351366        request, client_address = self.socket.accept() 
    352367        if hasattr(request,'setblocking'): 
  • trunk/cherrypy/_cpwsgiserver.py

    r654 r691  
    225225     
    226226    def __init__(self, server): 
     227        self.ready = False 
    227228        self.server = server 
    228229        threading.Thread.__init__(self) 
    229230     
    230231    def run(self): 
     232        self.ready = True 
    231233        while self.server._running: 
    232234            request = self.server.requests.get() 
     
    264266 
    265267class CherryPyWSGIServer(object): 
     268     
    266269    version = "CherryPy/2.1.0-rc1" 
     270    ready = False 
     271     
    267272    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, 
    268273                 stderr=sys.stderr, bufsize=-1, max=-1, 
     
    295300        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
    296301        self.socket.bind(self.bind_addr) 
     302        # Timeout so KeyboardInterrupt can be caught on Win32 
    297303        self.socket.settimeout(1) 
    298304        self.socket.listen(5) 
     
    305311        for worker in self._workerThreads: 
    306312            worker.start() 
     313        for worker in self._workerThreads: 
     314            while not worker.ready: 
     315                time.sleep(.1) 
     316        self.ready = True 
    307317         
    308318        while self._running: 
     
    312322        """Gracefully shutdown a server that is serving forever.""" 
    313323        self._running = False 
     324        self.socket.close() 
    314325         
    315326        # Must shut down threads here so the code that calls 
     
    318329            self.requests.put(_SHUTDOWNREQUEST) 
    319330         
     331        # Don't join currentThread (when stop is called inside a request). 
    320332        current = threading.currentThread() 
    321333        for worker in self._workerThreads: 
    322334            if worker is not current: 
    323335                worker.join() 
     336         
    324337        self._workerThreads = [] 
    325338     
  • trunk/cherrypy/server.py

    r688 r691  
    6464 
    6565def _start(initOnly=False, serverClass=None): 
    66     """ 
    67         Main function. All it does is this: 
    68             - output config options 
    69             - create response and request objects 
    70             - starts a server 
    71             - initilizes built in filters 
    72     """ 
    73      
    74     if cherrypy.codecoverage: 
    75         from cherrypy.lib import covercp 
    76         covercp.start() 
    77      
    78     # Use a flag to indicate the state of the cherrypy application server. 
    79     # 0 = Not started 
    80     # None = In process of starting 
    81     # 1 = Started, ready to receive requests 
    82     cherrypy._appserver_state = None 
    83      
    84     # Output config options to log 
    85     if cherrypy.config.get("server.logConfigOptions", True): 
    86         cherrypy.config.outputConfigMap() 
    87      
    88     # Check the config options 
    89     # TODO 
    90     # config.checkConfigOptions() 
    91      
    92     # If sessions are stored in files and we 
    93     # use threading, we need a lock on the file 
    94     if (cherrypy.config.get('server.threadPool') > 1 
    95         and cherrypy.config.get('session.storageType') == 'file'): 
    96         cherrypy._sessionFileLock = threading.RLock() 
    97      
    98     # set cgi.maxlen which will limit the size of POST request bodies 
    99     import cgi 
    100     cgi.maxlen = cherrypy.config.get('server.maxRequestSize') 
    101      
    102     # Call the functions from cherrypy.server.onStartServerList 
    103     for func in cherrypy.server.onStartServerList: 
    104         func() 
    105      
    106     # Set up the profiler if requested. 
    107     if cherrypy.config.get("profiling.on", False): 
    108         ppath = cherrypy.config.get("profiling.path", "") 
    109         cherrypy.profiler = profiler.Profiler(ppath) 
    110     else: 
    111         cherrypy.profiler = None 
    112  
    113     # Initilize the built in filters 
    114     cherrypy._cputil._cpInitDefaultFilters() 
    115     cherrypy._cputil._cpInitUserDefinedFilters() 
    116      
    117     if initOnly: 
    118         cherrypy._appserver_state = 1 
    119     else: 
    120         run_server(serverClass) 
    121  
    122 def check_port(host, port): 
    123     """Raise an error if the given port is not free on the given host.""" 
    124      
    125     import socket 
    126     try: 
    127         s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
    128         s.connect((host, int(port))) 
    129         s.close() 
    130         raise IOError("Port %s is in use on %s; perhaps the previous " 
    131                       "server did not shut down properly." % (port, host)) 
    132     except socket.error: 
    133         pass 
     66    """Main function.""" 
     67    try: 
     68        if cherrypy.codecoverage: 
     69            from cherrypy.lib import covercp 
     70            covercp.start() 
     71         
     72        # Use a flag to indicate the state of the cherrypy application server. 
     73        # 0 = Not started 
     74        # None = In process of starting 
     75        # 1 = Started, ready to receive requests 
     76        cherrypy._appserver_state = None 
     77         
     78        # Output config options to log 
     79        if cherrypy.config.get("server.logConfigOptions", True): 
     80            cherrypy.config.outputConfigMap() 
     81         
     82        # Check the config options 
     83        # TODO 
     84        # config.checkConfigOptions() 
     85         
     86        # If sessions are stored in files and we 
     87        # use threading, we need a lock on the file 
     88        if (cherrypy.config.get('server.threadPool') > 1 
     89            and cherrypy.config.get('session.storageType') == 'file'): 
     90            cherrypy._sessionFileLock = threading.RLock() 
     91         
     92        # set cgi.maxlen which will limit the size of POST request bodies 
     93        import cgi 
     94        cgi.maxlen = cherrypy.config.get('server.maxRequestSize') 
     95         
     96        # Call the functions from cherrypy.server.onStartServerList 
     97        for func in cherrypy.server.onStartServerList: 
     98            func() 
     99         
     100        # Set up the profiler if requested. 
     101        if cherrypy.config.get("profiling.on", False): 
     102            ppath = cherrypy.config.get("profiling.path", "") 
     103            cherrypy.profiler = profiler.Profiler(ppath) 
     104        else: 
     105            cherrypy.profiler = None 
     106 
     107        # Initilize the built in filters 
     108        cherrypy._cputil._cpInitDefaultFilters() 
     109        cherrypy._cputil._cpInitUserDefinedFilters() 
     110         
     111        if initOnly: 
     112            cherrypy._appserver_state = 1 
     113        else: 
     114            run_server(serverClass) 
     115    except: 
     116        # _start may be called as the target of a Thread, in which case 
     117        # any errors would pass silently. Log them at least. 
     118        cherrypy.log(cherrypy._cputil.formatExc()) 
     119        raise 
     120 
    134121 
    135122def run_server(serverClass=None): 
    136123    """Prepare the requested server and then run it.""" 
    137      
    138124    if cherrypy._httpserver is not None: 
    139125        warnings.warn("You seem to have an HTTP server still running." 
    140126                      "Please call cherrypy.server.stop() before continuing.") 
     127     
     128    if cherrypy.config.get('server.socketPort'): 
     129        host = cherrypy.config.get('server.socketHost') 
     130        port = cherrypy.config.get('server.socketPort') 
     131         
     132        wait_for_free_port(host, port) 
     133         
     134        if not host: 
     135            host = 'localhost' 
     136        onWhat = "http://%s:%s/" % (host, port) 
     137    else: 
     138        onWhat = "socket file: %s" % cherrypy.config.get('server.socketFile') 
     139    cherrypy.log("Serving HTTP on %s" % onWhat, 'HTTP') 
    141140     
    142141    # Instantiate the server. 
     
    148147        import _cpwsgi 
    149148        serverClass = _cpwsgi.WSGIServer 
    150      
    151     if cherrypy.config.get('server.socketPort'): 
    152         host = cherrypy.config.get('server.socketHost') 
    153         port = cherrypy.config.get('server.socketPort') 
    154         check_port(host, port) 
    155         if not host: 
    156             host = 'localhost' 
    157         onWhat = "http://%s:%s/" % (host, port) 
    158     else: 
    159         onWhat = "socket file: %s" % cherrypy.config.get('server.socketFile') 
    160     cherrypy.log("Serving HTTP on %s" % onWhat, 'HTTP') 
    161      
    162     # Start the http server. This must be done after check_port, above. 
    163149    cherrypy._httpserver = serverClass() 
    164     try: 
    165         try: 
    166             cherrypy._appserver_state = 1 
    167             # This should block until the http server stops. 
    168             cherrypy._httpserver.start() 
    169         except (KeyboardInterrupt, SystemExit): 
    170             pass 
    171     finally: 
    172         cherrypy.log("<Ctrl-C> hit: shutting down", "HTTP") 
     150     
     151    # Start the http server. Must be done after wait_for_free_port (above). 
     152    # Note that _httpserver.start() will block this thread, so there 
     153    # isn't any notification in this thread that the HTTP server is 
     154    # truly ready. See wait_until_ready() for all the things that 
     155    # other threads should wait for before proceeding with requests. 
     156    try: 
     157        cherrypy._appserver_state = 1 
     158        # This should block until the http server stops. 
     159        cherrypy._httpserver.start() 
     160    except (KeyboardInterrupt, SystemExit): 
     161        cherrypy.log("<Ctrl-C> hit: shutting down server", "HTTP") 
    173162        stop() 
    174163 
     
    189178    if cherrypy._appserver_state == 0: 
    190179        raise cherrypy.NotReady("No thread has called cherrypy.server.start().") 
    191      
    192     trials = 0 
    193     while cherrypy._appserver_state == None: 
    194         # Give the server thread time to complete. 
    195         trials += 1 
    196         if trials > 10: 
    197             raise cherrypy.NotReady("cherrypy.server.start() encountered errors.") 
    198         time.sleep(1) 
     180    elif cherrypy._appserver_state == None: 
     181        raise cherrypy.NotReady("cherrypy.server.start() encountered errors.") 
    199182     
    200183    threadID = threading._get_ident() 
     
    225208        pass 
    226209    else: 
     210        # httpstop() should block until the server is *truly* stopped. 
    227211        httpstop() 
     212        cherrypy.log("HTTP Server shut down", "HTTP") 
    228213     
    229214    # Call the functions from cherrypy.server.onStopThreadList 
     
    239224    cherrypy._httpserver = None 
    240225    cherrypy._appserver_state = 0 
     226    cherrypy.log("CherryPy shut down", "HTTP") 
    241227 
    242228def restart(): 
    243229    """Stop and start CherryPy.""" 
    244230    http = getattr(cherrypy, '_httpserver', None) 
     231    stop() 
    245232    if http: 
    246         stop() 
    247         # Give HTTP servers time to shut down their thread pools. 
    248         time.sleep(1) 
    249233        # Start the server in a new thread 
    250234        thread_args = {"serverClass": http.__class__} 
    251         threading.Thread(target=_start, kwargs=thread_args).start(
    252     else: 
    253         stop() 
     235        t = threading.Thread(target=_start, kwargs=thread_args
     236        t.start() 
     237    else: 
    254238        _start(initOnly=True) 
     239    wait_until_ready() 
     240 
     241def wait_until_ready(): 
     242    """Block the caller until CherryPy is ready to receive requests.""" 
     243     
     244    while cherrypy._appserver_state != 1: 
     245        time.sleep(.1) 
     246     
     247    http = getattr(cherrypy, '_httpserver', None) 
     248    if http: 
     249        # Wait for HTTP server to start up 
     250        while not http.ready: 
     251            time.sleep(.1) 
     252         
     253        # Wait for port to be occupied 
     254        if cherrypy.config.get('server.socketPort'): 
     255            host = cherrypy.config.get('server.socketHost') 
     256            port = cherrypy.config.get('server.socketPort') 
     257            wait_for_occupied_port(host, port) 
     258 
     259def check_port(host, port): 
     260    """Raise an error if the given port is not free on the given host.""" 
     261     
     262    import socket 
     263    try: 
     264        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
     265        s.connect((host, int(port))) 
     266        s.close() 
     267        raise IOError("Port %s is in use on %s; perhaps the previous " 
     268                      "server did not shut down properly." % (port, host)) 
     269    except socket.error: 
     270        pass 
     271 
     272def wait_for_free_port(host, port): 
     273    for trial in xrange(50): 
     274        try: 
     275            check_port(host, port) 
     276        except IOError: 
     277            # Give the old server thread time to free the port. 
     278            time.sleep(.1) 
     279        else: 
     280            return 
     281     
     282    cherrypy.log("Port %s not free" % port, 'HTTP') 
     283    raise cherrypy.NotReady("Port not free.") 
     284 
     285def wait_for_occupied_port(host, port): 
     286    for trial in xrange(50): 
     287        try: 
     288            check_port(host, port) 
     289        except IOError: 
     290            return 
     291        else: 
     292            time.sleep(.1) 
     293     
     294    cherrypy.log("Port %s not bound" % port, 'HTTP') 
     295    raise cherrypy.NotReady("Port not bound.") 
  • trunk/cherrypy/test/helper.py

    r689 r691  
    6464 
    6565def startServer(serverClass=None): 
    66     """Start the given server (default serverless) in a new thread.""" 
     66    """Start server in a new thread (same thread if serverClass is None).""" 
    6767    if serverClass is None: 
    6868        cherrypy.server.start(initOnly=True) 
     
    7171                             args=(False, serverClass)) 
    7272        t.start() 
    73         time.sleep(1
     73    cherrypy.server.wait_until_ready(
    7474 
    7575 
     
    7777    """Stop the current CP server.""" 
    7878    cherrypy.server.stop() 
    79     if cherrypy.config.get('server.threadPool') > 1: 
    80         # With thread-pools, it can take up to 1 sec for the server to stop 
    81         time.sleep(1.1) 
    8279 
    8380 
     
    192189        cherrypy.config.update(conf.copy()) 
    193190 
     191 
    194192def run_test_suite(moduleNames, server, conf): 
    195193    """Run the given test modules using the given server and conf. 
  • trunk/cherrypy/test/test.py

    r686 r691  
    307307            print 
    308308            print "Running tests:", name 
     309            reload(test_states) 
    309310            test_states.run(cls, conf) 
    310311            helper.run_test_suite(self.tests, cls, conf) 
  • trunk/cherrypy/test/test_states.py

    r686 r691  
    6363        self.assertEqual(cherrypy._appserver_state, 1) 
    6464         
    65         if cherrypy._httpserver
     65        if self.serverClass
    6666            host = cherrypy.config.get('server.socketHost') 
    6767            port = cherrypy.config.get('server.socketPort') 
     
    7979     
    8080    def test_1_KeyboardInterrupts(self): 
    81         helper.startServer(self.serverClass) 
    82          
    83         if cherrypy._httpserver: 
     81        if self.serverClass: 
    8482            # Raise a keyboard interrupt in the HTTP server's main thread. 
     83             
    8584            def raiser(x=None): 
    8685                raise KeyboardInterrupt 
     86             
     87            helper.startServer(self.serverClass) 
     88             
    8789            name = cherrypy._httpserver.__class__.__name__ 
    8890            if name == "WSGIServer": 
     
    9092            elif name in ("CherryHTTPServer", "PooledThreadServer"): 
    9193                cherrypy._httpserver.handle_request = raiser 
     94            else: 
     95                raise ValueError("Unknown HTTP server: %s" % name) 
     96             
    9297            # Give the server time to shut down. 
    93             time.sleep(1) 
    94             self.assertEqual(cherrypy._appserver_state, 0) 
     98            while cherrypy._appserver_state != 0: 
     99                time.sleep(.1) 
     100            self.assertEqual(cherrypy._httpserver, None) 
    95101             
    96102            # Once the server has stopped, we should get a NotReady error again. 
    97103            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    98 ##     
    99 ##    def test_2_Restart(self): 
    100 ##        # Test server start 
    101 ##        helper.startServer(self.serverClass) 
    102 ##        self.getPage("/") 
    103 ##        self.assertBody("Hello World") 
    104 ##         
    105 ##        # Test server stop 
    106 ##        cherrypy.server.restart() 
    107 ##        self.assertEqual(cherrypy._appserver_state, 1) 
    108 ##        self.getPage("/") 
    109 ##        self.assertBody("Hello World") 
     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") 
    110119 
    111120 
     
    126135            'server.threadPool': 10, 
    127136            'server.logToScreen': False, 
     137            'server.logConfigOptions': False, 
    128138            'server.environment': "production", 
    129139            'server.showTracebacks': True, 
  • trunk/cherrypy/test/webtest.py

    r678 r691  
    329329    while trial < 10: 
    330330        try: 
    331             conn = httplib.HTTPConnection('%s:%s' % (host, port)
     331            conn = httplib.HTTPConnection(host, port
    332332            conn.putrequest(method.upper(), url) 
    333333             

Hosted by WebFaction

Log in as guest/cpguest to create tickets