Skip to content
This repository was archived by the owner on Feb 4, 2020. It is now read-only.

Commit e441305

Browse files
authored
Merge pull request #286 from siu/master-safeRename
Cache corruption protection: Atomic replacement of manifests, statistics and object files
2 parents cded713 + 5ca3a14 commit e441305

2 files changed

Lines changed: 55 additions & 50 deletions

File tree

clcache.py

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ def normalizeBaseDir(baseDir):
116116
return None
117117

118118

119+
@contextlib.contextmanager
120+
def atomicWrite(fileName):
121+
tempFileName = fileName + '.new'
122+
with open(tempFileName, 'w') as f:
123+
yield f
124+
os.replace(tempFileName, fileName)
125+
126+
119127
class IncludeNotFoundException(Exception):
120128
pass
121129

@@ -157,8 +165,9 @@ def addEntry(self, entry):
157165
"""Adds entry at the top of the entries"""
158166
self._entries.insert(0, entry)
159167

160-
def touchEntry(self, entryIndex):
168+
def touchEntry(self, objectHash):
161169
"""Moves entry in entryIndex position to the top of entries()"""
170+
entryIndex = next((i for i, e in enumerate(self.entries()) if e.objectHash == objectHash), 0)
162171
self._entries.insert(0, self._entries.pop(entryIndex))
163172

164173

@@ -177,7 +186,7 @@ def setManifest(self, manifestHash, manifest):
177186
manifestPath = self.manifestPath(manifestHash)
178187
printTraceStatement("Writing manifest with manifestHash = {} to {}".format(manifestHash, manifestPath))
179188
ensureDirectoryExists(self.manifestSectionDir)
180-
with open(manifestPath, 'w') as outFile:
189+
with atomicWrite(manifestPath) as outFile:
181190
# Converting namedtuple to JSON via OrderedDict preserves key names and keys order
182191
entries = [e._asdict() for e in manifest.entries()]
183192
jsonobject = {'entries': entries}
@@ -637,7 +646,7 @@ def __init__(self, fileName):
637646

638647
def save(self):
639648
if self._dirty:
640-
with open(self._fileName, 'w') as f:
649+
with atomicWrite(self._fileName) as f:
641650
json.dump(self._dict, f, sort_keys=True, indent=4)
642651

643652
def __setitem__(self, key, value):
@@ -967,7 +976,7 @@ def copyOrLink(srcFilePath, dstFilePath):
967976
# lower the chances of corrupting it.
968977
tempDst = dstFilePath + '.tmp'
969978
copyfile(srcFilePath, tempDst)
970-
os.rename(tempDst, dstFilePath)
979+
os.replace(tempDst, dstFilePath)
971980

972981

973982
def myExecutablePath():
@@ -1392,17 +1401,17 @@ def printStatistics(cache):
13921401

13931402

13941403
def resetStatistics(cache):
1395-
with cache.statistics as stats:
1404+
with cache.statistics.lock, cache.statistics as stats:
13961405
stats.resetCounters()
13971406

13981407

13991408
def cleanCache(cache):
1400-
with cache.statistics as stats, cache.configuration as cfg:
1409+
with cache.lock, cache.statistics as stats, cache.configuration as cfg:
14011410
cache.clean(stats, cfg.maximumCacheSize())
14021411

14031412

14041413
def clearCache(cache):
1405-
with cache.statistics as stats:
1414+
with cache.lock, cache.statistics as stats:
14061415
cache.clean(stats, 0)
14071416

14081417

@@ -1508,25 +1517,21 @@ def main():
15081517
cache = Cache()
15091518

15101519
if len(sys.argv) == 2 and sys.argv[1] == "-s":
1511-
with cache.lock:
1512-
printStatistics(cache)
1520+
printStatistics(cache)
15131521
return 0
15141522

15151523
if len(sys.argv) == 2 and sys.argv[1] == "-c":
1516-
with cache.lock:
1517-
cleanCache(cache)
1524+
cleanCache(cache)
15181525
print('Cache cleaned')
15191526
return 0
15201527

15211528
if len(sys.argv) == 2 and sys.argv[1] == "-C":
1522-
with cache.lock:
1523-
clearCache(cache)
1529+
clearCache(cache)
15241530
print('Cache cleared')
15251531
return 0
15261532

15271533
if len(sys.argv) == 2 and sys.argv[1] == "-z":
1528-
with cache.lock:
1529-
resetStatistics(cache)
1534+
resetStatistics(cache)
15301535
print('Statistics reset')
15311536
return 0
15321537

