[PATCH 1/2] Per-account locking

Sebastian Spaeth Sebastian at SSpaeth.de
Sun Aug 14 08:32:47 UTC 2011


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. In the currenty system we will
attempt to loop three times before giving up, when running this in refresh
mode.

This also fixes Debian bug #586655

Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
 Changelog.draft.rst     |    4 ++++
 offlineimap/accounts.py |   36 ++++++++++++++++++++++++++++++++++++
 offlineimap/init.py     |   20 --------------------
 3 files changed, 40 insertions(+), 20 deletions(-)

diff --git a/Changelog.draft.rst b/Changelog.draft.rst
index 6b5b18e..d66ecf2 100644
--- a/Changelog.draft.rst
+++ b/Changelog.draft.rst
@@ -17,6 +17,10 @@ New Features
   synchronization, but only skip that message, informing the user at the
   end of the sync run.
  
+* Implement per-account locking, 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.
+
 Changes
 -------
 
diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py
index 8653d48..a9c6531 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):
-- 
1.7.4.1




More information about the OfflineIMAP-project mailing list