brintos

brintos / llvm-project-archived public Read only

0
0
Text · 23.4 KiB · e6b676a Raw
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