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

Changeset 1204

Show
Ignore:
Timestamp:
07/19/06 17:42:40
Author:
fumanchu
Message:

Merged the reexec branch. Autoreload now uses exec instead of spawn, and therefore never runs more than one process at a time. There's a new test for autoreload in test_states.py.

The state-management for the HTTP server is a mess, by the way. I only got test_states' KeyboardInterrupt? test working by inlining the client threads. So wait() and interrupts, etc. only really work when there's a single master thread. This needs fixed.

Files:

Legend:

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

    r1183 r1204  
    22 
    33import cgi 
     4import os 
    45import sys 
    56import threading 
     
    89import cherrypy 
    910from cherrypy import _cprequest 
    10 from cherrypy.lib import autoreload 
    1111 
    1212# Use a flag to indicate the state of the application engine. 
     
    1616 
    1717 
     18def fileattr(m): 
     19    if hasattr(m, "__loader__"): 
     20        if hasattr(m.__loader__, "archive"): 
     21            return m.__loader__.archive 
     22    return getattr(m, "__file__", None) 
     23 
     24 
    1825class Engine(object): 
    1926    """The application engine, which exposes a request interface to (HTTP) servers.""" 
     
    2431    def __init__(self): 
    2532        self.state = STOPPED 
    26         self.interrupt = None 
    2733         
    2834        # Startup/shutdown hooks 
     
    3238        self.on_stop_thread_list = [] 
    3339        self.seen_threads = {} 
     40         
     41        self.mtimes = {} 
     42        self.reload_files = [] 
    3443     
    3544    def start(self, blocking=True): 
    3645        """Start the application engine.""" 
    3746        self.state = STARTING 
    38         self.interrupt = None 
    3947         
    4048        conf = cherrypy.config.get 
     
    4452            cherrypy.config.log_config() 
    4553         
    46         # Autoreload. Note that, if we're not starting our own HTTP server, 
    47         # autoreload could do Very Bad Things when it calls sys.exit, but 
    48         # deployers will just have to be educated and responsible for it. 
    49         if conf('autoreload.on', False): 
    50             try: 
    51                 freq = conf('autoreload.frequency', 1) 
    52                 autoreload.main(self._start, args=(blocking,), freq=freq) 
    53             except KeyboardInterrupt: 
    54                 cherrypy.log("<Ctrl-C> hit: shutting down autoreloader", "ENGINE") 
    55                 cherrypy.server.stop() 
    56                 self.stop() 
    57             except SystemExit: 
    58                 cherrypy.log("SystemExit raised: shutting down autoreloader", "ENGINE") 
    59                 cherrypy.server.stop() 
    60                 self.stop() 
    61                 # We must raise here: if this is a process spawned by 
    62                 # autoreload, then it must return its error code to 
    63                 # the parent. 
    64                 raise 
    65             return 
    66          
    67         self._start(blocking) 
    68      
    69     def _start(self, blocking=True): 
    70         # This is in a separate function so autoreload can call it. 
    7154        for func in self.on_start_engine_list: 
    7255            func() 
     
    7861        """Block forever (wait for stop(), KeyboardInterrupt or SystemExit).""" 
    7962        try: 
     63            autoreload = cherrypy.config.get('autoreload.on', False) 
     64            if autoreload: 
     65                i = 0 
     66                freq = cherrypy.config.get('autoreload.frequency', 1) 
     67             
    8068            while self.state != STOPPED: 
    8169                time.sleep(.1) 
    82                 if self.interrupt: 
    83                     raise self.interrupt 
     70                 
     71                # Autoreload 
     72                if autoreload: 
     73                    i += .1 
     74                    if i > freq: 
     75                        i = 0 
     76                        self.autoreload() 
    8477        except KeyboardInterrupt: 
    8578            cherrypy.log("<Ctrl-C> hit: shutting down app engine", "ENGINE") 
     
    9386        except: 
    9487            # Don't bother logging, since we're going to re-raise. 
    95             self.interrupt = sys.exc_info()[1] 
    9688            # Note that we don't stop the HTTP server here. 
    9789            self.stop() 
    9890            raise 
     91     
     92    def reexec(self): 
     93        """Re-execute the current process.""" 
     94        cherrypy.server.stop() 
     95        self.stop() 
     96         
     97        args = sys.argv[:] 
     98        cherrypy.log("Re-spawning %s" % " ".join(args), "ENGINE") 
     99        args.insert(0, sys.executable) 
     100         
     101        if sys.platform == "win32": 
     102            args = ['"%s"' % arg for arg in args] 
     103        os.execv(sys.executable, args) 
     104     
     105    def autoreload(self): 
     106        """Reload the process if registered files have been modified.""" 
     107        for filename in map(fileattr, sys.modules.values()) + self.reload_files: 
     108            if filename: 
     109                if filename.endswith(".pyc"): 
     110                    filename = filename[:-1] 
     111                 
     112                try: 
     113                    mtime = os.stat(filename).st_mtime 
     114                except OSError: 
     115                    if filename in self.mtimes: 
     116                        # The file was probably deleted. 
     117                        self.reexec() 
     118                 
     119                if filename not in self.mtimes: 
     120                    self.mtimes[filename] = mtime 
     121                    continue 
     122                 
     123                if mtime > self.mtimes[filename]: 
     124                    # The file has been modified. 
     125                    self.reexec() 
    99126     
    100127    def stop(self): 
     
    121148        while not self.ready: 
    122149            time.sleep(.1) 
    123             if self.interrupt: 
    124                 raise self.interrupt 
    125150     
    126151    def _is_ready(self): 
     
    181206        self.msg = msg 
    182207     
     208    def close(self): 
     209        pass 
     210     
    183211    def run(self, request_line, headers, rfile): 
    184212        self.method = "GET" 
  • trunk/cherrypy/_cpserver.py

    r1141 r1204  
    1717    def start(self, server=None): 
    1818        """Main function. MUST be called from the main thread.""" 
     19        self.interrupt = None 
     20         
    1921        conf = cherrypy.config.get 
    2022        if server is None: 
     
    6466    def wait(self): 
    6567        """Wait until the HTTP server is ready to receive requests.""" 
    66         while (not getattr(self.httpserver, "ready", True) 
     68        while (not getattr(self.httpserver, "ready", False) 
    6769               and not self.interrupt): 
    6870            time.sleep(.1) 
     71        if self.interrupt: 
     72            raise self.interrupt 
    6973         
    7074        # Wait for port to be occupied 
     
    8387            # httpstop() MUST block until the server is *truly* stopped. 
    8488            httpstop() 
     89            conf = cherrypy.config.get 
     90            if conf('server.socket_port'): 
     91                host = conf('server.socket_host') 
     92                port = conf('server.socket_port') 
     93                wait_for_free_port(host, port) 
    8594            cherrypy.log("HTTP Server shut down", "HTTP") 
    8695     
  • trunk/cherrypy/_cpwsgiserver.py

    r1192 r1204  
    215215    version = "CherryPy/3.0.0alpha" 
    216216    ready = False 
    217     interrupt = None 
     217    _interrupt = None 
    218218    RequestHandlerClass = HTTPRequest 
    219219     
     
    315315            self.tick() 
    316316            if self.interrupt: 
     317                while self.interrupt is True: 
     318                    # Wait for self.stop() to complete 
     319                    time.sleep(0.1) 
    317320                raise self.interrupt 
    318321     
     
    320323        try: 
    321324            s, addr = self.socket.accept() 
     325            if not self.ready: 
     326                return 
    322327            if hasattr(s, 'settimeout'): 
    323328                s.settimeout(self.timeout) 
     
    329334            # accept() by default 
    330335            return 
     336        except socket.error, x: 
     337            if x.args[1] == "Bad file descriptor": 
     338                # Our socket was closed 
     339                return 
     340            raise 
     341     
     342    def _get_interrupt(self): 
     343        return self._interrupt 
     344    def _set_interrupt(self, interrupt): 
     345        self._interrupt = True 
     346        self.stop() 
     347        self._interrupt = interrupt 
     348    interrupt = property(_get_interrupt, _set_interrupt) 
    331349     
    332350    def stop(self): 
    333351        """Gracefully shutdown a server that is serving forever.""" 
    334352        self.ready = False 
    335         s = getattr(self, "socket", None) 
    336         if s and hasattr(s, "close"): 
    337             s.close() 
     353         
     354        sock = getattr(self, "socket", None) 
     355        if sock: 
     356            if not isinstance(self.bind_addr, basestring): 
     357                # Ping our own socket to make accept() return immediately. 
     358                try: 
     359                    host, port = sock.getsockname()[:2] 
     360                except socket.error, x: 
     361                    if x.args[1] != "Bad file descriptor": 
     362                        raise 
     363                else: 
     364                    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, 
     365                                                  socket.SOCK_STREAM): 
     366                        af, socktype, proto, canonname, sa = res 
     367                        s = None 
     368                        try: 
     369                            s = socket.socket(af, socktype, proto) 
     370                            # See http://groups.google.com/group/cherrypy-users/ 
     371                            #        browse_frm/thread/bbfe5eb39c904fe0 
     372                            s.settimeout(1.0) 
     373                            s.connect((host, port)) 
     374                            s.close() 
     375                        except socket.error: 
     376                            if s: 
     377                                s.close() 
     378            if hasattr(sock, "close"): 
     379                sock.close() 
     380            self.socket = None 
    338381         
    339382        # Must shut down threads here so the code that calls 
     
    344387        # Don't join currentThread (when stop is called inside a request). 
    345388        current = threading.currentThread() 
    346         for worker in self._workerThreads: 
     389        while self._workerThreads: 
     390            worker = self._workerThreads.pop() 
    347391            if worker is not current and worker.isAlive: 
    348                 worker.join() 
    349          
    350         self._workerThreads = [] 
     392                try: 
     393                    worker.join() 
     394                except AssertionError: 
     395                    pass 
     396 
  • trunk/cherrypy/config.py

    r1201 r1204  
    77 
    88import cherrypy 
     9 
    910 
    1011environments = { 
     
    3738    """Merge one app config (from a dict, file, or filename) into another.""" 
    3839    if isinstance(other, basestring): 
    39         if other not in cherrypy.lib.autoreload.reloadFiles: 
    40             cherrypy.lib.autoreload.reloadFiles.append(other) 
     40        if other not in cherrypy.engine.reload_files: 
     41            cherrypy.engine.reload_files.append(other) 
    4142        other = Parser().dict_from_file(other) 
    4243    elif hasattr(other, 'read'): 
     
    7980    """Update globalconf from a dict, file or filename.""" 
    8081    if isinstance(conf, basestring): 
    81         if conf not in cherrypy.lib.autoreload.reloadFiles: 
    82             cherrypy.lib.autoreload.reloadFiles.append(conf) 
     82        if conf not in cherrypy.engine.reload_files: 
     83            cherrypy.engine.reload_files.append(conf) 
    8384        conf = Parser().dict_from_file(conf) 
    8485    elif hasattr(conf, 'read'): 
  • trunk/cherrypy/test/test_states.py

    r1183 r1204  
     1import os 
     2import sys 
     3import threading 
     4import time 
     5 
    16import test 
    27test.prefer_parent_path() 
    3  
    4 import threading 
    58 
    69import cherrypy 
     
    148151             
    149152            # Raise a keyboard interrupt in the HTTP server's main thread. 
    150             def interrupt(): 
    151                 cherrypy.server.wait() 
    152                 cherrypy.server.httpserver.interrupt = KeyboardInterrupt 
    153             threading.Thread(target=interrupt).start() 
    154              
    155153            # We must start the server in this, the main thread 
     154            cherrypy.engine.start(blocking=False) 
    156155            cherrypy.server.start(self.server_class) 
    157             # Time passes... 
     156            cherrypy.server.httpserver.interrupt = KeyboardInterrupt 
     157            while cherrypy.engine.state != 0: 
     158                time.sleep(0.1) 
     159             
    158160            self.assertEqual(db_connection.running, False) 
    159161            self.assertEqual(len(db_connection.threads), 0) 
     162            self.assertEqual(cherrypy.engine.state, 0) 
    160163             
    161164            # Raise a keyboard interrupt in a page handler; on multithreaded 
     
    163166            # This should raise a BadStatusLine error, since the worker 
    164167            # thread will just die without writing a response. 
    165             def interrupt(): 
    166                 cherrypy.server.wait() 
    167                 from httplib import BadStatusLine 
    168                 self.assertRaises(BadStatusLine, self.getPage, "/ctrlc") 
    169             threading.Thread(target=interrupt).start() 
    170              
     168            cherrypy.engine.start(blocking=False) 
    171169            cherrypy.server.start(self.server_class) 
    172             # Time passes... 
     170             
     171            from httplib import BadStatusLine 
     172            try: 
     173                self.getPage("/ctrlc") 
     174            except BadStatusLine: 
     175                pass 
     176            else: 
     177                print self.body 
     178                self.fail("AssertionError: BadStatusLine not raised") 
     179             
     180            while cherrypy.engine.state != 0: 
     181                time.sleep(0.1) 
    173182            self.assertEqual(db_connection.running, False) 
    174183            self.assertEqual(len(db_connection.threads), 0) 
     184     
     185    def test_3_Autoreload(self): 
     186        if self.server_class: 
     187            demoscript = os.path.join(os.getcwd(), os.path.dirname(__file__), 
     188                                      "test_states_demo.py") 
     189             
     190            # Start the demo script in a new process 
     191            host = cherrypy.config.get("server.socket_host") 
     192            port = cherrypy.config.get("server.socket_port") 
     193            cherrypy._cpserver.wait_for_free_port(host, port) 
     194            os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, 
     195                      demoscript, host, str(port)) 
     196            cherrypy._cpserver.wait_for_occupied_port(host, port) 
     197             
     198            try: 
     199                self.getPage("/pid") 
     200                pid = self.body 
     201                 
     202                # Give the autoreloader time to cache the file time. 
     203                time.sleep(2) 
     204                 
     205                # Touch the file 
     206                f = open(demoscript, 'ab') 
     207                f.write(" ") 
     208                f.close() 
     209                 
     210                # Give the autoreloader time to re-exec the process 
     211                time.sleep(2) 
     212                cherrypy._cpserver.wait_for_occupied_port(host, port) 
     213                 
     214                self.getPage("/pid") 
     215                self.assertNotEqual(self.body, pid) 
     216            finally: 
     217                # Shut down the spawned process 
     218                self.getPage("/stop") 
    175219 
    176220 
     
    189233        cherrypy.engine.on_stop_thread_list.append(db_connection.stopthread) 
    190234         
    191         helper.CPTestRunner.run(suite) 
     235        import pyconquer 
     236        tr = pyconquer.Logger("cherrypy") 
     237        tr.out = open(os.path.join(os.path.dirname(__file__), "state.log"), "wb") 
     238        try: 
     239            tr.start() 
     240            helper.CPTestRunner.run(suite) 
     241        finally: 
     242            tr.stop() 
     243            tr.out.close() 
    192244    finally: 
    193         cherrypy.server.stop() 
     245        if cherrypy.server.httpserver.ready: 
     246            cherrypy.server.stop() 
    194247        cherrypy.engine.stop() 
    195248 

Hosted by WebFaction

Log in as guest/cpguest to create tickets