From 0149e84af280523f74bc2bccb6505eb12cf5bf9c Mon Sep 17 00:00:00 2001
From: Tim Peters <tim.peters@gmail.com>
Date: Sun, 28 Jan 2001 05:07:00 +0000
Subject: SF bug #130306:  statcache.py full of thread problems. Fixed the
 thread races.  Function forget_dir was also utterly Unix-specific.

---
 Lib/statcache.py | 77 ++++++++++++++++++++++++++++----------------------------
 Misc/ACKS        |  1 +
 2 files changed, 39 insertions(+), 39 deletions(-)

diff --git a/Lib/statcache.py b/Lib/statcache.py
index 85a3e79..056ec40 100644
--- a/Lib/statcache.py
+++ b/Lib/statcache.py
@@ -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.
-#
-cache = {}
+# 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)
-    for path in cache.keys():
-        if path[:n] != prefix:
-            del cache[path]
 
+    Normally used with prefix = '/' after a chdir().
+    """
+
+    for path in cache.keys():
+        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])
diff --git a/Misc/ACKS b/Misc/ACKS
index 554203f..12a1f99 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -197,6 +197,7 @@ Andreas Jung
 Bob Kahn
 Tamito Kajiyama
 Lou Kates
+Randall Kern
 Magnus Kessler
 Lawrence Kesteloot
 Vivek Khera
-- 
cgit v0.12

