| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.test.test_failure -*- | |
| 2 # See also test suite twisted.test.test_pbfailure | |
| 3 | |
| 4 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 5 # See LICENSE for details. | |
| 6 | |
| 7 | |
| 8 """ | |
| 9 Asynchronous-friendly error mechanism. | |
| 10 | |
| 11 See L{Failure}. | |
| 12 """ | |
| 13 | |
| 14 # System Imports | |
| 15 import sys | |
| 16 import linecache | |
| 17 import inspect | |
| 18 import opcode | |
| 19 from cStringIO import StringIO | |
| 20 | |
| 21 from twisted.python import reflect | |
| 22 | |
| 23 count = 0 | |
| 24 traceupLength = 4 | |
| 25 | |
| 26 class DefaultException(Exception): | |
| 27 pass | |
| 28 | |
| 29 def format_frames(frames, write, detail="default"): | |
| 30 """Format and write frames. | |
| 31 | |
| 32 @param frames: is a list of frames as used by Failure.frames, with | |
| 33 each frame being a list of | |
| 34 (funcName, fileName, lineNumber, locals.items(), globals.items()) | |
| 35 @type frames: list | |
| 36 @param write: this will be called with formatted strings. | |
| 37 @type write: callable | |
| 38 @param detail: Three detail levels are available: | |
| 39 default, brief, and verbose. | |
| 40 @type detail: string | |
| 41 """ | |
| 42 if detail not in ('default', 'brief', 'verbose'): | |
| 43 raise ValueError, "Detail must be default, brief, or verbose. (not %r)"
% (detail,) | |
| 44 w = write | |
| 45 if detail == "brief": | |
| 46 for method, filename, lineno, localVars, globalVars in frames: | |
| 47 w('%s:%s:%s\n' % (filename, lineno, method)) | |
| 48 elif detail == "default": | |
| 49 for method, filename, lineno, localVars, globalVars in frames: | |
| 50 w( ' File "%s", line %s, in %s\n' % (filename, lineno, method)) | |
| 51 w( ' %s\n' % linecache.getline(filename, lineno).strip()) | |
| 52 elif detail == "verbose": | |
| 53 for method, filename, lineno, localVars, globalVars in frames: | |
| 54 w("%s:%d: %s(...)\n" % (filename, lineno, method)) | |
| 55 w(' [ Locals ]\n') | |
| 56 # Note: the repr(val) was (self.pickled and val) or repr(val))) | |
| 57 for name, val in localVars: | |
| 58 w(" %s : %s\n" % (name, repr(val))) | |
| 59 w(' ( Globals )\n') | |
| 60 for name, val in globalVars: | |
| 61 w(" %s : %s\n" % (name, repr(val))) | |
| 62 | |
| 63 # slyphon: i have a need to check for this value in trial | |
| 64 # so I made it a module-level constant | |
| 65 EXCEPTION_CAUGHT_HERE = "--- <exception caught here> ---" | |
| 66 | |
| 67 | |
| 68 | |
| 69 class NoCurrentExceptionError(Exception): | |
| 70 """ | |
| 71 Raised when trying to create a Failure from the current interpreter | |
| 72 exception state and there is no current exception state. | |
| 73 """ | |
| 74 | |
| 75 | |
| 76 class _Traceback(object): | |
| 77 """ | |
| 78 Fake traceback object which can be passed to functions in the standard | |
| 79 library L{traceback} module. | |
| 80 """ | |
| 81 | |
| 82 def __init__(self, frames): | |
| 83 """ | |
| 84 Construct a fake traceback object using a list of frames. Note that | |
| 85 although frames generally include locals and globals, this information | |
| 86 is not kept by this object, since locals and globals are not used in | |
| 87 standard tracebacks. | |
| 88 | |
| 89 @param frames: [(methodname, filename, lineno, locals, globals), ...] | |
| 90 """ | |
| 91 assert len(frames) > 0, "Must pass some frames" | |
| 92 head, frames = frames[0], frames[1:] | |
| 93 name, filename, lineno, localz, globalz = head | |
| 94 self.tb_frame = _Frame(name, filename) | |
| 95 self.tb_lineno = lineno | |
| 96 if len(frames) == 0: | |
| 97 self.tb_next = None | |
| 98 else: | |
| 99 self.tb_next = _Traceback(frames) | |
| 100 | |
| 101 | |
| 102 class _Frame(object): | |
| 103 """ | |
| 104 A fake frame object, used by L{_Traceback}. | |
| 105 """ | |
| 106 | |
| 107 def __init__(self, name, filename): | |
| 108 self.f_code = _Code(name, filename) | |
| 109 self.f_globals = {} | |
| 110 | |
| 111 | |
| 112 class _Code(object): | |
| 113 """ | |
| 114 A fake code object, used by L{_Traceback} via L{_Frame}. | |
| 115 """ | |
| 116 def __init__(self, name, filename): | |
| 117 self.co_name = name | |
| 118 self.co_filename = filename | |
| 119 | |
| 120 | |
| 121 class Failure: | |
| 122 """A basic abstraction for an error that has occurred. | |
| 123 | |
| 124 This is necessary because Python's built-in error mechanisms are | |
| 125 inconvenient for asynchronous communication. | |
| 126 | |
| 127 @ivar value: The exception instance responsible for this failure. | |
| 128 @ivar type: The exception's class. | |
| 129 """ | |
| 130 | |
| 131 pickled = 0 | |
| 132 stack = None | |
| 133 | |
| 134 # The opcode of "yield" in Python bytecode. We need this in _findFailure in | |
| 135 # order to identify whether an exception was thrown by a | |
| 136 # throwExceptionIntoGenerator. | |
| 137 _yieldOpcode = chr(opcode.opmap["YIELD_VALUE"]) | |
| 138 | |
| 139 def __init__(self, exc_value=None, exc_type=None, exc_tb=None): | |
| 140 """Initialize me with an explanation of the error. | |
| 141 | |
| 142 By default, this will use the current X{exception} | |
| 143 (L{sys.exc_info}()). However, if you want to specify a | |
| 144 particular kind of failure, you can pass an exception as an | |
| 145 argument. | |
| 146 | |
| 147 If no C{exc_value} is passed, then an "original" Failure will | |
| 148 be searched for. If the current exception handler that this | |
| 149 Failure is being constructed in is handling an exception | |
| 150 raised by L{raiseException}, then this Failure will act like | |
| 151 the original Failure. | |
| 152 """ | |
| 153 global count | |
| 154 count = count + 1 | |
| 155 self.count = count | |
| 156 self.type = self.value = tb = None | |
| 157 | |
| 158 #strings Exceptions/Failures are bad, mmkay? | |
| 159 if isinstance(exc_value, (str, unicode)) and exc_type is None: | |
| 160 import warnings | |
| 161 warnings.warn( | |
| 162 "Don't pass strings (like %r) to failure.Failure (replacing with
a DefaultException)." % | |
| 163 exc_value, DeprecationWarning, stacklevel=2) | |
| 164 exc_value = DefaultException(exc_value) | |
| 165 | |
| 166 stackOffset = 0 | |
| 167 | |
| 168 if exc_value is None: | |
| 169 exc_value = self._findFailure() | |
| 170 | |
| 171 if exc_value is None: | |
| 172 self.type, self.value, tb = sys.exc_info() | |
| 173 if self.type is None: | |
| 174 raise NoCurrentExceptionError() | |
| 175 stackOffset = 1 | |
| 176 elif exc_type is None: | |
| 177 if isinstance(exc_value, Exception): | |
| 178 self.type = exc_value.__class__ | |
| 179 else: #allow arbitrary objects. | |
| 180 self.type = type(exc_value) | |
| 181 self.value = exc_value | |
| 182 else: | |
| 183 self.type = exc_type | |
| 184 self.value = exc_value | |
| 185 if isinstance(self.value, Failure): | |
| 186 self.__dict__ = self.value.__dict__ | |
| 187 return | |
| 188 if tb is None: | |
| 189 if exc_tb: | |
| 190 tb = exc_tb | |
| 191 # else: | |
| 192 # log.msg("Erf, %r created with no traceback, %s %s." % ( | |
| 193 # repr(self), repr(exc_value), repr(exc_type))) | |
| 194 # for s in traceback.format_stack(): | |
| 195 # log.msg(s) | |
| 196 | |
| 197 frames = self.frames = [] | |
| 198 stack = self.stack = [] | |
| 199 | |
| 200 # added 2003-06-23 by Chris Armstrong. Yes, I actually have a | |
| 201 # use case where I need this traceback object, and I've made | |
| 202 # sure that it'll be cleaned up. | |
| 203 self.tb = tb | |
| 204 | |
| 205 if tb: | |
| 206 f = tb.tb_frame | |
| 207 elif not isinstance(self.value, Failure): | |
| 208 # we don't do frame introspection since it's expensive, | |
| 209 # and if we were passed a plain exception with no | |
| 210 # traceback, it's not useful anyway | |
| 211 f = stackOffset = None | |
| 212 | |
| 213 while stackOffset and f: | |
| 214 # This excludes this Failure.__init__ frame from the | |
| 215 # stack, leaving it to start with our caller instead. | |
| 216 f = f.f_back | |
| 217 stackOffset -= 1 | |
| 218 | |
| 219 # Keeps the *full* stack. Formerly in spread.pb.print_excFullStack: | |
| 220 # | |
| 221 # The need for this function arises from the fact that several | |
| 222 # PB classes have the peculiar habit of discarding exceptions | |
| 223 # with bareword "except:"s. This premature exception | |
| 224 # catching means tracebacks generated here don't tend to show | |
| 225 # what called upon the PB object. | |
| 226 | |
| 227 while f: | |
| 228 localz = f.f_locals.copy() | |
| 229 if f.f_locals is f.f_globals: | |
| 230 globalz = {} | |
| 231 else: | |
| 232 globalz = f.f_globals.copy() | |
| 233 for d in globalz, localz: | |
| 234 if d.has_key("__builtins__"): | |
| 235 del d["__builtins__"] | |
| 236 stack.insert(0, [ | |
| 237 f.f_code.co_name, | |
| 238 f.f_code.co_filename, | |
| 239 f.f_lineno, | |
| 240 localz.items(), | |
| 241 globalz.items(), | |
| 242 ]) | |
| 243 f = f.f_back | |
| 244 | |
| 245 while tb is not None: | |
| 246 f = tb.tb_frame | |
| 247 localz = f.f_locals.copy() | |
| 248 if f.f_locals is f.f_globals: | |
| 249 globalz = {} | |
| 250 else: | |
| 251 globalz = f.f_globals.copy() | |
| 252 for d in globalz, localz: | |
| 253 if d.has_key("__builtins__"): | |
| 254 del d["__builtins__"] | |
| 255 | |
| 256 frames.append([ | |
| 257 f.f_code.co_name, | |
| 258 f.f_code.co_filename, | |
| 259 tb.tb_lineno, | |
| 260 localz.items(), | |
| 261 globalz.items(), | |
| 262 ]) | |
| 263 tb = tb.tb_next | |
| 264 if inspect.isclass(self.type) and issubclass(self.type, Exception): | |
| 265 parentCs = reflect.allYourBase(self.type) | |
| 266 self.parents = map(reflect.qual, parentCs) | |
| 267 self.parents.append(reflect.qual(self.type)) | |
| 268 else: | |
| 269 self.parents = [self.type] | |
| 270 | |
| 271 def trap(self, *errorTypes): | |
| 272 """Trap this failure if its type is in a predetermined list. | |
| 273 | |
| 274 This allows you to trap a Failure in an error callback. It will be | |
| 275 automatically re-raised if it is not a type that you expect. | |
| 276 | |
| 277 The reason for having this particular API is because it's very useful | |
| 278 in Deferred errback chains: | |
| 279 | |
| 280 | def _ebFoo(self, failure): | |
| 281 | r = failure.trap(Spam, Eggs) | |
| 282 | print 'The Failure is due to either Spam or Eggs!' | |
| 283 | if r == Spam: | |
| 284 | print 'Spam did it!' | |
| 285 | elif r == Eggs: | |
| 286 | print 'Eggs did it!' | |
| 287 | |
| 288 If the failure is not a Spam or an Eggs, then the Failure | |
| 289 will be 'passed on' to the next errback. | |
| 290 | |
| 291 @type errorTypes: L{Exception} | |
| 292 """ | |
| 293 error = self.check(*errorTypes) | |
| 294 if not error: | |
| 295 raise self | |
| 296 return error | |
| 297 | |
| 298 def check(self, *errorTypes): | |
| 299 """Check if this failure's type is in a predetermined list. | |
| 300 | |
| 301 @type errorTypes: list of L{Exception} classes or | |
| 302 fully-qualified class names. | |
| 303 @returns: the matching L{Exception} type, or None if no match. | |
| 304 """ | |
| 305 for error in errorTypes: | |
| 306 err = error | |
| 307 if inspect.isclass(error) and issubclass(error, Exception): | |
| 308 err = reflect.qual(error) | |
| 309 if err in self.parents: | |
| 310 return error | |
| 311 return None | |
| 312 | |
| 313 | |
| 314 def raiseException(self): | |
| 315 """ | |
| 316 raise the original exception, preserving traceback | |
| 317 information if available. | |
| 318 """ | |
| 319 raise self.type, self.value, self.tb | |
| 320 | |
| 321 | |
| 322 def throwExceptionIntoGenerator(self, g): | |
| 323 """ | |
| 324 Throw the original exception into the given generator, | |
| 325 preserving traceback information if available. | |
| 326 | |
| 327 @return: The next value yielded from the generator. | |
| 328 @raise StopIteration: If there are no more values in the generator. | |
| 329 @raise anything else: Anything that the generator raises. | |
| 330 """ | |
| 331 return g.throw(self.type, self.value, self.tb) | |
| 332 | |
| 333 | |
| 334 def _findFailure(cls): | |
| 335 """ | |
| 336 Find the failure that represents the exception currently in context. | |
| 337 """ | |
| 338 tb = sys.exc_info()[-1] | |
| 339 if not tb: | |
| 340 return | |
| 341 | |
| 342 secondLastTb = None | |
| 343 lastTb = tb | |
| 344 while lastTb.tb_next: | |
| 345 secondLastTb = lastTb | |
| 346 lastTb = lastTb.tb_next | |
| 347 | |
| 348 lastFrame = lastTb.tb_frame | |
| 349 | |
| 350 # NOTE: f_locals.get('self') is used rather than | |
| 351 # f_locals['self'] because psyco frames do not contain | |
| 352 # anything in their locals() dicts. psyco makes debugging | |
| 353 # difficult anyhow, so losing the Failure objects (and thus | |
| 354 # the tracebacks) here when it is used is not that big a deal. | |
| 355 | |
| 356 # handle raiseException-originated exceptions | |
| 357 if lastFrame.f_code is cls.raiseException.func_code: | |
| 358 return lastFrame.f_locals.get('self') | |
| 359 | |
| 360 # handle throwExceptionIntoGenerator-originated exceptions | |
| 361 # this is tricky, and differs if the exception was caught | |
| 362 # inside the generator, or above it: | |
| 363 | |
| 364 # it is only really originating from | |
| 365 # throwExceptionIntoGenerator if the bottom of the traceback | |
| 366 # is a yield. | |
| 367 # Pyrex and Cython extensions create traceback frames | |
| 368 # with no co_code, but they can't yield so we know it's okay to just ret
urn here. | |
| 369 if ((not lastFrame.f_code.co_code) or | |
| 370 lastFrame.f_code.co_code[lastTb.tb_lasti] != cls._yieldOpcode): | |
| 371 return | |
| 372 | |
| 373 # if the exception was caught above the generator.throw | |
| 374 # (outside the generator), it will appear in the tb (as the | |
| 375 # second last item): | |
| 376 if secondLastTb: | |
| 377 frame = secondLastTb.tb_frame | |
| 378 if frame.f_code is cls.throwExceptionIntoGenerator.func_code: | |
| 379 return frame.f_locals.get('self') | |
| 380 | |
| 381 # if the exception was caught below the generator.throw | |
| 382 # (inside the generator), it will appear in the frames' linked | |
| 383 # list, above the top-level traceback item (which must be the | |
| 384 # generator frame itself, thus its caller is | |
| 385 # throwExceptionIntoGenerator). | |
| 386 frame = tb.tb_frame.f_back | |
| 387 if frame and frame.f_code is cls.throwExceptionIntoGenerator.func_code: | |
| 388 return frame.f_locals.get('self') | |
| 389 | |
| 390 _findFailure = classmethod(_findFailure) | |
| 391 | |
| 392 def __repr__(self): | |
| 393 return "<%s %s>" % (self.__class__, self.type) | |
| 394 | |
| 395 def __str__(self): | |
| 396 return "[Failure instance: %s]" % self.getBriefTraceback() | |
| 397 | |
| 398 def __getstate__(self): | |
| 399 """Avoid pickling objects in the traceback. | |
| 400 """ | |
| 401 if self.pickled: | |
| 402 return self.__dict__ | |
| 403 c = self.__dict__.copy() | |
| 404 | |
| 405 c['frames'] = [ | |
| 406 [ | |
| 407 v[0], v[1], v[2], | |
| 408 [(j[0], reflect.safe_repr(j[1])) for j in v[3]], | |
| 409 [(j[0], reflect.safe_repr(j[1])) for j in v[4]] | |
| 410 ] for v in self.frames | |
| 411 ] | |
| 412 | |
| 413 # added 2003-06-23. See comment above in __init__ | |
| 414 c['tb'] = None | |
| 415 | |
| 416 if self.stack is not None: | |
| 417 # XXX: This is a band-aid. I can't figure out where these | |
| 418 # (failure.stack is None) instances are coming from. | |
| 419 c['stack'] = [ | |
| 420 [ | |
| 421 v[0], v[1], v[2], | |
| 422 [(j[0], reflect.safe_repr(j[1])) for j in v[3]], | |
| 423 [(j[0], reflect.safe_repr(j[1])) for j in v[4]] | |
| 424 ] for v in self.stack | |
| 425 ] | |
| 426 | |
| 427 c['pickled'] = 1 | |
| 428 return c | |
| 429 | |
| 430 def cleanFailure(self): | |
| 431 """Remove references to other objects, replacing them with strings. | |
| 432 """ | |
| 433 self.__dict__ = self.__getstate__() | |
| 434 | |
| 435 def getTracebackObject(self): | |
| 436 """ | |
| 437 Get an object that represents this Failure's stack that can be passed | |
| 438 to traceback.extract_tb. | |
| 439 | |
| 440 If the original traceback object is still present, return that. If this | |
| 441 traceback object has been lost but we still have the information, | |
| 442 return a fake traceback object (see L{_Traceback}). If there is no | |
| 443 traceback information at all, return None. | |
| 444 """ | |
| 445 if self.tb is not None: | |
| 446 return self.tb | |
| 447 elif len(self.frames) > 0: | |
| 448 return _Traceback(self.frames) | |
| 449 else: | |
| 450 return None | |
| 451 | |
| 452 def getErrorMessage(self): | |
| 453 """Get a string of the exception which caused this Failure.""" | |
| 454 if isinstance(self.value, Failure): | |
| 455 return self.value.getErrorMessage() | |
| 456 return reflect.safe_str(self.value) | |
| 457 | |
| 458 def getBriefTraceback(self): | |
| 459 io = StringIO() | |
| 460 self.printBriefTraceback(file=io) | |
| 461 return io.getvalue() | |
| 462 | |
| 463 def getTraceback(self, elideFrameworkCode=0, detail='default'): | |
| 464 io = StringIO() | |
| 465 self.printTraceback(file=io, elideFrameworkCode=elideFrameworkCode, deta
il=detail) | |
| 466 return io.getvalue() | |
| 467 | |
| 468 def printTraceback(self, file=None, elideFrameworkCode=0, detail='default'): | |
| 469 """Emulate Python's standard error reporting mechanism. | |
| 470 """ | |
| 471 if file is None: | |
| 472 file = log.logerr | |
| 473 w = file.write | |
| 474 | |
| 475 # Preamble | |
| 476 if detail == 'verbose': | |
| 477 w( '*--- Failure #%d%s---\n' % | |
| 478 (self.count, | |
| 479 (self.pickled and ' (pickled) ') or ' ')) | |
| 480 elif detail == 'brief': | |
| 481 if self.frames: | |
| 482 hasFrames = 'Traceback' | |
| 483 else: | |
| 484 hasFrames = 'Traceback (failure with no frames)' | |
| 485 w("%s: %s: %s\n" % (hasFrames, self.type, self.value)) | |
| 486 else: | |
| 487 w( 'Traceback (most recent call last):\n') | |
| 488 | |
| 489 # Frames, formatted in appropriate style | |
| 490 if self.frames: | |
| 491 if not elideFrameworkCode: | |
| 492 format_frames(self.stack[-traceupLength:], w, detail) | |
| 493 w("%s\n" % (EXCEPTION_CAUGHT_HERE,)) | |
| 494 format_frames(self.frames, w, detail) | |
| 495 elif not detail == 'brief': | |
| 496 # Yeah, it's not really a traceback, despite looking like one... | |
| 497 w("Failure: ") | |
| 498 | |
| 499 # postamble, if any | |
| 500 if not detail == 'brief': | |
| 501 # Unfortunately, self.type will not be a class object if this | |
| 502 # Failure was created implicitly from a string exception. | |
| 503 # qual() doesn't make any sense on a string, so check for this | |
| 504 # case here and just write out the string if that's what we | |
| 505 # have. | |
| 506 if isinstance(self.type, (str, unicode)): | |
| 507 w(self.type + "\n") | |
| 508 else: | |
| 509 w("%s: %s\n" % (reflect.qual(self.type), | |
| 510 reflect.safe_str(self.value))) | |
| 511 # chaining | |
| 512 if isinstance(self.value, Failure): | |
| 513 # TODO: indentation for chained failures? | |
| 514 file.write(" (chained Failure)\n") | |
| 515 self.value.printTraceback(file, elideFrameworkCode, detail) | |
| 516 if detail == 'verbose': | |
| 517 w('*--- End of Failure #%d ---\n' % self.count) | |
| 518 | |
| 519 def printBriefTraceback(self, file=None, elideFrameworkCode=0): | |
| 520 """Print a traceback as densely as possible. | |
| 521 """ | |
| 522 self.printTraceback(file, elideFrameworkCode, detail='brief') | |
| 523 | |
| 524 def printDetailedTraceback(self, file=None, elideFrameworkCode=0): | |
| 525 """Print a traceback with detailed locals and globals information. | |
| 526 """ | |
| 527 self.printTraceback(file, elideFrameworkCode, detail='verbose') | |
| 528 | |
| 529 # slyphon: make post-morteming exceptions tweakable | |
| 530 | |
| 531 DO_POST_MORTEM = True | |
| 532 | |
| 533 def _debuginit(self, exc_value=None, exc_type=None, exc_tb=None, | |
| 534 Failure__init__=Failure.__init__.im_func): | |
| 535 if (exc_value, exc_type, exc_tb) == (None, None, None): | |
| 536 exc = sys.exc_info() | |
| 537 if not exc[0] == self.__class__ and DO_POST_MORTEM: | |
| 538 print "Jumping into debugger for post-mortem of exception '%s':" % e
xc[1] | |
| 539 import pdb | |
| 540 pdb.post_mortem(exc[2]) | |
| 541 Failure__init__(self, exc_value, exc_type, exc_tb) | |
| 542 | |
| 543 def startDebugMode(): | |
| 544 """Enable debug hooks for Failures.""" | |
| 545 Failure.__init__ = _debuginit | |
| 546 | |
| 547 | |
| 548 # Sibling imports - at the bottom and unqualified to avoid unresolvable | |
| 549 # circularity | |
| 550 import log | |
| OLD | NEW |