Skip to content

Commit 9faa744

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

File tree

1 file changed

+133
-0
lines changed

1 file changed

+133
-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);

0 commit comments

Comments
 (0)