@@ -58,6 +58,7 @@ def main():
5858
5959from google .cloud .ndb import context as context_module
6060from google .cloud .ndb import _eventloop
61+ from google .cloud .ndb import exceptions
6162from google .cloud .ndb import _remote
6263
6364__all__ = [
@@ -232,20 +233,26 @@ def add_done_callback(self, callback):
232233 self ._callbacks .append (callback )
233234
234235 def cancel (self ):
235- """Cancel the task for this future.
236+ """Attempt to cancel the task for this future.
236237
237- Raises:
238- NotImplementedError: Always, not supported.
238+ If the task has already completed, this call will do nothing.
239+ Otherwise, this will attempt to cancel whatever task this future is
240+ waiting on. There is no specific guarantee the underlying task will be
241+ cancelled.
239242 """
240- raise NotImplementedError
243+ if not self .done ():
244+ self .set_exception (exceptions .Cancelled ())
241245
242246 def cancelled (self ):
243- """Get whether task for this future has been canceled .
247+ """Get whether the task for this future has been cancelled .
244248
245249 Returns:
246- :data:`False`: Always.
250+ :data:`True`: If this future's task has been cancelled, otherwise
251+ :data:`False`.
247252 """
248- return False
253+ return self ._exception is not None and isinstance (
254+ self ._exception , exceptions .Cancelled
255+ )
249256
250257 @staticmethod
251258 def wait_any (futures ):
@@ -278,6 +285,7 @@ def __init__(self, generator, context, info="Unknown"):
278285 super (_TaskletFuture , self ).__init__ (info = info )
279286 self .generator = generator
280287 self .context = context
288+ self .waiting_on = None
281289
282290 def _advance_tasklet (self , send_value = None , error = None ):
283291 """Advance a tasklet one step by sending in a value or error."""
@@ -324,6 +332,8 @@ def done_callback(yielded):
324332 # in Legacy) directly. Doing so, it has been found, can lead to
325333 # exceeding the maximum recursion depth. Queing it up to run on the
326334 # event loop avoids this issue by keeping the call stack shallow.
335+ self .waiting_on = None
336+
327337 error = yielded .exception ()
328338 if error :
329339 _eventloop .call_soon (self ._advance_tasklet , error = error )
@@ -332,19 +342,30 @@ def done_callback(yielded):
332342
333343 if isinstance (yielded , Future ):
334344 yielded .add_done_callback (done_callback )
345+ self .waiting_on = yielded
335346
336347 elif isinstance (yielded , _remote .RemoteCall ):
337348 _eventloop .queue_rpc (yielded , done_callback )
349+ self .waiting_on = yielded
338350
339351 elif isinstance (yielded , (list , tuple )):
340352 future = _MultiFuture (yielded )
341353 future .add_done_callback (done_callback )
354+ self .waiting_on = future
342355
343356 else :
344357 raise RuntimeError (
345358 "A tasklet yielded an illegal value: {!r}" .format (yielded )
346359 )
347360
361+ def cancel (self ):
362+ """Overrides :meth:`Future.cancel`."""
363+ if self .waiting_on :
364+ self .waiting_on .cancel ()
365+
366+ else :
367+ super (_TaskletFuture , self ).cancel ()
368+
348369
349370def _get_return_value (stop ):
350371 """Inspect `StopIteration` instance for return value of tasklet.
@@ -399,6 +420,11 @@ def _dependency_done(self, dependency):
399420 result = tuple ((future .result () for future in self ._dependencies ))
400421 self .set_result (result )
401422
423+ def cancel (self ):
424+ """Overrides :meth:`Future.cancel`."""
425+ for dependency in self ._dependencies :
426+ dependency .cancel ()
427+
402428
403429def tasklet (wrapped ):
404430 """
0 commit comments