diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..92be83e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=crlf diff --git a/integrationtests.py b/integrationtests.py index 7d06ff2a..850277ed 100644 --- a/integrationtests.py +++ b/integrationtests.py @@ -18,6 +18,8 @@ import sys import tempfile import unittest +import time +import concurrent.futures import clcache @@ -1071,6 +1073,44 @@ def testHitsViaMpConcurrent(self): self.assertEqual(stats.numCacheMisses(), 2) self.assertEqual(stats.numCacheEntries(), 2) +class TestConcurrent(unittest.TestCase): + @staticmethod + def runCompiler(): + with cd(os.path.join(ASSETS_DIR, "hits-and-misses")): + cmd = CLCACHE_CMD + ["/nologo", "/EHsc", "/c", 'hit.cpp'] + return subprocess.check_call(cmd) + + def testRaceAddingAnObject(self): + """ This tests attempts to expose a race condition when two clcache invocations decide to add an object to the + cache at the same time """ + + # object cache key + cachekey = '5080f03ed6fda8fecd7562c0fad99926' + + # Obtain final object lock and write the object file + cache = clcache.Cache() + section = cache.compilerArtifactsRepository.section(cachekey) + section.lock.acquire() + + executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) + compilerTask = executor.submit(TestConcurrent.runCompiler) + + # Give the clcache process time to get locked acquiring the artifactsSection lock + time.sleep(5) + + # Replacing artifacts + print('Replacing artifacts') + artifactsPath = lambda objectName: os.path.join(section.cacheEntryDir(cachekey), objectName) + artifacts = [artifactsPath('object'), artifactsPath('output.txt'), artifactsPath('stderr.txt')] + clcache.ensureDirectoryExists(section.cacheEntryDir(cachekey)) + for artifact in artifacts: + with open(artifact, 'w+') as f: + f.write('dummy') + + section.lock.release() + + timeoutSeconds = 5 + self.assertEqual(0, compilerTask.result(timeout=timeoutSeconds)) class TestBasedir(unittest.TestCase): def testBasedir(self):