diff Lib/statcache.py @ 16317:861bf8dcfb58 legacy-trunk

SF bug #130306: statcache.py full of thread problems. Fixed the thread races. Function forget_dir was also utterly Unix-specific.
author Tim Peters <tim.peters@gmail.com>
date Sun, 28 Jan 2001 05:07:00 +0000
parents 3685ef2a55a4
children f68dbffff927
line wrap: on
line diff
--- a/Lib/statcache.py	Sun Jan 28 03:57:39 2001 +0000
+++ b/Lib/statcache.py	Sun Jan 28 05:07:00 2001 +0000
@@ -3,73 +3,72 @@
 There are functions to reset the cache or to selectively remove items.
 """
 
-import os
+import os as _os
 from stat import *
 
-# The cache.
-# Keys are pathnames, values are `os.stat' outcomes.
-#
+# The cache.  Keys are pathnames, values are os.stat outcomes.
+# Remember that multiple threads may be calling this!  So, e.g., that
+# cache.has_key(path) returns 1 doesn't mean the cache will still contain
+# path on the next line.  Code defensively.
+
 cache = {}
 
-
 def stat(path):
     """Stat a file, possibly out of the cache."""
-    if cache.has_key(path):
-        return cache[path]
-    cache[path] = ret = os.stat(path)
+    ret = cache.get(path, None)
+    if ret is None:
+        cache[path] = ret = _os.stat(path)
     return ret
 
-
 def reset():
-    """Reset the cache completely."""
-    global cache
-    cache = {}
+    """Clear the cache."""
+    cache.clear()
 
-
+# For thread saftey, always use forget() internally too.
 def forget(path):
     """Remove a given item from the cache, if it exists."""
-    if cache.has_key(path):
+    try:
         del cache[path]
-
+    except KeyError:
+        pass
 
 def forget_prefix(prefix):
     """Remove all pathnames with a given prefix."""
-    n = len(prefix)
     for path in cache.keys():
-        if path[:n] == prefix:
-            del cache[path]
-
+        if path.startswith(prefix):
+            forget(path)
 
 def forget_dir(prefix):
-    """Forget about a directory and all entries in it, but not about
-    entries in subdirectories."""
-    if prefix[-1:] == '/' and prefix != '/':
-        prefix = prefix[:-1]
+    """Forget a directory and all entries except for entries in subdirs."""
+
+    # Remove trailing separator, if any.  This is tricky to do in a
+    # x-platform way.  For example, Windows accepts both / and \ as
+    # separators, and if there's nothing *but* a separator we want to
+    # preserve that this is the root.  Only os.path has the platform
+    # knowledge we need.
+    from os.path import split, join
+    prefix = split(join(prefix, "xxx"))[0]
     forget(prefix)
-    if prefix[-1:] != '/':
-        prefix = prefix + '/'
-    n = len(prefix)
     for path in cache.keys():
-        if path[:n] == prefix:
-            rest = path[n:]
-            if rest[-1:] == '/': rest = rest[:-1]
-            if '/' not in rest:
-                del cache[path]
-
+        # First check that the path at least starts with the prefix, so
+        # that when it doesn't we can avoid paying for split().
+        if path.startswith(prefix) and split(path)[0] == prefix:
+            forget(path)
 
 def forget_except_prefix(prefix):
     """Remove all pathnames except with a given prefix.
-    Normally used with prefix = '/' after a chdir()."""
-    n = len(prefix)
+
+    Normally used with prefix = '/' after a chdir().
+    """
+
     for path in cache.keys():
-        if path[:n] != prefix:
-            del cache[path]
-
+        if not path.startswith(prefix):
+            forget(path)
 
 def isdir(path):
-    """Check for directory."""
+    """Return 1 if directory, else 0."""
     try:
         st = stat(path)
-    except os.error:
+    except _os.error:
         return 0
     return S_ISDIR(st[ST_MODE])