How to get a raised exception from other thread

P

Peter Hansen

dcrespo said:
In my above example, I want to catch in ProgramA the raised exception
in ProgramB.

Better not to call them "programs" since they're not separate programs,
just separate modules, one of which starts a thread using methods in the
other module.

And you haven't really answered the questions I asked. Please show
exactly where you expect to catch the exception, given the points I note
above. Before, after, or during the .start() call, or somewhere else?
Please, look at my example one more time.

Done. It didn't really help looking at it a third time. It's still
unclear what you want.

Maybe explaining the use case would help you help us help you. :)

Your example seems contrived. In the real code you want to write, why
do you expect an exception to occur? Will it be raised deliberately, as
you show in the example, or is it unexpected (and unhandled by any
try/except at the top level of the thread's run() method)? Or do you
really need to use an exception here?

I'm quite sure the problem you are trying to solve can be solved, but
you are still describing part of the solution you believe you need,
rather than explaining why you want to do this (which may let us show
you other, simpler or cleaner ways to accomplish your goals).

-Peter
 
D

dcrespo

Before, after, or during the .start() call, or somewhere else?

I'd like to catch *just after* the .start() call.
I'm quite sure the problem you are trying to solve can be solved, but
you are still describing part of the solution you believe you need,
rather than explaining why you want to do this (which may let us show
you other, simpler or cleaner ways to accomplish your goals).

The thing is that I have in ProgramB a class derived from
threading.Thread, that runs a TCPServer, listening to a port on local
ip address.

As you surely know, you have to specify certain parameters when
instantiating the tcpserver:

SocketServer.TCPServer(server_address, RequestHandlerClass)
where server_address is a tuple (ip,port)
ip has to be one of three modes of values (If I remember well):

1. A null string ''
Listens on all IP local address

2. A string containing the local IP address where you want it to listen
Listens only in the specified local IP address

3. A string containing "localhost".
Listens only for connections from "localhost".

Here comes the problem:
When you specify the case number 2, the IP must be valid for the
computer where the program runs, otherwise, it raises an exception
saying that "Can't assign requested address".
The TCPServer class, defined in a module, is ran (instantiatedly) from
the main program through a started thread. The thread also is in the
same module as TCPServer class.
It looks like (it's much more code than this. If you want the whole
code, tell me):

MainProgram.py
....
SrvrTCP = module.ThreadedTCPServer(ip,port)
SrvrTCP.start()
#Here, I want to know if the TCPServer started well.
....


module.py
....
class ThreadedTCPServer(threading.Thread):

def __init__(self, ip,port):
threading.Thread.__init__(self)
self.ip= ip
self.port= port

def run(self):
TCPServer((self.ip,self.port)) #Here, if the self.ip is
invalid, it raises an exception.
....
 
P

Peter Hansen

dcrespo said:
I'd like to catch *just after* the .start() call.

Excellent. That makes it pretty easy then, since even though you are
spawning a thread to do the work, your main thread isn't expected to
continue processing in parallel. See my draft solution below. If it
doesn't seem to suit, explain where it fails you and we can try to
evolve it (assuming we're on the same page at all here).
When you specify the case number 2, the IP must be valid for the
computer where the program runs, otherwise, it raises an exception
saying that "Can't assign requested address".

MainProgram.py
...
SrvrTCP = module.ThreadedTCPServer(ip,port)
SrvrTCP.start()
> #Here, I want to know if the TCPServer started well.

Leave that part as it is. We'll override the start() method in your
subclass to solve your problem.
module.py
...
class ThreadedTCPServer(threading.Thread):

def __init__(self, ip,port):
threading.Thread.__init__(self)
self.ip= ip
self.port= port
self.startError = None
self.startedEvent = threading.Event()


def start(self):
'''start thread and wait for successful start'''
threading.Thread.start(self)
self.startedEvent.wait()
if self.startError:
raise self.startError

def run(self):
TCPServer((self.ip,self.port)) #Here, if the self.ip is
invalid, it raises an exception.
...

We have to change the run() method slightly now:

def run(self):
try:
try:
TCPServer((self.ip, self.port))
except Exception, ex:
self.startError = ex
return
finally:
self.startedEvent.set()

# do the rest of the existing run() code here


Now that may not be perfect, since I'm not familiar with the TCPServer()
call. I've sort of assumed it does something quickly and returns, or
raises an exception if the input is bad. If it doesn't actually return
if it starts successfully, you would need a little different approach.
Probably adding a simple timeout to the self.startEvent.wait() call
would work.

Note also that you don't get the full original traceback with this
technique. I'm not sure you can, actually (though I can imagine that
with Python one could "stitch together" a traceback from a previous
exception as one raises a new exception). From what you describe,
however, it sounds like you just need to know that the exception
occurred, not the precise line of code down in TCPServer() where it was
originally raised.

I hope that helps and/or gives you some leads on a better solution.

-Peter
 
A

Antoon Pardon

Op 2005-10-18 said:
I'd like to catch *just after* the .start() call.


The thing is that I have in ProgramB a class derived from
threading.Thread, that runs a TCPServer, listening to a port on local
ip address.

As you surely know, you have to specify certain parameters when
instantiating the tcpserver:

SocketServer.TCPServer(server_address, RequestHandlerClass)
where server_address is a tuple (ip,port)
ip has to be one of three modes of values (If I remember well):

1. A null string ''
Listens on all IP local address

2. A string containing the local IP address where you want it to listen
Listens only in the specified local IP address

3. A string containing "localhost".
Listens only for connections from "localhost".

Here comes the problem:
When you specify the case number 2, the IP must be valid for the
computer where the program runs, otherwise, it raises an exception
saying that "Can't assign requested address".
The TCPServer class, defined in a module, is ran (instantiatedly) from
the main program through a started thread. The thread also is in the
same module as TCPServer class.
It looks like (it's much more code than this. If you want the whole
code, tell me):

MainProgram.py
...
SrvrTCP = module.ThreadedTCPServer(ip,port)
SrvrTCP.start()
#Here, I want to know if the TCPServer started well.
...


module.py
...
class ThreadedTCPServer(threading.Thread):

def __init__(self, ip,port):
threading.Thread.__init__(self)
self.ip= ip
self.port= port

def run(self):
TCPServer((self.ip,self.port)) #Here, if the self.ip is
invalid, it raises an exception.

Just a suggestion, but since it is the main thread you want notified,
you could use signals here. Something like the following:

MainProgram.py

class TCPProblem(Exception):
pass

def signalcallback(signum, frame):
raise TCPProblem

signal.signal(signal.SIGHUP, signalcallback)

SrvrTCP = module.ThreadedTCPServer(ip,port)
SrvrTCP.start()
....


module.py

mainpid = os.getpid()

class ThreadedTCPServer(threading.Thread):

def __init__(self, ip,port):
threading.Thread.__init__(self)
self.ip= ip
self.port= port

def run(self):
try:
TCPServer((self.ip,self.port)) #Here, if the self.ip is invalid, it raises an exception.
except ...:
os.kill(mainpid, signal.SIGHUP)
 
D

dcrespo

Now that may not be perfect, since I'm not familiar with the TCPServer()
call. I've sort of assumed it does something quickly and returns, or
raises an exception if the input is bad. If it doesn't actually return
if it starts successfully, you would need a little different approach.
Probably adding a simple timeout to the self.startEvent.wait() call
would work.

It's perfect! Thank you very much.

After the .start() call in the main thread, it just waits until was a
succesfull thread start. It's just perfect.

One more thing, I would like to catch the description string of the
error, so I can show it to the user in a Message Box. How can I do that
in the main threa, once that I allready catched the exception?

Thank you so much
 
D

dcrespo

Ok, sorry about the above question. I solved it adding this to the main
thread:

try:
SrvrTCP = module.ThreadedTCPServer(ip,port)
SrvrTCP.start()
except Exception, description:
MsgBox(self,"TCPServer
Error:\n\n"+str(description),title="TCPServer",style=wx.OK |
wx.ICON_ERROR)
return

Peter, thank you very much.
 
P

Peter Hansen

dcrespo said:
Ok, sorry about the above question. I solved it adding this to the main
thread:

try:
SrvrTCP = module.ThreadedTCPServer(ip,port)
SrvrTCP.start()
except Exception, description:
MsgBox(self,"TCPServer
Error:\n\n"+str(description),title="TCPServer",style=wx.OK |
wx.ICON_ERROR)
return

Peter, thank you very much.

You're quite welcome. It's nice to be able to provide a "perfect"
answer, for a change. :)

