Skip to content

Commit 687f2f0

Browse files
committed
[clangd] [Modules] Avoid building the same module at the same time
1 parent 4ed7bf5 commit 687f2f0

File tree

2 files changed

+180
-0
lines changed

2 files changed

+180
-0
lines changed

clang-tools-extra/clangd/ModulesBuilder.cpp

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,13 +327,91 @@ class ModulesBuilder::ModuleFileCache {
327327
const ThreadsafeFS &TFS,
328328
PrerequisiteModules &BuiltModuleFiles);
329329

330+
void startBuildingModule(StringRef ModuleName) {
331+
std::lock_guard<std::mutex> _(ModulesBuildingMutex);
332+
BuildingModules.insert(ModuleName);
333+
}
334+
void endBuildingModule(StringRef ModuleName) {
335+
std::lock_guard<std::mutex> _(ModulesBuildingMutex);
336+
BuildingModules.erase(ModuleName);
337+
}
338+
bool isBuildingModule(StringRef ModuleName) {
339+
std::lock_guard<std::mutex> _(ModulesBuildingMutex);
340+
return BuildingModules.contains(ModuleName);
341+
}
342+
330343
const GlobalCompilationDatabase &CDB;
331344

332345
llvm::StringMap<std::shared_ptr<ModuleFile>> ModuleFiles;
333346
// Mutex to guard accesses to ModuleFiles.
334347
std::mutex ModuleFilesMutex;
348+
349+
// We should only build a unique module at most at the same time.
350+
// When we want to build a module, use the mutex to lock it and use the
351+
// condition variable to notify other threads the status of the build results.
352+
//
353+
// Store the mutex and the condition_variable in shared_ptr since they may be
354+
// accessed by many threads.
355+
llvm::StringMap<std::shared_ptr<std::mutex>> BuildingModuleMutexes;
356+
llvm::StringMap<std::shared_ptr<std::condition_variable>> BuildingModuleCVs;
357+
// The building modules set. A successed built module or a failed module or
358+
// an unbuilt module shouldn't be in this set.
359+
// This set is helpful to control the behavior of the condition variables.
360+
llvm::StringSet<> BuildingModules;
361+
// Lock when we access BuildingModules, BuildingModuleMutexes and
362+
// BuildingModuleCVs.
363+
std::mutex ModulesBuildingMutex;
364+
365+
/// An RAII object to guard the process to build a specific module.
366+
struct ModuleBuildingSharedOwner {
367+
public:
368+
ModuleBuildingSharedOwner(StringRef ModuleName,
369+
std::shared_ptr<std::mutex> &Mutex,
370+
std::shared_ptr<std::condition_variable> &CV,
371+
ModuleFileCache &Cache)
372+
: ModuleName(ModuleName), Mutex(Mutex), CV(CV), Cache(Cache) {
373+
IsFirstTask = (Mutex.use_count() == 2);
374+
}
375+
376+
~ModuleBuildingSharedOwner();
377+
378+
bool isUniqueBuildingOwner() { return IsFirstTask; }
379+
380+
std::mutex &getMutex() { return *Mutex; }
381+
382+
std::condition_variable &getCV() { return *CV; }
383+
384+
private:
385+
StringRef ModuleName;
386+
std::shared_ptr<std::mutex> Mutex;
387+
std::shared_ptr<std::condition_variable> CV;
388+
ModuleFileCache &Cache;
389+
bool IsFirstTask;
390+
};
391+
392+
ModuleBuildingSharedOwner
393+
getOrCreateModuleBuildingOwner(StringRef ModuleName);
335394
};
336395

