changeset 1314:62b8e7e8d5a5

Merge with main.
author Alexander Schremmer <alex AT alexanderweb DOT de>
date Thu, 17 Aug 2006 19:11:38 +0200
parents 73ea47cbfc79 (current diff) fca5b70800b2 (diff)
children ed3baf538cd5
files
diffstat 4 files changed, 141 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/caching.py	Thu Aug 17 19:11:00 2006 +0200
+++ b/MoinMoin/caching.py	Thu Aug 17 19:11:38 2006 +0200
@@ -8,11 +8,7 @@
 
 import os
 from MoinMoin import config
-from MoinMoin.util import filesys
-
-locking = 1
-if locking:
-    from MoinMoin.util import lock
+from MoinMoin.util import filesys, lock
 
 class CacheEntry:
     def __init__(self, request, arena, key, scope='page_or_wiki', do_locking=True):
@@ -28,6 +24,8 @@
                           'farm' - a cache for the whole farm
         """
         self.request = request
+        self.key = key
+        self.locking = do_locking
         if scope == 'page_or_wiki': # XXX DEPRECATED, remove later
             if isinstance(arena, str):
                 self.arena_dir = os.path.join(request.cfg.cache_dir, request.cfg.siteid, arena)
@@ -43,16 +41,17 @@
         elif scope == 'farm':
             self.arena_dir = os.path.join(request.cfg.cache_dir, '__common__', arena)
             filesys.makeDirs(self.arena_dir)
-        self.key = key
-        self.locking = do_locking and locking
         if self.locking:
             self.lock_dir = os.path.join(self.arena_dir, '__lock__')
-            self.rlock = lock.ReadLock(self.lock_dir, 60.0)
-            self.wlock = lock.WriteLock(self.lock_dir, 60.0)
+            self.rlock = lock.LazyReadLock(self.lock_dir, 60.0)
+            self.wlock = lock.LazyWriteLock(self.lock_dir, 60.0)
 
     def _filename(self):
         return os.path.join(self.arena_dir, self.key)
 
+    def _tmpfilename(self):
+        return os.tempnam(self.arena_dir, self.key)
+
     def exists(self):
         return os.path.exists(self._filename())
 
@@ -85,10 +84,15 @@
         return needsupdate
 
     def copyto(self, filename):
+        # currently unused function
         import shutil
+        tmpfname = self._tmpfilename()
+        fname = self._filename()
         if not self.locking or self.locking and self.wlock.acquire(1.0):
             try:
-                shutil.copyfile(filename, self._filename())
+                shutil.copyfile(filename, tmpfname)
+                # this is either atomic or happening with real locks set:
+                filesys.rename(tmpfname, fname)
                 try:
                     os.chmod(self._filename(), 0666 & config.umask)
                 except OSError:
@@ -100,15 +104,22 @@
             self.request.log("Can't acquire write lock in %s" % self.lock_dir)
 
     def update(self, content, encode=False):
+        tmpfname = self._tmpfilename()
+        fname = self._filename()
         if encode:
             content = content.encode(config.charset)
         if not self.locking or self.locking and self.wlock.acquire(1.0):
             try:
-                f = open(self._filename(), 'wb')
+                # we do not write content to old inode, but to a new file
+                # se we don't need to lock when we just want to read the file
+                # (at least on POSIX, this works)
+                f = open(tmpfname, 'wb')
                 f.write(content)
                 f.close()
+                # this is either atomic or happening with real locks set:
+                filesys.rename(tmpfname, fname)
                 try:
-                    os.chmod(self._filename(), 0666 & config.umask)
+                    os.chmod(fname, 0666 & config.umask)
                 except OSError:
                     pass
             finally:
@@ -118,10 +129,17 @@
             self.request.log("Can't acquire write lock in %s" % self.lock_dir)
 
     def remove(self):
-        try:
-            os.remove(self._filename())
-        except OSError:
-            pass
+        if not self.locking or self.locking and self.wlock.acquire(1.0):
+            try:
+                try:
+                    os.remove(self._filename())
+                except OSError:
+                    pass
+            finally:
+                if self.locking:
+                    self.wlock.release()
+        else:
+            self.request.log("Can't acquire write lock in %s" % self.lock_dir)
 
     def content(self, decode=False):
         if not self.locking or self.locking and self.rlock.acquire(1.0):
--- a/MoinMoin/parser/text_moin_wiki.py	Thu Aug 17 19:11:00 2006 +0200
+++ b/MoinMoin/parser/text_moin_wiki.py	Thu Aug 17 19:11:38 2006 +0200
@@ -30,8 +30,11 @@
 
     # some common strings
     PARENT_PREFIX = wikiutil.PARENT_PREFIX
-    sq_string = ur"('.*?')" # single quoted string
-    dq_string = ur"(\".*?\")" # double quoted string
+    # quoted strings (we require that there is at least one char (that is not the quoting char)
+    # inside to not confuse stuff like '''Contact:''' (just a bold Contact:) with interwiki markup
+    # OtherWiki:'Page with blanks'
+    sq_string = ur"('[^']+?')" # single quoted string
+    dq_string = ur"(\"[^\"]+?\")" # double quoted string
     q_string = ur"(%s|%s)" % (sq_string, dq_string) # quoted string
     attachment_schemas = ["attachment", "inline", "drawing"]
     punct_pattern = re.escape(u'''"\'}]|:,.)?!''')
--- a/MoinMoin/util/filesys.py	Thu Aug 17 19:11:00 2006 +0200
+++ b/MoinMoin/util/filesys.py	Thu Aug 17 19:11:38 2006 +0200
@@ -52,7 +52,7 @@
 
 
 def rename(oldname, newname):
-    ''' Multiplatform rename
+    """ Multiplatform rename
 
     Needed because win32 rename is not POSIX compliant, and does not
     remove target file if it exists.
