#9-2020-Mar-“Delay a function or setTimeout equivalent for python. How to do it and be aware of hanging threads”

Example use case: You are running a bash script and want to make sure it is closing

Decorator approach

import threading
from functools import wraps
from bash_utils import bash

def delay(delay=0.0):
    """
    Decorator delaying the execution of a function for a while.
    """

    def wrap(f):
        @wraps(f)
        def delayed(*args, **kwargs):
            timer = threading.Timer(delay, f, args=args, kwargs=kwargs)
            timer.start()

        return delayed

    return wrap

script_proc = bash('docker build image')

@delay(120)
def give_up_build():
    if not script_proc.done():
        script_proc.kill() 

give_up_build()

The decorator works, but if you are doing multiple builds it might lead to a lot of hanging threads. A better example is when you are testing your code, then pytest will mark the test as green, but the test will still be spinning until 2 minutes have passed.

Class with cancel

A better approach is to cancel the timer on success

import threading
from typing import Callable
from bash_utils import bash

class SafeDelay:
   def __init__(self, t: float, func: Callable, *args, **kwargs):
       self.t = t
       self.func = func
       self(*args, **kwargs)

   def __call__(self, *args, **kwargs):
       timer = threading.Timer(self.t, self.func, args=args, kwargs=kwargs)
       timer.start()
       self.timer = timer

   def cancel(self):
       if self.timer:
           self.timer.cancel()

script_proc = bash('docker build image')

def give_up_build():
   if not script_proc.done():
       script_proc.kill() 

give_up_build = SafeDelay(120, give_up_build)
script_proc.complete_flag.add_done_callback(lambda _: give_up_build.cancel()) # CANCEL

The difference is:

  1. The delay is not a decorator anymore and we call it immediately to initialize the self.timer

  2. We support cancel

  3. Notice the script_proc.complete_flag is a from concurrent.futures import Future If there is interest I will show how to create the bash in another article.

Conclusions

Spinning up threads is easy. But MAN can it be a pain in the ass when the test continues to spin, or the process doesn’t shutdown cleanly. So be careful with the lifetime of your threads and processes.

I will end the article with a few key takeaways from an excellent presentation on multiprocessing by Pamela McA’Nulty

  1. Don’t share pass messages

  2. Always clean up after yourself

  3. Handle TERM and INT signals

  4. Don’t ever wait forever

  5. Report and log all the things