8

Is there a way to have the parent that spawned a new thread catch the spawned threads exception? Below is a real basic example of what I am trying to accomplish. It should stop counting when Exception is raised, but I don't know how to catch it. Are exceptions thread safe? I would love to be able to use the Subprocess module, but I am stuck using Python 2.3 and am not sure how else to do this. Possibly using the threading module?

import time
import thread

def test(): 
    try:
        test = thread.start_new_thread(watchdog, (5,))
        count(10)
    except:
        print('Stopped Counting')

def count(num):
    for i in range(num):
        print i
        time.sleep(1)

def watchdog(timeout):
    time.sleep(timeout)
    raise Exception('Ran out of time')

if __name__ == '__main__':
    test()

UPDATE

My original code was a little misleading. It am really looking for something more like this:

import time
import thread
import os

def test(): 
    try:
        test = thread.start_new_thread(watchdog, (5,))
        os.system('count_to_10.exe')
    except:
        print('Stopped Counting')

def watchdog(timeout):
    time.sleep(timeout)
    raise Exception('Ran out of time')

if __name__ == '__main__':
    test()

I am trying to create a watchdog to kill the os.system call if the program hangs up for some reason.

Das.Rot
  • 638
  • 4
  • 11
  • 25
  • Possible dupe of http://stackoverflow.com/questions/2829329/catch-a-threads-exception-in-the-caller-thread-in-python – synthesizerpatel Jul 28 '11 at 23:58
  • what are you trying to achieve, looks like you want to signal some event from child thread to parent thread? or do you really want to pass exceptions around? – Anurag Uniyal Jul 29 '11 at 00:47
  • I am trying to kill a `os.system` call if it is taking to long, most likely not the right way. – Das.Rot Aug 02 '11 at 15:53

4 Answers4

3

Why not something like this

def test(): 
    def exeption_cb():
        os._exit()
    test = thread.start_new_thread(watchdog, (5, exception_cb))
    os.system('count_to_10.exe')
    print('Stopped Counting')

def watchdog(timeout, callback):
    time.sleep(timeout)
    callback()

This will stop the entire process. Another thing you could do is to start os.system in a different thread, then countdown and then kill that thread. Something like this,

def system_call():
    os.system('count_to_10.exe')

system_thread = thread.start_new_thread(system_call)
time.sleep(timeout)
system_thread.kill()
  • thread.kill is what I am trying to accomplish, but unfortunately that command does not exist. – Das.Rot Aug 09 '11 at 18:54
  • But os.kill does exist, and works fine. Several of the answers to http://stackoverflow.com/questions/337863/python-popen-and-select-waiting-for-a-process-to-terminate-or-a-timeout demonstrate how to have a parent execute a system call with timeout, which it seems is really what you're trying to achieve. – Peter Aug 09 '11 at 22:59
  • As fantastic as it would be to use the `Subprocess` module, I can't because, as I stated in original post, I am stuck using 2.3. I was trying to find a work around for this, but it looks like until I am able to move to python 2.4 or higher I am unable to. As of now the modules used for the program I am working with are compiled in 2.3 and won't work in 2.4+. – Das.Rot Aug 10 '11 at 21:43
  • thread.kill() has to be handcoded. Please, try the threading module and use class based threads. You could accomplish a lot with that. –  Aug 11 '11 at 18:09
2

stuck using Python 2.3

Python 2.3 is like 10 years old now. Why are you still using it?

Possibly using the threading module

You should be using threading anyway.

You are probably thinking about the problem wrong though. You should probably create some classes and rethink the approach to your problem.

Also if you're creating a watchdog, it probably doesn't make much sense to have it in the same process as what you're doing. time.sleep() is a system call that a regular python Exception won't cancel anyway.

Falmarri
  • 47,727
  • 41
  • 151
  • 191
  • I am stuck using 2.3 because that is what PSS/E rev 30 python modules are compiled in and that is what we use. – Das.Rot Aug 02 '11 at 14:57
2

If what you're really trying to do is pass/handle an exception then I don't think you want to use a Subprocess, since the parent process can only "see" the status code (and output) produced by the child process - only in cases of immediate & catastrophic failure in the child process does an exception get "re-raised" in the parent: http://docs.python.org/library/subprocess.html#exceptions.

And (again if you're trying to pass/handle an exception) I'm not sure you want threads, either. After all, the whole point (IMO) of exceptions is to have something which can either be handled "by the caller" (inside a try block) or can provide meaningful backtrace information (the call sequence) if not handled. Neither idea really works with "throw" in one thread and "catch" in another.

If your real goal is to have one piece of logic "time out" another one, then I think it makes sense for your "watchdog" to be a separate process - either a "parent" that monitors output from a "child" (as well as time elapsed), or a "peer" that "watches" something like log lines and/or DB updates by the monitored process (as well as the clock). In neither case are exceptions particularly relevant. And I recommend taking a look at Alex Martelli's answer to this question: Using module 'subprocess' with timeout

This question also has a couple of good answers that are relevant to your question: Catch a thread's exception in the caller thread in Python

Community
  • 1
  • 1
Peter
  • 2,526
  • 1
  • 23
  • 32
  • I am trying to kill a `os.system` call if for some reason it hangs up. So what I was attempting was kill the parent process when the spawned watchdog returns with an exception. Is there something else other than `os.system` or `os.popen` that I can call that will immediately continue in Python that I could hold in a `while` loop until timeout or completion? – Das.Rot Aug 02 '11 at 15:15
  • You're still thinking too specifically. Tell us what you're trying to accomplish. `I am trying to kill a os.system call if for some reason it hangs up.` You will be really hard pressed to find a real solution to this. Your problem is most likely that the system call is hanging, not that you can't interrupt it. – Falmarri Aug 04 '11 at 00:21
  • Or you could think of it this way (which is not specific to Python, by the way) - if there's an system call that may "hang" for some reason which is outside your control, then you make the *parent* process be the "watchdog", and have the *child* process be the one which actually runs the system call. The parent can then monitor the child process in various ways - the child can write "heartbeat" messages to STDOUT (say every 30 seconds) that are monitored by the parent, for example, and the parent can decide when the child process is "hung" and kill it if necessary. – Peter Aug 04 '11 at 13:49
  • Falmarri - It is simple, I am calling a non-python program (in this example count_to_10.exe) and my python program must wait until its done to continue. Now consider instead of count_to_10 it was count_to_number, and the number was infinity. I want to have a watchdog that looks to see if the non-python program is taking too long and stop it, then raise an exception. – Das.Rot Aug 09 '11 at 18:59
1

I know you're stuck using Python 2.3, but if you could only make the (very) modest advancement to Python 2.4 then you could take advantage of this more straightforward approach, copied from an answer given by Yaroslav Bulatov to a question (recommended reading) about running an external command with timeout: Python, Popen and select - waiting for a process to terminate or a timeout

from threading import Timer
from subprocess import Popen, PIPE

def kill_proc():
    proc.kill()

proc = Popen("count_to_10.exe", shell=True)
t = Timer(60, kill_proc)
t.start()
proc.wait()

This is an example of a parent process timing out a child process, as mentioned in my earlier answer.

Community
  • 1
  • 1
Peter
  • 2,526
  • 1
  • 23
  • 32