From c7938dc0819e0a14d1a97e2362e46daa86a7ce85 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Fri, 8 Jul 2011 12:23:16 +0200 Subject: [PATCH] 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 --- Changelog.draft.rst | 7 +++++++ offlineimap/accounts.py | 36 ++++++++++++++++++++++++++++++++++++ offlineimap/init.py | 20 -------------------- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Changelog.draft.rst b/Changelog.draft.rst index 909ebd6..1b1e644 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -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 ------- diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 428cbd8..5a2a120 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -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) diff --git a/offlineimap/init.py b/offlineimap/init.py index 93b7224..f7b2eef 100644 --- a/offlineimap/init.py +++ b/offlineimap/init.py @@ -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):