@@ -62,14 +62,14 @@
     FIXME: What about rename locking? we can have a lock file in the
     page directory, named: PageName.lock, and lock this file before we
     rename, then unlock when finished.
-    '''
+    """
     if os.name == 'nt':
         if os.path.isfile(newname):
             try:
                 os.remove(newname)
             except OSError, er:
                 pass # let os.rename give us the error (if any)
-    return os.rename(oldname, newname)
+    os.rename(oldname, newname)
 
 
 def copystat(src, dst):
--- a/MoinMoin/util/lock.py	Thu Aug 17 19:11:00 2006 +0200
+++ b/MoinMoin/util/lock.py	Thu Aug 17 19:11:38 2006 +0200
@@ -6,7 +6,7 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import os, tempfile, time, errno
+import os, sys, tempfile, time, errno
 
 
 # Temporary debugging aid, to be replaced with system wide debuging
@@ -262,7 +262,6 @@
                 return True
         return False
 
-
 class ReadLock(ExclusiveLock):
     """ Read lock
     
@@ -305,3 +304,101 @@
                 self.writeLock.release()
         return False
 
+
+class LazyReadLock(ReadLock):
+    """ Lazy Read lock
+    
+    See ReadLock, but we do an optimization here:
+    If (and ONLY if) the resource protected by this lock is updated in a POSIX
+    style "write new content to tmpfile, rename tmpfile -> origfile", then reading
+    from an open origfile handle will give either the old content (when opened
+    before the rename happens) or the new content (when opened after the rename
+    happened), but never cause any trouble. This means that we don't have to lock
+    at all in that case.
+
+    Of course this doesn't work for us on the win32 platform:
+    * using MoveFileEx requires opening the file with some FILE_SHARE_DELETE
+      mode - we currently don't do that
+    * Win 95/98/ME do not have MoveFileEx
+    We currently solve by using the non-lazy locking code in ReadLock class.
+    """
+    def __init__(self, dir, timeout=None):
+        if sys.platform == 'win32':
+            ReadLock.__init__(self, dir, timeout)
+        else: # POSIX
+            self._locked = False
+
+    def acquire(self, timeout=None):
+        if sys.platform == 'win32':
+            return ReadLock.acquire(self, timeout)
+        else: # POSIX
+            self._locked = True
+            return True
+
+    def release(self):
+        if sys.platform == 'win32':
+            return ReadLock.release(self)
+        else:  # POSIX
+            self._locked = False
+
+    def exists(self):
+        if sys.platform == 'win32':
+            return ReadLock.exists(self)
+        else: # POSIX
+            return True
+
+    def isExpired(self):
+        if sys.platform == 'win32':
+            return ReadLock.isExpired(self)
+        else: # POSIX
+            return True
+
+    def expire(self):
+        if sys.platform == 'win32':
+            return ReadLock.expire(self)
+        else: # POSIX
+            return True
+
+class LazyWriteLock(WriteLock):
+    """ Lazy Write lock
+    
+    See WriteLock and LazyReadLock docs.
+    """
+    def __init__(self, dir, timeout=None):
+        if sys.platform == 'win32':
+            WriteLock.__init__(self, dir, timeout)
+        else: # POSIX
+            self._locked = False
+
+    def acquire(self, timeout=None):
+        if sys.platform == 'win32':
+            return WriteLock.acquire(self, timeout)
+        else: # POSIX
+            self._locked = True
+            return True
+
+    def release(self):
+        if sys.platform == 'win32':
+            return WriteLock.release(self)
+        else:  # POSIX
+            self._locked = False
+
+    def exists(self):
+        if sys.platform == 'win32':
+            return WriteLock.exists(self)
+        else: # POSIX
+            return True
+
+    def isExpired(self):
+        if sys.platform == 'win32':
+            return WriteLock.isExpired(self)
+        else: # POSIX
+            return True
+
+    def expire(self):
+        if sys.platform == 'win32':
+            return WriteLock.expire(self)
+        else: # POSIX
+            return True
+
+