blob: 022fef76efe5fd75243f2e389c0a29fbbc41f90e [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 Stinner4c3c6992013-12-19 22:42:40 +010010import sys
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070011import traceback
12
13from . import events
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070014
15# States for Future.
16_PENDING = 'PENDING'
17_CANCELLED = 'CANCELLED'
18_FINISHED = 'FINISHED'
19
Victor Stinner4c3c6992013-12-19 22:42:40 +010020_PY34 = sys.version_info >= (3, 4)
21
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070022# TODO: Do we really want to depend on concurrent.futures internals?
23Error = 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."""
32 # TODO: Show the future, its state, the method, and the required state.
33
34
35class _TracebackLogger:
36 """Helper to log a traceback upon destruction if not cleared.
37
38 This solves a nasty problem with Futures and Tasks that have an
39 exception set: if nobody asks for the exception, the exception is
40 never logged. This violates the Zen of Python: 'Errors should
41 never pass silently. Unless explicitly silenced.'
42
43 However, we don't want to log the exception as soon as
44 set_exception() is called: if the calling code is written
45 properly, it will get the exception and handle it properly. But
46 we *do* want to log it if result() or exception() was never called
47 -- otherwise developers waste a lot of time wondering why their
48 buggy code fails silently.
49
50 An earlier attempt added a __del__() method to the Future class
51 itself, but this backfired because the presence of __del__()
52 prevents garbage collection from breaking cycles. A way out of
53 this catch-22 is to avoid having a __del__() method on the Future
54 class itself, but instead to have a reference to a helper object
55 with a __del__() method that logs the traceback, where we ensure
56 that the helper object doesn't participate in cycles, and only the
57 Future has a reference to it.
58
59 The helper object is added when set_exception() is called. When
60 the Future is collected, and the helper is present, the helper
61 object is also collected, and its __del__() method will log the
62 traceback. When the Future's result() or exception() method is
63 called (and a helper object is present), it removes the the helper
64 object, after calling its clear() method to prevent it from
65 logging.
66
67 One downside is that we do a fair amount of work to extract the
68 traceback from the exception, even when it is never logged. It
69 would seem cheaper to just store the exception object, but that
70 references the traceback, which references stack frames, which may
71 reference the Future, which references the _TracebackLogger, and
72 then the _TracebackLogger would be included in a cycle, which is
73 what we're trying to avoid! As an optimization, we don't
74 immediately format the exception; we only do the work when
75 activate() is called, which call is delayed until after all the
76 Future's callbacks have run. Since usually a Future has at least
77 one callback (typically set by 'yield from') and usually that
78 callback extracts the callback, thereby removing the need to
79 format the exception.
80
81 PS. I don't claim credit for this solution. I first heard of it
82 in a discussion about closing files when they are collected.
83 """
84
Victor Stinner80f53aa2014-06-27 13:52:20 +020085 __slots__ = ('loop', 'source_traceback', 'exc', 'tb')
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070086
Victor Stinner80f53aa2014-06-27 13:52:20 +020087 def __init__(self, future, exc):
88 self.loop = future._loop
89 self.source_traceback = future._source_traceback
Guido van Rossum27b7c7e2013-10-17 13:40:50 -070090 self.exc = exc
91 self.tb = None
92
93 def activate(self):
94 exc = self.exc
95 if exc is not None:
96 self.exc = None
97 self.tb = traceback.format_exception(exc.__class__, exc,
98 exc.__traceback__)
99
100 def clear(self):
101 self.exc = None
102 self.tb = None
103
104 def __del__(self):
105 if self.tb:
Victor Stinner80f53aa2014-06-27 13:52:20 +0200106 msg = 'Future/Task exception was never retrieved'
107 if self.source_traceback:
108 msg += '\nFuture/Task created at (most recent call last):\n'
109 msg += ''.join(traceback.format_list(self.source_traceback))
110 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
136
137 _blocking = False # proper use of future (yield vs yield from)
138
Victor Stinnere40c0782013-12-21 00:19:33 +0100139 _log_traceback = False # Used for Python 3.4 and later
140 _tb_logger = None # Used for Python 3.3 only
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700141
142 def __init__(self, *, loop=None):
143 """Initialize the future.
144
145 The optional event_loop argument allows to explicitly set the event
146 loop object used by the future. If it's not provided, the future uses
147 the default event loop.
148 """
149 if loop is None:
150 self._loop = events.get_event_loop()
151 else:
152 self._loop = loop
153 self._callbacks = []
Victor Stinner80f53aa2014-06-27 13:52:20 +0200154 if self._loop.get_debug():
155 self._source_traceback = traceback.extract_stack(sys._getframe(1))
156 else:
157 self._source_traceback = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700158
Victor Stinner975735f2014-06-25 21:41:58 +0200159 def _format_callbacks(self):
160 cb = self._callbacks
161 size = len(cb)
162 if not size:
163 cb = ''
164
165 def format_cb(callback):
166 return events._format_callback(callback, ())
167
168 if size == 1:
169 cb = format_cb(cb[0])
170 elif size == 2:
171 cb = '{}, {}'.format(format_cb(cb[0]), format_cb(cb[1]))
172 elif size > 2:
173 cb = '{}, <{} more>, {}'.format(format_cb(cb[0]),
174 size-2,
175 format_cb(cb[-1]))
176 return 'cb=[%s]' % cb
177
178 def _format_result(self):
179 if self._state != _FINISHED:
180 return None
181 elif self._exception is not None:
182 return 'exception={!r}'.format(self._exception)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700183 else:
Victor Stinner975735f2014-06-25 21:41:58 +0200184 return 'result={!r}'.format(self._result)
185
186 def __repr__(self):
187 info = [self._state.lower()]
188 if self._state == _FINISHED:
189 info.append(self._format_result())
190 if self._callbacks:
191 info.append(self._format_callbacks())
192 return '<%s %s>' % (self.__class__.__name__, ' '.join(info))
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700193
Victor Stinnera02f81f2014-06-24 22:37:53 +0200194 # On Python 3.3 or older, objects with a destructor part of a reference
195 # cycle are never destroyed. It's not more the case on Python 3.4 thanks to
196 # the PEP 442.
Victor Stinner4c3c6992013-12-19 22:42:40 +0100197 if _PY34:
198 def __del__(self):
Victor Stinnere40c0782013-12-21 00:19:33 +0100199 if not self._log_traceback:
200 # set_exception() was not called, or result() or exception()
201 # has consumed the exception
202 return
203 exc = self._exception
Yury Selivanov569efa22014-02-18 18:02:19 -0500204 context = {
Victor Stinner80f53aa2014-06-27 13:52:20 +0200205 'message': ('%s exception was never retrieved'
206 % self.__class__.__name__),
Yury Selivanov569efa22014-02-18 18:02:19 -0500207 'exception': exc,
208 'future': self,
209 }
Victor Stinner80f53aa2014-06-27 13:52:20 +0200210 if self._source_traceback:
211 context['source_traceback'] = self._source_traceback
Yury Selivanov569efa22014-02-18 18:02:19 -0500212 self._loop.call_exception_handler(context)
Victor Stinner4c3c6992013-12-19 22:42:40 +0100213
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700214 def cancel(self):
215 """Cancel the future and schedule callbacks.
216
217 If the future is already done or cancelled, return False. Otherwise,
218 change the future's state to cancelled, schedule the callbacks and
219 return True.
220 """
221 if self._state != _PENDING:
222 return False
223 self._state = _CANCELLED
224 self._schedule_callbacks()
225 return True
226
227 def _schedule_callbacks(self):
228 """Internal: Ask the event loop to call all callbacks.
229
230 The callbacks are scheduled to be called as soon as possible. Also
231 clears the callback list.
232 """
233 callbacks = self._callbacks[:]
234 if not callbacks:
235 return
236
237 self._callbacks[:] = []
238 for callback in callbacks:
239 self._loop.call_soon(callback, self)
240
241 def cancelled(self):
242 """Return True if the future was cancelled."""
243 return self._state == _CANCELLED
244
245 # Don't implement running(); see http://bugs.python.org/issue18699
246
247 def done(self):
248 """Return True if the future is done.
249
250 Done means either that a result / exception are available, or that the
251 future was cancelled.
252 """
253 return self._state != _PENDING
254
255 def result(self):
256 """Return the result this future represents.
257
258 If the future has been cancelled, raises CancelledError. If the
259 future's result isn't yet available, raises InvalidStateError. If
260 the future is done and has an exception set, this exception is raised.
261 """
262 if self._state == _CANCELLED:
263 raise CancelledError
264 if self._state != _FINISHED:
265 raise InvalidStateError('Result is not ready.')
Victor Stinnere40c0782013-12-21 00:19:33 +0100266 self._log_traceback = False
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700267 if self._tb_logger is not None:
268 self._tb_logger.clear()
Victor Stinnere40c0782013-12-21 00:19:33 +0100269 self._tb_logger = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700270 if self._exception is not None:
271 raise self._exception
272 return self._result
273
274 def exception(self):
275 """Return the exception that was set on this future.
276
277 The exception (or None if no exception was set) is returned only if
278 the future is done. If the future has been cancelled, raises
279 CancelledError. If the future isn't done yet, raises
280 InvalidStateError.
281 """
282 if self._state == _CANCELLED:
283 raise CancelledError
284 if self._state != _FINISHED:
285 raise InvalidStateError('Exception is not set.')
Victor Stinnere40c0782013-12-21 00:19:33 +0100286 self._log_traceback = False
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700287 if self._tb_logger is not None:
288 self._tb_logger.clear()
Victor Stinnere40c0782013-12-21 00:19:33 +0100289 self._tb_logger = None
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700290 return self._exception
291
292 def add_done_callback(self, fn):
293 """Add a callback to be run when the future becomes done.
294
295 The callback is called with a single argument - the future object. If
296 the future is already done when this is called, the callback is
297 scheduled with call_soon.
298 """
299 if self._state != _PENDING:
300 self._loop.call_soon(fn, self)
301 else:
302 self._callbacks.append(fn)
303
304 # New method not in PEP 3148.
305
306 def remove_done_callback(self, fn):
307 """Remove all instances of a callback from the "call when done" list.
308
309 Returns the number of callbacks removed.
310 """
311 filtered_callbacks = [f for f in self._callbacks if f != fn]
312 removed_count = len(self._callbacks) - len(filtered_callbacks)
313 if removed_count:
314 self._callbacks[:] = filtered_callbacks
315 return removed_count
316
317 # So-called internal methods (note: no set_running_or_notify_cancel()).
318
Victor Stinnera9acbe82014-07-05 15:29:41 +0200319 def _set_result_unless_cancelled(self, result):
320 """Helper setting the result only if the future was not cancelled."""
321 if self.cancelled():
322 return
323 self.set_result(result)
324
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700325 def set_result(self, result):
326 """Mark the future done and set its result.
327
328 If the future is already done when this method is called, raises
329 InvalidStateError.
330 """
331 if self._state != _PENDING:
332 raise InvalidStateError('{}: {!r}'.format(self._state, self))
333 self._result = result
334 self._state = _FINISHED
335 self._schedule_callbacks()
336
337 def set_exception(self, exception):
338 """Mark the future done and set an exception.
339
340 If the future is already done when this method is called, raises
341 InvalidStateError.
342 """
343 if self._state != _PENDING:
344 raise InvalidStateError('{}: {!r}'.format(self._state, self))
Victor Stinner95728982014-01-30 16:01:54 -0800345 if isinstance(exception, type):
346 exception = exception()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700347 self._exception = exception
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700348 self._state = _FINISHED
349 self._schedule_callbacks()
Victor Stinner4c3c6992013-12-19 22:42:40 +0100350 if _PY34:
Victor Stinnere40c0782013-12-21 00:19:33 +0100351 self._log_traceback = True
Victor Stinner4c3c6992013-12-19 22:42:40 +0100352 else:
Victor Stinner80f53aa2014-06-27 13:52:20 +0200353 self._tb_logger = _TracebackLogger(self, exception)
Victor Stinner4c3c6992013-12-19 22:42:40 +0100354 # Arrange for the logger to be activated after all callbacks
355 # have had a chance to call result() or exception().
356 self._loop.call_soon(self._tb_logger.activate)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700357
358 # Truly internal methods.
359
360 def _copy_state(self, other):
361 """Internal helper to copy state from another Future.
362
363 The other Future may be a concurrent.futures.Future.
364 """
365 assert other.done()
Guido van Rossum7a465642013-11-22 11:47:22 -0800366 if self.cancelled():
367 return
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700368 assert not self.done()
369 if other.cancelled():
370 self.cancel()
371 else:
372 exception = other.exception()
373 if exception is not None:
374 self.set_exception(exception)
375 else:
376 result = other.result()
377 self.set_result(result)
378
379 def __iter__(self):
380 if not self.done():
381 self._blocking = True
382 yield self # This tells Task to wait for completion.
383 assert self.done(), "yield from wasn't used with future"
384 return self.result() # May raise too.
385
386
387def wrap_future(fut, *, loop=None):
388 """Wrap concurrent.futures.Future object."""
389 if isinstance(fut, Future):
390 return fut
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700391 assert isinstance(fut, concurrent.futures.Future), \
392 'concurrent.futures.Future is expected, got {!r}'.format(fut)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700393 if loop is None:
394 loop = events.get_event_loop()
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700395 new_future = Future(loop=loop)
Guido van Rossum7a465642013-11-22 11:47:22 -0800396
397 def _check_cancel_other(f):
398 if f.cancelled():
399 fut.cancel()
400
401 new_future.add_done_callback(_check_cancel_other)
Guido van Rossum27b7c7e2013-10-17 13:40:50 -0700402 fut.add_done_callback(
403 lambda future: loop.call_soon_threadsafe(
404 new_future._copy_state, fut))
405 return new_future