summaryrefslogtreecommitdiffstats
path: root/Lib/asyncio/futures.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/asyncio/futures.py')
-rw-r--r--Lib/asyncio/futures.py122
1 files changed, 16 insertions, 106 deletions
diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py
index d11d289..39721ea 100644
--- a/Lib/asyncio/futures.py
+++ b/Lib/asyncio/futures.py
@@ -27,86 +27,6 @@ _FINISHED = base_futures._FINISHED
STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
-class _TracebackLogger:
- """Helper to log a traceback upon destruction if not cleared.
-
- This solves a nasty problem with Futures and Tasks that have an
- exception set: if nobody asks for the exception, the exception is
- never logged. This violates the Zen of Python: 'Errors should
- never pass silently. Unless explicitly silenced.'
-
- However, we don't want to log the exception as soon as
- set_exception() is called: if the calling code is written
- properly, it will get the exception and handle it properly. But
- we *do* want to log it if result() or exception() was never called
- -- otherwise developers waste a lot of time wondering why their
- buggy code fails silently.
-
- An earlier attempt added a __del__() method to the Future class
- itself, but this backfired because the presence of __del__()
- prevents garbage collection from breaking cycles. A way out of
- this catch-22 is to avoid having a __del__() method on the Future
- class itself, but instead to have a reference to a helper object
- with a __del__() method that logs the traceback, where we ensure
- that the helper object doesn't participate in cycles, and only the
- Future has a reference to it.
-
- The helper object is added when set_exception() is called. When
- the Future is collected, and the helper is present, the helper
- object is also collected, and its __del__() method will log the
- traceback. When the Future's result() or exception() method is
- called (and a helper object is present), it removes the helper
- object, after calling its clear() method to prevent it from
- logging.
-
- One downside is that we do a fair amount of work to extract the
- traceback from the exception, even when it is never logged. It
- would seem cheaper to just store the exception object, but that
- references the traceback, which references stack frames, which may
- reference the Future, which references the _TracebackLogger, and
- then the _TracebackLogger would be included in a cycle, which is
- what we're trying to avoid! As an optimization, we don't
- immediately format the exception; we only do the work when
- activate() is called, which call is delayed until after all the
- Future's callbacks have run. Since usually a Future has at least
- one callback (typically set by 'yield from') and usually that
- callback extracts the callback, thereby removing the need to
- format the exception.
-
- PS. I don't claim credit for this solution. I first heard of it
- in a discussion about closing files when they are collected.
- """
-
- __slots__ = ('loop', 'source_traceback', 'exc', 'tb')
-
- def __init__(self, future, exc):
- self.loop = future._loop
- self.source_traceback = future._source_traceback
- self.exc = exc
- self.tb = None
-
- def activate(self):
- exc = self.exc
- if exc is not None:
- self.exc = None
- self.tb = traceback.format_exception(exc.__class__, exc,
- exc.__traceback__)
-
- def clear(self):
- self.exc = None
- self.tb = None
-
- def __del__(self):
- if self.tb:
- msg = 'Future/Task exception was never retrieved\n'
- if self.source_traceback:
- src = ''.join(traceback.format_list(self.source_traceback))
- msg += 'Future/Task created at (most recent call last):\n'
- msg += '%s\n' % src.rstrip()
- msg += ''.join(self.tb).rstrip()
- self.loop.call_exception_handler({'message': msg})
-
-
class Future:
"""This class is *almost* compatible with concurrent.futures.Future.
@@ -164,25 +84,21 @@ class Future:
def __repr__(self):
return '<%s %s>' % (self.__class__.__name__, ' '.join(self._repr_info()))
- # On Python 3.3 and older, objects with a destructor part of a reference
- # cycle are never destroyed. It's not more the case on Python 3.4 thanks
- # to the PEP 442.
- if compat.PY34:
- def __del__(self):
- if not self._log_traceback:
- # set_exception() was not called, or result() or exception()
- # has consumed the exception
- return
- exc = self._exception
- context = {
- 'message': ('%s exception was never retrieved'
- % self.__class__.__name__),
- 'exception': exc,
- 'future': self,
- }
- if self._source_traceback:
- context['source_traceback'] = self._source_traceback
- self._loop.call_exception_handler(context)
+ def __del__(self):
+ if not self._log_traceback:
+ # set_exception() was not called, or result() or exception()
+ # has consumed the exception
+ return
+ exc = self._exception
+ context = {
+ 'message': ('%s exception was never retrieved'
+ % self.__class__.__name__),
+ 'exception': exc,
+ 'future': self,
+ }
+ if self._source_traceback:
+ context['source_traceback'] = self._source_traceback
+ self._loop.call_exception_handler(context)
def cancel(self):
"""Cancel the future and schedule callbacks.
@@ -317,13 +233,7 @@ class Future:
self._exception = exception
self._state = _FINISHED
self._schedule_callbacks()
- if compat.PY34:
- self._log_traceback = True
- else:
- self._tb_logger = _TracebackLogger(self, exception)
- # Arrange for the logger to be activated after all callbacks
- # have had a chance to call result() or exception().
- self._loop.call_soon(self._tb_logger.activate)
+ self._log_traceback = True
def __iter__(self):
if not self.done():