X-Git-Url: https://scripts.mit.edu/gitweb/wizard.git/blobdiff_plain/a0587472768ec8c72baa48708a4c3519814aa0f0..a29acb618699431bf1fdd2c7df6d3b0a9e0282e0:/wizard/util.py diff --git a/wizard/util.py b/wizard/util.py index 0cd8fc8..a6e8f24 100644 --- a/wizard/util.py +++ b/wizard/util.py @@ -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