Welcome, guest | Sign In | My Account | Store | Cart

This recipe presents two ways to time out the execution of a callable. It relies on signal.SIGALRM; I've only tested in on MacOSX. One way (TimedOutFn) works on Python 2.3.4, and the second uses the decorator syntax introduced in 2.4a. In this version, I've used the code from John Speno's page (http://www.pycs.net/users/0000231/). He's done a better job of handling the signals, and I like the try/finally expression.

Python, 97 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import signal, time

class TimedOutExc(Exception):
    def __init__(self, value = "Timed Out"):
        self.value = value
    def __str__(self):
        return repr(self.value)

def TimedOutFn(f, timeout, *args, **kwargs):
    def handler(signum, frame):
        raise TimedOutExc()
    
    old = signal.signal(signal.SIGALRM, handler)
    signal.alarm(timeout)
    try:
        result = f(*args, **kwargs)
    finally:
        signal.signal(signal.SIGALRM, old)
    signal.alarm(0)
    return result


def timed_out(timeout):
    def decorate(f):
        def handler(signum, frame):
            raise TimedOutExc()
        
        def new_f(*args, **kwargs):
            old = signal.signal(signal.SIGALRM, handler)
            signal.alarm(timeout)
            try:
                result = f(*args, **kwargs)
            finally:
                signal.signal(signal.SIGALRM, old)
            signal.alarm(0)
            return result
        
        new_f.func_name = f.func_name
        return new_f

    return decorate


def fn_1(secs):
    time.sleep(secs)
    return "Finished"

@timed_out(4)
def fn_2(secs):
    time.sleep(secs)
    return "Finished"

@timed_out(2)
def fn_3(secs):
    time.sleep(secs)
    return "Finished"

@timed_out(2)
def fn_4(secs):
    try:
        time.sleep(secs)
        return "Finished"
    except TimedOutExc:
        print "(Caught TimedOutExc, so cleaining up, and re-raising it) - ",
        raise TimedOutExc
        
if __name__ == '__main__':

    try:
        print "fn_1 (sleep 2, timeout 4): ",
        print TimedOutFn(fn_1, 4, 2)
    except TimedOutExc:
        print "took too long"
        
    try:
        print "fn_2 (sleep 2, timeout 4): ",
        print fn_2(2)
    except TimedOutExc:
        print "took too long"

    try:
        print "fn_1 (sleep 4, timeout 2): ",
        print TimedOutFn(fn_1, 2, 4)
    except TimedOutExc:
        print "took too long"
        
    try:
        print "fn_3 (sleep 4, timeout 2): ",
        print fn_3(4)
    except TimedOutExc:
        print "took too long"

    try:
        print "fn_4 (sleep 4, timeout 2): ",
        print fn_4(4)
    except TimedOutExc:
        print "took too long"

I needed to call a list of callables, and not have any one of them going on forever. It's pretty hard to tell by inspection whether a function is gong to take a long time to execute :), so the above recipe solved the problem for me. Each callable either runs to completion, or is stopped by the raising of an exception. You don't have any control over what part of the function will be running when the exception is raised, so it's important to "clean up after yourself", and fn_4 shows how to do that.

It's been developed and tested on OS X.

6 comments

chris wright (author) 19 years, 5 months ago  # | flag

ack!!! print strings and code not in sync! This version 1) fixes the inconsistency between the printed strings and the code and 2) shows how the TimedOutException can be caught to do any cleaning up required:

import signal, time

class TimedOutExc(Exception):
    def __init__(self, value = "Timed Out"):
        self.value = value
    def __str__(self):
        return repr(self.value)

def TimedOutFn(f, timeout, *args):
    def handler(signum, frame):
        raise TimedOutExc()

    signal.signal(signal.SIGALRM, handler)
    signal.alarm(timeout)

    return f(*args)

    signal.arlam(0)

def timed_out(timeout, *args):
    def decorate(f):
        def handler(signum, frame):
            raise TimedOutExc()

        def new_f(*args):
            signal.signal(signal.SIGALRM, handler)
            signal.alarm(timeout)
            return f(*args)
            signal.alarm(0)
        new_f.func_name = f.func_name
        return new_f
    return decorate


def fn_1(secs):
    time.sleep(secs)
    return "Finished"

@timed_out(4)
def fn_2(secs):
    time.sleep(secs)
    return "Finished"

@timed_out(2)
def fn_3(secs):
    time.sleep(secs)
    return "Finished"

@timed_out(2)
def fn_4(secs):
    try:
        time.sleep(secs)
        return "Finished"
    except TimedOutExc:
        print "Caught TimedOutExc, and re-raising it"
        raise TimedOutExc

if __name__ == '__main__':

    try:
        print "fn_1 (sleep 2, timeout 4): ",
        print TimedOutFn(fn_1, 4, 2)
    except TimedOutExc:
        print "took too long"

    try:
        print "fn_2 (sleep 2, timeout 4): ",
        print fn_2(2)
    except TimedOutExc:
        print "took too long"

    try:
        print "fn_1 (sleep 4, timeout 2): ",
        print TimedOutFn(fn_1, 2, 4)
    except TimedOutExc:
        print "took too long"

    try:
        print "fn_3 (sleep 4, timeout 2): ",
        print fn_3(4)
    except TimedOutExc:
        print "took too long"

    try:
        print "fn_4 (sleep 4, timeout 2): ",
        print fn_4(4)
    except TimedOutExc:
        print "took too long"


~/src/python caw$ python sig_test.py
fn_1 (sleep 2, timeout 4):  Finished
fn_2 (sleep 2, timeout 4):  Finished
fn_1 (sleep 4, timeout 2):  took too long
fn_3 (sleep 4, timeout 2):  took too long
fn_4 (sleep 4, timeout 2):  Caught TimedOutExc, and re-raising it
took too long

I'd be grateful if the editors could perhaps replace the code I submitted with this (perhaps more useful and less confusing) version!

Hamish Lawson 19 years, 5 months ago  # | flag

You can edit the original. Go to My Recipes and click the recipe's edit link.

chris wright (author) 19 years, 5 months ago  # | flag

Ignore the above comment! I've been hit by a clue stick, so have edited (and slightly improved) the recipe (by adding in **kwargs). My above comment is superfluous.

Paul Du Bois 19 years, 5 months ago  # | flag

reset alarm? You have this code:

return f(*args)
signal.arlam(0)   # sic

Does the typo'd signal.arlam() serve any purpose? I don't see how it could ever get executed.

marco mambelli 17 years, 5 months ago  # | flag

Alarm remains active in case of exception. I would modify the try block in the wrapper function as below adding also signal.alarm(0) in the finally clause to reset the timer. Else the signal could trigger TimedOutExc in some outside block if 'f' raises some other exception.

try:
    result = f(*args, **kwargs)
finally:
    signal.signal(signal.SIGALRM, old)
    signal.alarm(0)
JUJIUPENG 14 years, 2 months ago  # | flag

how can i do this under WINDOWS platform i want to timeout execution of an exe program outside my python script

Created by chris wright on Mon, 11 Oct 2004 (PSF)
Python recipes (4591)
chris wright's recipes (1)

Required Modules

Other Information and Tasks