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:
parent
1c8917b036
commit
5c842c01bd
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
|
@ -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):
|
||||||
|
@ -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 = []
|
||||||
|
Loading…
Reference in New Issue
Block a user