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

Changeset 1830

Show
Ignore:
Timestamp:
11/13/07 20:08:22
Author:
fumanchu
Message:

Fix for #751 (logging: Python file objects are not thread-safe). Whew. Finally got all the bus operations where I want em:

  1. There's a new EXITING state
  2. bus.block() waits for EXITING now instead of STOPPED, and also wait for all threads to exit, so the main thread doesn't exit (and run atexit callbacks) too early.
  3. bus.exit() no longer calls sys.exit. Instead, exit is used to signal the main thread to unblock and terminate normally. This means some callers of stop need(ed) to be changed to call exit instead.
  4. bus.block() no longer takes a state arg; it's now only for use by the main thread. Call bus.wait(state) to wait for other states.
Files:

Legend:

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

    r1824 r1830  
    9494     
    9595    def cherrypy_cleanup(data): 
    96         cherrypy.engine.stop() 
     96        cherrypy.engine.exit() 
    9797    try: 
    9898        from mod_python import apache 
  • trunk/cherrypy/restsrv/servers.py

    r1824 r1830  
    7676            self.bus.log("<Ctrl-C> hit: shutting down HTTP server") 
    7777            self.interrupt = exc 
    78             self.bus.stop() 
     78            self.bus.exit() 
    7979        except SystemExit, exc: 
    8080            self.bus.log("SystemExit raised: shutting down HTTP server") 
    8181            self.interrupt = exc 
    82             self.bus.stop() 
     82            self.bus.exit() 
    8383            raise 
    8484        except: 
     
    8787            self.bus.log("Error in HTTP server: shutting down", 
    8888                         traceback=True) 
    89             self.bus.stop() 
     89            self.bus.exit() 
    9090            raise 
    9191     
  • trunk/cherrypy/restsrv/win32.py

    r1824 r1830  
    4343                pass 
    4444             
    45             self.stop() 
     45            self.exit() 
    4646            # 'First to return True stops the calls' 
    4747            return 1 
     
    6767    state = property(_get_state, _set_state) 
    6868     
    69     def block(self, state=wspbus.states.STOPPED, interval=1): 
     69    def wait(self, state, interval=0.1): 
    7070        """Wait for the given state, KeyboardInterrupt or SystemExit. 
    7171         
     
    7474        """ 
    7575        # Don't wait for an event that beat us to the punch ;) 
    76         if self.state == state: 
    77             return 
    78          
    79         event = self._get_state_event(state) 
    80         try: 
     76        if self.state != state: 
     77            event = self._get_state_event(state) 
    8178            win32event.WaitForSingleObject(event, win32event.INFINITE) 
    82             if self.execv: 
    83                 self._do_execv() 
    84         except SystemExit: 
    85             self.log('SystemExit raised: shutting down bus') 
    86             self.stop() 
    87             raise 
    8879 
    8980 
     
    138129        from cherrypy import restsrv 
    139130        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 
    140         restsrv.bus.stop() 
     131        restsrv.bus.exit() 
    141132     
    142133    def SvcOther(self, control): 
  • trunk/cherrypy/restsrv/wspbus.py

    r1826 r1830  
    5252                        | 
    5353                        V 
    54        STOPPING --> STOPPED -->
     54       STOPPING --> STOPPED --> EXITING ->
    5555          A   A         | 
    5656          |    \___     | 
     
    8888states.STARTED = states.State() 
    8989states.STOPPING = states.State() 
     90states.EXITING = states.State() 
    9091 
    9192 
     
    172173            e_info = sys.exc_info() 
    173174            try: 
    174                 self.stop() 
     175                self.exit() 
    175176            except: 
    176                 # Any stop errors will be logged inside publish(). 
     177                # Any stop/exit errors will be logged inside publish(). 
    177178                pass 
    178179            self.log("Exception that caused shutdown: %s" % start_trace) 
    179180            raise e_info[0], e_info[1], e_info[2] 
    180181     
    181     def exit(self, status=0): 
    182         """Stop all services and exit the process.""" 
     182    def exit(self): 
     183        """Stop all services and prepare to exit the process.""" 
    183184        self.stop() 
     185         
     186        self.state = states.EXITING 
    184187        self.log('Bus exit') 
    185188        self.publish('exit') 
    186         sys.exit(status) 
    187189     
    188190    def restart(self): 
     
    193195        """ 
    194196        self.execv = True 
    195         self.stop() 
     197        self.exit() 
    196198     
    197199    def graceful(self): 
     
    200202        self.publish('graceful') 
    201203     
    202     def block(self, state=states.STOPPED, interval=0.1): 
    203         """Wait for the given state, KeyboardInterrupt or SystemExit.""" 
     204    def block(self, interval=0.1): 
     205        """Wait for the EXITING state, KeyboardInterrupt or SystemExit.""" 
    204206        try: 
    205             while self.state != state: 
    206                 time.sleep(interval) 
    207             if self.execv: 
    208                 self._do_execv() 
     207            self.wait(states.EXITING, interval=interval) 
    209208        except (KeyboardInterrupt, IOError): 
    210209            # The time.sleep call might raise 
    211210            # "IOError: [Errno 4] Interrupted function call" on KBInt. 
    212211            self.log('Keyboard Interrupt: shutting down bus') 
    213             self.stop() 
     212            self.exit() 
    214213        except SystemExit: 
    215214            self.log('SystemExit raised: shutting down bus') 
    216             self.stop() 
     215            self.exit() 
    217216            raise 
    218      
    219     def _do_execv(self): 
    220         """Re-execute the current process.""" 
    221         self.execv = False 
    222          
    223         self.log('Bus restart') 
    224         self.publish('exit') 
    225          
    226         # Re-execute the current process. We must do this in the 
    227         # main thread (which is the only thread that should be 
    228         # calling block) because OS X doesn't allow execv to be 
    229         # called in a child thread very well. 
     217         
    230218        # Waiting for ALL child threads to finish is necessary on OS X. 
    231         # See: http://www.cherrypy.org/ticket/581 
     219        # See http://www.cherrypy.org/ticket/581. 
     220        # It's also good to let them all shut down before allowing 
     221        # the main thread to call atexit handlers. 
     222        # See http://www.cherrypy.org/ticket/751. 
    232223        self.log("Waiting for child threads to terminate...") 
    233224        for t in threading.enumerate(): 
    234             if t != threading.currentThread(): 
     225            if (t != threading.currentThread() and t.isAlive() 
     226                # Note that any dummy (external) threads are always daemonic. 
     227                and not t.isDaemon()): 
    235228                t.join() 
    236229         
     230        if self.execv: 
     231            self._do_execv() 
     232     
     233    def wait(self, state, interval=0.1): 
     234        """Wait for the given state.""" 
     235        while self.state != state: 
     236            time.sleep(interval) 
     237     
     238    def _do_execv(self): 
     239        """Re-execute the current process. 
     240         
     241        This must be called from the main thread, because certain platforms 
     242        (OS X) don't allow execv to be called in a child thread very well. 
     243        """ 
    237244        args = sys.argv[:] 
    238245        self.log('Re-spawning %s' % ' '.join(args)) 
    239246        args.insert(0, sys.executable) 
    240          
    241247        if sys.platform == 'win32': 
    242248            args = ['"%s"' % arg for arg in args] 
     
    261267         
    262268        def _callback(func, *a, **kw): 
    263             self.block(states.STARTED) 
     269            self.wait(states.STARTED) 
    264270            func(*a, **kw) 
    265271        t = threading.Thread(target=_callback, args=args, kwargs=kwargs) 
  • trunk/cherrypy/test/benchmark.py

    r1793 r1830  
    378378                run_standard_benchmarks() 
    379379            finally: 
    380                 cherrypy.engine.stop() 
     380                cherrypy.engine.exit() 
    381381                cherrypy.server.stop() 
    382382     
  • trunk/cherrypy/test/helper.py

    r1824 r1830  
    150150 
    151151def _run_test_suite_thread(moduleNames, conf): 
    152     for testmod in moduleNames: 
    153         # Must run each module in a separate suite, 
    154         # because each module uses/overwrites cherrypy globals. 
    155         cherrypy.tree = cherrypy._cptree.Tree() 
    156         cherrypy.config.reset() 
    157         setConfig(conf) 
    158          
    159         m = __import__(testmod, globals(), locals()) 
    160         setup = getattr(m, "setup_server", None) 
    161         if setup: 
    162             setup() 
    163          
    164         # The setup functions probably mounted new apps. 
    165         # Tell our server about them. 
    166         sync_apps(profile=conf.get("profiling.on", False), 
    167                   validate=conf.get("validator.on", False), 
    168                   conquer=conf.get("conquer.on", False), 
    169                   ) 
    170          
    171         suite = CPTestLoader.loadTestsFromName(testmod) 
    172         result = CPTestRunner.run(suite) 
    173         cherrypy.engine.test_success &= result.wasSuccessful() 
    174          
    175         teardown = getattr(m, "teardown_server", None) 
    176         if teardown: 
    177             teardown() 
    178     cherrypy.engine.stop() 
     152    try: 
     153        for testmod in moduleNames: 
     154            # Must run each module in a separate suite, 
     155            # because each module uses/overwrites cherrypy globals. 
     156            cherrypy.tree = cherrypy._cptree.Tree() 
     157            cherrypy.config.reset() 
     158            setConfig(conf) 
     159             
     160            m = __import__(testmod, globals(), locals()) 
     161            setup = getattr(m, "setup_server", None) 
     162            if setup: 
     163                setup() 
     164             
     165            # The setup functions probably mounted new apps. 
     166            # Tell our server about them. 
     167            sync_apps(profile=conf.get("profiling.on", False), 
     168                      validate=conf.get("validator.on", False), 
     169                      conquer=conf.get("conquer.on", False), 
     170                      ) 
     171             
     172            suite = CPTestLoader.loadTestsFromName(testmod) 
     173            result = CPTestRunner.run(suite) 
     174            cherrypy.engine.test_success &= result.wasSuccessful() 
     175             
     176            teardown = getattr(m, "teardown_server", None) 
     177            if teardown: 
     178                teardown() 
     179    finally: 
     180        cherrypy.engine.exit() 
    179181 
    180182def testmain(conf=None): 
     
    192194        webtest.main() 
    193195    finally: 
    194         cherrypy.engine.stop() 
     196        cherrypy.engine.exit() 
    195197 
  • trunk/cherrypy/test/test_states.py

    r1828 r1830  
    135135            self.assertStatus(503) 
    136136         
    137         # Block the main thread now and verify that stop() works. 
    138         def stoptest(): 
     137        # Block the main thread now and verify that exit() works. 
     138        def exittest(): 
    139139            self.getPage("/") 
    140140            self.assertBody("Hello World") 
    141             engine.stop() 
     141            engine.exit() 
    142142        cherrypy.server.start() 
    143         engine.start_with_callback(stoptest) 
     143        engine.start_with_callback(exittest) 
    144144        engine.block() 
    145         self.assertEqual(engine.state, engine.states.STOPPED
     145        self.assertEqual(engine.state, engine.states.EXITING
    146146     
    147147    def test_1_Restart(self): 
     
    202202                self.assertEqual(db_connection.running, False) 
    203203                self.assertEqual(len(db_connection.threads), 0) 
    204                 self.assertEqual(engine.state, engine.states.STOPPED
     204                self.assertEqual(engine.state, engine.states.EXITING
    205205            finally: 
    206206                self.persistent = False 
     
    254254            self.assertInBody("raise cherrypy.TimeoutError()") 
    255255        finally: 
    256             engine.stop() 
     256            engine.exit() 
    257257     
    258258    def test_4_Autoreload(self): 
     
    295295        finally: 
    296296            # Shut down the spawned process 
    297             self.getPage("/stop") 
     297            self.getPage("/exit") 
    298298         
    299299        try: 
     
    368368        finally: 
    369369            # Shut down the spawned process 
    370             self.getPage("/stop") 
     370            self.getPage("/exit") 
    371371         
    372372        try: 
     
    430430                tr.out.close() 
    431431    finally: 
    432         engine.stop() 
     432        engine.exit() 
    433433 
    434434 
  • trunk/cherrypy/test/test_states_demo.py

    r1828 r1830  
    2828    start.exposed = True 
    2929     
    30     def stop(self): 
     30    def exit(self): 
    3131        # This handler might be called before the engine is STARTED if an 
    3232        # HTTP worker thread handles it before the HTTP server returns 
    3333        # control to engine.start. We avoid that race condition here 
    3434        # by waiting for the Bus to be STARTED. 
    35         cherrypy.engine.block(state=cherrypy.engine.states.STARTED) 
    36         cherrypy.engine.stop() 
    37     stop.exposed = True 
     35        cherrypy.engine.wait(state=cherrypy.engine.states.STARTED) 
     36        cherrypy.engine.exit() 
     37    exit.exposed = True 
    3838 
    3939 

Hosted by WebFaction

Log in as guest/cpguest to create tickets