Commits

Jason R. Coombs committed 3f8a8e5

Implementing platform-assisted locking support for more robust locking behavior.

Comments (0)

Files changed (2)

cherrypy/lib/lockfile.py

+"""
+Platform-independent file locking.
+"""
+
+import os
+
+try:
+    import msvcrt
+except ImportError:
+    pass
+
+try:
+    import fcntl
+except ImportError:
+    pass
+
+class LockError(Exception):
+    "Could not obtain a lock"
+
+# first, a default, naive locking implementation
+class LockFile(object):
+    """
+    A default, naive locking implementation. Always fails if the file
+    already exists.
+    """
+    def __init__(self, path):
+        self.path = path
+        try:
+            fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
+        except OSError:
+            raise LockError("Unable to lock %s" % self.path)
+        os.close(fd)
+
+    def release(self):
+        os.remove(self.path)
+
+
+class SystemLockFile(object):
+
+    def __init__(self, path):
+        self.path = path
+        fp = open(path, 'w+')
+
+        self._lock_file(fp)
+
+        self.fp = fp
+        fp.write(" %s\n" % os.getpid())
+        fp.truncate()
+        fp.flush()
+
+    def release(self):
+        if not hasattr(self, 'fp'):
+            return
+        self._unlock_file()
+        self.fp.close()
+        del self.fp
+
+    def _unlock_file(self):
+        pass
+
+
+class WindowsLockFile(SystemLockFile):
+    def _lock_file(self):
+        # Lock just the first byte
+        try:
+            msvcrt.locking(self.fp.fileno(), msvcrt.LK_NBLCK, 1)
+        except IOError:
+            raise LockError("Unable to lock %r" % self.fp.name)
+
+    def _unlock_file(self):
+        try:
+            self.fp.seek(0)
+            msvcrt.locking(self.fp.fileno(), msvcrt.LK_UNLCK, 1)
+        except IOError:
+            raise LockError("Unable to unlock %r" % self.fp.name)
+
+if 'msvcrt' in globals():
+    LockFile = WindowsLockFile
+
+
+class UnixLockFile(SystemLockFile):
+    def _lock_file(self):
+        flags = fcntl.LOCK_EX | fcntl.LOCK_NB
+        try:
+            fcntl.flock(self.fp.fileno(), flags)
+        except IOError:
+            raise LockError("Unable to lock %r" % self.fp.name)
+
+    # no need to implement _unlock_file, it will be unlocked on close()
+
+if 'fcntl' in globals():
+    LockFile = UnixLockFile

cherrypy/lib/sessions.py

 
 import datetime
 import os
-import random
 import time
 import threading
 import types
-from warnings import warn
 
 import cherrypy
 from cherrypy._cpcompat import copyitems, pickle, random20, unicodestr
 from cherrypy.lib import httputil
-
+from cherrypy.lib import lockfile
 
 missing = object()
 
     LOCK_SUFFIX = '.lock'
     pickle_protocol = pickle.HIGHEST_PROTOCOL
 
+    # class-level objects - don't rebind these
+    locks = {}
+
     def __init__(self, id=None, **kwargs):
         # The 'storage_path' arg is required for file-based sessions.
         kwargs['storage_path'] = os.path.abspath(kwargs['storage_path'])
 
         for k, v in kwargs.items():
             setattr(cls, k, v)
-
-        # Warn if any lock files exist at startup.
-        lockfiles = [fname for fname in os.listdir(cls.storage_path)
-                     if (fname.startswith(cls.SESSION_PREFIX)
-                         and fname.endswith(cls.LOCK_SUFFIX))]
-        if lockfiles:
-            plural = ('', 's')[len(lockfiles) > 1]
-            warn("%s session lockfile%s found at startup. If you are "
-                 "only running one process, then you may need to "
-                 "manually delete the lockfiles found at %r."
-                 % (len(lockfiles), plural, cls.storage_path))
     setup = classmethod(setup)
 
     def _get_file_path(self):
         path += self.LOCK_SUFFIX
         while True:
             try:
-                lockfd = os.open(path, os.O_CREAT|os.O_WRONLY|os.O_EXCL)
-            except OSError:
+                self.locks[path] = lockfile.LockFile(path)
+            except lockfile.LockError:
                 time.sleep(0.1)
             else:
-                os.close(lockfd)
                 break
         self.locked = True
 
         """Release the lock on the currently-loaded session data."""
         if path is None:
             path = self._get_file_path()
-        os.unlink(path + self.LOCK_SUFFIX)
+        path += self.LOCK_SUFFIX
+
+        self.locks.pop(path).release()
         self.locked = False
 
     def clean_up(self):