Merge "Prevent corruption of cache by using atomic update"

This commit is contained in:
Jenkins 2016-05-21 00:15:32 +00:00 committed by Gerrit Code Review
commit 008082568c
1 changed files with 21 additions and 12 deletions

View File

@ -23,6 +23,7 @@ import operator
import os import os
from pprint import pformat from pprint import pformat
import re import re
import tempfile
import time import time
import xml.etree.ElementTree as XML import xml.etree.ElementTree as XML
import yaml import yaml
@ -45,8 +46,10 @@ class CacheStorage(object):
# modules so that they are available to be used when the destructor # modules so that they are available to be used when the destructor
# is being called since python will not guarantee that it won't have # is being called since python will not guarantee that it won't have
# removed global module references during teardown. # removed global module references during teardown.
_yaml = yaml
_logger = logger _logger = logger
_os = os
_tempfile = tempfile
_yaml = yaml
def __init__(self, jenkins_url, flush=False): def __init__(self, jenkins_url, flush=False):
cache_dir = self.get_cache_dir() cache_dir = self.get_cache_dir()
@ -97,23 +100,29 @@ class CacheStorage(object):
return True return True
def save(self): def save(self):
# check we initialized sufficiently in case called via __del__ # use self references to required modules in case called via __del__
# write to tempfile under same directory and then replace to avoid
# issues around corruption such the process be killed
tfile = self._tempfile.NamedTemporaryFile(dir=self.get_cache_dir(),
delete=False)
self._yaml.dump(self.data, utils.wrap_stream(tfile))
# force contents to be synced on disk before overwriting cachefile
tfile.flush()
self._os.fsync(tfile.fileno())
tfile.close()
self._os.rename(tfile.name, self.cachefilename)
self._logger.debug("Cache written out to '%s'" % self.cachefilename)
def __del__(self):
# check we initialized sufficiently in case called
# due to an exception occurring in the __init__ # due to an exception occurring in the __init__
if getattr(self, 'data', None) is not None: if getattr(self, 'data', None) is not None:
try: try:
with io.open(self.cachefilename, 'w', self.save()
encoding='utf-8') as yfile:
self._yaml.dump(self.data, yfile)
except Exception as e: except Exception as e:
self._logger.error("Failed to write to cache file '%s' on " self._logger.error("Failed to write to cache file '%s' on "
"exit: %s" % (self.cachefilename, e)) "exit: %s" % (self.cachefilename, e))
else:
self._logger.info("Cache saved")
self._logger.debug("Cache written out to '%s'" %
self.cachefilename)
def __del__(self):
self.save()
class Jenkins(object): class Jenkins(object):