612 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9/// \file10/// Encapsulates \p OnDiskGraphDB and \p OnDiskKeyValueDB instances within one11/// directory while also restricting storage growth with a scheme of chaining12/// the two most recent directories (primary & upstream), where the primary13/// "faults-in" data from the upstream one. When the primary (most recent)14/// directory exceeds its intended limit a new empty directory becomes the15/// primary one.16///17/// Within the top-level directory (the path that \p UnifiedOnDiskCache::open18/// receives) there are directories named like this:19///20/// 'v<version>.<x>'21/// 'v<version>.<x+1>'22/// 'v<version>.<x+2>'23/// ...24///25/// 'version' is the version integer for this \p UnifiedOnDiskCache's scheme and26/// the part after the dot is an increasing integer. The primary directory is27/// the one with the highest integer and the upstream one is the directory28/// before it. For example, if the sub-directories contained are:29///30/// 'v1.5', 'v1.6', 'v1.7', 'v1.8'31///32/// Then the primary one is 'v1.8', the upstream one is 'v1.7', and the rest are33/// unused directories that can be safely deleted at any time and by any34/// process.35///36/// Contained within the top-level directory is a file named "lock" which is37/// used for processes to take shared or exclusive locks for the contents of the38/// top directory. While a \p UnifiedOnDiskCache is open it keeps a shared lock39/// for the top-level directory; when it closes, if the primary sub-directory40/// exceeded its limit, it attempts to get an exclusive lock in order to create41/// a new empty primary directory; if it can't get the exclusive lock it gives42/// up and lets the next \p UnifiedOnDiskCache instance that closes to attempt43/// again.44///45/// The downside of this scheme is that while \p UnifiedOnDiskCache is open on a46/// directory, by any process, the storage size in that directory will keep47/// growing unrestricted. But the major benefit is that garbage-collection can48/// be triggered on a directory concurrently, at any time and by any process,49/// without affecting any active readers/writers in the same process or other50/// processes.51///52/// The \c UnifiedOnDiskCache also provides validation and recovery on top of53/// the underlying on-disk storage. The low-level storage is designed to remain54/// coherent across regular process crashes, but may be invalid after power loss55/// or similar system failures. \c UnifiedOnDiskCache::validateIfNeeded allows56/// validating the contents once per boot and can recover by marking invalid57/// data for garbage collection.58///59/// The data recovery described above requires exclusive access to the CAS, and60/// it is an error to attempt recovery if the CAS is open in any process/thread.61/// In order to maximize backwards compatibility with tools that do not perform62/// validation before opening the CAS, we do not attempt to get exclusive access63/// until recovery is actually performed, meaning as long as the data is valid64/// it will not conflict with concurrent use.65//66//===----------------------------------------------------------------------===//67 68#include "llvm/CAS/UnifiedOnDiskCache.h"69#include "BuiltinCAS.h"70#include "OnDiskCommon.h"71#include "llvm/ADT/STLExtras.h"72#include "llvm/ADT/ScopeExit.h"73#include "llvm/ADT/SmallString.h"74#include "llvm/ADT/SmallVector.h"75#include "llvm/ADT/StringExtras.h"76#include "llvm/ADT/StringRef.h"77#include "llvm/CAS/ActionCache.h"78#include "llvm/CAS/OnDiskGraphDB.h"79#include "llvm/CAS/OnDiskKeyValueDB.h"80#include "llvm/Support/Compiler.h"81#include "llvm/Support/Errc.h"82#include "llvm/Support/Error.h"83#include "llvm/Support/FileSystem.h"84#include "llvm/Support/FileUtilities.h"85#include "llvm/Support/MemoryBuffer.h"86#include "llvm/Support/Path.h"87#include "llvm/Support/Program.h"88#include "llvm/Support/raw_ostream.h"89#include <optional>90 91#if __has_include(<sys/sysctl.h>)92#include <sys/sysctl.h>93#endif94 95using namespace llvm;96using namespace llvm::cas;97using namespace llvm::cas::ondisk;98 99/// FIXME: When the version of \p DBDirPrefix is bumped up we need to figure out100/// how to handle the leftover sub-directories of the previous version, within101/// the \p UnifiedOnDiskCache::collectGarbage function.102static constexpr StringLiteral DBDirPrefix = "v1.";103 104static constexpr StringLiteral ValidationFilename = "v1.validation";105static constexpr StringLiteral CorruptPrefix = "corrupt.";106 107ObjectID UnifiedOnDiskCache::getObjectIDFromValue(ArrayRef<char> Value) {108 // little endian encoded.109 assert(Value.size() == sizeof(uint64_t));110 return ObjectID::fromOpaqueData(support::endian::read64le(Value.data()));111}112 113UnifiedOnDiskCache::ValueBytes114UnifiedOnDiskCache::getValueFromObjectID(ObjectID ID) {115 // little endian encoded.116 UnifiedOnDiskCache::ValueBytes ValBytes;117 static_assert(ValBytes.size() == sizeof(ID.getOpaqueData()));118 support::endian::write64le(ValBytes.data(), ID.getOpaqueData());119 return ValBytes;120}121 122Expected<std::optional<ArrayRef<char>>>123UnifiedOnDiskCache::faultInFromUpstreamKV(ArrayRef<uint8_t> Key) {124 assert(UpstreamGraphDB);125 assert(UpstreamKVDB);126 127 std::optional<ArrayRef<char>> UpstreamValue;128 if (Error E = UpstreamKVDB->get(Key).moveInto(UpstreamValue))129 return std::move(E);130 if (!UpstreamValue)131 return std::nullopt;132 133 // The value is the \p ObjectID in the context of the upstream134 // \p OnDiskGraphDB instance. Translate it to the context of the primary135 // \p OnDiskGraphDB instance.136 ObjectID UpstreamID = getObjectIDFromValue(*UpstreamValue);137 auto PrimaryID =138 PrimaryGraphDB->getReference(UpstreamGraphDB->getDigest(UpstreamID));139 if (LLVM_UNLIKELY(!PrimaryID))140 return PrimaryID.takeError();141 return PrimaryKVDB->put(Key, getValueFromObjectID(*PrimaryID));142}143 144/// \returns all the 'v<version>.<x>' names of sub-directories, sorted with145/// ascending order of the integer after the dot. Corrupt directories, if146/// included, will come first.147static Expected<SmallVector<std::string, 4>>148getAllDBDirs(StringRef Path, bool IncludeCorrupt = false) {149 struct DBDir {150 uint64_t Order;151 std::string Name;152 };153 SmallVector<DBDir> FoundDBDirs;154 155 std::error_code EC;156 for (sys::fs::directory_iterator DirI(Path, EC), DirE; !EC && DirI != DirE;157 DirI.increment(EC)) {158 if (DirI->type() != sys::fs::file_type::directory_file)159 continue;160 StringRef SubDir = sys::path::filename(DirI->path());161 if (IncludeCorrupt && SubDir.starts_with(CorruptPrefix)) {162 FoundDBDirs.push_back({0, std::string(SubDir)});163 continue;164 }165 if (!SubDir.starts_with(DBDirPrefix))166 continue;167 uint64_t Order;168 if (SubDir.substr(DBDirPrefix.size()).getAsInteger(10, Order))169 return createStringError(inconvertibleErrorCode(),170 "unexpected directory " + DirI->path());171 FoundDBDirs.push_back({Order, std::string(SubDir)});172 }173 if (EC)174 return createFileError(Path, EC);175 176 llvm::sort(FoundDBDirs, [](const DBDir &LHS, const DBDir &RHS) -> bool {177 return LHS.Order < RHS.Order;178 });179 180 SmallVector<std::string, 4> DBDirs;181 for (DBDir &Dir : FoundDBDirs)182 DBDirs.push_back(std::move(Dir.Name));183 return DBDirs;184}185 186static Expected<SmallVector<std::string, 4>> getAllGarbageDirs(StringRef Path) {187 auto DBDirs = getAllDBDirs(Path, /*IncludeCorrupt=*/true);188 if (!DBDirs)189 return DBDirs.takeError();190 191 // FIXME: When the version of \p DBDirPrefix is bumped up we need to figure192 // out how to handle the leftover sub-directories of the previous version.193 194 for (unsigned Keep = 2; Keep > 0 && !DBDirs->empty(); --Keep) {195 StringRef Back(DBDirs->back());196 if (Back.starts_with(CorruptPrefix))197 break;198 DBDirs->pop_back();199 }200 return *DBDirs;201}202 203/// \returns Given a sub-directory named 'v<version>.<x>', it outputs the204/// 'v<version>.<x+1>' name.205static void getNextDBDirName(StringRef DBDir, llvm::raw_ostream &OS) {206 assert(DBDir.starts_with(DBDirPrefix));207 uint64_t Count;208 bool Failed = DBDir.substr(DBDirPrefix.size()).getAsInteger(10, Count);209 assert(!Failed);210 (void)Failed;211 OS << DBDirPrefix << Count + 1;212}213 214static Error validateOutOfProcess(StringRef LLVMCasBinary, StringRef RootPath,215 bool CheckHash) {216 SmallVector<StringRef> Args{LLVMCasBinary, "-cas", RootPath, "-validate"};217 if (CheckHash)218 Args.push_back("-check-hash");219 220 llvm::SmallString<128> StdErrPath;221 int StdErrFD = -1;222 if (std::error_code EC = sys::fs::createTemporaryFile(223 "llvm-cas-validate-stderr", "txt", StdErrFD, StdErrPath,224 llvm::sys::fs::OF_Text))225 return createStringError(EC, "failed to create temporary file");226 FileRemover OutputRemover(StdErrPath.c_str());227 228 std::optional<llvm::StringRef> Redirects[] = {229 {""}, // stdin = /dev/null230 {""}, // stdout = /dev/null231 StdErrPath.str(),232 };233 234 std::string ErrMsg;235 int Result =236 sys::ExecuteAndWait(LLVMCasBinary, Args, /*Env=*/std::nullopt, Redirects,237 /*SecondsToWait=*/120, /*MemoryLimit=*/0, &ErrMsg);238 239 if (Result == -1)240 return createStringError("failed to exec " + join(Args, " ") + ": " +241 ErrMsg);242 if (Result != 0) {243 llvm::SmallString<64> Err("cas contents invalid");244 if (!ErrMsg.empty()) {245 Err += ": ";246 Err += ErrMsg;247 }248 auto StdErrBuf = MemoryBuffer::getFile(StdErrPath.c_str());249 if (StdErrBuf && !(*StdErrBuf)->getBuffer().empty()) {250 Err += ": ";251 Err += (*StdErrBuf)->getBuffer();252 }253 return createStringError(Err);254 }255 return Error::success();256}257 258static Error validateInProcess(StringRef RootPath, StringRef HashName,259 unsigned HashByteSize, bool CheckHash) {260 std::shared_ptr<UnifiedOnDiskCache> UniDB;261 if (Error E = UnifiedOnDiskCache::open(RootPath, std::nullopt, HashName,262 HashByteSize)263 .moveInto(UniDB))264 return E;265 auto CAS = builtin::createObjectStoreFromUnifiedOnDiskCache(UniDB);266 if (Error E = CAS->validate(CheckHash))267 return E;268 auto Cache = builtin::createActionCacheFromUnifiedOnDiskCache(UniDB);269 if (Error E = Cache->validate())270 return E;271 return Error::success();272}273 274static Expected<uint64_t> getBootTime() {275#if __has_include(<sys/sysctl.h>) && defined(KERN_BOOTTIME)276 struct timeval TV;277 size_t TVLen = sizeof(TV);278 int KernBoot[2] = {CTL_KERN, KERN_BOOTTIME};279 if (sysctl(KernBoot, 2, &TV, &TVLen, nullptr, 0) < 0)280 return createStringError(llvm::errnoAsErrorCode(),281 "failed to get boottime");282 if (TVLen != sizeof(TV))283 return createStringError("sysctl kern.boottime unexpected format");284 return TV.tv_sec;285#elif defined(__linux__)286 // Use the mtime for /proc, which is recreated during system boot.287 // We could also read /proc/stat and search for 'btime'.288 sys::fs::file_status Status;289 if (std::error_code EC = sys::fs::status("/proc", Status))290 return createFileError("/proc", EC);291 return Status.getLastModificationTime().time_since_epoch().count();292#else293 llvm::report_fatal_error("getBootTime unimplemented");294#endif295}296 297Expected<ValidationResult> UnifiedOnDiskCache::validateIfNeeded(298 StringRef RootPath, StringRef HashName, unsigned HashByteSize,299 bool CheckHash, bool AllowRecovery, bool ForceValidation,300 std::optional<StringRef> LLVMCasBinaryPath) {301 if (std::error_code EC = sys::fs::create_directories(RootPath))302 return createFileError(RootPath, EC);303 304 SmallString<256> PathBuf(RootPath);305 sys::path::append(PathBuf, ValidationFilename);306 int FD = -1;307 if (std::error_code EC = sys::fs::openFileForReadWrite(308 PathBuf, FD, sys::fs::CD_OpenAlways, sys::fs::OF_None))309 return createFileError(PathBuf, EC);310 assert(FD != -1);311 312 sys::fs::file_t File = sys::fs::convertFDToNativeFile(FD);313 auto CloseFile = make_scope_exit([&]() { sys::fs::closeFile(File); });314 315 if (std::error_code EC = lockFileThreadSafe(FD, sys::fs::LockKind::Exclusive))316 return createFileError(PathBuf, EC);317 auto UnlockFD = make_scope_exit([&]() { unlockFileThreadSafe(FD); });318 319 SmallString<8> Bytes;320 if (Error E = sys::fs::readNativeFileToEOF(File, Bytes))321 return createFileError(PathBuf, std::move(E));322 323 uint64_t ValidationBootTime = 0;324 if (!Bytes.empty() &&325 StringRef(Bytes).trim().getAsInteger(10, ValidationBootTime))326 return createFileError(PathBuf, errc::illegal_byte_sequence,327 "expected integer");328 329 static uint64_t BootTime = 0;330 if (BootTime == 0)331 if (Error E = getBootTime().moveInto(BootTime))332 return std::move(E);333 334 if (ValidationBootTime == BootTime && !ForceValidation)335 return ValidationResult::Skipped;336 337 // Validate!338 bool NeedsRecovery = false;339 if (Error E =340 LLVMCasBinaryPath341 ? validateOutOfProcess(*LLVMCasBinaryPath, RootPath, CheckHash)342 : validateInProcess(RootPath, HashName, HashByteSize,343 CheckHash)) {344 if (AllowRecovery) {345 consumeError(std::move(E));346 NeedsRecovery = true;347 } else {348 return std::move(E);349 }350 }351 352 if (NeedsRecovery) {353 sys::path::remove_filename(PathBuf);354 sys::path::append(PathBuf, "lock");355 356 int LockFD = -1;357 if (std::error_code EC = sys::fs::openFileForReadWrite(358 PathBuf, LockFD, sys::fs::CD_OpenAlways, sys::fs::OF_None))359 return createFileError(PathBuf, EC);360 sys::fs::file_t LockFile = sys::fs::convertFDToNativeFile(LockFD);361 auto CloseLock = make_scope_exit([&]() { sys::fs::closeFile(LockFile); });362 if (std::error_code EC = tryLockFileThreadSafe(LockFD)) {363 if (EC == std::errc::no_lock_available)364 return createFileError(365 PathBuf, EC,366 "CAS validation requires exclusive access but CAS was in use");367 return createFileError(PathBuf, EC);368 }369 auto UnlockFD = make_scope_exit([&]() { unlockFileThreadSafe(LockFD); });370 371 auto DBDirs = getAllDBDirs(RootPath);372 if (!DBDirs)373 return DBDirs.takeError();374 375 for (StringRef DBDir : *DBDirs) {376 sys::path::remove_filename(PathBuf);377 sys::path::append(PathBuf, DBDir);378 std::error_code EC;379 int Attempt = 0, MaxAttempts = 100;380 SmallString<128> GCPath;381 for (; Attempt < MaxAttempts; ++Attempt) {382 GCPath.assign(RootPath);383 sys::path::append(GCPath, CorruptPrefix + std::to_string(Attempt) +384 "." + DBDir);385 EC = sys::fs::rename(PathBuf, GCPath);386 // Darwin uses ENOTEMPTY. Linux may return either ENOTEMPTY or EEXIST.387 if (EC != errc::directory_not_empty && EC != errc::file_exists)388 break;389 }390 if (Attempt == MaxAttempts)391 return createStringError(392 EC, "rename " + PathBuf +393 " failed: too many CAS directories awaiting pruning");394 if (EC)395 return createStringError(EC, "rename " + PathBuf + " to " + GCPath +396 " failed: " + EC.message());397 }398 }399 400 if (ValidationBootTime != BootTime) {401 // Fix filename in case we have error to report.402 sys::path::remove_filename(PathBuf);403 sys::path::append(PathBuf, ValidationFilename);404 if (std::error_code EC = sys::fs::resize_file(FD, 0))405 return createFileError(PathBuf, EC);406 raw_fd_ostream OS(FD, /*shouldClose=*/false);407 OS.seek(0); // resize does not reset position408 OS << BootTime << '\n';409 if (OS.has_error())410 return createFileError(PathBuf, OS.error());411 }412 413 return NeedsRecovery ? ValidationResult::Recovered : ValidationResult::Valid;414}415 416Expected<std::unique_ptr<UnifiedOnDiskCache>>417UnifiedOnDiskCache::open(StringRef RootPath, std::optional<uint64_t> SizeLimit,418 StringRef HashName, unsigned HashByteSize,419 OnDiskGraphDB::FaultInPolicy FaultInPolicy) {420 if (std::error_code EC = sys::fs::create_directories(RootPath))421 return createFileError(RootPath, EC);422 423 SmallString<256> PathBuf(RootPath);424 sys::path::append(PathBuf, "lock");425 int LockFD = -1;426 if (std::error_code EC = sys::fs::openFileForReadWrite(427 PathBuf, LockFD, sys::fs::CD_OpenAlways, sys::fs::OF_None))428 return createFileError(PathBuf, EC);429 assert(LockFD != -1);430 // Locking the directory using shared lock, which will prevent other processes431 // from creating a new chain (essentially while a \p UnifiedOnDiskCache432 // instance holds a shared lock the storage for the primary directory will433 // grow unrestricted).434 if (std::error_code EC =435 lockFileThreadSafe(LockFD, sys::fs::LockKind::Shared))436 return createFileError(PathBuf, EC);437 438 auto DBDirs = getAllDBDirs(RootPath);439 if (!DBDirs)440 return DBDirs.takeError();441 if (DBDirs->empty())442 DBDirs->push_back((Twine(DBDirPrefix) + "1").str());443 444 assert(!DBDirs->empty());445 446 /// If there is only one directory open databases on it. If there are 2 or447 /// more directories, get the most recent directories and chain them, with the448 /// most recent being the primary one. The remaining directories are unused449 /// data than can be garbage-collected.450 auto UniDB = std::unique_ptr<UnifiedOnDiskCache>(new UnifiedOnDiskCache());451 std::unique_ptr<OnDiskGraphDB> UpstreamGraphDB;452 std::unique_ptr<OnDiskKeyValueDB> UpstreamKVDB;453 if (DBDirs->size() > 1) {454 StringRef UpstreamDir = *(DBDirs->end() - 2);455 PathBuf = RootPath;456 sys::path::append(PathBuf, UpstreamDir);457 if (Error E = OnDiskGraphDB::open(PathBuf, HashName, HashByteSize,458 /*UpstreamDB=*/nullptr, FaultInPolicy)459 .moveInto(UpstreamGraphDB))460 return std::move(E);461 if (Error E = OnDiskKeyValueDB::open(PathBuf, HashName, HashByteSize,462 /*ValueName=*/"objectid",463 /*ValueSize=*/sizeof(uint64_t))464 .moveInto(UpstreamKVDB))465 return std::move(E);466 }467 468 StringRef PrimaryDir = *(DBDirs->end() - 1);469 PathBuf = RootPath;470 sys::path::append(PathBuf, PrimaryDir);471 std::unique_ptr<OnDiskGraphDB> PrimaryGraphDB;472 if (Error E = OnDiskGraphDB::open(PathBuf, HashName, HashByteSize,473 UpstreamGraphDB.get(), FaultInPolicy)474 .moveInto(PrimaryGraphDB))475 return std::move(E);476 std::unique_ptr<OnDiskKeyValueDB> PrimaryKVDB;477 // \p UnifiedOnDiskCache does manual chaining for key-value requests,478 // including an extra translation step of the value during fault-in.479 if (Error E =480 OnDiskKeyValueDB::open(PathBuf, HashName, HashByteSize,481 /*ValueName=*/"objectid",482 /*ValueSize=*/sizeof(uint64_t), UniDB.get())483 .moveInto(PrimaryKVDB))484 return std::move(E);485 486 UniDB->RootPath = RootPath;487 UniDB->SizeLimit = SizeLimit.value_or(0);488 UniDB->LockFD = LockFD;489 UniDB->NeedsGarbageCollection = DBDirs->size() > 2;490 UniDB->PrimaryDBDir = PrimaryDir;491 UniDB->UpstreamGraphDB = std::move(UpstreamGraphDB);492 UniDB->PrimaryGraphDB = std::move(PrimaryGraphDB);493 UniDB->UpstreamKVDB = std::move(UpstreamKVDB);494 UniDB->PrimaryKVDB = std::move(PrimaryKVDB);495 496 return std::move(UniDB);497}498 499void UnifiedOnDiskCache::setSizeLimit(std::optional<uint64_t> SizeLimit) {500 this->SizeLimit = SizeLimit.value_or(0);501}502 503uint64_t UnifiedOnDiskCache::getStorageSize() const {504 uint64_t TotalSize = getPrimaryStorageSize();505 if (UpstreamGraphDB)506 TotalSize += UpstreamGraphDB->getStorageSize();507 if (UpstreamKVDB)508 TotalSize += UpstreamKVDB->getStorageSize();509 return TotalSize;510}511 512uint64_t UnifiedOnDiskCache::getPrimaryStorageSize() const {513 return PrimaryGraphDB->getStorageSize() + PrimaryKVDB->getStorageSize();514}515 516bool UnifiedOnDiskCache::hasExceededSizeLimit() const {517 uint64_t CurSizeLimit = SizeLimit;518 if (!CurSizeLimit)519 return false;520 521 // If the hard limit is beyond 85%, declare above limit and request clean up.522 unsigned CurrentPercent =523 std::max(PrimaryGraphDB->getHardStorageLimitUtilization(),524 PrimaryKVDB->getHardStorageLimitUtilization());525 if (CurrentPercent > 85)526 return true;527 528 // We allow each of the directories in the chain to reach up to half the529 // intended size limit. Check whether the primary directory has exceeded half530 // the limit or not, in order to decide whether we need to start a new chain.531 //532 // We could check the size limit against the sum of sizes of both the primary533 // and upstream directories but then if the upstream is significantly larger534 // than the intended limit, it would trigger a new chain to be created before535 // the primary has reached its own limit. Essentially in such situation we536 // prefer reclaiming the storage later in order to have more consistent cache537 // hits behavior.538 return (CurSizeLimit / 2) < getPrimaryStorageSize();539}540 541Error UnifiedOnDiskCache::close(bool CheckSizeLimit) {542 if (LockFD == -1)543 return Error::success(); // already closed.544 auto CloseLock = make_scope_exit([&]() {545 assert(LockFD >= 0);546 sys::fs::file_t LockFile = sys::fs::convertFDToNativeFile(LockFD);547 sys::fs::closeFile(LockFile);548 LockFD = -1;549 });550 551 bool ExceededSizeLimit = CheckSizeLimit ? hasExceededSizeLimit() : false;552 UpstreamKVDB.reset();553 PrimaryKVDB.reset();554 UpstreamGraphDB.reset();555 PrimaryGraphDB.reset();556 if (std::error_code EC = unlockFileThreadSafe(LockFD))557 return createFileError(RootPath, EC);558 559 if (!ExceededSizeLimit)560 return Error::success();561 562 // The primary directory exceeded its intended size limit. Try to get an563 // exclusive lock in order to create a new primary directory for next time564 // this \p UnifiedOnDiskCache path is opened.565 566 if (std::error_code EC = tryLockFileThreadSafe(567 LockFD, std::chrono::milliseconds(0), sys::fs::LockKind::Exclusive)) {568 if (EC == errc::no_lock_available)569 return Error::success(); // couldn't get exclusive lock, give up.570 return createFileError(RootPath, EC);571 }572 auto UnlockFile = make_scope_exit([&]() { unlockFileThreadSafe(LockFD); });573 574 // Managed to get an exclusive lock which means there are no other open575 // \p UnifiedOnDiskCache instances for the same path, so we can safely start a576 // new primary directory. To start a new primary directory we just have to577 // create a new empty directory with the next consecutive index; since this is578 // an atomic operation we will leave the top-level directory in a consistent579 // state even if the process dies during this code-path.580 581 SmallString<256> PathBuf(RootPath);582 raw_svector_ostream OS(PathBuf);583 OS << sys::path::get_separator();584 getNextDBDirName(PrimaryDBDir, OS);585 if (std::error_code EC = sys::fs::create_directory(PathBuf))586 return createFileError(PathBuf, EC);587 588 NeedsGarbageCollection = true;589 return Error::success();590}591 592UnifiedOnDiskCache::UnifiedOnDiskCache() = default;593 594UnifiedOnDiskCache::~UnifiedOnDiskCache() { consumeError(close()); }595 596Error UnifiedOnDiskCache::collectGarbage(StringRef Path) {597 auto DBDirs = getAllGarbageDirs(Path);598 if (!DBDirs)599 return DBDirs.takeError();600 601 SmallString<256> PathBuf(Path);602 for (StringRef UnusedSubDir : *DBDirs) {603 sys::path::append(PathBuf, UnusedSubDir);604 if (std::error_code EC = sys::fs::remove_directories(PathBuf))605 return createFileError(PathBuf, EC);606 sys::path::remove_filename(PathBuf);607 }608 return Error::success();609}610 611Error UnifiedOnDiskCache::collectGarbage() { return collectGarbage(RootPath); }612