@@ -59,6 +59,10 @@ def my_method(self):
5959 pass
6060
6161
62+ def make_dummy_object (_ ):
63+ return MyObject ()
64+
65+
6266class BaseTestCase (unittest .TestCase ):
6367 def setUp (self ):
6468 self ._thread_key = test .support .threading_setup ()
@@ -396,6 +400,38 @@ def test_duplicate_futures(self):
396400 completed = [f for f in futures .as_completed ([future1 ,future1 ])]
397401 self .assertEqual (len (completed ), 1 )
398402
403+ def test_free_reference_yielded_future (self ):
404+ # Issue #14406: Generator should not keep references
405+ # to finished futures.
406+ futures_list = [Future () for _ in range (8 )]
407+ futures_list .append (create_future (state = CANCELLED_AND_NOTIFIED ))
408+ futures_list .append (create_future (state = SUCCESSFUL_FUTURE ))
409+
410+ with self .assertRaises (futures .TimeoutError ):
411+ for future in futures .as_completed (futures_list , timeout = 0 ):
412+ futures_list .remove (future )
413+ wr = weakref .ref (future )
414+ del future
415+ self .assertIsNone (wr ())
416+
417+ futures_list [0 ].set_result ("test" )
418+ for future in futures .as_completed (futures_list ):
419+ futures_list .remove (future )
420+ wr = weakref .ref (future )
421+ del future
422+ self .assertIsNone (wr ())
423+ if futures_list :
424+ futures_list [0 ].set_result ("test" )
425+
426+ def test_correct_timeout_exception_msg (self ):
427+ futures_list = [CANCELLED_AND_NOTIFIED_FUTURE , PENDING_FUTURE ,
428+ RUNNING_FUTURE , SUCCESSFUL_FUTURE ]
429+
430+ with self .assertRaises (futures .TimeoutError ) as cm :
431+ list (futures .as_completed (futures_list , timeout = 0 ))
432+
433+ self .assertEqual (str (cm .exception ), '2 (of 4) futures unfinished' )
434+
399435
400436class ThreadPoolAsCompletedTests (ThreadPoolMixin , AsCompletedTests , BaseTestCase ):
401437 pass
@@ -421,6 +457,10 @@ def test_map(self):
421457 list (self .executor .map (pow , range (10 ), range (10 ))),
422458 list (map (pow , range (10 ), range (10 ))))
423459
460+ self .assertEqual (
461+ list (self .executor .map (pow , range (10 ), range (10 ), chunksize = 3 )),
462+ list (map (pow , range (10 ), range (10 ))))
463+
424464 def test_map_exception (self ):
425465 i = self .executor .map (divmod , [1 , 1 , 1 , 1 ], [2 , 3 , 0 , 5 ])
426466 self .assertEqual (i .__next__ (), (0 , 1 ))
@@ -471,6 +511,14 @@ def test_max_workers_negative(self):
471511 "than 0" ):
472512 self .executor_type (max_workers = number )
473513
514+ def test_free_reference (self ):
515+ # Issue #14406: Result iterator should not keep an internal
516+ # reference to result objects.
517+ for obj in self .executor .map (make_dummy_object , range (10 )):
518+ wr = weakref .ref (obj )
519+ del obj
520+ self .assertIsNone (wr ())
521+
474522
475523class ThreadPoolExecutorTest (ThreadPoolMixin , ExecutorTest , BaseTestCase ):
476524 def test_map_submits_without_iteration (self ):
0 commit comments