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

Changeset 1690

Show
Ignore:
Timestamp:
06/24/07 00:37:24
Author:
fumanchu
Message:

restsrv changes:

  1. Renamed base.Engine to wspbus.Bus and made it generally useful in isolation.
  2. Renamed 'reexec' channel to 'restart'.
  3. Merged engine.wait into bus.block. The block method now takes an optional 'state' arg.
  4. Made the SignalHandler? auto-register some signals on init.
  5. Added win32events for all states.
  6. Made all states into sentinels instead of ints. New wspbus.states (and bus.states) enums.
  7. Added an 'exit' channel.
  8. The stop event now publishes even if state is already STOPPED.
  9. Added a Bus instance to wspbus (instead of restsrv init).
Files:

Legend:

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

    r1671 r1690  
    171171from cherrypy._cptree import Application 
    172172from cherrypy import _cpwsgi as wsgi 
    173 from cherrypy import _cpserver 
    174 server = _cpserver.Server() 
    175173 
    176174from cherrypy import restsrv 
    177 engine = restsrv.engine 
     175try: 
     176    from cherrypy.restsrv import win32 as restsrvwin 
     177    engine = restsrvwin.Win32Bus() 
     178except ImportError: 
     179    engine = restsrv.bus 
     180 
    178181 
    179182# Timeout monitor 
     
    203206restsrv.plugins.Reexec(engine) 
    204207_thread_manager = restsrv.plugins.ThreadManager(engine) 
     208 
     209from cherrypy import _cpserver 
     210server = _cpserver.Server() 
    205211 
    206212 
  • trunk/cherrypy/_cpconfig.py

    r1648 r1690  
    303303        engine.publish('CherryPy Timeout Monitor', 'frequency', v) 
    304304    elif k == 'reexec_retry': 
    305         engine.publish('reexec', 'retry', v) 
     305        engine.publish('restart', 'retry', v) 
    306306    elif k == 'SIGHUP': 
    307307        engine.listeners['SIGHUP'] = set([v]) 
  • trunk/cherrypy/_cpmodpy.py

    r1689 r1690  
    9090                            }) 
    9191     
    92     cherrypy.engine.start() 
    93     cherrypy.engine.wait() 
     92    engine = cherrypy.engine 
     93    engine.start() 
     94    engine.block(engine.states.STARTED) 
    9495     
    9596    def cherrypy_cleanup(data): 
  • trunk/cherrypy/lib/sessions.py

    r1673 r1690  
    113113        if not cls.clean_thread: 
    114114            # clean_up is in instancemethod and not a classmethod, 
    115             # so tool config can be accessed inside the method. 
    116             from cherrypy import restsrv 
    117             t = restsrv.plugins.Monitor(cherrypy.engine, self.clean_up, 
    118                                         "CP Session Cleanup") 
     115            # so that tool config can be accessed inside the method. 
     116            t = cherrypy.restsrv.plugins.Monitor( 
     117                    cherrypy.engine, self.clean_up, "CP Session Cleanup") 
    119118            t.frequency = self.clean_freq 
    120119            cls.clean_thread = t 
  • trunk/cherrypy/restsrv/__init__.py

    r1642 r1690  
    1 """Manage an HTTP server process via an extensible Engine object
     1"""Site container for an HTTP server
    22 
    3 An Engine object is used to contain and manage site-wide behavior: 
    4 daemonization, HTTP server instantiation, autoreload, signal handling, 
    5 drop privileges, initial logging, PID file management, etc. 
     3A Web Site Process Bus object is used to connect applications, servers, 
     4and frameworks with site-wide services such as daemonization, process 
     5reload, signal handling, drop privileges, PID file management, logging 
     6for all of these, and many more. 
    67 
    7 In addition, an Engine object provides a place for each web framework 
    8 to hook in custom code that runs in response to site-wide events (like 
    9 process start and stop), or which controls or otherwise interacts with 
    10 the site-wide components mentioned above. For example, a framework which 
    11 uses file-based templates would add known template filenames to the 
    12 autoreload component. 
    13  
    14 Ideally, an Engine object will be flexible enough to be useful in a variety 
    15 of invocation scenarios: 
    16  
    17  1. The deployer starts a site from the command line via a framework- 
    18      neutral deployment script; applications from multiple frameworks 
    19      are mixed in a single site. Command-line arguments and configuration 
    20      files are used to define site-wide components such as the HTTP server, 
    21      autoreload behavior, signal handling, etc. 
    22  2. The deployer starts a site via some other process, such as Apache; 
    23      applications from multiple frameworks are mixed in a single site. 
    24      Autoreload and signal handling (from Python at least) are disabled. 
    25  3. The deployer starts a site via a framework-specific mechanism; 
    26      for example, when running tests, exploring tutorials, or deploying 
    27      single applications from a single framework. The framework controls 
    28      which site-wide components are enabled as it sees fit. 
    29  
    30 The Engine object in this package uses topic-based publish-subscribe 
    31 messaging to accomplish all this. A few topic channels are built in 
    32 ('start', 'stop', 'restart' and 'graceful'). The 'plugins' module 
    33 defines a few others which are specific to each tool. Frameworks are 
    34 free to define their own. If a message is sent to a channel that has 
    35 not been defined or has no listeners, there is no effect. 
    36  
    37 In general, there should only ever be a single Engine object per process. 
    38 Frameworks share a single Engine object by publishing messages and 
    39 registering (subscribing) listeners. 
     8The 'plugins' module defines a few abstract and concrete services for 
     9use with the bus. Some use tool-specific channels; see the documentation 
     10for each class. 
    4011""" 
    4112 
     13from cherrypy.restsrv.wspbus import bus 
    4214from cherrypy.restsrv import plugins 
    43  
    44 try: 
    45     from cherrypy.restsrv import win32 
    46     engine = win32.Engine() 
    47     del win32 
    48 except ImportError: 
    49     from cherrypy.restsrv import base 
    50     engine = base.Engine() 
    51     del base 
  • trunk/cherrypy/restsrv/plugins.py

    r1684 r1690  
    1 """Plugins for a restsrv Engine.""" 
     1"""Site services for use with a Web Site Process Bus.""" 
    22 
    33import os 
     
    2020     
    2121    getattr: 
    22         >>> values = engine.publish('thing', 'attr') 
     22        >>> values = bus.publish('thing', 'attr') 
    2323        Note that the 'publish' method will return a list of values 
    2424        (from potentially multiple subscribed objects). 
    2525     
    2626    setattr: 
    27         >>> engine.publish('thing', 'attr', value) 
    28      
    29     call
    30         >>> engine.publish('thing', 'attr()', *a, **kw) 
    31     """ 
    32      
    33     def __init__(self, engine, channel): 
    34         self.engine = engine 
     27        >>> bus.publish('thing', 'attr', value) 
     28     
     29    call an attribute
     30        >>> bus.publish('thing', 'attr()', *a, **kw) 
     31    """ 
     32     
     33    def __init__(self, bus, channel): 
     34        self.bus = bus 
    3535        self.channel = channel 
    36         engine.subscribe(self.channel, self.handle) 
     36        bus.subscribe(self.channel, self.handle) 
    3737     
    3838    def handle(self, attr, *args, **kwargs): 
     
    5050 
    5151class SignalHandler(object): 
    52      
    53     def __init__(self, engine): 
    54         # Make a map from signal numbers to names 
    55         self.signals = {} 
    56         for k, v in vars(_signal).items(): 
    57             if k.startswith('SIG') and not k.startswith('SIG_'): 
    58                 self.signals[v] = k 
    59          
    60         self.engine = engine 
    61      
    62     def set_handler(self, signal, callback=None): 
     52    """Register bus channels (and listeners) for system signals. 
     53     
     54    By default, instantiating this object registers the following signals 
     55    and listeners: 
     56     
     57        TERM: bus.exit 
     58        HUP : bus.restart 
     59        USR1: bus.graceful 
     60    """ 
     61     
     62    # Map from signal numbers to names 
     63    signals = {} 
     64    for k, v in vars(_signal).items(): 
     65        if k.startswith('SIG') and not k.startswith('SIG_'): 
     66            signals[v] = k 
     67    del k, v 
     68     
     69    def __init__(self, bus): 
     70        self.bus = bus 
     71         
     72        # Set default handlers 
     73        for sig, func in [('SIGTERM', bus.exit), 
     74                          ('SIGHUP', bus.restart), 
     75                          ('SIGUSR1', bus.graceful)]: 
     76            try: 
     77                self.set_handler(sig, func) 
     78            except ValueError: 
     79                pass 
     80     
     81    def set_handler(self, signal, listener=None): 
    6382        """Register a handler for the given signal (number or name). 
    6483         
    65         If the optional callback is included, it will be registered 
    66         as a listener for the given signal. 
     84        If the optional 'listener' argument is provided, it will be 
     85        registered as a listener for the given signal's channel. 
     86         
     87        If the given signal name or number is not available on the current 
     88        platform, ValueError is raised. 
    6789        """ 
    6890        if isinstance(signal, basestring): 
    6991            signum = getattr(_signal, signal, None) 
    7092            if signum is None: 
    71                 return  # ? 
     93                raise ValueError("No such signal: %r" % signal) 
    7294            signame = signal 
    7395        else: 
     96            try: 
     97                signame = self.signals[signal] 
     98            except KeyError: 
     99                raise ValueError("No such signal: %r" % signal) 
    74100            signum = signal 
    75             signame = self.signals[signal] 
    76101         
    77102        # Should we do something with existing signal handlers? 
    78103        # cur = _signal.getsignal(signum) 
    79104        _signal.signal(signum, self._handle_signal) 
    80         if callback is not None: 
    81             self.engine.subscribe(signame, callback
     105        if listener is not None: 
     106            self.bus.subscribe(signame, listener
    82107     
    83108    def _handle_signal(self, signum=None, frame=None): 
    84109        """Python signal handler (self.set_handler registers it for you).""" 
    85         self.engine.publish(self.signals[signum]) 
     110        self.bus.publish(self.signals[signum]) 
    86111 
    87112 
    88113class Reexec(SubscribedObject): 
    89      
    90     def __init__(self, engine, retry=2): 
    91         self.engine = engine 
     114    """A process restarter (using execv) for the 'restart' WSPBus channel. 
     115     
     116    retry: the number of seconds to wait for all parent threads to stop. 
     117        This is only necessary for platforms like OS X which error if all 
     118        threads are not absolutely terminated before calling execv. 
     119    """ 
     120     
     121    def __init__(self, bus, retry=2): 
     122        self.bus = bus 
    92123        self.retry = retry 
    93         engine.subscribe('reexec', self) 
     124        bus.subscribe('restart', self) 
    94125     
    95126    def __call__(self): 
    96127        """Re-execute the current process.""" 
    97128        args = sys.argv[:] 
    98         self.engine.log('Re-spawning %s' % ' '.join(args)) 
     129        self.bus.log('Re-spawning %s' % ' '.join(args)) 
    99130        args.insert(0, sys.executable) 
    100131         
     
    124155    """ 
    125156     
    126     def __init__(self, engine): 
    127         self.engine = engine 
     157    def __init__(self, bus): 
     158        self.bus = bus 
    128159     
    129160    try: 
     
    143174                if umask is not None: 
    144175                    old_umask = os.umask(umask) 
    145                     self.engine.log('umask old: %03o, new: %03o' % 
     176                    self.bus.log('umask old: %03o, new: %03o' % 
    146177                                    (old_umask, umask)) 
    147178    else: 
     
    172203                    return name, group 
    173204                 
    174                 self.engine.log('Started as %r/%r' % names()) 
     205                self.bus.log('Started as %r/%r' % names()) 
    175206                if gid is not None: 
    176207                    os.setgid(gid) 
    177208                if uid is not None: 
    178209                    os.setuid(uid) 
    179                 self.engine.log('Running as %r/%r' % names()) 
     210                self.bus.log('Running as %r/%r' % names()) 
    180211             
    181212            if umask is not None: 
    182213                old_umask = os.umask(umask) 
    183                 self.engine.log('umask old: %03o, new: %03o' % 
     214                self.bus.log('umask old: %03o, new: %03o' % 
    184215                                (old_umask, umask)) 
    185216    __call__.priority = 70 
     
    189220    """Daemonize the running script. 
    190221     
     222    Use this with a Web Site Process Bus via: 
     223         
     224        bus.subscribe('start', daemonize) 
     225     
    191226    When this method returns, the process is completely decoupled from the 
    192     parent environment.""" 
     227    parent environment. 
     228    """ 
    193229     
    194230    # See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 
     
    211247    except OSError, exc: 
    212248        # Python raises OSError rather than returning negative numbers. 
    213         sys.exit("%s: fork #1 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) 
     249        sys.exit("%s: fork #1 failed: (%d) %s\n" 
     250                 % (sys.argv[0], exc.errno, exc.strerror)) 
    214251     
    215252    os.setsid() 
     
    222259            sys.exit(0) # Exit second parent 
    223260    except OSError, exc: 
    224         sys.exit("%s: fork #2 failed: (%d) %s\n" % (sys.argv[0], exc.errno, exc.strerror)) 
     261        sys.exit("%s: fork #2 failed: (%d) %s\n" 
     262                 % (sys.argv[0], exc.errno, exc.strerror)) 
    225263     
    226264    os.chdir("/") 
     
    237275 
    238276class PIDFile(object): 
    239      
    240     def __init__(self, engine, pidfile): 
    241         self.engine = engine 
     277    """Maintain a PID file via a WSPBus.""" 
     278     
     279    def __init__(self, bus, pidfile): 
     280        self.bus = bus 
    242281        self.pidfile = pidfile 
    243         engine.subscribe('start', self.start) 
    244         engine.subscribe('stop', self.stop) 
     282        bus.subscribe('start', self.start) 
     283        bus.subscribe('stop', self.stop) 
    245284     
    246285    def start(self): 
     
    258297 
    259298class PerpetualTimer(threading._Timer): 
     299    """A subclass of threading._Timer whose run() method repeats.""" 
    260300     
    261301    def run(self): 
     
    268308 
    269309class Monitor(SubscribedObject): 
    270     """Subscriber which periodically runs a callback in a separate thread.""" 
     310    """WSPBus listener to periodically run a callback in its own thread. 
     311     
     312    bus: a Web Site Process Bus object. 
     313    callback: the function to call at intervals. 
     314    channel: optional. If provided, the name of the channel to use 
     315        for managing this object. Defaults to class.__name__, 
     316        so either provide a channel name or only use one instance 
     317        of any given subclass. 
     318     
     319    frequency: the time in seconds between callback runs. 
     320    """ 
    271321     
    272322    frequency = 60 
    273323     
    274     def __init__(self, engine, callback, channel=None): 
     324    def __init__(self, bus, callback, channel=None): 
    275325        self.callback = callback 
    276326        self.thread = None 
     
    278328        if channel is None: 
    279329            channel = self.__class__.__name__ 
    280         SubscribedObject.__init__(self, engine, channel) 
     330        SubscribedObject.__init__(self, bus, channel) 
    281331         
    282332        self.listeners = [('start', self.start), 
     
    287337     
    288338    def attach(self): 
     339        """Register this monitor as a (multi-channel) listener on the bus.""" 
    289340        for point, callback in self.listeners: 
    290             self.engine.subscribe(point, callback) 
     341            self.bus.subscribe(point, callback) 
    291342     
    292343    def detach(self): 
     344        """Unregister this monitor as a listener on the bus.""" 
    293345        for point, callback in self.listeners: 
    294             self.engine.unsubscribe(point, callback) 
     346            self.bus.unsubscribe(point, callback) 
    295347     
    296348    def start(self): 
     349        """Start our callback in its own perpetual timer thread.""" 
    297350        if self.frequency > 0: 
    298351            self.thread = PerpetualTimer(self.frequency, self.callback) 
     
    301354     
    302355    def stop(self): 
     356        """Stop our callback's perpetual timer thread.""" 
    303357        if self.thread: 
    304358            if self.thread is not threading.currentThread(): 
     
    308362     
    309363    def graceful(self): 
     364        """Stop the callback's perpetual timer thread and restart it.""" 
    310365        self.stop() 
    311366        self.start() 
     
    318373    match = '.*' 
    319374     
    320     def __init__(self, engine): 
     375    def __init__(self, bus): 
    321376        self.mtimes = {} 
    322377        self.files = set() 
    323         Monitor.__init__(self, engine, self.run) 
     378        Monitor.__init__(self, bus, self.run) 
    324379     
    325380    def add(self, filename): 
     381        """Add a file to monitor for changes.""" 
    326382        self.files.add(filename) 
    327383     
    328384    def discard(self, filename): 
     385        """Remove a file to monitor for changes.""" 
    329386        self.files.discard(filename) 
    330387     
    331388    def start(self): 
     389        """Start our own perpetual timer thread for self.run.""" 
    332390        self.mtimes = {} 
    333391        self.files = set() 
     
    367425                    if mtime is None or mtime > oldtime: 
    368426                        # The file has been deleted or modified. 
    369                         self.engine.restart() 
     427                        self.bus.restart() 
    370428 
    371429 
     
    376434    the 'acquire_thread' and 'release_thread' channels (for each thread). 
    377435    This will register/unregister the current thread and publish to 
    378     'start_thread' and 'stop_thread' listeners in the engine as needed. 
     436    'start_thread' and 'stop_thread' listeners in the bus as needed. 
    379437     
    380438    If threads are created and destroyed by code you do not control 
     
    382440    publish to 'acquire_thread' only. You should not publish to 
    383441    'release_thread' in this case, since you do not know whether 
    384     the thread will be re-used or not. The engine will call 
     442    the thread will be re-used or not. The bus will call 
    385443    'stop_thread' listeners for you when it stops. 
    386444    """ 
    387445     
    388     def __init__(self, engine): 
     446    def __init__(self, bus): 
    389447        self.threads = {} 
    390         self.engine = engine 
    391         engine.subscribe('acquire_thread', self.acquire) 
    392         engine.subscribe('release_thread', self.release) 
    393         engine.subscribe('stop', self.release_all) 
    394         engine.subscribe('graceful', self.release_all) 
     448        self.bus = bus 
     449        bus.subscribe('acquire_thread', self.acquire) 
     450        bus.subscribe('release_thread', self.release) 
     451        bus.subscribe('stop', self.release_all) 
     452        bus.subscribe('graceful', self.release_all) 
    395453     
    396454    def acquire(self): 
     
    406464            i = len(self.threads) + 1 
    407465            self.threads[thread_ident] = i 
    408             self.engine.publish('start_thread', i) 
     466            self.bus.publish('start_thread', i) 
    409467     
    410468    def release(self): 
     
    413471        i = self.threads.pop(thread_ident, None) 
    414472        if i is not None: 
    415             self.engine.publish('stop_thread', i) 
     473            self.bus.publish('stop_thread', i) 
    416474     
    417475    def release_all(self): 
     476        """Release all threads and run all 'stop_thread' listeners.""" 
    418477        for thread_ident, i in self.threads.iteritems(): 
    419             self.engine.publish('stop_thread', i) 
     478            self.bus.publish('stop_thread', i) 
    420479        self.threads.clear() 
  • trunk/cherrypy/restsrv/win32.py

    r1644 r1690  
    88import win32serviceutil 
    99 
    10 from cherrypy.restsrv import base 
     10from cherrypy.restsrv import wspbus 
    1111 
    1212 
    13 class Engine(base.Engine): 
     13class Win32Bus(wspbus.Bus): 
     14    """A Web Site Process Bus implementation for Win32. 
     15     
     16    Instead of using time.sleep for blocking, this bus uses native 
     17    win32event objects. 
     18    """ 
    1419     
    1520    def __init__(self): 
    16         base.Engine.__init__(self) 
    17         self.stop_event = win32event.CreateEvent(None, 0, 0, None
    18         win32api.SetConsoleCtrlHandler(self.console_event
     21        self.events = {} 
     22        win32api.SetConsoleCtrlHandler(self._console_event
     23        wspbus.Bus.__init__(self
    1924     
    20     def console_event(self, event): 
     25    def _console_event(self, event): 
     26        """.""" 
    2127        if event in (win32con.CTRL_C_EVENT, 
    2228                     win32con.CTRL_BREAK_EVENT, 
    2329                     win32con.CTRL_CLOSE_EVENT): 
    24             self.log('Console event %s: shutting down engine' % event) 
     30            self.log('Console event %s: shutting down bus' % event) 
    2531            self.stop() 
    2632            return 1 
    2733        return 0 
    2834     
    29     def block(self, interval=1): 
    30         """Block forever (wait for stop(), KeyboardInterrupt or SystemExit).""" 
     35    def _get_state_event(self, state): 
     36        """Return a win32event for the given state (creating it if needed).""" 
    3137        try: 
    32             win32event.WaitForSingleObject(self.stop_event, win32event.INFINITE) 
     38            return self.events[state] 
     39        except KeyError: 
     40            event = win32event.CreateEvent(None, 0, 0, None) 
     41            self.events[state] = event 
     42            return event 
     43     
     44    def _get_state(self): 
     45        return self._state 
     46    def _set_state(self, value): 
     47        self._state = value 
     48        event = self._get_state_event(value) 
     49        win32event.PulseEvent(event) 
     50    state = property(_get_state, _set_state) 
     51     
     52    def block(self, state=wspbus.states.STOPPED, interval=1): 
     53        """Wait for the given state, KeyboardInterrupt or SystemExit. 
     54         
     55        Since this class uses native win32event objects, the interval 
     56        argument is ignored. 
     57        """ 
     58        event = self._get_state_event(state) 
     59        try: 
     60            win32event.WaitForSingleObject(event, win32event.INFINITE) 
    3361        except SystemExit: 
    34             self.log('SystemExit raised: shutting down engine') 
     62            self.log('SystemExit raised: shutting down bus') 
    3563            self.stop() 
    3664            raise 
    37      
    38     def stop(self): 
    39         """Stop the engine.""" 
    40         if self.state != base.STOPPED: 
    41             self.log('Engine shutting down') 
    42             self.publish('stop') 
    43             win32event.PulseEvent(self.stop_event) 
    44             self.state = base.STOPPED 
    4565 
    4666 
     
    89109    def SvcDoRun(self): 
    90110        from cherrypy import restsrv 
    91         restsrv.engine.start() 
    92         restsrv.engine.block() 
     111        restsrv.bus.start() 
     112        restsrv.bus.block() 
    93113     
    94114    def SvcStop(self): 
    95115        from cherrypy import restsrv 
    96116        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) 
    97         restsrv.engine.stop() 
     117        restsrv.bus.stop() 
    98118     
    99119    def SvcOther(self, control): 
    100         restsrv.engine.publish(control_codes.key_for(control)) 
     120        restsrv.bus.publish(control_codes.key_for(control)) 
    101121 
    102122 
  • trunk/cherrypy/restsrv/wspbus.py

    r1683 r1690  
    1 """Base Engine class for restsrv.""" 
     1"""An implementation of the Web Site Process Bus. 
     2 
     3This module is completely standalone, depending only on the stdlib. 
     4 
     5Web Site Process Bus 
     6-------------------- 
     7 
     8A Bus object is used to contain and manage site-wide behavior: 
     9daemonization, HTTP server start/stop, process reload, signal handling, 
     10drop privileges, PID file management, logging for all of these, 
     11and many more. 
     12 
     13In addition, a Bus object provides a place for each web framework 
     14to register code that runs in response to site-wide events (like 
     15process start and stop), or which controls or otherwise interacts with 
     16the site-wide components mentioned above. For example, a framework which 
     17uses file-based templates would add known template filenames to an 
     18autoreload component. 
     19 
     20Ideally, a Bus object will be flexible enough to be useful in a variety 
     21of invocation scenarios: 
     22 
     23 1. The deployer starts a site from the command line via a framework- 
     24     neutral deployment script; applications from multiple frameworks 
     25     are mixed in a single site. Command-line arguments and configuration 
     26     files are used to define site-wide components such as the HTTP server, 
     27     WSGI component graph, autoreload behavior, signal handling, etc. 
     28 2. The deployer starts a site via some other process, such as Apache; 
     29     applications from multiple frameworks are mixed in a single site. 
     30     Autoreload and signal handling (from Python at least) are disabled. 
     31 3. The deployer starts a site via a framework-specific mechanism; 
     32     for example, when running tests, exploring tutorials, or deploying 
     33     single applications from a single framework. The framework controls 
     34     which site-wide components are enabled as it sees fit. 
     35 
     36The Bus object in this package uses topic-based publish-subscribe 
     37messaging to accomplish all this. A few topic channels are built in 
     38('start', 'stop', 'exit', 'restart' and 'graceful'). Frameworks and 
     39site containers are free to define their own. If a message is sent to a 
     40channel that has not been defined or has no listeners, there is no effect. 
     41 
     42In general, there should only ever be a single Bus object per process. 
     43Frameworks and site containers share a single Bus object by publishing 
     44messages and registering (subscribing) listeners. 
     45 
     46The Bus object works as a finite state machine which models the current 
     47state of the process. Bus methods move it from one state to another; 
     48those methods then publish to subscribed listeners on the channel for 
     49the new state. 
     50""" 
    251 
    352try: 
     
    1160 
    1261 
    13 # Use a flag to indicate the state of the engine. 
    14 STOPPED = 0 
    15 STARTING = None 
    16 STARTED = 1 
    17  
    18  
    19 class Engine(object): 
    20     """Process controls for HTTP site deployment.""" 
    21      
    22     state = STOPPED 
     62# Use a flag to indicate the state of the bus. 
     63class _StateEnum(object): 
     64    class State(object): 
     65        pass 
     66states = _StateEnum() 
     67states.STOPPED = states.State() 
     68states.STARTING = states.State() 
     69states.STARTED = states.State() 
     70states.STOPPING = states.State() 
     71 
     72 
     73class Bus(object): 
     74    """Process state-machine and messenger for HTTP site deployment.""" 
     75     
     76    states = states 
     77    state = states.STOPPED 
    2378     
    2479    def __init__(self): 
    25         self.state = STOPPED 
    26         self.listeners = {} 
     80        self.state = states.STOPPED 
     81        self.listeners = dict([(channel, set()) for channel 
     82                               in ('start', 'stop', 'exit', 
     83                                   'restart', 'graceful')]) 
    2784        self._priorities = {} 
    2885     
     
    74131     
    75132    def start(self): 
    76         """Start the engine.""" 
    77         self.state = STARTING 
    78         self.log('Engine starting') 
     133        """Start all services.""" 
     134        self.state = states.STARTING 
     135        self.log('Bus starting') 
    79136        self.publish('start') 
    80         self.state = STARTED 
    81      
    82     def wait(self, interval=0.1): 
    83         """Block the caller until the Engine is in the STARTED state.""" 
    84         while not (self.state == STARTED): 
    85             time.sleep(interval) 
     137        self.state = states.STARTED 
    86138     
    87139    def exit(self, status=0): 
    88         """Stop the engine and exit the process.""" 
     140        """Stop all services and exit the process.""" 
    89141        self.stop() 
     142         
     143        self.log('Bus exit') 
     144        self.publish('exit') 
    90145        sys.exit(status) 
    91146     
     
    93148        """Restart the process (may close connections).""" 
    94149        self.stop() 
    95         self.log('Engine restart') 
    96         self.publish('reexec') 
     150         
     151        self.log('Bus restart') 
     152        self.publish('restart') 
    97153     
    98154    def graceful(self): 
    99         """Restart the engine without closing connections.""" 
    100         self.log('Engine graceful restart') 
     155        """Advise all services to reload.""" 
     156        self.log('Bus graceful') 
    101157        self.publish('graceful') 
    102158     
    103     def block(self, interval=1): 
    104         """Block forever (wait for stop(), KeyboardInterrupt or SystemExit).""" 
     159    def block(self, state=states.STOPPED, interval=0.1): 
     160        """Wait for the given state, KeyboardInterrupt or SystemExit.""" 
    105161        try: 
    106             while self.state != STOPPED
     162            while self.state != state
    107163                time.sleep(interval) 
    108164        except (KeyboardInterrupt, IOError): 
    109165            # The time.sleep call might raise 
    110             # "IOError: [Errno 4] Interrupted function call"
    111             self.log('Keyboard Interrupt: shutting down engine') 
     166            # "IOError: [Errno 4] Interrupted function call" on KBInt
     167            self.log('Keyboard Interrupt: shutting down bus') 
    112168            self.stop() 
    113169        except SystemExit: 
    114             self.log('SystemExit raised: shutting down engine') 
     170            self.log('SystemExit raised: shutting down bus') 
    115171            self.stop() 
    116172            raise 
    117173     
    118174    def stop(self): 
    119         """Stop the engine.""" 
    120         if self.state != STOPPED: 
    121             self.log('Engine shutting down') 
    122             self.publish('stop') 
    123             self.state = STOPPED 
     175        """Stop all services.""" 
     176        self.state = states.STOPPING 
     177        self.log('Bus stopping') 
     178        self.publish('stop') 
     179        self.state = states.STOPPED 
    124180     
    125181    def start_with_callback(self, func, args=None, kwargs=None): 
     
    132188         
    133189        def _callback(func, *a, **kw): 
    134             self.wait(
     190            self.block(states.STARTED
    135191            func(*a, **kw) 
    136192        t = threading.Thread(target=_callback, args=args, kwargs=kwargs) 
    137         t.setName('Engine Callback ' + t.getName()) 
     193        t.setName('Bus Callback ' + t.getName()) 
    138194        t.start() 
    139195         
     
    156212    return "".join(traceback.format_exception(*exc)) 
    157213 
     214bus = Bus() 
     215 
  • trunk/cherrypy/test/test_states.py

    r1660 r1690  
    1111 
    1212import cherrypy 
     13engine = cherrypy.engine 
    1314 
    1415 
     
    2324     
    2425    def graceful(self): 
    25         cherrypy.engine.graceful() 
     26        engine.graceful() 
    2627        return "app was (gracefully) restarted succesfully" 
    2728    graceful.exposed = True 
     
    7778    def test_0_NormalStateFlow(self): 
    7879        if not self.server_class: 
    79             # Without having called "cherrypy.engine.start()", we should 
     80            # Without having called "engine.start()", we should 
    8081            # get a 503 Service Unavailable response. 
    8182            self.getPage("/") 
     
    8990        # Test server start 
    9091        cherrypy.server.quickstart(self.server_class) 
    91         cherrypy.engine.start() 
    92         self.assertEqual(cherrypy.engine.state, 1
     92        engine.start() 
     93        self.assertEqual(engine.state, engine.states.STARTED
    9394         
    9495        if self.server_class: 
     
    107108         
    108109        # Test engine stop. This will also stop the HTTP server. 
    109         cherrypy.engine.stop() 
    110         self.assertEqual(cherrypy.engine.state, 0
     110        engine.stop() 
     111        self.assertEqual(engine.state, engine.states.STOPPED
    111112         
    112113        # Verify that our custom stop function was called 
     
    125126            self.getPage("/") 
    126127            self.assertBody("Hello World") 
    127             cherrypy.engine.stop() 
     128            engine.stop() 
    128129        cherrypy.server.start() 
    129         cherrypy.engine.start_with_callback(stoptest) 
    130         cherrypy.engine.block() 
    131         self.assertEqual(cherrypy.engine.state, 0
     130        engine.start_with_callback(stoptest) 
     131        engine.block() 
     132        self.assertEqual(engine.state, engine.states.STOPPED
    132133     
    133134    def test_1_Restart(self): 
    134135        cherrypy.server.start() 
    135         cherrypy.engine.start() 
     136        engine.start() 
    136137         
    137138        # The db_connection should be running now 
     
    144145         
    145146        # Test server restart from this thread 
    146         cherrypy.engine.graceful() 
    147         self.assertEqual(cherrypy.engine.state, 1
     147        engine.graceful() 
     148        self.assertEqual(engine.state, engine.states.STARTED
    148149        self.getPage("/") 
    149150        self.assertBody("Hello World") 
     
    154155        # Test server restart from inside a page handler 
    155156        self.getPage("/graceful") 
    156         self.assertEqual(cherrypy.engine.state, 1
     157        self.assertEqual(engine.state, engine.states.STARTED
    157158        self.assertBody("app was (gracefully) restarted succesfully") 
    158159        self.assertEqual(db_connection.running, True) 
     
    162163        self.assertEqual(len(db_connection.threads), 0) 
    163164         
    164         cherrypy.engine.stop() 
    165         self.assertEqual(cherrypy.engine.state, 0
     165        engine.stop() 
     166        self.assertEqual(engine.state, engine.states.STOPPED
    166167        self.assertEqual(db_connection.running, False) 
    167168        self.assertEqual(len(db_connection.threads), 0) 
     
    172173            # Raise a keyboard interrupt in the HTTP server's main thread. 
    173174            # We must start the server in this, the main thread 
    174             cherrypy.engine.start() 
     175            engine.start() 
    175176            cherrypy.server.start() 
    176177             
     
    184185                 
    185186                cherrypy.server.httpservers.keys()[0].interrupt = KeyboardInterrupt 
    186                 while cherrypy.engine.state != 0: 
    187                     time.sleep(0.1) 
     187                engine.block() 
    188188                 
    189189                self.assertEqual(db_connection.running, False) 
    190190                self.assertEqual(len(db_connection.threads), 0) 
    191                 self.assertEqual(cherrypy.engine.state, 0
     191                self.assertEqual(engine.state, engine.states.STOPPED
    192192            finally: 
    193193                self.persistent = False 
     
    197197            # This should raise a BadStatusLine error, since the worker 
    198198            # thread will just die without writing a response. 
    199             cherrypy.engine.start() 
     199            engine.start() 
    200200            cherrypy.server.start() 
    201201             
     
    208208                self.fail("AssertionError: BadStatusLine not raised") 
    209209             
    210             while cherrypy.engine.state != 0: 
    211                 time.sleep(0.1) 
     210            engine.block() 
    212211            self.assertEqual(db_connection.running, False) 
    213212            self.assertEqual(len(db_connection.threads), 0) 
     
    216215        cherrypy.config.update({'response.timeout': 0.2}) 
    217216         
    218         cherrypy.engine.start() 
     217        engine.start() 
    219218        cherrypy.server.start() 
    220219        try: 
     
    242241            self.assertInBody("raise cherrypy.TimeoutError()") 
    243242        finally: 
    244             cherrypy.engine.stop() 
     243            engine.stop() 
    245244     
    246245    def test_4_Autoreload(self): 
     
    302301    ServerStateTests.server_class = server 
    303302    suite = helper.CPTestLoader.loadTestsFromTestCase(ServerStateTests) 
    304     engine = cherrypy.engine 
    305303    try: 
    306304        global db_connection 

Hosted by WebFaction

Log in as guest/cpguest to create tickets