Making it simpler to gracefully exit threads

Currently, telling a thread to gracefully exit requires setting up some custom signaling mechanism, for example via a threading.Event. To make this process a bit more efficient, cleaner and simpler, what if some methods were added to:

  1. Set a soft interruption flag for a specific thread, such as threading.Thread.interrupt.
  2. Set the interruption flag for all threads, such as threading.interrupt_all_threads.
  3. Query whether the current thread’s interruption flag is set, such as threading.interruption_requested.

An example snippet demonstrating the use of these methods:

from time import sleep
from threading import Thread, interruption_requested, interrupt_all_threads

def loop():
  while not interruption_requested():
    sleep(1)
  print('exiting thread!')

threads = [Thread(target=loop) for i in range(8)]
for thread in threads:
  thread.start()

try:
  # tell the first four threads to stop after five seconds
  sleep(5)
  for thread in threads[:4]:
    thread.interrupt()
    thread.join()
  
  # tell the next four threads to stop after another five seconds
  sleep(5)
  for thread in threads[4:]:
    thread.interrupt()
    thread.join()

except KeyboardInterrupt:
  # tell them all to stop if they haven't already

  interrupt_all_threads()
  for thread in threads:
     thread.join()

Additionally, for convenience, situations such as unhandled SIGHUP, SIGINT/uncaught KeyboardInterrupt or a call to sys.exit in the main thread could automatically set all threads’ interruption flags.

You basically want built-in support for thread cancellation in Python? Something similar, on the Python-level, as pthread_cancel?

Doesn’t ThreadPoolExecutor already achieves that?

1 Like

Yes, there is concurrent.futures.Future.cancel().

I don’t know all the details of pthread_cancel, so can’t say for sure how much this idea lines up with that.

The main goal of this suggestion is to simplify a common pattern for graceful thread termination. There are multiple questions on stackoverflow regarding this topic. For example:

Most of these answers are a variation of using a threading.Event or some other shared state to tell the thread that it should exit.

Could you clarify which aspect of ThreadPoolExecutor achieves this and how?

My understanding is that concurrent.futures.Future.cancel can only stop calls that haven’t yet been executed, and there is no way to query the state of the future from within the call itself to tell whether it has been cancelled. Is that not the case?

Yeah - I don’t know if there is a way to see if the Future was cancelled from within that Future - but that’s a bit of a separate issue, is it not (if you can cancel it conveniently, do you care how)? The ‘cancel’ doc is also a bit ambiguous, but suggests that an executing thread could in principle be cancelled – but I’ve never used that API so don’t know. Anyway - I wonder what the core devs think of your request.

IIUC, what you are suggesting is simply a standardized place and access methods for the recommended practice of a termination flag.

+1 on this.

A possible extension: a check_interrupt() function that polls this flag, and if set raises ThreadExit exception, a subclass of SystemExit. This will let any finally: blocks execute on the way out and possibly caught and cancelled, if you choose to.

Another possible extension: Thread.enable_async_interrupt = True indicates that this thread is not going to poll the interruption flag, but it is ok for interruption to raise ThreadExit exception in the thread.

There is a python C API for this (PyThreadState_SetAsyncExc). It is not exposed to python code by default against “naive misuse”. I suggest that applying it only to threads that opt-in should be sufficient.

Finally, another flag, with the proposed name enable_interrupt_io would enable sending a signal to the thread using signal.pthread_kill() to interrupt any blocking I/O that could otherwise prevent the thread from stopping (+windows equivalent, if possible).

These would all keep the same interface to request interruption of a thread, but opt-in to progressively more intrusive implementations.

3 Likes