Exceptions inside threads are propagated to the main thread. Here's an example
import threading
import time
from concurrent.futures import ThreadPoolExecutor, TimeoutError
def func(raise_exc):
print("Running in {}".format(threading.current_thread().name))
if raise_exc:
time.sleep(1)
raise Exception
time.sleep(3)
with ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(func, False), executor.submit(func, True)]
while len(futures) > 0:
for fut in futures[:]:
try:
# check if thread has finished its work, with timeout
result = fut.result(timeout=1)
except TimeoutError as exc:
print("Timeout.. retry in thread {}".format(threading.current_thread().name))
except Exception as exc:
print("Exception was thrown in thread {}, exiting".format(threading.current_thread().name))
# we remove this fut from the list, as it's finished
futures.remove(fut)
else:
# op succeeded
print("Thread finished successfully {}".format(threading.current_thread().name))
futures.remove(fut)
print("Bye")
Which outputs
➜ python3 exception-in-thread.py
Running in ThreadPoolExecutor-0_0
Running in ThreadPoolExecutor-0_1
Timeout.. retry in thread MainThread
Exception was thrown in thread MainThread, exiting
Timeout.. retry in thread MainThread
Thread finished successfully MainThread
Bye
But as you can see, exception in one thread isn't affecting other threads. If that's what you are after, you need to capture the signal in the main thread and send it to other active threads.
You can do so with a global variable to indicate if we are in a RUNNING state. Then, when exception propagates, we capture it and updating the RUNNING state. To signal other threads, we call shutdown on the threadpool object. This is how the looks:
import threading
import time
from concurrent.futures import ThreadPoolExecutor, TimeoutError
def func(raise_exc):
print("Running in {}".format(threading.current_thread().name))
if raise_exc:
time.sleep(1)
raise Exception
time.sleep(3)
RUNNING = True
LOCK = threading.Lock()
with ThreadPoolExecutor(max_workers=2) as executor:
futures = [executor.submit(func, False), executor.submit(func, True)]
while RUNNING:
for fut in futures[:]:
if not RUNNING:
break
try:
# check if thread has finished its work, with timeout
result = fut.result(timeout=1)
except TimeoutError as exc:
print("Timeout.. retry in thread {}".format(threading.current_thread().name))
except Exception as exc:
print("Exception was thrown in thread {}, exiting".format(threading.current_thread().name))
# we remove this fut from the list, as it's finished
with LOCK:
print("Stop execution due to exception..")
RUNNING = False
executor.shutdown(wait=False)
else:
# op succeeded
print("Thread finished successfully {}".format(threading.current_thread().name))
futures.remove(fut)
print("Bye")
Which outputs
➜ python3 exception-in-thread.py
Running in ThreadPoolExecutor-0_0
Running in ThreadPoolExecutor-0_1
Timeout.. retry in thread MainThread
Exception was thrown in thread MainThread, exiting
Stop execution due to exception..
Bye
Note that we protect the global with a lock, since more than one thread can access it at the same time.