blob: 19212a94b9f9822d12af2552dedface73d6b7088 [file] [log] [blame]
Guido van Rossum27b7c7e2013-10-17 13:40:50 -07001"""A Future class similar to the one in PEP 3148."""
2
3__all__ = ['CancelledError', 'TimeoutError',
4 'InvalidStateError',
5 'Future', 'wrap_future',
6 ]
7
8import concurrent.futures._base
9import logging
Victor Stinner313a9802014-07-29 12:58:23 +020010import reprlib
Victor Stinner4c3c6992013-12-19 22:42:40 +010011import sys
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070012import traceback
13
14from . import events
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070015
16# States for Future.
17_PENDING = 'PENDING'
18_CANCELLED = 'CANCELLED'
19_FINISHED = 'FINISHED'
20
Victor Stinner4c3c6992013-12-19 22:42:40 +010021_PY34 = sys.version_info >= (3, 4)
22
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070023Error = concurrent.futures._base.Error
24CancelledError = concurrent.futures.CancelledError
25TimeoutError = concurrent.futures.TimeoutError
26
27STACK_DEBUG = logging.DEBUG - 1 # heavy-duty debugging
28
29
30class InvalidStateError(Error):
31 """The operation is not allowed in this state."""
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070032
33
34class _TracebackLogger:
35 """Helper to log a traceback upon destruction if not cleared.
36
37 This solves a nasty problem with Futures and Tasks that have an
38 exception set: if nobody asks for the exception, the exception is
39 never logged. This violates the Zen of Python: 'Errors should
40 never pass silently. Unless explicitly silenced.'
41
42 However, we don't want to log the exception as soon as
43 set_exception() is called: if the calling code is written
44 properly, it will get the exception and handle it properly. But
45 we *do* want to log it if result() or exception() was never called
46 -- otherwise developers waste a lot of time wondering why their
47 buggy code fails silently.
48
49 An earlier attempt added a __del__() method to the Future class
50 itself, but this backfired because the presence of __del__()
51 prevents garbage collection from breaking cycles. A way out of
52 this catch-22 is to avoid having a __del__() method on the Future
53 class itself, but instead to have a reference to a helper object
54 with a __del__() method that logs the traceback, where we ensure
55 that the helper object doesn't participate in cycles, and only the
56 Future has a reference to it.
57
58 The helper object is added when set_exception() is called. When
59 the Future is collected, and the helper is present, the helper
60 object is also collected, and its __del__() method will log the
61 traceback. When the Future's result() or exception() method is
Serhiy Storchaka56a6d852014-12-01 18:28:43 +020062 called (and a helper object is present), it removes the helper
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070063 object, after calling its clear() method to prevent it from
64 logging.
65
66 One downside is that we do a fair amount of work to extract the
67 traceback from the exception, even when it is never logged. It
68 would seem cheaper to just store the exception object, but that
69 references the traceback, which references stack frames, which may
70 reference the Future, which references the _TracebackLogger, and
71 then the _TracebackLogger would be included in a cycle, which is
72 what we're trying to avoid! As an optimization, we don't
73 immediately format the exception; we only do the work when
74 activate() is called, which call is delayed until after all the
75 Future's callbacks have run. Since usually a Future has at least
76 one callback (typically set by 'yield from') and usually that
77 callback extracts the callback, thereby removing the need to
78 format the exception.
79
80 PS. I don't claim credit for this solution. I first heard of it
81 in a discussion about closing files when they are collected.
82 """
83
Victor Stinner80f53aa2014-06-27 13:52:20 +020084 __slots__ = ('loop', 'source_traceback', 'exc', 'tb')
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070085
Victor Stinner80f53aa2014-06-27 13:52:20 +020086 def __init__(self, future, exc):
87 self.loop = future._loop
88 self.source_traceback = future._source_traceback
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070089 self.exc = exc
90 self.tb = None
91
92 def activate(self):
93 exc = self.exc
94 if exc is not None:
95 self.exc = None
96 self.tb = traceback.format_exception(exc.__class__, exc,
97 exc.__traceback__)
98
99 def clear(self):
100 self.exc = None
101 self.tb = None
102
103 def __del__(self):
104 if self.tb:
Victor Stinner662fd5f2014-11-20 14:16:31 +0100105 msg = 'Future/Task exception was never retrieved\n'
Victor Stinner80f53aa2014-06-27 13:52:20 +0200106 if self.source_traceback:
Victor Stinner662fd5f2014-11-20 14:16:31 +0100107 src = ''.join(traceback.format_list(self.source_traceback))
108 msg += 'Future/Task created at (most recent call last):\n'
109 msg += '%s\n' % src.rstrip()
Victor Stinner80f53aa2014-06-27 13:52:20 +0200110 msg += ''.join(self.tb).rstrip()
111 self.loop.call_exception_handler({'message': msg})
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700112
113
114class Future:
115 """This class is *almost* compatible with concurrent.futures.Future.
116
117 Differences:
118
119 - result() and exception() do not take a timeout argument and
120 raise an exception when the future isn't done yet.
121
122 - Callbacks registered with add_done_callback() are always called
123 via the event loop's call_soon_threadsafe().
124
125 - This class is not compatible with the wait() and as_completed()
126 methods in the concurrent.futures package.
127
128 (In Python 3.4 or later we may be able to unify the implementations.)
129 """
130
131 # Class variables serving as defaults for instance variables.
132 _state = _PENDING
133 _result = None
134 _exception = None
135 _loop = None
Victor Stinnerfe22e092014-12-04 23:00:13 +0100136 _source_traceback = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700137
138 _blocking = False # proper use of future (yield vs yield from)
139
Victor Stinnere40c0782013-12-21 00:19:33 +0100140 _log_traceback = False # Used for Python 3.4 and later
141 _tb_logger = None # Used for Python 3.3 only
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700142
143 def __init__(self, *, loop=None):
144 """Initialize the future.
145
146 The optional event_loop argument allows to explicitly set the event
147 loop object used by the future. If it's not provided, the future uses
148 the default event loop.
149 """
150 if loop is None:
151 self._loop = events.get_event_loop()
152 else:
153 self._loop = loop
154 self._callbacks = []
Victor Stinner80f53aa2014-06-27 13:52:20 +0200155 if self._loop.get_debug():
156 self._source_traceback = traceback.extract_stack(sys._getframe(1))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700157
Victor Stinner975735f2014-06-25 21:41:58 +0200158 def _format_callbacks(self):
159 cb = self._callbacks
160 size = len(cb)
161 if not size:
162 cb = ''
163
164 def format_cb(callback):
165 return events._format_callback(callback, ())
166
167 if size == 1:
168 cb = format_cb(cb[0])
169 elif size == 2:
170 cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
171 elif size > 2:
172 cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
173 size-2,
174 format_cb(cb[-1]))
175 return 'cb=[%s]' % cb
176
Victor Stinner313a9802014-07-29 12:58:23 +0200177 def _repr_info(self):
Victor Stinner975735f2014-06-25 21:41:58 +0200178 info = [self._state.lower()]
179 if self._state == _FINISHED:
Victor Stinner313a9802014-07-29 12:58:23 +0200180 if self._exception is not None:
181 info.append('exception={!r}'.format(self._exception))
182 else:
183 # use reprlib to limit the length of the output, especially
184 # for very long strings
185 result = reprlib.repr(self._result)
186 info.append('result={}'.format(result))
Victor Stinner975735f2014-06-25 21:41:58 +0200187 if self._callbacks:
188 info.append(self._format_callbacks())
Victor Stinner313a9802014-07-29 12:58:23 +0200189 if self._source_traceback:
190 frame = self._source_traceback[-1]
191 info.append('created at %s:%s' % (frame[0], frame[1]))
192 return info
193
194 def __repr__(self):
195 info = self._repr_info()
Victor Stinner975735f2014-06-25 21:41:58 +0200196 return '<%s %s>' % (self.__class__.__name__, ' '.join(info))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700197
Victor Stinnera02f81f2014-06-24 22:37:53 +0200198 # On Python 3.3 or older, objects with a destructor part of a reference
199 # cycle are never destroyed. It's not more the case on Python 3.4 thanks to
200 # the PEP 442.
Victor Stinner4c3c6992013-12-19 22:42:40 +0100201 if _PY34:
202 def __del__(self):
Victor Stinnere40c0782013-12-21 00:19:33 +0100203 if not self._log_traceback:
204 # set_exception() was not called, or result() or exception()
205 # has consumed the exception
206 return
207 exc = self._exception
Yury Selivanov569efa22014-02-18 18:02:19 -0500208 context = {
Victor Stinner80f53aa2014-06-27 13:52:20 +0200209 'message': ('%s exception was never retrieved'
210 % self.__class__.__name__),
Yury Selivanov569efa22014-02-18 18:02:19 -0500211 'exception': exc,
212 'future': self,
213 }
Victor Stinner80f53aa2014-06-27 13:52:20 +0200214 if self._source_traceback:
215 context['source_traceback'] = self._source_traceback
Yury Selivanov569efa22014-02-18 18:02:19 -0500216 self._loop.call_exception_handler(context)
Victor Stinner4c3c6992013-12-19 22:42:40 +0100217
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700218 def cancel(self):
219 """Cancel the future and schedule callbacks.
220
221 If the future is already done or cancelled, return False. Otherwise,
222 change the future's state to cancelled, schedule the callbacks and
223 return True.
224 """
225 if self._state != _PENDING:
226 return False
227 self._state = _CANCELLED
228 self._schedule_callbacks()
229 return True
230
231 def _schedule_callbacks(self):
232 """Internal: Ask the event loop to call all callbacks.
233
234 The callbacks are scheduled to be called as soon as possible. Also
235 clears the callback list.
236 """
237 callbacks = self._callbacks[:]
238 if not callbacks:
239 return
240
241 self._callbacks[:] = []
242 for callback in callbacks:
243 self._loop.call_soon(callback, self)
244
245 def cancelled(self):
246 """Return True if the future was cancelled."""
247 return self._state == _CANCELLED
248
249 # Don't implement running(); see http://bugs.python.org/issue18699
250
251 def done(self):
252 """Return True if the future is done.
253
254 Done means either that a result / exception are available, or that the
255 future was cancelled.
256 """
257 return self._state != _PENDING
258
259 def result(self):
260 """Return the result this future represents.
261
262 If the future has been cancelled, raises CancelledError. If the
263 future's result isn't yet available, raises InvalidStateError. If
264 the future is done and has an exception set, this exception is raised.
265 """
266 if self._state == _CANCELLED:
267 raise CancelledError
268 if self._state != _FINISHED:
269 raise InvalidStateError('Result is not ready.')
Victor Stinnere40c0782013-12-21 00:19:33 +0100270 self._log_traceback = False
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700271 if self._tb_logger is not None:
272 self._tb_logger.clear()
Victor Stinnere40c0782013-12-21 00:19:33 +0100273 self._tb_logger = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700274 if self._exception is not None:
275 raise self._exception
276 return self._result
277
278 def exception(self):
279 """Return the exception that was set on this future.
280
281 The exception (or None if no exception was set) is returned only if
282 the future is done. If the future has been cancelled, raises
283 CancelledError. If the future isn't done yet, raises
284 InvalidStateError.
285 """
286 if self._state == _CANCELLED:
287 raise CancelledError
288 if self._state != _FINISHED:
289 raise InvalidStateError('Exception is not set.')
Victor Stinnere40c0782013-12-21 00:19:33 +0100290 self._log_traceback = False
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700291 if self._tb_logger is not None:
292 self._tb_logger.clear()
Victor Stinnere40c0782013-12-21 00:19:33 +0100293 self._tb_logger = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700294 return self._exception
295
296 def add_done_callback(self, fn):
297 """Add a callback to be run when the future becomes done.
298
299 The callback is called with a single argument - the future object. If
300 the future is already done when this is called, the callback is
301 scheduled with call_soon.
302 """
303 if self._state != _PENDING:
304 self._loop.call_soon(fn, self)
305 else:
306 self._callbacks.append(fn)
307
308 # New method not in PEP 3148.
309
310 def remove_done_callback(self, fn):
311 """Remove all instances of a callback from the "call when done" list.
312
313 Returns the number of callbacks removed.
314 """
315 filtered_callbacks = [f for f in self._callbacks if f != fn]
316 removed_count = len(self._callbacks) - len(filtered_callbacks)
317 if removed_count:
318 self._callbacks[:] = filtered_callbacks
319 return removed_count
320
321 # So-called internal methods (note: no set_running_or_notify_cancel()).
322
Victor Stinnera9acbe82014-07-05 15:29:41 +0200323 def _set_result_unless_cancelled(self, result):
324 """Helper setting the result only if the future was not cancelled."""
325 if self.cancelled():
326 return
327 self.set_result(result)
328
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700329 def set_result(self, result):
330 """Mark the future done and set its result.
331
332 If the future is already done when this method is called, raises
333 InvalidStateError.
334 """
335 if self._state != _PENDING:
336 raise InvalidStateError('{}: {!r}'.format(self._state, self))
337 self._result = result
338 self._state = _FINISHED
339 self._schedule_callbacks()
340
341 def set_exception(self, exception):
342 """Mark the future done and set an exception.
343
344 If the future is already done when this method is called, raises
345 InvalidStateError.
346 """
347 if self._state != _PENDING:
348 raise InvalidStateError('{}: {!r}'.format(self._state, self))
Victor Stinner95728982014-01-30 16:01:54 -0800349 if isinstance(exception, type):
350 exception = exception()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700351 self._exception = exception
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700352 self._state = _FINISHED
353 self._schedule_callbacks()
Victor Stinner4c3c6992013-12-19 22:42:40 +0100354 if _PY34:
Victor Stinnere40c0782013-12-21 00:19:33 +0100355 self._log_traceback = True
Victor Stinner4c3c6992013-12-19 22:42:40 +0100356 else:
Victor Stinner80f53aa2014-06-27 13:52:20 +0200357 self._tb_logger = _TracebackLogger(self, exception)
Victor Stinner4c3c6992013-12-19 22:42:40 +0100358 # Arrange for the logger to be activated after all callbacks
359 # have had a chance to call result() or exception().
360 self._loop.call_soon(self._tb_logger.activate)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700361
362 # Truly internal methods.
363
364 def _copy_state(self, other):
365 """Internal helper to copy state from another Future.
366
367 The other Future may be a concurrent.futures.Future.
368 """
369 assert other.done()
Guido van Rossum7a465642013-11-22 11:47:22 -0800370 if self.cancelled():
371 return
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700372 assert not self.done()
373 if other.cancelled():
374 self.cancel()
375 else:
376 exception = other.exception()
377 if exception is not None:
378 self.set_exception(exception)
379 else:
380 result = other.result()
381 self.set_result(result)
382
383 def __iter__(self):
384 if not self.done():
385 self._blocking = True
386 yield self # This tells Task to wait for completion.
387 assert self.done(), "yield from wasn't used with future"
388 return self.result() # May raise too.
389
390
391def wrap_future(fut, *, loop=None):
392 """Wrap concurrent.futures.Future object."""
393 if isinstance(fut, Future):
394 return fut
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700395 assert isinstance(fut, concurrent.futures.Future), \
396 'concurrent.futures.Future is expected, got {!r}'.format(fut)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700397 if loop is None:
398 loop = events.get_event_loop()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700399 new_future = Future(loop=loop)
Guido van Rossum7a465642013-11-22 11:47:22 -0800400
401 def _check_cancel_other(f):
402 if f.cancelled():
403 fut.cancel()
404
405 new_future.add_done_callback(_check_cancel_other)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700406 fut.add_done_callback(
407 lambda future: loop.call_soon_threadsafe(
Victor Stinner587feb12015-01-09 21:34:27 +0100408 new_future._copy_state, future))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700409 return new_future