Per-account locking

Previously, we were simply locking offlineimap whenever it was
running. Howver there is no reason why we shouldn't be able to invoke it
in parallel, e.g. to synchronize several accounts in one offlineimap
each.

This patch implements the locking per-account, so that it is possible to
sync different accounts at the same time. If in refresh mode, we will
attempt to loop three times before giving up.

This also fixes Debian bug #586655

Signed-off-by: Sebastian Spaeth <Sebastian@SSpaeth.de>
This commit is contained in:
Sebastian Spaeth 2011-07-08 12:23:16 +02:00
parent 42c9e04966
commit c7938dc081
3 changed files with 43 additions and 20 deletions

View File

@ -13,6 +13,13 @@ others.
New Features
------------
* Implement per-account locking, so that it will possible to sync
different accounts at the same time. The old global lock is still in
place for backward compatibility reasons (to be able to run old and
new versions of OfflineImap concurrently) and will be removed in the
future. Starting with this version, OfflineImap will be
forward-compatible with the per-account locking style.
Changes
-------

View File

@ -25,6 +25,11 @@ import os
from sys import exc_info
import traceback
try:
import fcntl
except:
pass # ok if this fails, we can do without
def getaccountlist(customconfig):
return customconfig.getsectionlist('Account')
@ -159,6 +164,35 @@ class SyncableAccount(Account):
functions :meth:`syncrunner`, :meth:`sync`, :meth:`syncfolders`,
used for syncing."""
def __init__(self, *args, **kwargs):
Account.__init__(self, *args, **kwargs)
self._lockfd = None
self._lockfilepath = os.path.join(self.config.getmetadatadir(),
"%s.lock" % self)
def lock(self):
"""Lock the account, throwing an exception if it is locked already"""
self._lockfd = open(self._lockfilepath, 'w')
try:
fcntl.lockf(self._lockfd, fcntl.LOCK_EX|fcntl.LOCK_NB)
except NameError:
#fcntl not available (Windows), disable file locking... :(
pass
except IOError:
self._lockfd.close()
raise OfflineImapError("Could not lock account %s." % self,
OfflineImapError.ERROR.REPO)
def unlock(self):
"""Unlock the account, deleting the lock file"""
#If we own the lock file, delete it
if self._lockfd and not self._lockfd.closed:
self._lockfd.close()
try:
os.unlink(self._lockfilepath)
except OSError:
pass #Failed to delete for some reason.
def syncrunner(self):
self.ui.registerthread(self.name)
self.ui.acct(self.name)
@ -175,6 +209,7 @@ class SyncableAccount(Account):
while looping:
try:
try:
self.lock()
self.sync()
except (KeyboardInterrupt, SystemExit):
raise
@ -194,6 +229,7 @@ class SyncableAccount(Account):
if self.refreshperiod:
looping = 3
finally:
self.unlock()
if looping and self.sleeper() >= 2:
looping = 0
self.ui.acctdone(self.name)

View File

@ -30,14 +30,6 @@ from offlineimap.ui import UI_LIST, setglobalui, getglobalui
from offlineimap.CustomConfig import CustomConfigParser
try:
import fcntl
hasfcntl = 1
except:
hasfcntl = 0
lockfd = None
class OfflineImap:
"""The main class that encapsulates the high level use of OfflineImap.
@ -46,17 +38,6 @@ class OfflineImap:
oi = OfflineImap()
oi.run()
"""
def lock(self, config, ui):
global lockfd, hasfcntl
if not hasfcntl:
return
lockfd = open(config.getmetadatadir() + "/lock", "w")
try:
fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
ui.locked()
ui.terminate(1)
def run(self):
"""Parse the commandline and invoke everything"""
@ -253,7 +234,6 @@ class OfflineImap:
config.set(section, "folderfilter", folderfilter)
config.set(section, "folderincludes", folderincludes)
self.lock(config, ui)
self.config = config
def sigterm_handler(signum, frame):