@@ -342,6 +342,29 @@ def test_hang_issue12364(self):
342342 for f in fs :
343343 f .result ()
344344
345+ def test_cancel_futures (self ):
346+ executor = self .executor_type (max_workers = 3 )
347+ fs = [executor .submit (time .sleep , .1 ) for _ in range (50 )]
348+ executor .shutdown (cancel_futures = True )
349+ # We can't guarantee the exact number of cancellations, but we can
350+ # guarantee that *some* were cancelled. With setting max_workers to 3,
351+ # most of the submitted futures should have been cancelled.
352+ cancelled = [fut for fut in fs if fut .cancelled ()]
353+ self .assertTrue (len (cancelled ) >= 35 , msg = f"{ len (cancelled )= } " )
354+
355+ # Ensure the other futures were able to finish.
356+ # Use "not fut.cancelled()" instead of "fut.done()" to include futures
357+ # that may have been left in a pending state.
358+ others = [fut for fut in fs if not fut .cancelled ()]
359+ for fut in others :
360+ self .assertTrue (fut .done (), msg = f"{ fut ._state = } " )
361+ self .assertIsNone (fut .exception ())
362+
363+ # Similar to the number of cancelled futures, we can't guarantee the
364+ # exact number that completed. But, we can guarantee that at least
365+ # one finished.
366+ self .assertTrue (len (others ) > 0 , msg = f"{ len (others )= } " )
367+
345368 def test_hang_issue39205 (self ):
346369 """shutdown(wait=False) doesn't hang at exit with running futures.
347370
@@ -422,6 +445,22 @@ def test_thread_names_default(self):
422445 self .assertRegex (t .name , r'ThreadPoolExecutor-\d+_[0-4]$' )
423446 t .join ()
424447
448+ def test_cancel_futures_wait_false (self ):
449+ # Can only be reliably tested for TPE, since PPE often hangs with
450+ # `wait=False` (even without *cancel_futures*).
451+ rc , out , err = assert_python_ok ('-c' , """if True:
452+ from concurrent.futures import ThreadPoolExecutor
453+ from test.test_concurrent_futures import sleep_and_print
454+ if __name__ == "__main__":
455+ t = ThreadPoolExecutor()
456+ t.submit(sleep_and_print, .1, "apple")
457+ t.shutdown(wait=False, cancel_futures=True)
458+ """ .format (executor_type = self .executor_type .__name__ ))
459+ # Errors in atexit hooks don't change the process exit code, check
460+ # stderr manually.
461+ self .assertFalse (err )
462+ self .assertEqual (out .strip (), b"apple" )
463+
425464
426465class ProcessPoolShutdownTest (ExecutorShutdownTest ):
427466 def _prime_executor (self ):
0 commit comments