Fix deadlock for IMAP folder synced in singlethreaded mode

The problem lies in the fact that offlineimap.folder.Base's method
syncmessagesto_copy() uses threaded code everytime it is suggested by
the derived class's suggeststhreads() (currently, only IMAP does this
suggestion), but offlineimap/init.py will not spawn the
exitnotifymonitorloop() from offlineimap.threadutil.

The root cause is that ExitNotifyThread-derived threads need
offlineimap.threadutil's exitnotifymonitorloop() to be running the
cleaner for the exitthreads Queue(), because it fills the queue via
the run() method from this class: it wants to put() itself to the
Queue on exit, so when no exitnotifymonitorloop() is running, the
queue will fill up.  And if this thread is an instance of
InstanceLimitedThread that hits the limit on the number of threads,
then it will hold the instancelimitedsems[] semaphore will prevent
other InstanceLimitedThread()s of the same name to pass its start()
method.

The fix is to avoid using threaded code if we're running
single-threaded.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
Obtained-from: X-Ryl669 <boite.pour.spam@gmail.com>
This commit is contained in:
X-Ryl669 2013-01-17 12:02:41 +01:00 committed by Eygene Ryabinkin
parent 1c8917b036
commit 5c842c01bd
5 changed files with 19 additions and 10 deletions

View File

@ -7,6 +7,8 @@ ChangeLog
WIP (add new stuff for the next release) WIP (add new stuff for the next release)
======================================== ========================================
* Avoid lockups for IMAP synchronizations running with the
"-1" command-line switch (X-Ryl669 <boite.pour.spam@gmail.com>)
* Dump stacktrace for all threads on SIGQUIT: ease debugging * Dump stacktrace for all threads on SIGQUIT: ease debugging
of threading and other issues of threading and other issues
* SIGHUP is now handled as the termination notification rather than * SIGHUP is now handled as the termination notification rather than

View File

@ -321,6 +321,7 @@ class SyncableAccount(Account):
self.ui.debug('', "Not syncing filtered folder '%s'" self.ui.debug('', "Not syncing filtered folder '%s'"
"[%s]" % (localfolder, localfolder.repository)) "[%s]" % (localfolder, localfolder.repository))
continue # Ignore filtered folder continue # Ignore filtered folder
if self.config.get('general', 'single-thread') == 'False':
thread = InstanceLimitedThread(\ thread = InstanceLimitedThread(\
instancename = 'FOLDER_' + self.remoterepos.getname(), instancename = 'FOLDER_' + self.remoterepos.getname(),
target = syncfolder, target = syncfolder,
@ -328,6 +329,8 @@ class SyncableAccount(Account):
args = (self, remotefolder, quick)) args = (self, remotefolder, quick))
thread.start() thread.start()
folderthreads.append(thread) folderthreads.append(thread)
else:
syncfolder(self, remotefolder, quick)
# wait for all threads to finish # wait for all threads to finish
for thr in folderthreads: for thr in folderthreads:
thr.join() thr.join()
@ -372,8 +375,7 @@ class SyncableAccount(Account):
self.ui.error(e, exc_info()[2], msg = "Calling hook") self.ui.error(e, exc_info()[2], msg = "Calling hook")
def syncfolder(account, remotefolder, quick): def syncfolder(account, remotefolder, quick):
"""This function is called as target for the """Synchronizes given remote folder for the specified account.
InstanceLimitedThread invokation in SyncableAccount.
Filtered folders on the remote side will not invoke this function.""" Filtered folders on the remote side will not invoke this function."""
remoterepos = account.remoterepos remoterepos = account.remoterepos

View File

@ -403,7 +403,7 @@ class BaseFolder(object):
break break
self.ui.copyingmessage(uid, num+1, num_to_copy, self, dstfolder) self.ui.copyingmessage(uid, num+1, num_to_copy, self, dstfolder)
# exceptions are caught in copymessageto() # exceptions are caught in copymessageto()
if self.suggeststhreads(): if self.suggeststhreads() and self.config.get('general', 'single-thread') == 'False':
self.waitforthread() self.waitforthread()
thread = threadutil.InstanceLimitedThread(\ thread = threadutil.InstanceLimitedThread(\
self.getcopyinstancelimit(), self.getcopyinstancelimit(),

View File

@ -251,6 +251,9 @@ class OfflineImap:
if type.lower() == 'imap': if type.lower() == 'imap':
imaplib.Debug = 5 imaplib.Debug = 5
# XXX: can we avoid introducing fake configuration item?
config.set_if_not_exists('general', 'single-thread', 'True' if options.singlethreading else 'False')
if options.runonce: if options.runonce:
# FIXME: maybe need a better # FIXME: maybe need a better
for section in accounts.getaccountlist(config): for section in accounts.getaccountlist(config):

View File

@ -39,6 +39,8 @@ def semaphorereset(semaphore, originalstate):
semaphore.release() semaphore.release()
class threadlist: class threadlist:
"""Store the list of all threads in the software so it can be used to find out
what's running and what's not."""
def __init__(self): def __init__(self):
self.lock = Lock() self.lock = Lock()
self.list = [] self.list = []