WebOrCode
WebOrCode

Reputation: 7302

Automatic restart of thread if unexpected exception

This ie example program:

import logging
import threading
import random
import time
import sys

def Thread1(a, b):
    logging.info('INIT')

    while True:
        r = random.randint(a, b)
    
        logging.debug(f'sleep for {r} seconds')
        logging.debug(f'5 / {r} = { 5 / r}') # possible: ZeroDivisionError: division by zero
        time.sleep(r)
        continue

    logging.info('Exiting')


def Thread2(a, b):
    logging.info('INIT')

    while True:
        r = random.randint(a, b)
        logging.debug(f'5 / {r} = { 5 / r}') # possible: ZeroDivisionError: division by zero

        logging.debug(f'sleep for {r} seconds')
        time.sleep(r)
        continue

    logging.info('Exiting')


#
# my_excepthook
#
def my_excepthook(args):
    #print(f'In excepthook {args=} --- {type(args)}')

    exc_type, exc_value, exc_traceback = sys.exc_info()
    print( 'Handling %s exception with message "%s" in %s' % \
        (exc_type.__name__, exc_value, threading.current_thread().name))

    if threading.current_thread().name == 'Thread_1':
        t1 = threading.Thread(name='Thread_1', target=Thread1, args=(0, 4), daemon=False)
        t1.start()

    if threading.current_thread().name == 'Thread_2':
        t2 = threading.Thread(name='Thread_2', target=Thread2, args=(0, 4), daemon=False)
        t2.start()

threading.excepthook = my_excepthook


if __name__ == "__main__":
    format='%(asctime)s - %(threadName)10s - %(lineno)4d - %(message)s'
    logging.basicConfig(level=logging.DEBUG, format=format)

    #
    # When there is exception, thread will stop working
    #
    t1 = threading.Thread(name='Thread_1', target=Thread1, args=(0, 4), daemon=False)
    t1.start()

    #
    # When there is exception, thread will stop working
    #
    t2 = threading.Thread(name='Thread_2', target=Thread2, args=(0, 4), daemon=False)       
    t2.start()

    #
    # How to restart thread manually
    #
    # logging.debug('Before Main loop')
    # while True:
    #     time.sleep(5)  # how often to check

    #     if t1.is_alive() == False:
    #         logging.critical('Restarting Thread_1')
    #         t1 = threading.Thread(name='Thread_1', target=Thread1, args=(0, 10), daemon=True)
    #         t1.start()

    #     if t2.is_alive() == False:
    #         logging.critical('Restarting Thread_2')
    #         t2 = threading.Thread(name='Thread_2', target=Thread1, args=(0, 10), daemon=True)
    #         t2.start()
   
    logging.info('MAIN END') 

My question if what is Python way to restart thread ?

So far I have found 2 alternatives ?

First: is to have infinite loop ate end of main program, where check is_alive() is called on each thread and restart it if necessary.

Second: is to use threading.excepthook with custom function. Because in both alternatives I need to check is_alive()on each thread, they are more or less same approach. Only benefit of threading.excepthook is that there is no delay in restarting thread.

Am I missing something, is there a better way ?

Any feedback appreciated.

Upvotes: 1

Views: 851

Answers (1)

Artiom  Kozyrev
Artiom Kozyrev

Reputation: 3846

You can try to restructure your code and use ThreadPoolExecutor and pool.submit, if any error happens inside pool's thread, the error will be ignored. On the other hand, my personal choice is to wrap all code in other threads in try ... except Exception block.

import logging
import random
import time
from concurrent.futures import ThreadPoolExecutor
from functools import partial


def some_func(_, a, b):
    r = random.randint(a, b)
    logging.debug(f'sleep for {r} seconds')
    logging.debug(f'5 / {r} = {5 / r}')  # possible: ZeroDivisionError: division by zero
    time.sleep(r)


if __name__ == '__main__':
    logging.basicConfig(format="%(threadName)s | %(asctime)s | %(msg)s", level=logging.DEBUG)
    # better to generate tasks in Main thread, than make infinite loop in child thread
    tasks = [_ for _ in range(10000)] 
    with ThreadPoolExecutor(4) as pool:
        # send tasks to other threads
        for t in tasks:
            pool.submit(partial(some_func, a=0, b=1), t)

Upvotes: 1

Related Questions