tags:

views:

100

answers:

3

Hi,

I have a shell script that loops through a text file containing URL:s that I want to visit and take screenshots of.

All this is done and simple. The script initializes a class that when run creates a screenshot of each site in the list. Some sites take a very, very long time to load, and some might not be loaded at all. So I want to wrap the screengrabber-function in a timeout script, making the function return False if it couldn't finish within 10 seconds.

I'm content with the simplest solution possible, maybe setting a asynchronous timer that will return False after 10 seconds no matter what actually happens inside the function?

Ideas?

Thanks!

+1  A: 

The solution to this is multithreading: Execute your website loader code in one thread. Start a second Timer thread, which sends an interrupt to the worker thread after 10s.

Take a look at the Python Threading API.

Daniel M.
A: 

I have a reply here for this question.

piro
+2  A: 

The process for timing out an operations is described in the documentation for signal.

The basic idea is to use signal handlers to set an alarm for some time interval and raise an exception once that timer expires.

Here's an implementation that creates a decorator (save the following code as timeout.py).

from functools import wraps
import errno
import os
import signal

class TimeoutError(Exception):
    pass

def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
    def decorator(func):
        def _handle_timeout(signum, frame):
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)
            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
            return result

        return wraps(func)(wrapper)

    return decorator

This creates a decorator called @timeout that can be applied to any long running functions.

So, in your application code, you can use the decorator like so:

from timeout import timeout

# Timeout a long running function with the default expiry of 10 seconds.
@timeout
def long_running_function1():
    ...

# Timeout after 5 seconds
@timeout(5)
def long_running_function2():
    ...

# Timeout after 30 seconds, with the error "Connection timed out"
@timeout(30, os.strerror(errno.ETIMEDOUT))
def long_running_function3():
    ...
David Narayan
Beware that this is not thread-safe: if you're using multithreading, the signal will get caught by a random thread. For single-threaded programs though, this is the easiest solution.
Wim