[PATCH 1/3] Per-account locking

Sebastian Spaeth Sebastian at SSpaeth.de
Mon Aug 22 11:12:05 BST 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. 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 at SSpaeth.de>
---
 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 74b6368..3da9689 100644
--- a/Changelog.draft.rst
+++ b/Changelog.draft.rst
@@ -17,6 +17,13 @@ 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 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 31ea2b7..e3dad0e 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