@@ -1638,8 +1643,7 @@ def scheduleJobs(cache, compiler, cmdLine, environment, sourceFiles, objectFiles
16381643
break
16391644

16401645
if cleanupRequired:
1641-
with cache.lock:
1642-
cleanCache(cache)
1646+
cleanCache(cache)
16431647

16441648
return exitCode
16451649

@@ -1661,33 +1665,35 @@ def processSingleSource(compiler, cmdLine, sourceFile, objectFile, environment):
16611665
def processDirect(cache, objectFile, compiler, cmdLine, sourceFile):
16621666
manifestHash = ManifestRepository.getManifestHash(compiler, cmdLine, sourceFile)
16631667
manifestHit = None
1664-
with cache.manifestLockFor(manifestHash):
1665-
manifest = cache.getManifest(manifestHash)
1666-
if manifest:
1667-
for entryIndex, entry in enumerate(manifest.entries()):
1668-
# NOTE: command line options already included in hash for manifest name
1669-
try:
1670-
includesContentHash = ManifestRepository.getIncludesContentHashForFiles(
1671-
[expandBasedirPlaceholder(path) for path in entry.includeFiles])
1668+
manifest = cache.getManifest(manifestHash)
1669+
if manifest:
1670+
for entryIndex, entry in enumerate(manifest.entries()):
1671+
# NOTE: command line options already included in hash for manifest name
1672+
try:
1673+
includesContentHash = ManifestRepository.getIncludesContentHashForFiles(
1674+
[expandBasedirPlaceholder(path) for path in entry.includeFiles])
16721675

1673-
if entry.includesContentHash == includesContentHash:
1674-
cachekey = entry.objectHash
1675-
assert cachekey is not None
1676+
if entry.includesContentHash == includesContentHash:
1677+
cachekey = entry.objectHash
1678+
assert cachekey is not None
1679+
if entryIndex > 0:
16761680
# Move manifest entry to the top of the entries in the manifest
1677-
manifest.touchEntry(entryIndex)
1678-
cache.setManifest(manifestHash, manifest)
1681+
with cache.manifestLockFor(manifestHash):
1682+
manifest = cache.getManifest(manifestHash) or Manifest()
1683+
manifest.touchEntry(cachekey)
1684+
cache.setManifest(manifestHash, manifest)
16791685

1680-
manifestHit = True
1681-
with cache.lockFor(cachekey):
1682-
if cache.hasEntry(cachekey):
1683-
return processCacheHit(cache, objectFile, cachekey)
1686+
manifestHit = True
1687+
with cache.lockFor(cachekey):
1688+
if cache.hasEntry(cachekey):
1689+
return processCacheHit(cache, objectFile, cachekey)
16841690

1685-
except IncludeNotFoundException:
1686-
pass
1691+
except IncludeNotFoundException:
1692+
pass
16871693

1688-
unusableManifestMissReason = Statistics.registerHeaderChangedMiss
1689-
else:
1690-
unusableManifestMissReason = Statistics.registerSourceChangedMiss
1694+
unusableManifestMissReason = Statistics.registerHeaderChangedMiss
1695+
else:
1696+
unusableManifestMissReason = Statistics.registerSourceChangedMiss
16911697

16921698
if manifestHit is None:
16931699
stripIncludes = False
@@ -1700,22 +1706,21 @@ def processDirect(cache, objectFile, compiler, cmdLine, sourceFile):
17001706
includePaths, compilerOutput = parseIncludesSet(compilerResult[1], sourceFile, stripIncludes)
17011707
compilerResult = (compilerResult[0], compilerOutput, compilerResult[2])
17021708

1703-
with cache.manifestLockFor(manifestHash):
1704-
if manifestHit is not None:
1705-
return ensureArtifactsExist(cache, cachekey, unusableManifestMissReason,
1706-
objectFile, compilerResult)
1707-
1708-
entry = createManifestEntry(manifestHash, includePaths)
1709-
cachekey = entry.objectHash
1709+
if manifestHit is not None:
1710+
return ensureArtifactsExist(cache, cachekey, unusableManifestMissReason,
1711+
objectFile, compilerResult)
17101712

1711-
def addManifest():
1713+
entry = createManifestEntry(manifestHash, includePaths)
1714+
cachekey = entry.objectHash
17121715

1716+
def addManifest():
1717+
with cache.manifestLockFor(manifestHash):
17131718
manifest = cache.getManifest(manifestHash) or Manifest()
17141719
manifest.addEntry(entry)
17151720
cache.setManifest(manifestHash, manifest)
17161721

1717-
return ensureArtifactsExist(cache, cachekey, unusableManifestMissReason,
1718-
objectFile, compilerResult, addManifest)
1722+
return ensureArtifactsExist(cache, cachekey, unusableManifestMissReason,
1723+
objectFile, compilerResult, addManifest)
17191724

17201725

17211726
def processNoDirect(cache, objectFile, compiler, cmdLine, environment):

unittests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -973,7 +973,7 @@ def testAddEntry(self):
973973
def testTouchEntry(self):
974974
manifest = Manifest(TestManifest.entries)
975975
self.assertEqual(TestManifest.entry1, manifest.entries()[0])
976-
manifest.touchEntry(1)
976+
manifest.touchEntry("8771d7ebcf6c8bd57a3d6485f63e3a89")
977977
self.assertEqual(TestManifest.entry2, manifest.entries()[0])
978978

979979

0 commit comments

Comments
 (0)