Imaplib2: use proper tracebacks for exceptions

Get real tracebacks (at the point that calls Request.abort()
and not from the point that handles collected abort requests)
and pass them to our calling functions to ease debugging
of user problems.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Eygene Ryabinkin 2015-01-19 00:13:17 +03:00
parent f69613965f
commit 7ab779ab21

View File

@ -48,7 +48,7 @@ __author__ = "Piers Lauder <piers@janeelix.com>"
__URL__ = "http://imaplib2.sourceforge.net" __URL__ = "http://imaplib2.sourceforge.net"
__license__ = "Python License" __license__ = "Python License"
import binascii, errno, os, Queue, random, re, select, socket, sys, time, threading, zlib import binascii, errno, os, Queue, random, re, select, socket, sys, time, threading, traceback, zlib
select_module = select select_module = select
@ -160,21 +160,25 @@ class Request(object):
self.data = None self.data = None
def abort(self, typ, val): def abort_tb(self, typ, val, tb):
self.aborted = (typ, val) self.aborted = (typ, val, tb)
self.deliver(None) self.deliver(None)
def abort(self, typ, val):
self.abort_tb(typ, val, traceback.extract_stack())
def get_response(self, exc_fmt=None): def get_response(self, exc_fmt=None):
self.callback = None self.callback = None
if __debug__: self.parent._log(3, '%s:%s.ready.wait' % (self.name, self.tag)) if __debug__: self.parent._log(3, '%s:%s.ready.wait' % (self.name, self.tag))
self.ready.wait() self.ready.wait()
if self.aborted is not None: if self.aborted is not None:
typ, val = self.aborted typ, val, tb = self.aborted
if exc_fmt is None: if exc_fmt is None:
exc_fmt = '%s - %%s' % typ exc_fmt = '%s - %%s' % typ
raise typ(exc_fmt % str(val)) raise typ, typ(exc_fmt % str(val)), tb
return self.response return self.response
@ -1661,7 +1665,7 @@ class IMAP4(object):
if __debug__: self._log(1, 'starting') if __debug__: self._log(1, 'starting')
typ, val = self.abort, 'connection terminated' typ, val, tb = self.abort, 'connection terminated', None
while not self.Terminate: while not self.Terminate:
@ -1684,6 +1688,7 @@ class IMAP4(object):
if resp_timeout is not None and self.tagged_commands: if resp_timeout is not None and self.tagged_commands:
if __debug__: self._log(1, 'response timeout') if __debug__: self._log(1, 'response timeout')
typ, val = self.abort, 'no response after %s secs' % resp_timeout typ, val = self.abort, 'no response after %s secs' % resp_timeout
tb = traceback.extract_stack()
break break
continue continue
if self.idle_timeout > time.time(): if self.idle_timeout > time.time():
@ -1695,23 +1700,33 @@ class IMAP4(object):
if __debug__: self._log(1, 'inq None - terminating') if __debug__: self._log(1, 'inq None - terminating')
break break
# self.inq can contain tuple, it means we got an exception.
# For example of code that produces tuple see IMAP4._reader()
# and IMAP4._writer().
#
# XXX: may be it will be more explicit to create own exception
# XXX: class and pass it instead of tuple.
if not isinstance(line, basestring): if not isinstance(line, basestring):
typ, val = line typ, val, tb = line
break break
try: try:
self._put_response(line) self._put_response(line)
except: except:
typ, val = self.error, 'program error: %s - %s' % sys.exc_info()[:2] typ, val = self.error, 'program error: %s - %s' % sys.exc_info()[:2]
tb = sys.exc_info()[2]
break break
self.Terminate = True self.Terminate = True
if not tb:
tb = traceback.extract_stack()
if __debug__: self._log(1, 'terminating: %s' % repr(val)) if __debug__: self._log(1, 'terminating: %s' % repr(val))
while not self.ouq.empty(): while not self.ouq.empty():
try: try:
self.ouq.get_nowait().abort(typ, val) self.ouq.get_nowait().abort_tb(typ, val, tb)
except Queue.Empty: except Queue.Empty:
break break
self.ouq.put(None) self.ouq.put(None)
@ -1719,7 +1734,7 @@ class IMAP4(object):
self.commands_lock.acquire() self.commands_lock.acquire()
for name in self.tagged_commands.keys(): for name in self.tagged_commands.keys():
rqb = self.tagged_commands.pop(name) rqb = self.tagged_commands.pop(name)
rqb.abort(typ, val) rqb.abort_tb(typ, val, tb)
self.state_change_free.set() self.state_change_free.set()
self.commands_lock.release() self.commands_lock.release()
if __debug__: self._log(3, 'state_change_free.set') if __debug__: self._log(3, 'state_change_free.set')
@ -1801,7 +1816,7 @@ class IMAP4(object):
self._print_log() self._print_log()
if self.debug: self.debug += 4 # Output all if self.debug: self.debug += 4 # Output all
self._log(1, reason) self._log(1, reason)
self.inq.put((self.abort, reason)) self.inq.put((self.abort, reason, sys.exc_info()[2]))
break break
poll.unregister(self.read_fd) poll.unregister(self.read_fd)
@ -1865,7 +1880,7 @@ class IMAP4(object):
self._print_log() self._print_log()
if self.debug: self.debug += 4 # Output all if self.debug: self.debug += 4 # Output all
self._log(1, reason) self._log(1, reason)
self.inq.put((self.abort, reason)) self.inq.put((self.abort, reason, sys.exc_info()[2]))
break break
if __debug__: self._log(1, 'finished') if __debug__: self._log(1, 'finished')
@ -1878,6 +1893,7 @@ class IMAP4(object):
if __debug__: self._log(1, 'starting') if __debug__: self._log(1, 'starting')
reason = 'Terminated' reason = 'Terminated'
tb = None
while not self.Terminate: while not self.Terminate:
rqb = self.ouq.get() rqb = self.ouq.get()
@ -1889,15 +1905,19 @@ class IMAP4(object):
if __debug__: self._log(4, '> %s' % rqb.data) if __debug__: self._log(4, '> %s' % rqb.data)
except: except:
reason = 'socket error: %s - %s' % sys.exc_info()[:2] reason = 'socket error: %s - %s' % sys.exc_info()[:2]
tb = sys.exc_info()[2]
if __debug__: if __debug__:
if not self.Terminate: if not self.Terminate:
self._print_log() self._print_log()
if self.debug: self.debug += 4 # Output all if self.debug: self.debug += 4 # Output all
self._log(1, reason) self._log(1, reason)
rqb.abort(self.abort, reason) rqb.abort_tb(self.abort, reason, tb)
break break
self.inq.put((self.abort, reason)) if not tb:
tb = traceback.extract_stack()
self.inq.put((self.abort, reason, tb))
if __debug__: self._log(1, 'finished') if __debug__: self._log(1, 'finished')