Blob Blame History Raw
From 6b9789250e19c4e02c1c8efad80cf324bb787466 Mon Sep 17 00:00:00 2001
From: Dan Callaghan <dcallagh@redhat.com>
Date: Wed, 7 May 2014 11:17:38 +1000
Subject: [PATCH 07/14] Create cache entries atomically in FilesystemCache

The filesystem cache needs to ensure entries only become visible once
they are completely written. Otherwise multiple processes sharing
a cache directory could see empty or incomplete files while they are
being written by another process. Fixes #313.

This patch uses the standard write-then-rename dance for POSIX systems.
I'm not sure if it works on Windows though...

(cherry picked from commit 18ffd83655f11898d4f7f52e9ed47e892a6955b9)

diff --git a/src/webassets/cache.py b/src/webassets/cache.py
index d1245e6..af2590e 100644
--- a/src/webassets/cache.py
+++ b/src/webassets/cache.py
@@ -16,6 +16,7 @@ also serve in other places.
 import os
 from os import path
 import errno
+import tempfile
 from webassets import six
 from webassets.merge import BaseHunk
 from webassets.filter import Filter, freezedicts
@@ -186,12 +187,18 @@ class FilesystemCache(BaseCache):
         return safe_unpickle(result)
 
     def set(self, key, data):
-        filename = path.join(self.directory, '%s' % make_md5(self.V, key))
-        f = open(filename, 'wb')
+        md5 = '%s' % make_md5(self.V, key)
+        filename = path.join(self.directory, md5)
+        fd, temp_filename = tempfile.mkstemp(prefix='.' + md5,
+                dir=self.directory)
         try:
-            f.write(pickle.dumps(data))
-        finally:
-            f.close()
+            with os.fdopen(fd, 'wb') as f:
+                pickle.dump(data, f)
+                f.flush()
+                os.rename(temp_filename, filename)
+        except:
+            os.unlink(temp_filename)
+            raise
 
 
 def get_cache(option, env):
-- 
1.9.3