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

Changeset 1261

Show
Ignore:
Timestamp:
08/21/06 12:40:20
Author:
fumanchu
Message:

Fix for #555 (Error propagation in hooks). Failsafe flags are now per-callback, not per-callpoint. If you want a given hook to be failsafe, either set callback.failsafe = True or (if you cannot do that) call attach(point, callback, failsafe=True).

The previous semantic of 'failsafe' unfortunately also meant 'fail silently'. This has been corrected; now, when errors occur during the run of callbacks for a given call point, the last one will be raised (after all failsafe callbacks have completed).

Files:

Legend:

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

    r1260 r1261  
    1414 
    1515class HookMap(object): 
    16      
    17     def __init__(self, points=None, failsafe=None): 
     16    """A map of call points to callback lists. 
     17     
     18    callbacks: a dict of the form {call point: [callbacks]}. 
     19        Each 'call point' is a name and each callback is a callable 
     20        that takes no arguments. 
     21    failsafes: a dict of the form {callback: failsafe}. If 'failsafe' is 
     22        True, the callback is guaranteed to run even if other callbacks 
     23        from the same call point raise exceptions. False values are 
     24        permissible, but ignored. 
     25    """ 
     26     
     27    def __init__(self, points=None): 
    1828        points = points or [] 
    1929        self.callbacks = dict([(point, []) for point in points]) 
    20         self.failsafe = failsafe or [] 
    21      
    22     def attach(self, point, callback, conf=None): 
    23         if not conf: 
    24             # No point adding a wrapper if there's no conf 
    25             self.callbacks[point].append(callback) 
    26         else: 
     30        self.failsafes = {} 
     31     
     32    def attach(self, point, callback, failsafe=None, **kwargs): 
     33        """Append callback at the given call point. 
     34         
     35        If failsafe is True, the supplied callback is guaranteed to 
     36            run, even is other callbacks at the same call point fail. 
     37            If failsafe is None or not given, callback.failsafe will 
     38            be used if present; otherwise, False is assumed. 
     39        If additional keyword args are provided, they will be passed 
     40            to the given callback for each call. 
     41        """ 
     42        func = callback 
     43        if kwargs: 
    2744            def wrapper(): 
    28                 callback(**conf) 
    29             self.callbacks[point].append(wrapper) 
     45                callback(**kwargs) 
     46            func = wrapper 
     47        self.callbacks[point].append(func) 
     48        if failsafe is None: 
     49            failsafe = getattr(callback, 'failsafe', False) 
     50        self.failsafes[func] = failsafe 
    3051     
    3152    def run(self, point): 
     
    3455            raise cherrypy.TimeoutError() 
    3556         
    36         failsafe = point in self.failsaf
     57        exc = Non
    3758        for callback in self.callbacks[point]: 
    3859            # Some hookpoints guarantee all callbacks are run even if 
     
    4263            # to raise SystemExit and stop the whole server. So, trap 
    4364            # your own errors in these callbacks! 
    44             if failsafe
     65            if exc is None or self.failsafes.get(callback, False)
    4566                try: 
    4667                    callback() 
     
    4869                    raise 
    4970                except: 
     71                    exc = sys.exc_info()[1] 
    5072                    cherrypy.log(traceback=True) 
    51             else
    52                 callback() 
     73        if exc
     74            raise 
    5375 
    5476 
     
    209231                 
    210232                self.hooks = HookMap(self.hookpoints) 
    211                 self.hooks.failsafe = ['on_start_resource', 'on_end_resource', 
    212                                        'on_end_request'] 
    213                  
    214233                self.get_resource(path_info) 
    215234                self.tool_up() 
  • trunk/cherrypy/_cptools.py

    r1235 r1261  
    9393        """ 
    9494        conf = self._merged_args() 
    95         cherrypy.request.hooks.attach(self._point, self.callable, conf) 
     95        cherrypy.request.hooks.attach(self._point, self.callable, **conf) 
    9696 
    9797 
     
    133133        """ 
    134134        # Don't pass conf (or our wrapper will get wrapped!) 
    135         cherrypy.request.hooks.attach(self._point, self._wrapper) 
     135        f = getattr(self.callable, "failsafe", False) 
     136        cherrypy.request.hooks.attach(self._point, self._wrapper, failsafe=f) 
    136137 
    137138 
  • trunk/cherrypy/lib/cptools.py

    r1243 r1261  
    109109    for name, value in (headers or []): 
    110110        cherrypy.response.headers[name] = value 
     111response_headers.failsafe = True 
    111112 
    112113 
  • trunk/cherrypy/lib/sessions.py

    r1240 r1261  
    352352        # If the session is still locked we release the lock 
    353353        sess.release_lock() 
     354close.failsafe = True 
    354355 
    355356def init(storage_type='ram', path=None, path_header=None, name='session_id', 
  • trunk/cherrypy/test/test_tools.py

    r1235 r1261  
    3333                cherrypy.request.numerify_map = m.items() 
    3434            cherrypy.request.hooks.attach('on_start_resource', makemap) 
     35             
     36            def critical(): 
     37                cherrypy.request.error_response = cherrypy.HTTPError(502).set_response 
     38            critical.failsafe = True 
     39            cherrypy.request.hooks.attach('on_start_resource', critical) 
     40             
    3541            cherrypy.request.hooks.attach(self._point, self.callable) 
     42     
    3643    tools.numerify = NumTool('before_finalize', numerify) 
    3744     
     
    4350            self.ended = {} 
    4451            self._name = "nadsat" 
    45          
     52             
    4653        def nadsat(self): 
    4754            def nadsat_it_up(body): 
     
    5663            cherrypy.response.body = "razdrez" 
    5764            self.ended[cherrypy.request.counter] = True 
     65        cleanup.failsafe = True 
    5866         
    5967        def _setup(self): 
     
    191199        self.getPage("/demo/err") 
    192200        # If body is "razdrez", then on_end_request is being called too early. 
    193         self.assertErrorPage(500, pattern=valerr) 
     201        self.assertErrorPage(502, pattern=valerr) 
    194202        # If this fails, then on_end_request isn't being called at all. 
    195203        self.getPage("/demo/ended/3") 
     
    211219     
    212220    def testGuaranteedHooks(self): 
    213         # The on_start_resource and on_end_request hooks are all 
    214         # guaranteed to run, even if there are failures in other on_start 
    215         # or on_end methods. This is NOT true of the other hooks. 
    216         # Here, we have set up a failure in NumerifyTool.on_start_resource, 
    217         # but because that failure is logged and passed over, the error 
    218         # page we obtain in the user agent should be from before_finalize. 
     221        # The 'critical' on_start_resource hook is 'failsafe' (guaranteed 
     222        # to run even if there are failures in other on_start methods). 
     223        # This is NOT true of the other hooks. 
     224        # Here, we have set up a failure in NumerifyTool.numerify_map, 
     225        # but our 'critical' hook should run and set the error to 502. 
    219226        self.getPage("/demo/err_in_onstart") 
    220         self.assertErrorPage(500) 
    221         self.assertInBody("AttributeError: 'Request' object has no " 
    222                           "attribute 'numerify_map'") 
     227        self.assertErrorPage(502) 
     228        self.assertInBody("AttributeError: 'str' object has no attribute 'items'") 
    223229     
    224230    def testCombinedTools(self): 

Hosted by WebFaction

Log in as guest/cpguest to create tickets