/head: changeset 78
Revamped thread system to handle exceptions better
This commit is contained in:
parent
2486a7796c
commit
daf4fd99ac
@ -1,3 +1,9 @@
|
|||||||
|
offlineimap (2.0.1) unstable; urgency=low
|
||||||
|
|
||||||
|
* Fixed a bug with not properly propogating foldersep changes.
|
||||||
|
|
||||||
|
-- John Goerzen <jgoerzen@complete.org> Thu, 4 Jul 2002 09:07:06 -0500
|
||||||
|
|
||||||
offlineimap (2.0.0) unstable; urgency=low
|
offlineimap (2.0.0) unstable; urgency=low
|
||||||
|
|
||||||
* This code is now multithreaded. New config file options control the
|
* This code is now multithreaded. New config file options control the
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from offlineimap import imaplib, imaputil, imapserver, repository, folder, mbnames, threadutil
|
from offlineimap import imaplib, imaputil, imapserver, repository, folder, mbnames, threadutil
|
||||||
from offlineimap.threadutil import InstanceLimitedThread
|
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
|
||||||
import re, os, os.path, offlineimap, sys
|
import re, os, os.path, offlineimap, sys
|
||||||
from ConfigParser import ConfigParser
|
from ConfigParser import ConfigParser
|
||||||
from threading import *
|
from threading import *
|
||||||
@ -111,6 +111,7 @@ def syncaccount(accountname, *args):
|
|||||||
(accountname, remotefolder.getvisiblename()),
|
(accountname, remotefolder.getvisiblename()),
|
||||||
args = (accountname, remoterepos, remotefolder, localrepos,
|
args = (accountname, remoterepos, remotefolder, localrepos,
|
||||||
statusrepos))
|
statusrepos))
|
||||||
|
thread.setDaemon(1)
|
||||||
thread.start()
|
thread.start()
|
||||||
folderthreads.append(thread)
|
folderthreads.append(thread)
|
||||||
threadutil.threadsreset(folderthreads)
|
threadutil.threadsreset(folderthreads)
|
||||||
@ -178,12 +179,15 @@ def syncitall():
|
|||||||
target = syncaccount,
|
target = syncaccount,
|
||||||
name = "syncaccount-%s" % accountname,
|
name = "syncaccount-%s" % accountname,
|
||||||
args = (accountname,))
|
args = (accountname,))
|
||||||
|
thread.setDaemon(1)
|
||||||
thread.start()
|
thread.start()
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
# Wait for the threads to finish.
|
# Wait for the threads to finish.
|
||||||
threadutil.threadsreset(threads)
|
threadutil.threadsreset(threads)
|
||||||
mbnames.genmbnames(config, mailboxes)
|
mbnames.genmbnames(config, mailboxes)
|
||||||
|
|
||||||
|
def sync_with_timer():
|
||||||
|
currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE')
|
||||||
syncitall()
|
syncitall()
|
||||||
if config.has_option('general', 'autorefresh'):
|
if config.has_option('general', 'autorefresh'):
|
||||||
refreshperiod = config.getint('general', 'autorefresh') * 60
|
refreshperiod = config.getint('general', 'autorefresh') * 60
|
||||||
@ -193,3 +197,27 @@ if config.has_option('general', 'autorefresh'):
|
|||||||
else:
|
else:
|
||||||
syncitall()
|
syncitall()
|
||||||
|
|
||||||
|
def threadexited(thread):
|
||||||
|
if thread.getExitCause() == 'EXCEPTION':
|
||||||
|
ui.threadException(thread) # Expected to terminate
|
||||||
|
sys.exit(100) # Just in case...
|
||||||
|
os._exit(100)
|
||||||
|
elif thread.getExitMessage() == 'SYNC_WITH_TIMER_TERMINATE':
|
||||||
|
ui.terminate()
|
||||||
|
# Just in case...
|
||||||
|
sys.exit(100)
|
||||||
|
os._exit(100)
|
||||||
|
else:
|
||||||
|
ui.threadExited(thread)
|
||||||
|
|
||||||
|
threadutil.initexitnotify()
|
||||||
|
t = ExitNotifyThread(target=sync_with_timer, name='sync_with_timer')
|
||||||
|
t.setDaemon(1)
|
||||||
|
t.start()
|
||||||
|
try:
|
||||||
|
threadutil.exitnotifymonitorloop(threadexited)
|
||||||
|
except:
|
||||||
|
ui.mainException() # Also expected to terminate.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -200,6 +200,7 @@ class BaseFolder:
|
|||||||
self.getcopyinstancelimit(),
|
self.getcopyinstancelimit(),
|
||||||
target = self.copymessageto,
|
target = self.copymessageto,
|
||||||
args = (uid, applyto))
|
args = (uid, applyto))
|
||||||
|
thread.setDaemon(1)
|
||||||
thread.start()
|
thread.start()
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
else:
|
else:
|
||||||
|
@ -17,6 +17,12 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from threading import *
|
from threading import *
|
||||||
|
from StringIO import StringIO
|
||||||
|
import sys, traceback
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# General utilities
|
||||||
|
######################################################################
|
||||||
|
|
||||||
def semaphorereset(semaphore, originalstate):
|
def semaphorereset(semaphore, originalstate):
|
||||||
"""Wait until the semaphore gets back to its original state -- all acquired
|
"""Wait until the semaphore gets back to its original state -- all acquired
|
||||||
@ -35,28 +41,121 @@ def threadsreset(threadlist):
|
|||||||
for thread in threadlist:
|
for thread in threadlist:
|
||||||
thread.join()
|
thread.join()
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Exit-notify threads
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
exitcondition = Condition(Lock())
|
||||||
|
exitthread = None
|
||||||
|
inited = 0
|
||||||
|
|
||||||
|
def initexitnotify():
|
||||||
|
"""Initialize the exit notify system. This MUST be called from the
|
||||||
|
SAME THREAD that will call monitorloop BEFORE it calls monitorloop.
|
||||||
|
This SHOULD be called before the main thread starts any other
|
||||||
|
ExitNotifyThreads, or else it may miss the ability to catch the exit
|
||||||
|
status from them!"""
|
||||||
|
exitcondition.acquire()
|
||||||
|
|
||||||
|
def exitnotifymonitorloop(callback):
|
||||||
|
"""Enter an infinite "monitoring" loop. The argument, callback,
|
||||||
|
defines the function to call when an ExitNotifyThread has terminated.
|
||||||
|
That function is called with a single argument -- the ExitNotifyThread
|
||||||
|
that has terminated. The monitor will not continue to monitor for
|
||||||
|
other threads until the function returns, so if it intends to perform
|
||||||
|
long calculations, it should start a new thread itself -- but NOT
|
||||||
|
an ExitNotifyThread, or else an infinite loop may result. Furthermore,
|
||||||
|
the monitor will hold the lock all the while the other thread is waiting.
|
||||||
|
"""
|
||||||
|
global exitcondition, exitthread
|
||||||
|
while 1: # Loop forever.
|
||||||
|
while exitthread == None:
|
||||||
|
exitcondition.wait(1)
|
||||||
|
callback(exitthread)
|
||||||
|
exitthread = None
|
||||||
|
|
||||||
|
|
||||||
|
class ExitNotifyThread(Thread):
|
||||||
|
"""This class is designed to alert a "monitor" to the fact that a thread has
|
||||||
|
exited and to provide for the ability for it to find out why."""
|
||||||
|
def run(self):
|
||||||
|
global exitcondition, exitthread
|
||||||
|
try:
|
||||||
|
Thread.run(self)
|
||||||
|
except:
|
||||||
|
self.setExitCause('EXCEPTION')
|
||||||
|
self.setExitException(sys.exc_info()[1])
|
||||||
|
sbuf = StringIO()
|
||||||
|
traceback.print_exc(file = sbuf)
|
||||||
|
self.setExitStackTrace(sbuf.getvalue())
|
||||||
|
else:
|
||||||
|
self.setExitCause('NORMAL')
|
||||||
|
if not hasattr(self, 'exitmessage'):
|
||||||
|
self.setExitMessage(None)
|
||||||
|
exitcondition.acquire()
|
||||||
|
exitthread = self
|
||||||
|
exitcondition.notify()
|
||||||
|
exitcondition.release()
|
||||||
|
|
||||||
|
def setExitCause(self, cause):
|
||||||
|
self.exitcause = cause
|
||||||
|
def getExitCause(self):
|
||||||
|
"""Returns the cause of the exit, one of:
|
||||||
|
'EXCEPTION' -- the thread aborted because of an exception
|
||||||
|
'NORMAL' -- normal termination."""
|
||||||
|
return self.exitcause
|
||||||
|
def setExitException(self, exc):
|
||||||
|
self.exitexception = exc
|
||||||
|
def getExitException(self):
|
||||||
|
"""If getExitCause() is 'EXCEPTION', holds the value from
|
||||||
|
sys.exc_info()[1] for this exception."""
|
||||||
|
return self.exitexception
|
||||||
|
def setExitStackTrace(self, st):
|
||||||
|
self.exitstacktrace = st
|
||||||
|
def getExitStackTrace(self):
|
||||||
|
"""If getExitCause() is 'EXCEPTION', returns a string representing
|
||||||
|
the stack trace for this exception."""
|
||||||
|
return self.exitstacktrace
|
||||||
|
def setExitMessage(self, msg):
|
||||||
|
"""Sets the exit message to be fetched by a subsequent call to
|
||||||
|
getExitMessage. This message may be any object or type except
|
||||||
|
None."""
|
||||||
|
self.exitmessage = msg
|
||||||
|
def getExitMessage(self):
|
||||||
|
"""For any exit cause, returns the message previously set by
|
||||||
|
a call to setExitMessage(), or None if there was no such message
|
||||||
|
set."""
|
||||||
|
return self.exitmessage
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# Instance-limited threads
|
||||||
|
######################################################################
|
||||||
|
|
||||||
instancelimitedsems = {}
|
instancelimitedsems = {}
|
||||||
instancelimitedlock = Lock()
|
instancelimitedlock = Lock()
|
||||||
|
|
||||||
def initInstanceLimit(instancename, instancemax):
|
def initInstanceLimit(instancename, instancemax):
|
||||||
|
"""Initialize the instance-limited thread implementation to permit
|
||||||
|
up to intancemax threads with the given instancename."""
|
||||||
instancelimitedlock.acquire()
|
instancelimitedlock.acquire()
|
||||||
if not instancelimitedsems.has_key(instancename):
|
if not instancelimitedsems.has_key(instancename):
|
||||||
instancelimitedsems[instancename] = BoundedSemaphore(instancemax)
|
instancelimitedsems[instancename] = BoundedSemaphore(instancemax)
|
||||||
instancelimitedlock.release()
|
instancelimitedlock.release()
|
||||||
|
|
||||||
class InstanceLimitedThread(Thread):
|
class InstanceLimitedThread(ExitNotifyThread):
|
||||||
def __init__(self, instancename, *args, **kwargs):
|
def __init__(self, instancename, *args, **kwargs):
|
||||||
self.instancename = instancename
|
self.instancename = instancename
|
||||||
|
|
||||||
apply(Thread.__init__, (self,) + args, kwargs)
|
apply(ExitNotifyThread.__init__, (self,) + args, kwargs)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
instancelimitedsems[self.instancename].acquire()
|
instancelimitedsems[self.instancename].acquire()
|
||||||
Thread.start(self)
|
ExitNotifyThread.start(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
Thread.run(self)
|
ExitNotifyThread.run(self)
|
||||||
finally:
|
finally:
|
||||||
instancelimitedsems[self.instancename].release()
|
instancelimitedsems[self.instancename].release()
|
||||||
|
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
from UIBase import UIBase
|
from UIBase import UIBase
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
import select, sys
|
import select, sys
|
||||||
|
from threading import *
|
||||||
|
|
||||||
class TTYUI(UIBase):
|
class TTYUI(UIBase):
|
||||||
def __init__(self, verbose = 0):
|
def __init__(self, verbose = 0):
|
||||||
self.verbose = 0
|
self.verbose = 0
|
||||||
|
self.iswaiting = 0
|
||||||
|
|
||||||
def _msg(s, msg):
|
def _msg(s, msg):
|
||||||
print msg
|
print msg
|
||||||
@ -28,11 +30,19 @@ class TTYUI(UIBase):
|
|||||||
UIBase.messagelistloaded(s, repos, folder, count)
|
UIBase.messagelistloaded(s, repos, folder, count)
|
||||||
|
|
||||||
def sleep(s, sleepsecs):
|
def sleep(s, sleepsecs):
|
||||||
|
s.iswaiting = 1
|
||||||
try:
|
try:
|
||||||
UIBase.sleep(s, sleepsecs)
|
UIBase.sleep(s, sleepsecs)
|
||||||
except KeyboardInterrupt:
|
finally:
|
||||||
|
s.iswaiting = 0
|
||||||
|
|
||||||
|
def mainException(s):
|
||||||
|
if isinstance(sys.exc_info()[1], KeyboardInterrupt) and \
|
||||||
|
s.iswaiting:
|
||||||
sys.stdout.write("Timer interrupted at user request; program terminating. \n")
|
sys.stdout.write("Timer interrupted at user request; program terminating. \n")
|
||||||
return 2
|
s.terminate()
|
||||||
|
else:
|
||||||
|
UIBase.mainException(s)
|
||||||
|
|
||||||
def sleeping(s, sleepsecs, remainingsecs):
|
def sleeping(s, sleepsecs, remainingsecs):
|
||||||
if remainingsecs > 0:
|
if remainingsecs > 0:
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
from offlineimap import repository
|
from offlineimap import repository
|
||||||
import offlineimap.version
|
import offlineimap.version
|
||||||
import re, time
|
import re, time, sys, traceback
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
class UIBase:
|
class UIBase:
|
||||||
################################################## UTILS
|
################################################## UTILS
|
||||||
@ -108,6 +109,30 @@ class UIBase:
|
|||||||
s._msg("Deleting flags %s to message %d on %s" % \
|
s._msg("Deleting flags %s to message %d on %s" % \
|
||||||
(", ".join(flags), uid, ds))
|
(", ".join(flags), uid, ds))
|
||||||
|
|
||||||
|
################################################## Threads
|
||||||
|
|
||||||
|
def threadException(s, thread):
|
||||||
|
"""Called when a thread has terminated with an exception.
|
||||||
|
The argument is the ExitNotifyThread that has so terminated."""
|
||||||
|
s._msg("Thread '%s' terminated with exception:\n%s" % \
|
||||||
|
(thread.getName(), thread.getExitStackTrace()))
|
||||||
|
s.terminate(100)
|
||||||
|
|
||||||
|
def mainException(s):
|
||||||
|
sbuf = StringIO()
|
||||||
|
traceback.print_exc(file = sbuf)
|
||||||
|
s._msg("Main program terminated with exception:\n" +
|
||||||
|
sbuf.getvalue())
|
||||||
|
|
||||||
|
def terminate(s, exitstatus = 0):
|
||||||
|
"""Called to terminate the application."""
|
||||||
|
sys.exit(exitstatus)
|
||||||
|
|
||||||
|
def threadExited(s, thread):
|
||||||
|
"""Called when a thread has exited normally. Many UIs will
|
||||||
|
just ignore this."""
|
||||||
|
pass
|
||||||
|
|
||||||
################################################## Other
|
################################################## Other
|
||||||
|
|
||||||
def sleep(s, sleepsecs):
|
def sleep(s, sleepsecs):
|
||||||
|
Loading…
Reference in New Issue
Block a user