396+
ModulesBuilder::ModuleFileCache::ModuleBuildingSharedOwner::
397+
~ModuleBuildingSharedOwner() {
398+
std::lock_guard<std::mutex> _(Cache.ModulesBuildingMutex);
399+
400+
Mutex.reset();
401+
CV.reset();
402+
403+
// Try to release the memory in builder if possible.
404+
if (auto Iter = Cache.BuildingModuleCVs.find(ModuleName);
405+
Iter != Cache.BuildingModuleCVs.end() &&
406+
Iter->getValue().use_count() == 1)
407+
Cache.BuildingModuleCVs.erase(Iter);
408+
409+
if (auto Iter = Cache.BuildingModuleMutexes.find(ModuleName);
410+
Iter != Cache.BuildingModuleMutexes.end() &&
411+
Iter->getValue().use_count() == 1)
412+
Cache.BuildingModuleMutexes.erase(Iter);
413+
}
414+
337415
std::shared_ptr<ModuleFile>
338416
ModulesBuilder::ModuleFileCache::isValidModuleFileUnlocked(
339417
StringRef ModuleName, ProjectModules &MDB, const ThreadsafeFS &TFS,
@@ -374,6 +452,28 @@ std::shared_ptr<ModuleFile> ModulesBuilder::ModuleFileCache::getValidModuleFile(
374452
return isValidModuleFileUnlocked(ModuleName, MDB, TFS, BuiltModuleFiles);
375453
}
376454

455+
ModulesBuilder::ModuleFileCache::ModuleBuildingSharedOwner
456+
ModulesBuilder::ModuleFileCache::getOrCreateModuleBuildingOwner(
457+
StringRef ModuleName) {
458+
std::lock_guard<std::mutex> _(ModulesBuildingMutex);
459+
460+
auto MutexIter = BuildingModuleMutexes.find(ModuleName);
461+
if (MutexIter == BuildingModuleMutexes.end())
462+
MutexIter = BuildingModuleMutexes
463+
.try_emplace(ModuleName, std::make_shared<std::mutex>())
464+
.first;
465+
466+
auto CVIter = BuildingModuleCVs.find(ModuleName);
467+
if (CVIter == BuildingModuleCVs.end())
468+
CVIter = BuildingModuleCVs
469+
.try_emplace(ModuleName,
470+
std::make_shared<std::condition_variable>())
471+
.first;
472+
473+
return ModuleBuildingSharedOwner(ModuleName, MutexIter->getValue(),
474+
CVIter->getValue(), *this);
475+
}
476+
377477
llvm::Error ModulesBuilder::ModuleFileCache::getOrBuildModuleFile(
378478
StringRef ModuleName, const ThreadsafeFS &TFS, ProjectModules &MDB,
379479
ReusablePrerequisiteModules &BuiltModuleFiles) {
@@ -405,7 +505,40 @@ llvm::Error ModulesBuilder::ModuleFileCache::getOrBuildModuleFile(
405505
return llvm::Error::success();
406506
}
407507

508+
ModuleBuildingSharedOwner ModuleBuildingOwner =
509+
getOrCreateModuleBuildingOwner(ModuleName);
510+
511+
std::condition_variable &CV = ModuleBuildingOwner.getCV();
512+
std::unique_lock<std::mutex> lk(ModuleBuildingOwner.getMutex());
513+
if (!ModuleBuildingOwner.isUniqueBuildingOwner()) {
514+
log("Waiting other task for module {0}", ModuleName);
515+
CV.wait(lk, [this, ModuleName] { return !isBuildingModule(ModuleName); });
516+
517+
// Try to access the built module files from other threads manually.
518+
// We don't call getValidModuleFile here since it may be too heavy.
519+
std::lock_guard<std::mutex> _(ModuleFilesMutex);
520+
auto Iter = ModuleFiles.find(ModuleName);
521+
if (Iter != ModuleFiles.end()) {
522+
log("Got module file from other task building {0}", ModuleName);
523+
BuiltModuleFiles.addModuleFile(Iter->second);
524+
return llvm::Error::success();
525+
}
526+
527+
// If the module file is not in the cache, it indicates that the building
528+
// from other thread failed, so we give up earlier in this case to avoid
529+
// wasting time.
530+
return llvm::createStringError(llvm::formatv(
531+
"The module file {0} may be failed to build in other thread.",
532+
ModuleName));
533+
}
534+
408535
log("Building module {0}", ModuleName);
536+
startBuildingModule(ModuleName);
537+
538+
auto _ = llvm::make_scope_exit([&]() {
539+
endBuildingModule(ModuleName);
540+
CV.notify_all();
541+
});
409542

410543
llvm::SmallString<256> ModuleFilesPrefix =
411544
getUniqueModuleFilesPath(ModuleUnitFileName);

clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,53 @@ export int B = 44 + M;
518518
EXPECT_EQ(HSOptsA.PrebuiltModuleFiles, HSOptsB.PrebuiltModuleFiles);
519519
}
520520

521+
TEST_F(PrerequisiteModulesTests, DeduplicateReusablePrerequisiteModulesTest) {
522+
MockDirectoryCompilationDatabase CDB(TestDir, FS);
523+
524+
CDB.addFile("M.cppm", R"cpp(
525+
export module M;
526+
export int M = 43;
527+
)cpp");
528+
CDB.addFile("A.cppm", R"cpp(
529+
export module A;
530+
import M;
531+
export int A = 43 + M;
532+
)cpp");
533+
CDB.addFile("B.cppm", R"cpp(
534+
export module B;
535+
import M;
536+
export int B = 44 + M;
537+
)cpp");
538+
539+
ModulesBuilder Builder(CDB);
540+
541+
std::unique_ptr<PrerequisiteModules> AInfo, BInfo;
542+
543+
// Trying to build A and B at the same time and check we will only build
544+
// module M once.
545+
AsyncTaskRunner ThreadPool;
546+
ThreadPool.runAsync("A Job", [&]() {
547+
AInfo = Builder.buildPrerequisiteModulesFor(getFullPath("A.cppm"), FS);
548+
});
549+
ThreadPool.runAsync("B Job", [&]() {
550+
BInfo = Builder.buildPrerequisiteModulesFor(getFullPath("B.cppm"), FS);
551+
});
552+
ThreadPool.wait();
553+
554+
EXPECT_TRUE(AInfo);
555+
EXPECT_TRUE(BInfo);
556+
HeaderSearchOptions HSOptsA(TestDir);
557+
HeaderSearchOptions HSOptsB(TestDir);
558+
AInfo->adjustHeaderSearchOptions(HSOptsA);
559+
BInfo->adjustHeaderSearchOptions(HSOptsB);
560+
561+
EXPECT_FALSE(HSOptsA.PrebuiltModuleFiles.empty());
562+
EXPECT_FALSE(HSOptsB.PrebuiltModuleFiles.empty());
563+
564+
// Check that we're reusing the module files.
565+
EXPECT_EQ(HSOptsA.PrebuiltModuleFiles, HSOptsB.PrebuiltModuleFiles);
566+
}
567+
521568
} // namespace
522569
} // namespace clang::clangd
523570

0 commit comments

Comments
 (0)