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

Changeset 698

Show
Ignore:
Timestamp:
10/01/05 13:44:53
Author:
fumanchu
Message:

Ugly fix for #321. cherrypy.server could really use some encapsulation now.

1. server.start now MUST be called from the main thread, or restart and interrupts won't work. You can stop and restart CherryPy safely now with the server.stop and server.restart methods. However, stop() only suspends the process; if you want to shut down the CP process, raise SystemExit? or KeyboardInterrupt?. If you need to do so in your own threads, set cherrypy._interrupt to an instance of one of those exceptions.

2. New cherrypy._httpserverclass attribute, so that threads can wait for the HTTP server to truly start.

3. server.start() now defaults serverClass to _missing, so if you were using None to get the WSGIServer, switch to _missing.

4. server has some new methods: start_app_server, start_http_server, stop_http_server, stop_app_server.

5. There's also a new start_with_callback function, so you don't have to code the threading yourself if you want to start the server but run another task in a new thread.

6. test/helper.py doesn't have startServer/stopServer methods anymore. Just call server.start/stop instead.

Files:

Legend:

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

    r665 r698  
    4747_appserver_state = 0 
    4848_httpserver = None 
     49_httpserverclass = None 
     50_interrupt = None 
    4951 
    5052codecoverage = False 
  • trunk/cherrypy/_cphttpserver.py

    r693 r698  
    223223            self.handle_request() 
    224224            if self.interrupt: 
    225                 i = self.interrupt 
    226                 self.interrupt = None 
    227                 raise i 
     225                raise self.interrupt 
    228226    start = serve_forever 
    229227     
     
    364362        while self.ready: 
    365363            if self.interrupt: 
    366                 i = self.interrupt 
    367                 self.interrupt = None 
    368                 raise i 
     364                raise self.interrupt 
    369365            if not self.handle_request(): 
    370366                break 
  • trunk/cherrypy/_cpwsgiserver.py

    r693 r698  
    117117        self.wfile = self.socket.makefile("w", self.server.bufsize) 
    118118        self.sent_headers = False 
     119     
    119120    def parse_request(self): 
    120121        self.sent_headers = False 
     
    323324            self.tick() 
    324325            if self.interrupt: 
    325                 i = self.interrupt 
    326                 self.interrupt = None 
    327                 raise i 
     326                raise self.interrupt 
    328327     
    329328    def tick(self): 
     
    355354        current = threading.currentThread() 
    356355        for worker in self._workerThreads: 
    357             if worker is not current
     356            if worker is not current and worker.isAlive
    358357                worker.join() 
    359358         
  • trunk/cherrypy/server.py

    r693 r698  
    3232""" 
    3333 
    34 import warnings 
     34import cgi 
    3535import threading 
    3636import time 
     37import warnings 
    3738 
    3839import cherrypy 
     
    4849 
    4950 
    50 def start(initOnly=False, serverClass=None): 
    51     defaultOn = (cherrypy.config.get("server.environment") == "development") 
    52     if cherrypy.config.get('autoreload.on', defaultOn): 
    53         # Check initOnly. If True, we're probably not starting 
    54         # our own webserver, and therefore could do Very Bad Things 
    55         # when autoreload calls sys.exit. 
    56         if not initOnly: 
     51_missing = object() 
     52 
     53def start(initOnly=False, serverClass=_missing): 
     54    """Main function. MUST be called from the main thread. 
     55     
     56    Set initOnly to True to keep this function from blocking. 
     57    Set serverClass to None to skip starting any HTTP server. 
     58    """ 
     59    # This duplicates the line in start_app_server on purpose. 
     60    cherrypy._appserver_state = None 
     61    cherrypy._interrupt = None 
     62     
     63    conf = cherrypy.config.get 
     64     
     65    if serverClass is _missing: 
     66        serverClass = conf("server.class", _missing) 
     67    if serverClass is _missing: 
     68        import _cpwsgi 
     69        serverClass = _cpwsgi.WSGIServer 
     70    elif serverClass and isinstance(serverClass, basestring): 
     71        # Dynamically load the class from the given string 
     72        serverClass = cherrypy._cputil.attributes(serverClass) 
     73     
     74    # Autoreload, but check serverClass. If None, we're not starting 
     75    # our own webserver, and therefore could do Very Bad Things when 
     76    # autoreload calls sys.exit. 
     77    if serverClass is not None: 
     78        defaultOn = (conf("server.environment") == "development") 
     79        if conf('autoreload.on', defaultOn): 
    5780            try: 
    5881                autoreload.main(_start, (initOnly, serverClass)) 
     
    6386    _start(initOnly, serverClass) 
    6487 
    65 def _start(initOnly=False, serverClass=None): 
    66     """Main function.""" 
     88def _start(initOnly, serverClass): 
     89    # This duplicates the line in start_app_server on purpose. 
     90    cherrypy._appserver_state = None 
     91     
     92    cherrypy._httpserverclass = serverClass 
     93    conf = cherrypy.config.get 
     94     
    6795    try: 
    6896        if cherrypy.codecoverage: 
     
    7098            covercp.start() 
    7199         
    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          
    78100        # Output config options to log 
    79         if cherrypy.config.get("server.logConfigOptions", True): 
     101        if conf("server.logConfigOptions", True): 
    80102            cherrypy.config.outputConfigMap() 
    81          
    82         # Check the config options 
    83         # TODO 
    84         # config.checkConfigOptions() 
     103        # TODO: config.checkConfigOptions() 
    85104         
    86105        # If sessions are stored in files and we 
    87106        # 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'): 
     107        if (conf('server.threadPool') > 1 
     108            and conf('session.storageType') == 'file'): 
    90109            cherrypy._sessionFileLock = threading.RLock() 
    91110         
    92111        # 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() 
     112        cgi.maxlen = conf('server.maxRequestSize') 
    99113         
    100114        # Set up the profiler if requested. 
    101         if cherrypy.config.get("profiling.on", False): 
    102             ppath = cherrypy.config.get("profiling.path", "") 
     115        if conf("profiling.on", False): 
     116            ppath = conf("profiling.path", "") 
    103117            cherrypy.profiler = profiler.Profiler(ppath) 
    104118        else: 
    105119            cherrypy.profiler = None 
    106  
    107         # Initilize the built in filters 
     120         
     121        # Initialize the built in filters 
    108122        cherrypy._cputil._cpInitDefaultFilters() 
    109123        cherrypy._cputil._cpInitUserDefinedFilters() 
    110124         
    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  
    121  
    122 def run_server(serverClass=None): 
    123     """Prepare the requested server and then run it.""" 
     125        start_app_server() 
     126        start_http_server() 
     127        wait_until_ready() 
     128         
     129        if not initOnly: 
     130            # Block forever (wait for KeyboardInterrupt or SystemExit). 
     131            while True: 
     132                time.sleep(.1) 
     133                if cherrypy._interrupt: 
     134                    raise cherrypy._interrupt 
     135    except KeyboardInterrupt: 
     136        cherrypy.log("<Ctrl-C> hit: shutting down server", "HTTP") 
     137        stop() 
     138    except SystemExit: 
     139        cherrypy.log("SystemExit raised: shutting down server", "HTTP") 
     140        stop() 
     141 
     142def start_app_server(): 
     143    """Start the CherryPy core.""" 
     144    # Use a flag to indicate the state of the cherrypy application server. 
     145    # 0 = Not started 
     146    # None = In process of starting 
     147    # 1 = Started, ready to receive requests 
     148    cherrypy._appserver_state = None 
     149     
     150    # Call the functions from cherrypy.server.onStartServerList 
     151    for func in cherrypy.server.onStartServerList: 
     152        func() 
     153     
     154    cherrypy._appserver_state = 1 
     155 
     156def start_http_server(serverClass=None): 
     157    """Start the requested HTTP server.""" 
     158    if serverClass is None: 
     159        serverClass = cherrypy._httpserverclass 
     160    if serverClass is None: 
     161        return 
     162     
    124163    if cherrypy._httpserver is not None: 
    125         warnings.warn("You seem to have an HTTP server still running." 
    126                       "Please call cherrypy.server.stop() before continuing.") 
     164        msg = ("You seem to have an HTTP server still running." 
     165               "Please call cherrypy.server.stop_http_server() " 
     166               "before continuing.") 
     167        warnings.warn(msg) 
    127168     
    128169    if cherrypy.config.get('server.socketPort'): 
     
    140181     
    141182    # Instantiate the server. 
    142     if serverClass is None: 
    143         serverClass = cherrypy.config.get("server.class", None) 
    144     if serverClass and isinstance(serverClass, basestring): 
    145         serverClass = cherrypy._cputil.attributes(serverClass) 
    146     if serverClass is None: 
    147         import _cpwsgi 
    148         serverClass = _cpwsgi.WSGIServer 
    149183    cherrypy._httpserver = serverClass() 
    150184     
    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: 
    161         cherrypy.log("<Ctrl-C> hit: shutting down server", "HTTP") 
    162         stop() 
    163     except SystemExit: 
    164         cherrypy.log("SystemExit raised: shutting down server", "HTTP") 
    165         stop() 
     185    # HTTP servers MUST be started in a new thread, so that the 
     186    # main thread persists to receive KeyboardInterrupt's. This 
     187    # wrapper traps an interrupt in the http server's main thread 
     188    # and shutdowns CherryPy. 
     189    def _start_http(): 
     190        try: 
     191            cherrypy._httpserver.start() 
     192        except (KeyboardInterrupt, SystemExit), exc: 
     193            cherrypy._interrupt = exc 
     194    threading.Thread(target=_start_http).start() 
    166195 
    167196 
     
    205234 
    206235def stop(): 
    207     """Shutdown CherryPy (and any HTTP servers it started).""" 
     236    """Stop CherryPy and any HTTP servers it started.""" 
     237    stop_http_server() 
     238    stop_app_server() 
     239 
     240def stop_app_server(): 
     241    """Stop CherryPy.""" 
     242    cherrypy._appserver_state = 0 
     243    cherrypy.log("CherryPy shut down", "HTTP") 
     244 
     245def stop_http_server(): 
     246    """Stop the HTTP server.""" 
    208247    try: 
    209248        httpstop = cherrypy._httpserver.stop 
     
    211250        pass 
    212251    else: 
    213         # httpstop() should block until the server is *truly* stopped. 
     252        # httpstop() MUST block until the server is *truly* stopped. 
    214253        httpstop() 
    215254        cherrypy.log("HTTP Server shut down", "HTTP") 
     
    226265     
    227266    cherrypy._httpserver = None 
    228     cherrypy._appserver_state = 0 
    229     cherrypy.log("CherryPy shut down", "HTTP") 
    230267 
    231268def restart(): 
    232     """Stop and start CherryPy.""" 
    233     http = getattr(cherrypy, '_httpserver', None) 
     269    """Restart CherryPy (and any HTTP servers it started).""" 
    234270    stop() 
    235     if http: 
    236         # Start the server in a new thread 
    237         thread_args = {"serverClass": http.__class__} 
    238         t = threading.Thread(target=_start, kwargs=thread_args) 
    239         t.start() 
    240     else: 
    241         _start(initOnly=True) 
     271    start_app_server() 
     272    start_http_server() 
    242273    wait_until_ready() 
    243274 
     
    245276    """Block the caller until CherryPy is ready to receive requests.""" 
    246277     
     278    # Wait for app to start up 
    247279    while cherrypy._appserver_state != 1: 
    248280        time.sleep(.1) 
    249281     
    250     http = getattr(cherrypy, '_httpserver', None) 
    251     if http: 
    252         # Wait for HTTP server to start up 
    253         while not http.ready: 
     282    # Wait for HTTP server to start up 
     283    if cherrypy._httpserverclass is not None: 
     284        while not getattr(cherrypy._httpserver, "ready", None): 
    254285            time.sleep(.1) 
    255286         
     
    299330    cherrypy.log("Port %s not bound" % port, 'HTTP') 
    300331    raise cherrypy.NotReady("Port not bound.") 
     332 
     333def start_with_callback(func, args=None, kwargs=None, serverClass=_missing): 
     334    """Start CherryPy, then callback the given func in a new thread.""" 
     335    if args is None: 
     336        args = () 
     337    if kwargs is None: 
     338        kwargs = {} 
     339    args = (func,) + args 
     340    threading.Thread(target=_callback_intermediary, args=args, kwargs=kwargs).start() 
     341    cherrypy.server.start(serverClass=serverClass) 
     342 
     343def _callback_intermediary(func, *args, **kwargs): 
     344    wait_until_ready() 
     345    func(*args, **kwargs) 
  • trunk/cherrypy/test/helper.py

    r693 r698  
    4646 
    4747import os, os.path 
    48 import sys 
    49 import time 
     48import re 
    5049import socket 
    5150import StringIO 
     51import sys 
     52import thread 
    5253import threading 
     54import time 
     55import types 
    5356 
    5457import cherrypy 
    5558import webtest 
    56 import types 
    57 import re 
    5859 
    5960for _x in dir(cherrypy): 
     
    6162    if isinstance(y, types.ClassType) and issubclass(y, cherrypy.Error): 
    6263        webtest.ignored_exceptions.append(y) 
    63  
    64  
    65 def startServer(serverClass=None): 
    66     """Start server in a new thread (same thread if serverClass is None).""" 
    67     if serverClass is None: 
    68         cherrypy.server.start(initOnly=True) 
    69     else: 
    70         t = threading.Thread(target=cherrypy.server.start, 
    71                              args=(False, serverClass)) 
    72         t.start() 
    73     cherrypy.server.wait_until_ready() 
    74  
    75  
    76 def stopServer(): 
    77     """Stop the current CP server.""" 
    78     cherrypy.server.stop() 
    7964 
    8065 
     
    8772 
    8873class CPWebCase(webtest.WebCase): 
     74     
     75    def exit(self): 
     76        sys.exit() 
    8977     
    9078    def _getRequest(self, url, headers, method, body): 
     
    198186    """ 
    199187    setConfig(conf) 
    200     startServer(server) 
     188    cherrypy.server.start_with_callback(_run_test_suite_thread, 
     189                                        args=(moduleNames, conf), 
     190                                        serverClass=server) 
     191 
     192def _run_test_suite_thread(moduleNames, conf): 
     193    cherrypy.server.wait_until_ready() 
    201194    for testmod in moduleNames: 
    202195        # Must run each module in a separate suite, 
     
    208201        suite = CPTestLoader.loadTestsFromName(testmod) 
    209202        CPTestRunner.run(suite) 
    210     stopServer() 
    211  
     203    thread.interrupt_main() 
    212204 
    213205def testmain(server=None, conf=None): 
     
    216208        conf = {} 
    217209    setConfig(conf) 
    218      
    219     startServer(server) 
     210    cherrypy.server.start_with_callback(_test_main_thread, serverClass=server) 
     211 
     212def _test_main_thread(): 
     213    cherrypy.server.wait_until_ready() 
     214    cherrypy._cputil._cpInitDefaultFilters() 
    220215    try: 
    221         cherrypy._cputil._cpInitDefaultFilters() 
    222216        webtest.main() 
    223217    finally: 
    224         # webtest.main == unittest.main, which raises SystemExit, 
    225         # so put stopServer in a finally clause 
    226         stopServer() 
    227  
     218        thread.interrupt_main() 
     219 
  • trunk/cherrypy/test/test_noserver.py

    r467 r698  
    2626 
    2727cherrypy.config.update({"server.environment": "production"}) 
    28 cherrypy.server.start(initOnly = True) 
     28cherrypy.server.start(serverClass=None) 
    2929 
  • trunk/cherrypy/test/test_states.py

    r696 r698  
    2828 
    2929import time 
     30import threading 
    3031 
    3132import cherrypy 
     
    6667         
    6768        # Test server start 
    68         helper.startServer(self.serverClass) 
     69        cherrypy.server.start(True, self.serverClass) 
    6970        self.assertEqual(cherrypy._appserver_state, 1) 
    7071         
     
    7879         
    7980        # Test server stop 
    80         helper.stopServer() 
     81        cherrypy.server.stop() 
    8182        self.assertEqual(cherrypy._appserver_state, 0) 
    8283         
     
    8485        self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    8586     
    86     def test_1_KeyboardInterrupt(self): 
    87         if self.serverClass: 
    88             # Raise a keyboard interrupt in the HTTP server's main thread. 
    89             helper.startServer(self.serverClass) 
    90             cherrypy._httpserver.interrupt = KeyboardInterrupt 
    91             # Give the server time to shut down. 
    92             while cherrypy._appserver_state != 0: 
    93                 time.sleep(.1) 
    94             self.assertEqual(cherrypy._httpserver, None) 
    95             self.assertEqual(cherrypy._appserver_state, 0) 
    96             self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    97              
    98             # Raise a keyboard interrupt in a page handler; on multithreaded 
    99             # servers, this should occur in one of the worker threads. 
    100             # This should raise a BadStatusLine error, since the worker 
    101             # thread will just die without writing a response. 
    102             from httplib import BadStatusLine 
    103             helper.startServer(self.serverClass) 
    104             self.assertRaises(BadStatusLine, self.getPage, "/ctrlc") 
    105             # Give the server time to shut down. 
    106             while cherrypy._appserver_state != 0: 
    107                 print ".", 
    108                 time.sleep(.1) 
    109             self.assertEqual(cherrypy._httpserver, None) 
    110             self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    111      
    112     def test_2_Restart(self): 
     87    def test_1_Restart(self): 
    11388        # Test server start 
    114         helper.startServer(self.serverClass) 
     89        cherrypy.server.start(True, self.serverClass) 
    11590        self.getPage("/") 
    11691        self.assertBody("Hello World") 
     
    127102        self.assertBody("app was restarted succesfully") 
    128103         
    129         # Now that we've restarted, test a KeyboardInterrupt (ticket 321). 
     104        cherrypy.server.stop() 
     105        self.assertEqual(cherrypy._appserver_state, 0) 
     106     
     107    def test_2_KeyboardInterrupt(self): 
    130108        if self.serverClass: 
    131             cherrypy._httpserver.interrupt = KeyboardInterrupt 
    132             # Give the server time to shut down. 
    133             while cherrypy._appserver_state != 0: 
    134                 time.sleep(.1) 
     109             
     110            # Raise a keyboard interrupt in the HTTP server's main thread. 
     111            def interrupt(): 
     112                cherrypy.server.wait_until_ready() 
     113                cherrypy._httpserver.interrupt = KeyboardInterrupt 
     114            threading.Thread(target=interrupt).start() 
     115             
     116            # We must start the server in this, the main thread 
     117            cherrypy.server.start(False, self.serverClass) 
     118            # Time passes... 
    135119            self.assertEqual(cherrypy._httpserver, None) 
     120            self.assertEqual(cherrypy._appserver_state, 0) 
     121            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    136122             
    137             # Once the server has stopped, we should get a NotReady error again. 
     123            # Raise a keyboard interrupt in a page handler; on multithreaded 
     124            # servers, this should occur in one of the worker threads. 
     125            # This should raise a BadStatusLine error, since the worker 
     126            # thread will just die without writing a response. 
     127            def interrupt(): 
     128                cherrypy.server.wait_until_ready() 
     129                from httplib import BadStatusLine 
     130                self.assertRaises(BadStatusLine, self.getPage, "/ctrlc") 
     131            threading.Thread(target=interrupt).start() 
     132             
     133            cherrypy.server.start(False, self.serverClass) 
     134            # Time passes... 
     135            self.assertEqual(cherrypy._httpserver, None) 
     136            self.assertEqual(cherrypy._appserver_state, 0) 
    138137            self.assertRaises(cherrypy.NotReady, self.getPage, "/") 
    139  
    140138 
    141139 
    142140def run(server, conf): 
    143141    helper.setConfig(conf) 
    144     cherrypy._cputil._cpInitDefaultFilters() 
    145142    ServerStateTests.serverClass = server 
    146143    suite = helper.CPTestLoader.loadTestsFromTestCase(ServerStateTests) 
     
    148145        helper.CPTestRunner.run(suite) 
    149146    finally: 
    150         helper.stopServer() 
     147        cherrypy.server.stop() 
    151148 
    152149 
     
    155152            'server.socketPort': 8000, 
    156153            'server.threadPool': 10, 
    157             'server.logToScreen': True, 
     154            'server.logToScreen': False, 
    158155            'server.logConfigOptions': False, 
    159156            'server.environment': "production", 
     
    166163    _run(None) 
    167164    _run("cherrypy._cpwsgi.WSGIServer") 
    168 ##    _run("cherrypy._cphttpserver.PooledThreadServer") 
    169 ##    conf['server.threadPool'] = 1 
    170 ##    _run("cherrypy._cphttpserver.CherryHTTPServer") 
    171  
     165    _run("cherrypy._cphttpserver.PooledThreadServer") 
     166    conf['server.threadPool'] = 1 
     167    _run("cherrypy._cphttpserver.CherryHTTPServer") 
  • trunk/cherrypy/test/test_tutorials.py

    r690 r698  
    5151        else: 
    5252            module = __import__(target, globals(), locals(), ['']) 
    53          
    54         cherrypy.server.start(initOnly=True) 
    5553     
    5654    def test01HelloWorld(self): 

Hosted by WebFaction

Log in as guest/cpguest to create tickets