64

I would like to call foo(n) but stop it if it runs for more than 10 seconds. What's a good way to do this?

I can see that I could in theory modify foo itself to periodically check how long it has been running for but I would prefer not to do that.

Wouter J
  • 41,455
  • 15
  • 107
  • 112
Simd
  • 19,447
  • 42
  • 136
  • 271
  • 2
    What do you have inside `foo`? Is it a loop? Are you going to run `foo` as a different thread? – ATOzTOA Feb 17 '13 at 11:05
  • I would really consider modifying `foo`, but otherwise see this question: http://stackoverflow.com/questions/492519/timeout-on-a-python-function-call or this one: http://stackoverflow.com/questions/4158502/python-kill-or-terminate-subprocess-when-timeout – The Nail Feb 17 '13 at 11:06
  • @ATOzTOA It's a complicated piece of code doing a complicated calculation. I didn't write that code. – Simd Feb 17 '13 at 11:06
  • @felipa So, do you run it in a different thread? – ATOzTOA Feb 17 '13 at 11:07
  • @ATOzTOA Currently there are no threads. Your link looks very promising however. – Simd Feb 17 '13 at 11:08
  • the answer to your question has to provide asynchronous call, which can either be provided by threading module, subprocess module or multiprocessing. All these modules provide ways, or wrappers can be written around them in order to kill the run after certian time has lapsed. Now, it depends on the need of your code as to which one it sohuld use. – GodMan Feb 17 '13 at 11:09

2 Answers2

76

Here you go:

import multiprocessing
import time

# Your foo function
def foo(n):
    for i in range(10000 * n):
        print "Tick"
        time.sleep(1)

if __name__ == '__main__':
    # Start foo as a process
    p = multiprocessing.Process(target=foo, name="Foo", args=(10,))
    p.start()

    # Wait 10 seconds for foo
    time.sleep(10)

    # Terminate foo
    p.terminate()

    # Cleanup
    p.join()

This will wait 10 seconds for foo and then kill it.

Update

Terminate the process only if it is running.

# If thread is active
if p.is_alive():
    print "foo is running... let's kill it..."

    # Terminate foo
    p.terminate()

Update 2 : Recommended

Use join with timeout. If foo finishes before timeout, then main can continue.

# Wait a maximum of 10 seconds for foo
# Usage: join([timeout in seconds])
p.join(10)

# If thread is active
if p.is_alive():
    print "foo is running... let's kill it..."

    # Terminate foo
    p.terminate()
    p.join()
ATOzTOA
  • 34,814
  • 22
  • 96
  • 117
  • 2
    Thanks! I would ideally like to only kill the process if it hasn't terminated already. Is there way to check for this? – Simd Feb 17 '13 at 17:22
  • Thanks very much (although `timeout` doesn't appear in your answer). This is very helpful and informative. If anyone ever upvotes this question (the linked questions don't have such useful answers it seems) then I will upvote you of course. I can't currently it seems. – Simd Feb 17 '13 at 18:27
  • Come on, using a signal is faster, and doesn't rely on a low level fork(). – LtWorf Feb 17 '13 at 21:01
  • @LtWorf If you are going to check for a variable in each loop iteration, then a global variable will suffice, no need for signals. – ATOzTOA Feb 18 '13 at 03:48
  • Minor correction: is_alive() is a method, not a property. – NattyBumppo Mar 02 '14 at 09:22
  • I am trying this solution, but it appears to significantly slow down the process I am running. (~5 seconds -> ~10 seconds) – StrugglingProgrammer Aug 24 '15 at 22:24
  • Why do you use: if __name__ = "main" ? Why does it matter if the script was not directly called? – JobHunter69 Jul 18 '17 at 11:16
  • 2
    @Goldname It doesn't really matter. It is a safe construct I normally use. You don't want the thread to run on import. – ATOzTOA Jul 19 '17 at 22:10
  • 4
    How we return the value from foo? – Mahendra S. Chouhan Feb 15 '19 at 14:17
  • @MahendraS.Chouhan You cannot as this is not a normal termination for the function, it may not be even reaching the return statement when we kill it. If you need return, you may need to use Signals. – ATOzTOA Feb 16 '19 at 14:31
  • If you have to return anything from foo(), this becomes a pain. Long term Python really needs to fix the global interpreter lock thing. I'm tired of constructs like this being the best we can do. – Pavel Komarov Jan 16 '20 at 04:35
10
import signal

#Sets an handler function, you can comment it if you don't need it.
signal.signal(signal.SIGALRM,handler_function) 

#Sets an alarm in 10 seconds
#If uncaught will terminate your process.
signal.alarm(10) 

The timeout is not very precise, but can do if you don't need extreme precision.

Another way is to use the resource module, and set the maximum CPU time.

LtWorf
  • 7,286
  • 6
  • 31
  • 45
  • 2
    What you be able to expand this a little to show how it terminates the function `foo` and not the whole python script for example? I want my script to carry on, just for the call to `foo` to timeout after 10 seconds. – Simd Feb 17 '13 at 21:26
  • Write a function handler to do that, for example in every iteration in the function check for a global variable, and change that variable in the signal handler. – LtWorf Feb 17 '13 at 21:32
  • @LtWorf OP already said the function is a complex one and not a single loop. So, this won't work. – ATOzTOA Feb 18 '13 at 03:46
  • I have to see a complex function that doesn't use loops or recursion. – LtWorf Feb 18 '13 at 12:21
  • 9
    just a heads up, signal only works in main thread , so for people using multiple threads it probably isn't a good idea to use signal module – Uddhav Mishra Nov 20 '18 at 07:22
  • @UddhavMishra Who uses multiple threads in python, when there is the lock anyway? – LtWorf Apr 02 '20 at 15:08
  • is handler_function a function I have to create myself? – Blaisem Jul 21 '23 at 04:21