]> scripts.mit.edu Git - wizard.git/blobdiff - wizard/util.py
Update TODO.
[wizard.git] / wizard / util.py
index 0cd8fc88cb8447fbd960a0d70cc64bbe3c536964..a6e8f243dabf48a8792b442f51e75183ef4698c9 100644 (file)
@@ -17,6 +17,8 @@ import itertools
 import signal
 import httplib
 import urllib
+import time
+import logging
 
 import wizard
 
@@ -96,17 +98,47 @@ class LockDirectory(object):
     """
     Context for locking a directory.
     """
-    def __init__(self, lockfile):
+    def __init__(self, lockfile, expiry = 3600):
         self.lockfile = lockfile
+        self.expiry = expiry # by default an hour
     def __enter__(self):
-        try:
-            os.open(self.lockfile, os.O_CREAT | os.O_EXCL)
-        except OSError as e:
-            if e.errno == errno.EEXIST:
-                raise DirectoryLockedError(os.getcwd())
-            elif e.errno == errno.EACCES:
-                raise PermissionsError(os.getcwd())
-            raise
+        # It's A WAVY
+        for i in range(0, 3):
+            try:
+                os.open(self.lockfile, os.O_CREAT | os.O_EXCL)
+                open(self.lockfile, "w").write("%d" % os.getpid())
+            except OSError as e:
+                if e.errno == errno.EEXIST:
+                    # There is a possibility of infinite recursion, but we
+                    # expect it to be unlikely, and not harmful if it does happen
+                    with LockDirectory(self.lockfile + "_"):
+                        # See if we can break the lock
+                        try:
+                            pid = open(self.lockfile, "r").read().strip()
+                            if not os.path.exists("/proc/%s" % pid):
+                                # break the lock, try again
+                                logging.warning("Breaking orphaned lock at %s", self.lockfile)
+                                os.unlink(self.lockfile)
+                                continue
+                            try:
+                                # check if the file is expiry old, if so, break the lock, try again
+                                if time.time() - os.stat(self.lockfile).st_mtime > self.expiry:
+                                    logging.warning("Breaking stale lock at %s", self.lockfile)
+                                    os.unlink(self.lockfile)
+                                    continue
+                            except OSError as e:
+                                if e.errno == errno.ENOENT:
+                                    continue
+                                raise
+                        except IOError:
+                            # oh hey, it went away; try again
+                            continue
+                    raise DirectoryLockedError(os.getcwd())
+                elif e.errno == errno.EACCES:
+                    raise PermissionsError(os.getcwd())
+                raise
+            return
+        raise DirectoryLockedError(os.getcwd())
     def __exit__(self, *args):
         try:
             os.unlink(self.lockfile)
@@ -304,6 +336,13 @@ def safe_unlink(file):
     os.rename(file, name)
     return name
 
+def soft_unlink(file):
+    """Unlink a file, but don't complain if it doesn't exist."""
+    try:
+        os.unlink(file)
+    except OSError:
+        pass
+
 def fetch(host, path, subpath, post=None):
     h = httplib.HTTPConnection(host)
     fullpath = path.rstrip("/") + "/" + subpath.lstrip("/") # to be lenient about input we accept
@@ -317,6 +356,14 @@ def fetch(host, path, subpath, post=None):
     h.close()
     return data
 
+def mixed_newlines(filename):
+    """Returns ``True`` if ``filename`` has mixed newlines."""
+    f = open(filename, "U") # requires universal newline support
+    f.read()
+    ret = isinstance(f.newlines, tuple)
+    f.close() # just to be safe
+    return ret
+
 class NoOperatorInfo(wizard.Error):
     """No information could be found about the operator from Kerberos."""
     pass