One suggestion about the above: "description" is actually the exception
instance (the object), not just the description. The except statement
can take two items after: the exception type(s) to catch and,
optionally, a name to bind to the exception instance. But since
exception objects know how to represent themselves as strings, calling
str() on it gives you the description you wanted. Therefore it would be
more readable/correct to say this:

except Exception, ex:
MsgBox(..... str(ex) ... )

But I'm happy it works either way!

-Peter
 
W

WalterHoward

Peter said:
As Dennis points out, your original attempt was destined to fail because
you were calling the method from the main thread, not the one you wanted
to kill. Threads don't magically execute any methods that are attached
to the Thread instance just because they're attached. You have to
actually call those methods *from* the thread, which means from the
run() method or from any of the routines it calls (whether they are
methods on that Thread or not), but it must be done in the context of
the thread you want to raise exceptions in or it won't work.

More importantly, you've now described your use case (and I hope that of
the OP as well, since he hasn't replied yet): killing threads.

This is an oft-discussed topic here, and searching the archives will
probably give you lots more answers, but the short answer is you cannot
kill a thread in Python (except by exiting the entire process). Instead,
as you've discovered, you must ask it nicely to exit. The nearly
canonical approach to doing that is as follows:

class MyThread(threading.Thread):
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self, *args, **kwargs)
self._keepRunning = True

def stop(self, timeout=None):
self._keepRunning = False
# optional: wait for thread to exit
self.join(timeout=timeout)

def run(self):
while self._keepRunning:
# do stuff here

Now you can do things like this:

thread = MyThread()
thread.start()
# other stuff...
thread.stop() # block here until the thread exits

I hope that clarifies things a little more...

-Peter
 
D

dcrespo

One suggestion about the above: "description" is actually the exception
instance (the object), not just the description.

Yes. In fact, it's a tuple. Maybe, I'll change it for just printing the
second item of it.

Thanks a lot.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,766
Messages
2,569,569
Members
45,045
Latest member
DRCM

Latest Threads

Top