Skip to content

Commit 60f3f1f

Browse files
authored
bpo-31249: Fix ref cycle in ThreadPoolExecutor (#3253)
[3.6] bpo-31249: Fix ref cycle in ThreadPoolExecutor
1 parent b5db7bb commit 60f3f1f

File tree

3 files changed

+26
-14
lines changed

3 files changed

+26
-14
lines changed

Lib/concurrent/futures/thread.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,10 @@ def run(self):
5353

5454
try:
5555
result = self.fn(*self.args, **self.kwargs)
56-
except BaseException as e:
57-
self.future.set_exception(e)
56+
except BaseException as exc:
57+
self.future.set_exception(exc)
58+
# Break a reference cycle with the exception 'exc'
59+
self = None
5860
else:
5961
self.future.set_result(result)
6062

Lib/test/test_concurrent_futures.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,20 @@ def my_method(self):
5959
pass
6060

6161

62+
class BaseTestCase(unittest.TestCase):
63+
def setUp(self):
64+
self._thread_key = test.support.threading_setup()
65+
66+
def tearDown(self):
67+
test.support.reap_children()
68+
test.support.threading_cleanup(*self._thread_key)
69+
70+
6271
class ExecutorMixin:
6372
worker_count = 5
6473

6574
def setUp(self):
66-
self._thread_cleanup = test.support.threading_setup()
75+
super().setUp()
6776

6877
self.t1 = time.time()
6978
try:
@@ -81,8 +90,7 @@ def tearDown(self):
8190
print("%.2fs" % dt, end=' ')
8291
self.assertLess(dt, 60, "synchronization issue: test lasted too long")
8392

84-
test.support.threading_cleanup(*self._thread_cleanup)
85-
test.support.reap_children()
93+
super().tearDown()
8694

8795
def _prime_executor(self):
8896
# Make sure that the executor is ready to do work before running the
@@ -130,7 +138,7 @@ def test_hang_issue12364(self):
130138
f.result()
131139

132140

133-
class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, unittest.TestCase):
141+
class ThreadPoolShutdownTest(ThreadPoolMixin, ExecutorShutdownTest, BaseTestCase):
134142
def _prime_executor(self):
135143
pass
136144

@@ -186,7 +194,7 @@ def test_thread_names_default(self):
186194
t.join()
187195

188196

189-
class ProcessPoolShutdownTest(ProcessPoolMixin, ExecutorShutdownTest, unittest.TestCase):
197+
class ProcessPoolShutdownTest(ProcessPoolMixin, ExecutorShutdownTest, BaseTestCase):
190198
def _prime_executor(self):
191199
pass
192200

@@ -323,7 +331,7 @@ def test_timeout(self):
323331
self.assertEqual(set([future2]), pending)
324332

325333

326-
class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, unittest.TestCase):
334+
class ThreadPoolWaitTests(ThreadPoolMixin, WaitTests, BaseTestCase):
327335

328336
def test_pending_calls_race(self):
329337
# Issue #14406: multi-threaded race condition when waiting on all
@@ -341,7 +349,7 @@ def future_func():
341349
sys.setswitchinterval(oldswitchinterval)
342350

343351

344-
class ProcessPoolWaitTests(ProcessPoolMixin, WaitTests, unittest.TestCase):
352+
class ProcessPoolWaitTests(ProcessPoolMixin, WaitTests, BaseTestCase):
345353
pass
346354

347355

@@ -390,11 +398,11 @@ def test_duplicate_futures(self):
390398
self.assertEqual(len(completed), 1)
391399

392400

393-
class ThreadPoolAsCompletedTests(ThreadPoolMixin, AsCompletedTests, unittest.TestCase):
401+
class ThreadPoolAsCompletedTests(ThreadPoolMixin, AsCompletedTests, BaseTestCase):
394402
pass
395403

396404

397-
class ProcessPoolAsCompletedTests(ProcessPoolMixin, AsCompletedTests, unittest.TestCase):
405+
class ProcessPoolAsCompletedTests(ProcessPoolMixin, AsCompletedTests, BaseTestCase):
398406
pass
399407

400408

@@ -465,7 +473,7 @@ def test_max_workers_negative(self):
465473
self.executor_type(max_workers=number)
466474

467475

468-
class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, unittest.TestCase):
476+
class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, BaseTestCase):
469477
def test_map_submits_without_iteration(self):
470478
"""Tests verifying issue 11777."""
471479
finished = []
@@ -482,7 +490,7 @@ def test_default_workers(self):
482490
(os.cpu_count() or 1) * 5)
483491

484492

485-
class ProcessPoolExecutorTest(ProcessPoolMixin, ExecutorTest, unittest.TestCase):
493+
class ProcessPoolExecutorTest(ProcessPoolMixin, ExecutorTest, BaseTestCase):
486494
def test_killed_child(self):
487495
# When a child process is abruptly terminated, the whole pool gets
488496
# "broken".
@@ -538,7 +546,7 @@ def test_traceback(self):
538546
f1.getvalue())
539547

540548

541-
class FutureTests(unittest.TestCase):
549+
class FutureTests(BaseTestCase):
542550
def test_done_callback_with_result(self):
543551
callback_result = None
544552
def fn(callback_future):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
concurrent.futures: WorkItem.run() used by ThreadPoolExecutor now breaks a
2+
reference cycle between an exception object and the WorkItem object.

0 commit comments

Comments
 (0)