[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[minion-cvs] Make file locking work crossplatform; clean up interface



Update of /home/minion/cvsroot/src/minion/lib/mixminion
In directory moria.mit.edu:/tmp/cvs-serv12239/lib/mixminion

Modified Files:
	ClientMain.py Common.py test.py 
Log Message:
Make file locking work crossplatform; clean up interface

Index: ClientMain.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/ClientMain.py,v
retrieving revision 1.101
retrieving revision 1.102
diff -u -d -r1.101 -r1.102
--- ClientMain.py	13 Jul 2003 03:45:33 -0000	1.101
+++ ClientMain.py	15 Jul 2003 15:30:56 -0000	1.102
@@ -33,7 +33,8 @@
 from mixminion.Common import AtomicFile, IntervalSet, LOG, floorDiv, \
      MixError, MixFatalError, MixProtocolError, MixProtocolBadAuth, UIError, \
      UsageError, ceilDiv, createPrivateDir, isPrintingAscii, isSMTPMailbox, \
-     formatDate, formatFnameTime, formatTime, Lockfile, openUnique, \
+     formatDate, formatFnameTime, formatTime, Lockfile, LockfileLocked, \
+     openUnique, \
      previousMidnight, readFile, readPickled, readPossiblyGzippedFile, \
      replaceFile, secureDelete, stringContains, succeedingMidnight, tryUnlink,\
      writeFile, \
@@ -60,7 +61,7 @@
     pidStr = str(os.getpid())
     try:
         _CLIENT_LOCKFILE.acquire(blocking=0, contents=pidStr)
-    except IOError:
+    except LockfileLocked:
         LOG.info("Waiting for pid %s", _CLIENT_LOCKFILE.getContents())
         _CLIENT_LOCKFILE.acquire(blocking=1, contents=pidStr)
 

Index: Common.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/Common.py,v
retrieving revision 1.101
retrieving revision 1.102
diff -u -d -r1.101 -r1.102
--- Common.py	13 Jul 2003 03:45:33 -0000	1.101
+++ Common.py	15 Jul 2003 15:30:56 -0000	1.102
@@ -5,7 +5,8 @@
 
    Common functionality and utility code for Mixminion"""
 
-__all__ = [ 'IntervalSet', 'Lockfile', 'LOG', 'LogStream', 'MixError',
+__all__ = [ 'IntervalSet', 'Lockfile', 'LockfileLocked', 'LOG', 'LogStream', 
+            'MixError',
             'MixFatalError', 'MixProtocolError', 'UIError', 'UsageError',
             'armorText', 'ceilDiv', 'checkPrivateDir', 'checkPrivateFile',
             'createPrivateDir', 'encodeBase64', 'floorDiv', 'formatBase64',
@@ -45,6 +46,10 @@
     import fcntl
 except ImportError:
     fcntl = None
+try:
+    import msvcrt
+except ImportError:
+    mcvcrt = None
 
 try:
     import pwd, grp
@@ -1368,6 +1373,11 @@
     raise MixFatalError("unreachable code")
 
 #----------------------------------------------------------------------
+class LockfileLocked(Exception):
+    """Exception raised when trying to get a nonblocking lock on a locked
+       lockfile"""
+    pass
+
 class Lockfile:
     """Class to implement a recursive advisory lock, using flock on a
        'well-known' filename."""
@@ -1388,26 +1398,18 @@
 
     def acquire(self, contents="", blocking=0):
         """Acquire this lock.  If we're acquiring the lock for the first time,
-           write 'contents' to the lockfile.  If 'blocking' is true, wait until
-           we can acquire the lock.  If 'blocking' is false, raise IOError if
-           we can't acquire the lock."""
+           write 'contents' to the lockfile.  If 'blocking' is true, wait
+           until we can acquire the lock.  If 'blocking' is false, raise
+           LockfileLocked if we can't acquire the lock."""
 
-        if not fcntl:
-            #WWWWW
-            LOG.warn("Skipping Lockfile.acquire")
-            return
         if self.count > 0:
-
             self.count += 1
             return
 
         assert self.fd is None
         self.fd = os.open(self.filename, os.O_RDWR|os.O_CREAT, 0600)
         try:
-            if blocking:
-                fcntl.flock(self.fd, fcntl.LOCK_EX)
-            else:
-                fcntl.flock(self.fd, fcntl.LOCK_EX|fcntl.LOCK_NB)
+            self._lock(self.fd, blocking)
             self.count += 1
             os.write(self.fd, contents)
             os.fsync(self.fd)
@@ -1418,12 +1420,6 @@
 
     def release(self):
         """Release the lock."""
-
-        if not fcntl:
-            #WWWWW
-            LOG.warn("Skipping Lockfile.release")
-            return
-
         assert self.fd is not None
         self.count -= 1
         if self.count > 0:
@@ -1433,7 +1429,7 @@
         except OSError:
             pass
         try:
-            fcntl.flock(self.fd, fcntl.LOCK_UN)
+            self._unlock(self.fd)
         except OSError:
             pass
         try:
@@ -1442,6 +1438,62 @@
             pass
 
         self.fd = None
+
+    def _lock(self, fd, blocking):
+        """Compatibility wrapper to implement file locking for posix and win32
+           systems.  If 'blocking' is false, and the lock cannot be obtained,
+           raises LockfileLocked."""
+        if fcntl:
+            # Posixy systems have a friendly neighborhood flock clone.
+            flags = fcntl.LOCK_EX
+            if not blocking: flags |= fcntl.LOCK_NB
+            try:
+                fcntl.flock(fd, flags)
+            except IOError, e:
+                if e.errno in (errno.EAGAIN, errno.EACCES) and not blocking:
+                    raise LockfileLocked()
+                else:
+                    raise
+        elif msvcrt:
+            # Windows has decided that System V's perennially unstandardized
+            # "locking" is a cool idea.
+            os.lseek(fd, 0, 0)
+            # The msvcrt.locking() function never gives you a blocking lock.
+            # If you ask for one, it just retries once per second for ten
+            # seconds.  This must be some genius's idea of a 'feature'.
+            while 1:
+                try:
+                    msvcrt.locking(fd, msvcrt.LK_NBLCK, 0)
+                    return
+                except IOError, e:
+                    if e.errno not in (errno.EAGAIN, errno.EACCES):
+                        raise
+                    elif not blocking:
+                        raise LockfileLocked()
+                    else:
+                        time.sleep(0.5)
+        else:
+            # There is no locking implementation.
+            _warn_no_locks()
+        
+    def _unlock(self, fd):
+        """Compatibility wrapper: unlock a file for unix and windows systems.
+        """
+        if fcntl:
+            fcntl.flock(fd, fcntl.LOCK_UN)
+        elif msvcrt:
+            os.lseek(fd, 0, 0)
+            msvcrt.locking(fd, msvcrt.LK_UNLCK, 0)
+        else:
+            _warn_no_locks()
+
+_warned_no_locks = 0
+def _warn_no_locks():
+    global _warned_no_locks
+    if not _warned_no_locks:
+        _warned_no_locks = 1
+        LOG.warn("Mixminion couldn't find a file locking implementation.")
+        LOG.warn("  (Simultaneous accesses may lead to data corruption.")
 
 #----------------------------------------------------------------------
 # Threading operations

Index: test.py
===================================================================
RCS file: /home/minion/cvsroot/src/minion/lib/mixminion/test.py,v
retrieving revision 1.139
retrieving revision 1.140
diff -u -d -r1.139 -r1.140
--- test.py	15 Jul 2003 04:40:57 -0000	1.139
+++ test.py	15 Jul 2003 15:30:56 -0000	1.140
@@ -482,28 +482,24 @@
         self.assertEquals(fn, dX+".2")
 
     def test_lockfile(self):
-        if ON_WIN32:
-            #WWWW
-            return
-
         fn = mix_mktemp()
         LF1 = Lockfile(fn)
         LF2 = Lockfile(fn)
         LF1.acquire("LF1")
         if not ON_WINDOWS:
             self.assertEquals("LF1", readFile(fn))
-        self.assertRaises(IOError, LF2.acquire, blocking=0)
+        self.assertRaises(LockfileLocked, LF2.acquire, blocking=0)
         LF1.release()
         LF2.acquire("LF2",1)
         if not ON_WINDOWS:
             self.assertEquals("LF2", readFile(fn))
-        self.assertRaises(IOError, LF1.acquire, blocking=0)
+        self.assertRaises(LockfileLocked, LF1.acquire, blocking=0)
 
         # Now try recursivity.
         LF2.acquire()
-        self.assertRaises(IOError, LF1.acquire, blocking=0)
+        self.assertRaises(LockfileLocked, LF1.acquire, blocking=0)
         LF2.release()
-        self.assertRaises(IOError, LF1.acquire, blocking=0)
+        self.assertRaises(LockfileLocked, LF1.acquire, blocking=0)
         LF2.release()
         LF1.acquire(blocking=1)