# HG changeset patch # User Julien Cristau # Date 1373555809 -7200 # Node ID 7a6ea34687650439e0ac009abbdc5c87fa35de2e # Parent 08d8fba05854de5cededa5f08c8b68751abb8f43 [web/views/staticcontrollers] Make ConcatFilesHandler write to a tempfile In order to be more resistant to errors such as ENOSPC, and against different threads accessing the same static data, we: - concatenate data files to a temporary file, and rename it once we're done - delete the tempfile in case of errors - protect the creation of the cache_concat file with a lock Closes #3005547 diff -r 08d8fba05854 -r 7a6ea3468765 web/views/staticcontrollers.py --- a/web/views/staticcontrollers.py Fri Jul 12 18:03:11 2013 +0200 +++ b/web/views/staticcontrollers.py Thu Jul 11 17:16:49 2013 +0200 @@ -27,6 +27,7 @@ import os.path as osp import hashlib import mimetypes +import threading from time import mktime from datetime import datetime, timedelta from logging import getLogger @@ -105,6 +106,7 @@ self._resources = {} self.config = config self.logger = getLogger('cubicweb.web') + self.lock = threading.Lock() def _resource(self, path): """get the resouce""" @@ -143,21 +145,32 @@ def concat_cached_filepath(self, paths): filepath = self.build_filepath(paths) if not self._up_to_date(filepath, paths): - with open(filepath, 'wb') as f: - for path in paths: - dirpath, rid = self._resource(path) - if rid is None: - # In production mode log an error, do not return a 404 - # XXX the erroneous content is cached anyway - self.logger.error('concatenated data url error: %r file ' - 'does not exist', path) - if self.config.debugmode: - raise NotFound(path) - else: - with open(osp.join(dirpath, rid), 'rb') as source: - for line in source: - f.write(line) - f.write('\n') + tmpfile = filepath + '.tmp' + try: + with self.lock: + if self._up_to_date(filepath, paths): + # first check could have raced with some other thread + # updating the file + return filepath + with open(tmpfile, 'wb') as f: + for path in paths: + dirpath, rid = self._resource(path) + if rid is None: + # In production mode log an error, do not return a 404 + # XXX the erroneous content is cached anyway + self.logger.error('concatenated data url error: %r file ' + 'does not exist', path) + if self.config.debugmode: + raise NotFound(path) + else: + with open(osp.join(dirpath, rid), 'rb') as source: + for line in source: + f.write(line) + f.write('\n') + os.rename(tmpfile, filepath) + except: + os.remove(tmpfile) + raise return filepath