Skip to content Skip to sidebar Skip to footer

Python Timeout Context Manager With Threads

I have timeout context manager that works perfectly with signals but it raises error in multithread mode because signals work only in main thread. def timeout_handler(signum, frame

Solution 1:

If the code guarded by the context manager is loop-based, consider handling this the way people handle thread killing. Killing another thread is generally unsafe, so the standard approach is to have the controlling thread set a flag that's visible to the worker thread. The worker thread periodically checks that flag and cleanly shuts itself down. Here's how you can do something analogous with timeouts:

classtimeout(object):
    def__init__(self, seconds):
        self.seconds = seconds
    def__enter__(self):
        self.die_after = time.time() + self.seconds
        return self
    def__exit__(self, type, value, traceback):
        pass    @propertydeftimed_out(self):
        return time.time() > self.die_after

Here's a single-threaded usage example:

with timeout(1) as t:
    whileTrue: # this will take a long time without a timeout# periodically check for timeoutsif t.timed_out:
            break# or raise an exception# do some "useful" workprint"."
        time.sleep(0.2)

and a multithreaded one:

import thread
defprint_for_n_secs(string, seconds):
    with timeout(seconds) as t:
        whileTrue:
            if t.timed_out:
                break# or raise an exceptionprint string,
            time.sleep(0.5)

for i in xrange(5):
    thread.start_new_thread(print_for_n_secs,
                            ('thread%d' % (i,), 2))
    time.sleep(0.25)

This approach is more intrusive than using signals but it works for arbitrary threads.

Solution 2:

I cannot see a way of doing what you are proposing with a context manager, you cannot yield the flow from one thread to another. What I would do is wrap your function with an interrutable thread with the timeout. Here is a recipe for that.

You will have an extra thread and the syntax won't be as nice but it would work.

Solution 3:

I know it's late but I'm only just reading this, but what about creating your own signaller/context manager? I'm new to python would love feedback from experienced devs this implementation.

This is based off of the answer from "Mr Fooz"

classTimeoutSignaller(Thread):
    def__init__(self, limit, handler):
        Thread.__init__(self)
        self.limit = limit
        self.running = True
        self.handler = handler
        assertcallable(handler), "Timeout Handler needs to be a method"defrun(self):
        timeout_limit = datetime.datetime.now() + datetime.timedelta(seconds=self.limit)
        while self.running:
            if datetime.datetime.now() >= timeout_limit:
                self.handler()
                self.stop_run()
                breakdefstop_run(self):
        self.running = FalseclassProcessContextManager:
    def__init__(self, process, seconds=0, minutes=0, hours=0):
        self.seconds = (hours * 3600) + (minutes * 60) + seconds
        self.process = process
        self.signal = TimeoutSignaller(self.seconds, self.signal_handler)

    def__enter__(self):
        self.signal.start()
        return self.process

    def__exit__(self, exc_type, exc_val, exc_tb):
        self.signal.stop_run()

    defsignal_handler(self):
        # Make process terminate however you like# using self.process referenceraise TimeoutError("Process took too long to execute")

Use case:

withProcessContextManager(my_proc) as p:
    # do stuff e.g.
    p.execute()

Solution 4:

Timeouts for system calls are done with signals. Most blocking system calls return with EINTR when a signal happens, so you can use alarm to implement timeouts.

Here's a context manager that works with most system calls, causing IOError to be raised from a blocking system call if it takes too long.

import signal, errno
from contextlib import contextmanager
import fcntl

@contextmanagerdeftimeout(seconds):
    deftimeout_handler(signum, frame):
        pass

    original_handler = signal.signal(signal.SIGALRM, timeout_handler)

    try:
        signal.alarm(seconds)
        yieldfinally:
        signal.alarm(0)
        signal.signal(signal.SIGALRM, original_handler)

with timeout(1):
    f = open("test.lck", "w")
    try:
        fcntl.flock(f.fileno(), fcntl.LOCK_EX)
    except IOError, e:
        if e.errno != errno.EINTR:
            raise e
        print"Lock timed out"

Post a Comment for "Python Timeout Context Manager With Threads"