Search code examples
pythonpython-multithreading

Gracefully exit using ThreadPoolExecutor


When using a ThreadPoolExecutor how do you gracefully exit on a signal interrupt? I would like to intercept a SIGINT and gracefully exit the process. I would like the currently running threads to finish, but no more to start, and to cancel all pending tasks.


Solution

  • In python 3.9 ThreadPoolExecutor#shutdown takes a cancel_futures argument that handles canceling the waiting tasks. In python 3.8 and below, though, this must be handled manually. Luckily we can use the code to do this in python 3.9 ourselves.

    import sys
    import queue
    from concurrent.futures import ThreadPoolExecutor
    
    def exit_threads( executor ):
        print( '\nWrapping up, please wait...' )
    
        py_version = sys.version_info
        if ( py_version.major == 3 ) and ( py_version.minor < 9 ):
            # py versions less than 3.9
            # Executor#shutdown does not accept
            # cancel_futures keyword
            # manually shutdown
            # code taken from https://github.com/python/cpython/blob/3.9/Lib/concurrent/futures/thread.py#L210
            
            # prevent new tasks from being submitted
            executor.shutdown( wait = False )
            while True:
                # cancel all waiting tasks
                try:
                    work_item = executor._work_queue.get_nowait()
                                    
                except queue.Empty:
                    break
                                    
                if work_item is not None:
                    work_item.future.cancel()
    
         else:
               executor.shutdown( cancel_futures = True )
                        
         sys.exit( 0 )
    
    
    executor = ThreadPoolExecutor( max_workers = 5 )            
    signal.signal( 
        signal.SIGINT, 
        lambda sig, frame: exit_threads( executor ) 
    )
    
    # run desired code here...