Index: trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/common.py
===================================================================
--- trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/common.py	(revision 779)
+++ trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/common.py	(revision 781)
@@ -1,9 +1,12 @@
 import unittest
+from fcntl import flock, LOCK_EX, LOCK_UN
+from os import remove
 
 class struct(object):
     'A simple namespace object.'
-    def __init__(self, d = {}):
+    def __init__(self, d = {}, **kwargs):
         'd is the dictionary to update my __dict__ with.'
         self.__dict__.update(d)
+        self.__dict__.update(kwargs)
 
 def dicts2struct(x):
@@ -19,8 +22,39 @@
         return x
 
-def wrap(rsrc, func):
-    "Utility to that emulates with Python 2.5's `with closing(rsrc)`."
-    try: return func(rsrc)
-    finally: rsrc.close()
+#
+# Hacks to work around lack of Python 2.5's `with` statement.
+#
+
+def with_closing(rsrc):
+    "Utility to emulate Python 2.5's `with closing(rsrc)` context manager."
+    def wrapper(func):
+        try: return func(rsrc)
+        finally: rsrc.close()
+    return wrapper
+
+def with_lock_file(path):
+    """
+    Context manager for lock files.  Example:
+
+    @with_lock_file('/tmp/mylock')
+    def input():
+        print 'locked'
+        return raw_input()
+    # decorator is executed immediately
+    print input # prints the input text
+    """
+    def wrapper(func):
+        @with_closing(file(path, 'w'))
+        def g(f):
+            flock(f, LOCK_EX)
+            try: return func()
+            finally: flock(f, LOCK_UN)
+        remove(path)
+        return g
+    return wrapper
+
+#
+# Tests.
+#
 
 class common_tests(unittest.TestCase):
Index: trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/config.py
===================================================================
--- trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/config.py	(revision 779)
+++ trunk/packages/sipb-xen-base/files/usr/share/python-support/sipb-xen-base/invirt/config.py	(revision 781)
@@ -1,9 +1,8 @@
 import json, yaml
 from invirt.common import *
-from os import error, makedirs
-from os.path import dirname, getmtime
+from os.path import getmtime
 
 default_src_path   = '/etc/invirt/master.yaml'
-default_cache_path = '/var/lib/invirt/invirt.json'
+default_cache_path = '/var/lib/invirt/cache.json'
 
 try:    default_loader = yaml.CSafeLoader
@@ -29,12 +28,17 @@
     if not do_refresh:
         # try reading from the cache first
-        try: cfg = wrap(file(cache_path), lambda f: json.read(f.read()))
+        try: cfg = with_closing(file(cache_path))(lambda f: json.read(f.read()))
         except: do_refresh = True
 
     if do_refresh:
-        # reload the source and regenerate the cache
-        cfg = wrap(file(src_path), lambda f: yaml.load(f, default_loader))
-        try: wrap(file(cache_path, 'w'), lambda f: f.write(json.write(cfg)))
-        except: pass # silent failure
+        # Atomically reload the source and regenerate the cache.  The read and
+        # write must be a single transaction, or a stale version may be
+        # written.
+        @with_lock_file('/var/lib/invirt/cache.lock')
+        def cfg():
+            cfg = with_closing(file(src_path))(lambda f: yaml.load(f, default_loader))
+            try: with_closing(file(cache_path, 'w'))(lambda f: f.write(json.write(cfg)))
+            except: pass # silent failure
+            return cfg
     return cfg
 
