1010import cProfile
1111import codecs
1212from collections import defaultdict , namedtuple
13+ import contextlib
1314import errno
1415import hashlib
1516import json
@@ -127,6 +128,7 @@ def __str__(self):
127128class ManifestSection (object ):
128129 def __init__ (self , manifestSectionDir ):
129130 self .manifestSectionDir = manifestSectionDir
131+ self .lock = CacheLock .forPath (self .manifestSectionDir )
130132
131133 def manifestPath (self , manifestHash ):
132134 return os .path .join (self .manifestSectionDir , manifestHash + ".json" )
@@ -152,6 +154,18 @@ def getManifest(self, manifestHash):
152154 return None
153155
154156
157+ @contextlib .contextmanager
158+ def allSectionsLocked (repository ):
159+ sections = list (repository .sections ())
160+ for section in sections :
161+ section .lock .acquire ()
162+ try :
163+ yield
164+ finally :
165+ for section in sections :
166+ section .lock .release ()
167+
168+
155169class ManifestRepository (object ):
156170 # Bump this counter whenever the current manifest file format changes.
157171 # E.g. changing the file format from {'oldkey': ...} to {'newkey': ...} requires
@@ -270,10 +284,17 @@ def acquire(self):
270284 def release (self ):
271285 windll .kernel32 .ReleaseMutex (self ._mutex )
272286
287+ @staticmethod
288+ def forPath (path ):
289+ timeoutMs = int (os .environ .get ('CLCACHE_OBJECT_CACHE_TIMEOUT_MS' , 10 * 1000 ))
290+ lockName = path .replace (':' , '-' ).replace ('\\ ' , '-' )
291+ return CacheLock (lockName , timeoutMs )
292+
273293
274294class CompilerArtifactsSection (object ):
275295 def __init__ (self , compilerArtifactsSectionDir ):
276296 self .compilerArtifactsSectionDir = compilerArtifactsSectionDir
297+ self .lock = CacheLock .forPath (self .compilerArtifactsSectionDir )
277298
278299 def cacheEntryDir (self , key ):
279300 return os .path .join (self .compilerArtifactsSectionDir , key )
@@ -424,13 +445,17 @@ def __init__(self, cacheDirectory=None):
424445 ensureDirectoryExists (compilerArtifactsRootDir )
425446 self .compilerArtifactsRepository = CompilerArtifactsRepository (compilerArtifactsRootDir )
426447
427- lockName = self .cacheDirectory ().replace (':' , '-' ).replace ('\\ ' , '-' )
428- timeoutMs = int (os .environ .get ('CLCACHE_OBJECT_CACHE_TIMEOUT_MS' , 10 * 1000 ))
429- self .lock = CacheLock (lockName , timeoutMs )
430-
431448 self .configuration = Configuration (os .path .join (self .dir , "config.txt" ))
432449 self .statistics = Statistics (os .path .join (self .dir , "stats.txt" ))
433450
451+ @property
452+ @contextlib .contextmanager
453+ def lock (self ):
454+ with allSectionsLocked (self .manifestRepository ), \
455+ allSectionsLocked (self .compilerArtifactsRepository ), \
456+ self .statistics .lock :
457+ yield
458+
434459 def cacheDirectory (self ):
435460 return self .dir
436461
@@ -551,6 +576,7 @@ class Statistics(object):
551576 def __init__ (self , statsFile ):
552577 self ._statsFile = statsFile
553578 self ._stats = None
579+ self .lock = CacheLock .forPath (self ._statsFile )
554580
555581 def __enter__ (self ):
556582 self ._stats = PersistentJSONDict (self ._statsFile )
@@ -1316,37 +1342,49 @@ def parseIncludesSet(compilerOutput, sourceFile, strip):
13161342 return includesSet , compilerOutput
13171343
13181344
1319- def addObjectToCache (stats , cache , cachekey , artifacts ):
1345+ def addObjectToCache (stats , cache , section , cachekey , artifacts ):
1346+ # This function asserts that the caller locked 'section' and 'stats'
1347+ # already and also saves them
13201348 printTraceStatement ("Adding file {} to cache using key {}" .format (artifacts .objectFilePath , cachekey ))
1321- cache .compilerArtifactsRepository .section (cachekey ).setEntry (cachekey , artifacts )
1349+
1350+ section .setEntry (cachekey , artifacts )
13221351 stats .registerCacheEntry (os .path .getsize (artifacts .objectFilePath ))
1352+
13231353 with cache .configuration as cfg :
1324- cache . clean ( stats , cfg .maximumCacheSize () )
1354+ return stats . currentCacheSize () >= cfg .maximumCacheSize ()
13251355
13261356
13271357def processCacheHit (cache , objectFile , cachekey ):
1328- with cache .statistics as stats :
1329- stats .registerCacheHit ()
13301358 printTraceStatement ("Reusing cached object for key {} for object file {}" .format (cachekey , objectFile ))
1331- if os .path .exists (objectFile ):
1332- os .remove (objectFile )
1359+
13331360 section = cache .compilerArtifactsRepository .section (cachekey )
1334- cachedArtifacts = section .getEntry (cachekey )
1335- copyOrLink (cachedArtifacts .objectFilePath , objectFile )
1336- printTraceStatement ("Finished. Exit code 0" )
1337- return 0 , cachedArtifacts .stdout , cachedArtifacts .stderr
1361+ with section .lock :
1362+ with cache .statistics .lock , cache .statistics as stats :
1363+ stats .registerCacheHit ()
1364+
1365+ if os .path .exists (objectFile ):
1366+ os .remove (objectFile )
1367+
1368+ cachedArtifacts = section .getEntry (cachekey )
1369+ copyOrLink (cachedArtifacts .objectFilePath , objectFile )
1370+ printTraceStatement ("Finished. Exit code 0" )
1371+ return 0 , cachedArtifacts .stdout , cachedArtifacts .stderr , False
13381372
13391373
13401374def postprocessObjectEvicted (cache , objectFile , cachekey , compilerResult ):
13411375 printTraceStatement ("Cached object already evicted for key {} for object {}" .format (cachekey , objectFile ))
13421376 returnCode , compilerOutput , compilerStderr = compilerResult
13431377
1344- with cache .lock , cache .statistics as stats :
1378+ cleanupRequired = False
1379+
1380+ section = cache .compilerArtifactsRepository .section (cachekey )
1381+ with section .lock , cache .statistics .lock , cache .statistics as stats :
13451382 stats .registerEvictedMiss ()
13461383 if returnCode == 0 and os .path .exists (objectFile ):
1347- addObjectToCache (stats , cache , cachekey , CompilerArtifacts (objectFile , compilerOutput , compilerStderr ))
1384+ artifacts = CompilerArtifacts (objectFile , compilerOutput , compilerStderr )
1385+ cleanupRequired = addObjectToCache (stats , cache , section , cachekey , artifacts )
13481386
1349- return compilerResult
1387+ return compilerResult + ( cleanupRequired ,)
13501388
13511389
13521390def createManifest (manifestHash , includePaths ):
@@ -1377,13 +1415,16 @@ def postprocessHeaderChangedMiss(
13771415 if returnCode == 0 and os .path .exists (objectFile ):
13781416 manifest , cachekey = createManifest (manifestHash , includePaths )
13791417
1380- with cache .lock , cache .statistics as stats :
1418+ cleanupRequired = False
1419+ section = cache .compilerArtifactsRepository .section (cachekey )
1420+ with section .lock , cache .statistics .lock , cache .statistics as stats :
13811421 stats .registerHeaderChangedMiss ()
13821422 if returnCode == 0 and os .path .exists (objectFile ):
1383- addObjectToCache (stats , cache , cachekey , CompilerArtifacts (objectFile , compilerOutput , compilerStderr ))
1423+ artifacts = CompilerArtifacts (objectFile , compilerOutput , compilerStderr )
1424+ cleanupRequired = addObjectToCache (stats , cache , section , cachekey , artifacts )
13841425 manifestSection .setManifest (manifestHash , manifest )
13851426
1386- return returnCode , compilerOutput , compilerStderr
1427+ return returnCode , compilerOutput , compilerStderr , cleanupRequired
13871428
13881429
13891430def postprocessNoManifestMiss (
@@ -1397,14 +1438,17 @@ def postprocessNoManifestMiss(
13971438 if returnCode == 0 and os .path .exists (objectFile ):
13981439 manifest , cachekey = createManifest (manifestHash , includePaths )
13991440
1400- with cache .lock , cache .statistics as stats :
1441+ cleanupRequired = False
1442+ section = cache .compilerArtifactsRepository .section (cachekey )
1443+ with section .lock , cache .statistics .lock , cache .statistics as stats :
14011444 stats .registerSourceChangedMiss ()
14021445 if returnCode == 0 and os .path .exists (objectFile ):
1446+ artifacts = CompilerArtifacts (objectFile , compilerOutput , compilerStderr )
14031447 # Store compile output and manifest
1404- addObjectToCache (stats , cache , cachekey , CompilerArtifacts ( objectFile , compilerOutput , compilerStderr ) )
1448+ cleanupRequired = addObjectToCache (stats , cache , section , cachekey , artifacts )
14051449 manifestSection .setManifest (manifestHash , manifest )
14061450
1407- return returnCode , compilerOutput , compilerStderr
1451+ return returnCode , compilerOutput , compilerStderr , cleanupRequired
14081452
14091453
14101454def main ():
@@ -1481,7 +1525,7 @@ def main():
14811525
14821526
14831527def updateCacheStatistics (cache , method ):
1484- with cache .lock , cache .statistics as stats :
1528+ with cache .statistics . lock , cache .statistics as stats :
14851529 method (stats )
14861530
14871531
@@ -1500,11 +1544,18 @@ def processCompileRequest(cache, compiler, args):
15001544 else :
15011545 assert objectFile is not None
15021546 if 'CLCACHE_NODIRECT' in os .environ :
1503- compilerResult = processNoDirect (cache , objectFile , compiler , cmdLine , environment )
1547+ returnCode , compilerOutput , compilerStderr , cleanupRequired = \
1548+ processNoDirect (cache , objectFile , compiler , cmdLine , environment )
15041549 else :
1505- compilerResult = processDirect (cache , objectFile , compiler , cmdLine , sourceFiles [0 ])
1506- printTraceStatement ("Finished. Exit code {0:d}" .format (compilerResult [0 ]))
1507- return compilerResult
1550+ returnCode , compilerOutput , compilerStderr , cleanupRequired = \
1551+ processDirect (cache , objectFile , compiler , cmdLine , sourceFiles [0 ])
1552+ printTraceStatement ("Finished. Exit code {0:d}" .format (returnCode ))
1553+
1554+ if cleanupRequired :
1555+ with cache .lock :
1556+ cleanCache (cache )
1557+
1558+ return returnCode , compilerOutput , compilerStderr
15081559 except InvalidArgumentError :
15091560 printTraceStatement ("Cannot cache invocation as {}: invalid argument" .format (cmdLine ))
15101561 updateCacheStatistics (cache , Statistics .registerCallWithInvalidArgument )
@@ -1528,6 +1579,8 @@ def processCompileRequest(cache, compiler, args):
15281579 except CalledForPreprocessingError :
15291580 printTraceStatement ("Cannot cache invocation as {}: called for preprocessing" .format (cmdLine ))
15301581 updateCacheStatistics (cache , Statistics .registerCallForPreprocessing )
1582+ except IncludeNotFoundException :
1583+ pass
15311584
15321585 return invokeRealCompiler (compiler , args [1 :])
15331586
@@ -1536,62 +1589,60 @@ def processDirect(cache, objectFile, compiler, cmdLine, sourceFile):
15361589 baseDir = normalizeBaseDir (os .environ .get ('CLCACHE_BASEDIR' ))
15371590 manifestHash = ManifestRepository .getManifestHash (compiler , cmdLine , sourceFile )
15381591 manifestSection = cache .manifestRepository .section (manifestHash )
1539- with cache .lock :
1540- createNewManifest = False
1592+ with manifestSection .lock :
15411593 manifest = manifestSection .getManifest (manifestHash )
1542- if manifest is not None :
1543- # NOTE: command line options already included in hash for manifest name
1544- try :
1545- includesContentHash = ManifestRepository .getIncludesContentHashForFiles ({
1546- expandBasedirPlaceholder (path , baseDir ):contentHash
1547- for path , contentHash in manifest .includeFiles .items ()
1548- })
1549-
1550- cachekey = manifest .includesContentToObjectMap .get (includesContentHash )
1551- assert cachekey is not None
1552- if cache .compilerArtifactsRepository .section (cachekey ).hasEntry (cachekey ):
1553- return processCacheHit (cache , objectFile , cachekey )
1554- else :
1555- postProcessing = lambda compilerResult : postprocessObjectEvicted (
1556- cache , objectFile , cachekey , compilerResult )
1557- except IncludeChangedException :
1558- createNewManifest = True
1559- postProcessing = lambda compilerResult : postprocessHeaderChangedMiss (
1560- cache , objectFile , manifestSection , manifestHash , sourceFile , compilerResult , stripIncludes )
1561- except IncludeNotFoundException :
1562- # register nothing. This is probably just a compile error
1563- postProcessing = None
1564- else :
1565- createNewManifest = True
1566- postProcessing = lambda compilerResult : postprocessNoManifestMiss (
1594+ if manifest is None :
1595+ stripIncludes = False
1596+ if '/showIncludes' not in cmdLine :
1597+ cmdLine .insert (0 , '/showIncludes' )
1598+ stripIncludes = True
1599+ compilerResult = invokeRealCompiler (compiler , cmdLine , captureOutput = True )
1600+ return postprocessNoManifestMiss (
15671601 cache , objectFile , manifestSection , manifestHash , sourceFile , compilerResult , stripIncludes )
15681602
1569- if createNewManifest :
1570- stripIncludes = False
1571- if '/showIncludes' not in cmdLine :
1572- cmdLine .insert (0 , '/showIncludes' )
1573- stripIncludes = True
1603+ # NOTE: command line options already included in hash for manifest name
1604+ try :
1605+ includesContentHash = ManifestRepository .getIncludesContentHashForFiles ({
1606+ expandBasedirPlaceholder (path , baseDir ):contentHash
1607+ for path , contentHash in manifest .includeFiles .items ()
1608+ })
1609+ except IncludeChangedException :
1610+ stripIncludes = False
1611+ if '/showIncludes' not in cmdLine :
1612+ cmdLine .insert (0 , '/showIncludes' )
1613+ stripIncludes = True
1614+ compilerResult = invokeRealCompiler (compiler , cmdLine , captureOutput = True )
1615+ return postprocessHeaderChangedMiss (
1616+ cache , objectFile , manifestSection , manifestHash , sourceFile , compilerResult , stripIncludes )
15741617
1575- compilerResult = invokeRealCompiler (compiler , cmdLine , captureOutput = True )
1576- if postProcessing :
1577- compilerResult = postProcessing (compilerResult )
1578- return compilerResult
1618+ cachekey = manifest .includesContentToObjectMap .get (includesContentHash )
1619+ assert cachekey is not None
1620+
1621+ artifactSection = cache .compilerArtifactsRepository .section (cachekey )
1622+ with artifactSection .lock :
1623+ if artifactSection .hasEntry (cachekey ):
1624+ return processCacheHit (cache , objectFile , cachekey )
1625+ compilerResult = invokeRealCompiler (compiler , cmdLine , captureOutput = True )
1626+ return postprocessObjectEvicted (cache , objectFile , cachekey , compilerResult )
15791627
15801628
15811629def processNoDirect (cache , objectFile , compiler , cmdLine , environment ):
15821630 cachekey = CompilerArtifactsRepository .computeKeyNodirect (compiler , cmdLine , environment )
1583- with cache .lock :
1584- if cache .compilerArtifactsRepository .section (cachekey ).hasEntry (cachekey ):
1631+ section = cache .compilerArtifactsRepository .section (cachekey )
1632+ cleanupRequired = False
1633+ with section .lock :
1634+ if section .hasEntry (cachekey ):
15851635 return processCacheHit (cache , objectFile , cachekey )
15861636
1587- compilerResult = invokeRealCompiler (compiler , cmdLine , captureOutput = True , environment = environment )
1588- returnCode , compilerStdout , compilerStderr = compilerResult
1589- with cache .lock , cache .statistics as stats :
1590- stats .registerCacheMiss ()
1591- if returnCode == 0 and os .path .exists (objectFile ):
1592- addObjectToCache (stats , cache , cachekey , CompilerArtifacts (objectFile , compilerStdout , compilerStderr ))
1637+ compilerResult = invokeRealCompiler (compiler , cmdLine , captureOutput = True , environment = environment )
1638+ returnCode , compilerStdout , compilerStderr = compilerResult
1639+ with cache .statistics .lock , cache .statistics as stats :
1640+ stats .registerCacheMiss ()
1641+ if returnCode == 0 and os .path .exists (objectFile ):
1642+ artifacts = CompilerArtifacts (objectFile , compilerStdout , compilerStderr )
1643+ cleanupRequired = addObjectToCache (stats , cache , section , cachekey , artifacts )
15931644
1594- return returnCode , compilerStdout , compilerStderr
1645+ return returnCode , compilerStdout , compilerStderr , cleanupRequired
15951646
15961647if __name__ == '__main__' :
15971648 if 'CLCACHE_PROFILE' in os .environ :
0 commit comments