From dae1963cf38f730291126b7dadfda89ffb21cefd Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
 <31488909+miss-islington@users.noreply.github.com>
Date: Mon, 29 Mar 2021 10:53:14 -0700
Subject: bpo-35930: Raising an exception raised in a "future" instance will
 create reference cycles (GH-24995) (#25071)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Before: https://lists.es.python.org/pipermail/general/attachments/20201229/0c14bc58/attachment-0002.png

After: https://lists.es.python.org/pipermail/general/attachments/20201229/0c14bc58/attachment-0003.png
(cherry picked from commit 32430aadadf6e012e39167d3c18a24e49fb84874)

Co-authored-by: Jesús Cea <jcea@jcea.es>

Co-authored-by: Jesús Cea <jcea@jcea.es>
---
 Lib/concurrent/futures/_base.py                    | 38 +++++++++++++---------
 .../2021-03-23-17-18-56.bpo-35930.RZ51pM.rst       |  2 ++
 2 files changed, 25 insertions(+), 15 deletions(-)
 create mode 100644 Misc/NEWS.d/next/Library/2021-03-23-17-18-56.bpo-35930.RZ51pM.rst

diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
index 6001e3b..fd2b244 100644
--- a/Lib/concurrent/futures/_base.py
+++ b/Lib/concurrent/futures/_base.py
@@ -385,7 +385,11 @@ class Future(object):
 
     def __get_result(self):
         if self._exception:
-            raise self._exception
+            try:
+                raise self._exception
+            finally:
+                # Break a reference cycle with the exception in self._exception
+                self = None
         else:
             return self._result
 
@@ -425,20 +429,24 @@ class Future(object):
                 timeout.
             Exception: If the call raised then that exception will be raised.
         """
-        with self._condition:
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                raise CancelledError()
-            elif self._state == FINISHED:
-                return self.__get_result()
-
-            self._condition.wait(timeout)
-
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                raise CancelledError()
-            elif self._state == FINISHED:
-                return self.__get_result()
-            else:
-                raise TimeoutError()
+        try:
+            with self._condition:
+                if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                    raise CancelledError()
+                elif self._state == FINISHED:
+                    return self.__get_result()
+
+                self._condition.wait(timeout)
+
+                if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+                    raise CancelledError()
+                elif self._state == FINISHED:
+                    return self.__get_result()
+                else:
+                    raise TimeoutError()
+        finally:
+            # Break a reference cycle with the exception in self._exception
+            self = None
 
     def exception(self, timeout=None):
         """Return the exception raised by the call that the future represents.
diff --git a/Misc/NEWS.d/next/Library/2021-03-23-17-18-56.bpo-35930.RZ51pM.rst b/Misc/NEWS.d/next/Library/2021-03-23-17-18-56.bpo-35930.RZ51pM.rst
new file mode 100644
index 0000000..71c6012
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2021-03-23-17-18-56.bpo-35930.RZ51pM.rst
@@ -0,0 +1,2 @@
+Raising an exception raised in a "future" instance will create reference
+cycles.
-- 
cgit v0.12