]> granicus.if.org Git - python/commitdiff
bpo-31249: Fix ref cycle in ThreadPoolExecutor (#3253)
authorVictor Stinner <victor.stinner@gmail.com>
Fri, 1 Sep 2017 12:46:44 +0000 (14:46 +0200)
committerGitHub <noreply@github.com>
Fri, 1 Sep 2017 12:46:44 +0000 (14:46 +0200)
[3.6] bpo-31249: Fix ref cycle in ThreadPoolExecutor

Lib/concurrent/futures/thread.py
Lib/test/test_concurrent_futures.py
Misc/NEWS.d/next/Library/2017-08-22-12-44-48.bpo-31249.STPbb9.rst [new file with mode: 0644]

index 03d276b63f63cae1cf711a4cb2236ed06c7b9b0a..5ade790e3db41002da43d7f3e8d8bb7b028be988 100644 (file)
@@ -53,8 +53,10 @@ class _WorkItem(object):
 
         try:
             result = self.fn(*self.args, **self.kwargs)
-        except BaseException as e:
-            self.future.set_exception(e)
+        except BaseException as exc:
+            self.future.set_exception(exc)
+            # Break a reference cycle with the exception 'exc'
+            self = None
         else:
             self.future.set_result(result)
 
index 92a3ebdd886c126ed21756d708d4bf7353c5ba7f..90dec61814cac6a0e0cf33b38ff890682e77d4ab 100644 (file)
@@ -59,11 +59,20 @@ class MyObject(object):
         pass
 
 
+class BaseTestCase(unittest.TestCase):
+    def setUp(self):
+        self._thread_key = test.support.threading_setup()
+
+    def tearDown(self):
+        test.support.reap_children()
+        test.support.threading_cleanup(*self._thread_key)
+
+
 class ExecutorMixin:
     worker_count = 5
 
     def setUp(self):
-        self._thread_cleanup = test.support.threading_setup()
+        super().setUp()
 
         self.t1 = time.time()
         try:
@@ -81,8 +90,7 @@ class ExecutorMixin:
             print("%.2fs" % dt, end=' ')
         self.assertLess(dt, 60, "synchronization issue: test lasted too long")
 
-        test.support.threading_cleanup(*self._thread_cleanup)
-        test.support.reap_children()
+        super().tearDown()
 
     def _prime_executor(self):
         # Make sure that the executor is ready to do work before running the
@@ -130,7 +138,7 @@ class ExecutorShutdownTest:
             f.result()
 
 
-class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, unittest.TestCase):
+class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, BaseTestCase):
     def _prime_executor(self):
         pass
 
@@ -186,7 +194,7 @@ class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, unittest.Tes
             t.join()
 
 
-class ProcessPoolShutdownTest(ProcessPoolMixin, ExecutorShutdownTest, unittest.TestCase):
+class ProcessPoolShutdownTest(ProcessPoolMixin, ExecutorShutdownTest, BaseTestCase):
     def _prime_executor(self):
         pass
 
@@ -323,7 +331,7 @@ class WaitTests:
         self.assertEqual(set([future2]), pending)
 
 
-class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, unittest.TestCase):
+class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, BaseTestCase):
 
     def test_pending_calls_race(self):
         # Issue #14406: multi-threaded race condition when waiting on all
@@ -341,7 +349,7 @@ class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, unittest.TestCase):
             sys.setswitchinterval(oldswitchinterval)
 
 
-class ProcessPoolWaitTests(ProcessPoolMixin, WaitTests, unittest.TestCase):
+class ProcessPoolWaitTests(ProcessPoolMixin, WaitTests, BaseTestCase):
     pass
 
 
@@ -390,11 +398,11 @@ class AsCompletedTests:
         self.assertEqual(len(completed), 1)
 
 
-class ThreadPoolAsCompletedTests(ThreadPoolMixin, AsCompletedTests, unittest.TestCase):
+class ThreadPoolAsCompletedTests(ThreadPoolMixin, AsCompletedTests, BaseTestCase):
     pass
 
 
-class ProcessPoolAsCompletedTests(ProcessPoolMixin, AsCompletedTests, unittest.TestCase):
+class ProcessPoolAsCompletedTests(ProcessPoolMixin, AsCompletedTests, BaseTestCase):
     pass
 
 
@@ -465,7 +473,7 @@ class ExecutorTest:
                 self.executor_type(max_workers=number)
 
 
-class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, unittest.TestCase):
+class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, BaseTestCase):
     def test_map_submits_without_iteration(self):
         """Tests verifying issue 11777."""
         finished = []
@@ -482,7 +490,7 @@ class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, unittest.TestCase):
                          (os.cpu_count() or 1) * 5)
 
 
-class ProcessPoolExecutorTest(ProcessPoolMixin, ExecutorTest, unittest.TestCase):
+class ProcessPoolExecutorTest(ProcessPoolMixin, ExecutorTest, BaseTestCase):
     def test_killed_child(self):
         # When a child process is abruptly terminated, the whole pool gets
         # "broken".
@@ -538,7 +546,7 @@ class ProcessPoolExecutorTest(ProcessPoolMixin, ExecutorTest, unittest.TestCase)
                       f1.getvalue())
 
 
-class FutureTests(unittest.TestCase):
+class FutureTests(BaseTestCase):
     def test_done_callback_with_result(self):
         callback_result = None
         def fn(callback_future):
diff --git a/Misc/NEWS.d/next/Library/2017-08-22-12-44-48.bpo-31249.STPbb9.rst b/Misc/NEWS.d/next/Library/2017-08-22-12-44-48.bpo-31249.STPbb9.rst
new file mode 100644 (file)
index 0000000..f11a668
--- /dev/null
@@ -0,0 +1,2 @@
+concurrent.futures: WorkItem.run() used by ThreadPoolExecutor now breaks a
+reference cycle between an exception object and the WorkItem object.