bpo-30508: Don't log exceptions if Task/Future "cancel()" method called (#2050)
authorYury Selivanov <yury@magic.io>
Sun, 11 Jun 2017 13:49:18 +0000 (13:49 +0000)
committerGitHub <noreply@github.com>
Sun, 11 Jun 2017 13:49:18 +0000 (13:49 +0000)
Lib/asyncio/futures.py
Lib/asyncio/tasks.py
Lib/test/test_asyncio/test_futures.py
Lib/test/test_asyncio/test_tasks.py
Misc/NEWS
Modules/_asynciomodule.c

index 39721eaf00fd42a8ea24af21f9f9c82cbcaa2596..215f72d1910ecf7bbbd26af1e8784ae01b1bee4f 100644 (file)
@@ -107,6 +107,7 @@ class Future:
         change the future's state to cancelled, schedule the callbacks and
         return True.
         """
+        self._log_traceback = False
         if self._state != _PENDING:
             return False
         self._state = _CANCELLED
index e4533000e7985f3a429e4a699ebacc79105e944a..575d205404ae394148e353ed569c2a3f72a9b17c 100644 (file)
@@ -144,6 +144,7 @@ class Task(futures.Future):
         terminates with a CancelledError exception (even if cancel()
         was not called).
         """
+        self._log_traceback = False
         if self.done():
             return False
         if self._fut_waiter is not None:
index 99336f86ab824ebe80cde2fd90587136e0bdda44..5d4b2d2aa0efc0b73ec266389a5823dab91c820d 100644 (file)
@@ -318,6 +318,14 @@ class BaseFutureTests:
         del fut
         self.assertFalse(m_log.error.called)
 
+    @mock.patch('asyncio.base_events.logger')
+    def test_tb_logger_not_called_after_cancel(self, m_log):
+        fut = self._new_future(loop=self.loop)
+        fut.set_exception(Exception())
+        fut.cancel()
+        del fut
+        self.assertFalse(m_log.error.called)
+
     @mock.patch('asyncio.base_events.logger')
     def test_tb_logger_result_unretrieved(self, m_log):
         fut = self._new_future(loop=self.loop)
index 988b2885c30cd93362ffe1407c92cd54abcc070a..686387f4f21d85f52ae2583e1f75b4eda332151c 100644 (file)
@@ -1864,6 +1864,25 @@ class BaseTaskTests:
         })
         mock_handler.reset_mock()
 
+    @mock.patch('asyncio.base_events.logger')
+    def test_tb_logger_not_called_after_cancel(self, m_log):
+        loop = asyncio.new_event_loop()
+        self.set_event_loop(loop)
+
+        @asyncio.coroutine
+        def coro():
+            raise TypeError
+
+        @asyncio.coroutine
+        def runner():
+            task = self.new_task(loop, coro())
+            yield from asyncio.sleep(0.05, loop=loop)
+            task.cancel()
+            task = None
+
+        loop.run_until_complete(runner())
+        self.assertFalse(m_log.error.called)
+
     @mock.patch('asyncio.coroutines.logger')
     def test_coroutine_never_yielded(self, m_log):
         with set_coroutine_debug(True):
index 00187cf7fefe34693e27f0ec4c5825cc1f0eee13..0b0350ef739d66a60a6bea1d553a17b9fd7751ef 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -359,6 +359,9 @@ Extension Modules
 Library
 -------
 
+- bpo-30508: Don't log exceptions if Task/Future "cancel()" method was
+  called.
+
 - bpo-11822: The dis.dis() function now is able to disassemble nested
   code objects.
 
index 150ca198d27cfcdbb4bdf9b0b0c58e786993b534..b8a88e61d4aba45fac501158e66cc6bb4b6cee32 100644 (file)
@@ -306,6 +306,8 @@ future_add_done_callback(FutureObj *fut, PyObject *arg)
 static PyObject *
 future_cancel(FutureObj *fut)
 {
+    fut->fut_log_tb = 0;
+
     if (fut->fut_state != STATE_PENDING) {
         Py_RETURN_FALSE;
     }
@@ -639,6 +641,17 @@ FutureObj_get_log_traceback(FutureObj *fut)
     }
 }
 
+static int
+FutureObj_set_log_traceback(FutureObj *fut, PyObject *val)
+{
+    int is_true = PyObject_IsTrue(val);
+    if (is_true < 0) {
+        return -1;
+    }
+    fut->fut_log_tb = is_true;
+    return 0;
+}
+
 static PyObject *
 FutureObj_get_loop(FutureObj *fut)
 {
@@ -883,7 +896,8 @@ static PyMethodDef FutureType_methods[] = {
     {"_callbacks", (getter)FutureObj_get_callbacks, NULL, NULL},              \
     {"_result", (getter)FutureObj_get_result, NULL, NULL},                    \
     {"_exception", (getter)FutureObj_get_exception, NULL, NULL},              \
-    {"_log_traceback", (getter)FutureObj_get_log_traceback, NULL, NULL},      \
+    {"_log_traceback", (getter)FutureObj_get_log_traceback,                   \
+                       (setter)FutureObj_set_log_traceback, NULL},            \
     {"_source_traceback", (getter)FutureObj_get_source_traceback, NULL, NULL},
 
 static PyGetSetDef FutureType_getsetlist[] = {
@@ -1569,6 +1583,8 @@ static PyObject *
 _asyncio_Task_cancel_impl(TaskObj *self)
 /*[clinic end generated code: output=6bfc0479da9d5757 input=13f9bf496695cb52]*/
 {
+    self->task_log_tb = 0;
+
     if (self->task_state != STATE_PENDING) {
         Py_RETURN_FALSE;
     }