runtimes_cache.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "toolchain/driver/runtimes_cache.h"
  5. #include <algorithm>
  6. #include <chrono>
  7. #include <filesystem>
  8. #include <memory>
  9. #include <numeric>
  10. #include <optional>
  11. #include <string>
  12. #include <system_error>
  13. #include <utility>
  14. #include <variant>
  15. #include "common/filesystem.h"
  16. #include "common/version.h"
  17. #include "common/vlog.h"
  18. #include "llvm/ADT/ArrayRef.h"
  19. #include "llvm/ADT/ScopeExit.h"
  20. #include "llvm/ADT/StringExtras.h"
  21. #include "llvm/ADT/StringRef.h"
  22. #include "llvm/Support/FormatAdapters.h"
  23. #include "llvm/Support/Program.h"
  24. #include "llvm/Support/SHA256.h"
  25. namespace Carbon {
  26. auto Runtimes::OpenExisting(std::filesystem::path path,
  27. llvm::raw_ostream* vlog_stream)
  28. -> ErrorOr<Runtimes> {
  29. CARBON_ASSIGN_OR_RETURN(Filesystem::Dir dir, Filesystem::Cwd().OpenDir(path));
  30. return Runtimes(std::move(path), std::move(dir), {}, {}, vlog_stream);
  31. }
  32. auto Runtimes::Make(std::filesystem::path path, llvm::raw_ostream* vlog_stream)
  33. -> ErrorOr<Runtimes> {
  34. CARBON_ASSIGN_OR_RETURN(Filesystem::Dir dir,
  35. Filesystem::Cwd().CreateDirectories(path));
  36. return Runtimes(std::move(path), std::move(dir), {}, {}, vlog_stream);
  37. }
  38. auto Runtimes::Destroy() -> void {
  39. // Release the lock on the runtimes and close the lock file.
  40. flock_ = {};
  41. auto close_result = std::move(lock_file_).Close();
  42. if (!close_result.ok()) {
  43. // Log and continue on close errors.
  44. CARBON_VLOG("Error closing lock file for runtimes '{0}': {1}", base_path_,
  45. close_result.error());
  46. }
  47. }
  48. auto Runtimes::Get(Component component) -> ErrorOr<std::filesystem::path> {
  49. std::filesystem::path path = base_path_ / ComponentPath(component);
  50. auto open_result =
  51. base_dir_.OpenDir(ComponentPath(component), Filesystem::OpenExisting);
  52. if (open_result.ok()) {
  53. return path;
  54. }
  55. return open_result.error().ToError();
  56. }
  57. auto Runtimes::Build(Component component)
  58. -> ErrorOr<std::variant<std::filesystem::path, Builder>> {
  59. return BuildImpl(component, BuildLockDeadline, BuildLockPollInterval);
  60. }
  61. auto Runtimes::Remove(Component component) -> ErrorOr<Success> {
  62. CARBON_RETURN_IF_ERROR(base_dir_.Rmtree(ComponentPath(component)));
  63. return Success();
  64. }
  65. auto Runtimes::BuildImpl(Component component, Filesystem::Duration deadline,
  66. Filesystem::Duration poll_interval)
  67. -> ErrorOr<std::variant<std::filesystem::path, Builder>> {
  68. // Try to get an existing resource directory first.
  69. auto existing_result = Get(component);
  70. if (existing_result.ok()) {
  71. return {*std::move(existing_result)};
  72. }
  73. // Otherwise, we will need to build the runtimes and commit them into this
  74. // directory once ready. Try and acquire an advisory lock to avoid redundant
  75. // computation.
  76. std::string_view component_path = ComponentPath(component);
  77. CARBON_ASSIGN_OR_RETURN(
  78. Filesystem::ReadWriteFile lock_file,
  79. base_dir_.OpenReadWrite(
  80. llvm::formatv(LockFileFormat, component_path).str(),
  81. Filesystem::OpenAlways, /*creation_mode=*/0700));
  82. CARBON_VLOG("PID {0} locking cache path: {1}\n", getpid(),
  83. base_path_ / component_path);
  84. Filesystem::FileLock flock;
  85. auto flock_result = lock_file.TryLock(Filesystem::FileLock::Exclusive,
  86. deadline, poll_interval);
  87. if (flock_result.ok()) {
  88. flock = *std::move(flock_result);
  89. CARBON_VLOG("Successfully locked cache path\n");
  90. // As a debugging aid, write our PID into the lock file when we
  91. // successfully acquire it. Ignore errors here though.
  92. (void)lock_file.WriteFileFromString(std::to_string(getpid()));
  93. } else if (!flock_result.error().would_block()) {
  94. // Some unexpected filesystem error, report that rather than trying to
  95. // continue.
  96. return std::move(flock_result).error();
  97. } else {
  98. CARBON_VLOG("Unable to lock cache path, held by: {1}\n",
  99. *lock_file.ReadFileToString());
  100. (void)std::move(lock_file).Close();
  101. }
  102. // See if another process has built the runtimes while we waited on the lock.
  103. // We do this even if we didn't successfully acquire the lock because we
  104. // ensure that a successful build atomically creates a viable directory.
  105. existing_result = Get(component);
  106. if (existing_result.ok()) {
  107. // Clear and close the lock file.
  108. (void)lock_file.WriteFileFromString("");
  109. flock = {};
  110. (void)std::move(lock_file).Close();
  111. return {*std::move(existing_result)};
  112. }
  113. // Whether we hold the lock file or not, we're going to now build these
  114. // runtimes. Create a temporary directory where we can do that safely
  115. // regardless of what else is happening.
  116. std::filesystem::path tmp_path =
  117. base_path_ / llvm::formatv(".{0}.tmp", component_path).str();
  118. CARBON_ASSIGN_OR_RETURN(Filesystem::RemovingDir tmp_dir,
  119. Filesystem::MakeTmpDirWithPrefix(tmp_path));
  120. return {Builder(*this, std::move(lock_file), std::move(flock),
  121. std::move(tmp_dir), component_path)};
  122. }
  123. auto Runtimes::Cache::FindXdgCachePath()
  124. -> std::optional<std::filesystem::path> {
  125. if (const char* xdg_cache_home = getenv("XDG_CACHE_HOME");
  126. xdg_cache_home != nullptr) {
  127. std::filesystem::path path = xdg_cache_home;
  128. if (path.is_absolute()) {
  129. CARBON_VLOG("Using '$XDG_CACHE_HOME' cache: {0}", path);
  130. return path;
  131. }
  132. }
  133. // Unable to use the standard environment variable. Try the designated
  134. // fallback of `$HOME/.cache`.
  135. const char* home = getenv("HOME");
  136. if (home == nullptr) {
  137. return std::nullopt;
  138. }
  139. std::filesystem::path path = home;
  140. if (!path.is_absolute()) {
  141. return std::nullopt;
  142. }
  143. path /= ".cache";
  144. CARBON_VLOG("Using '$HOME/.cache' cache: {0}", path);
  145. return path;
  146. }
  147. auto Runtimes::Cache::InitTmpSystemCache() -> ErrorOr<Success> {
  148. CARBON_ASSIGN_OR_RETURN(dir_owner_, Filesystem::MakeTmpDir());
  149. path_ = std::get<Filesystem::RemovingDir>(dir_owner_).path();
  150. dir_ = std::get<Filesystem::RemovingDir>(dir_owner_);
  151. CARBON_VLOG("Using temporary cache: {0}", path_);
  152. return Success();
  153. }
  154. auto Runtimes::Cache::InitSystemCache(const InstallPaths& install)
  155. -> ErrorOr<Success> {
  156. constexpr llvm::StringLiteral CachePath = "carbon_runtimes";
  157. // If we have a digest to use as the cache key, save it and we can try to
  158. // use persistent caches.
  159. auto read_digest_result =
  160. Filesystem::Cwd().ReadFileToString(install.digest_path());
  161. if (!read_digest_result.ok()) {
  162. return InitTmpSystemCache();
  163. }
  164. cache_key_ = *std::move(read_digest_result);
  165. auto xdg_path_result = FindXdgCachePath();
  166. if (!xdg_path_result) {
  167. return InitTmpSystemCache();
  168. }
  169. // We have a candidate XDG-based cache path. Try to open that, and a
  170. // directory below it for Carbon's runtimes. Note that we don't error on a
  171. // missing directory, we fall through to using a temporary directory.
  172. auto open_result = Filesystem::Cwd().OpenDir(*xdg_path_result);
  173. if (!open_result.ok()) {
  174. if (!open_result.error().no_entity()) {
  175. // Some other unexpected error in the filesystem, propagate that.
  176. return std::move(open_result).error();
  177. }
  178. // Otherwise we fall back to a temporary system cache.
  179. return InitTmpSystemCache();
  180. }
  181. path_ = *std::move(xdg_path_result);
  182. // Now open a subdirectory of the cache for Carbon's usage. This will
  183. // create a subdirectory if one doesn't yet exist.
  184. path_ /= std::string_view(CachePath);
  185. CARBON_ASSIGN_OR_RETURN(
  186. dir_owner_, open_result->OpenDir(CachePath.str(), Filesystem::OpenAlways,
  187. /*creation_mode=*/0700));
  188. dir_ = std::get<Filesystem::Dir>(dir_owner_);
  189. // Ensure the directory has narrow permissions so runtimes can't be
  190. // overwritten.
  191. CARBON_ASSIGN_OR_RETURN(auto dir_stat, dir_.Stat());
  192. if (dir_stat.permissions() != 0700 || dir_stat.unix_uid() != geteuid()) {
  193. return Error(llvm::formatv(
  194. "Found runtimes cache path '{0}' with excessive permissions ({1}) "
  195. "or an invalid owning UID ({2})",
  196. path_, dir_stat.permissions(), dir_stat.unix_uid()));
  197. }
  198. return Success();
  199. }
  200. auto Runtimes::Cache::InitCachePath(const InstallPaths& install,
  201. std::filesystem::path cache_path)
  202. -> ErrorOr<Success> {
  203. auto read_digest_result =
  204. Filesystem::Cwd().ReadFileToString(install.digest_path());
  205. if (read_digest_result.ok()) {
  206. // If we have a digest to use as the cache key, save it and we can try to
  207. // use persistent caches.
  208. cache_key_ = *std::move(read_digest_result);
  209. } else {
  210. // Without a digest, use the path itself as the key.
  211. cache_key_ = cache_path.string();
  212. }
  213. CARBON_ASSIGN_OR_RETURN(dir_owner_, Filesystem::Cwd().OpenDir(cache_path));
  214. dir_ = std::get<Filesystem::Dir>(dir_owner_);
  215. path_ = std::move(cache_path);
  216. CARBON_VLOG("Using custom cache: {0}", path_);
  217. return Success();
  218. }
  219. auto Runtimes::Cache::Lookup(const Features& features) -> ErrorOr<Runtimes> {
  220. // Compute the hash of the features. We'll use this to build the subdirectory
  221. // within the cache.
  222. llvm::SHA256 entry_hasher;
  223. // First incorporate our cache key that comes from the installation's digest.
  224. // This ensures we don't share a cache entry with any other Carbon
  225. // installations using different inputs.
  226. entry_hasher.update(cache_key_);
  227. // Then incorporate the specific features that are enabled in this entry.
  228. entry_hasher.update(features.target);
  229. std::array<uint8_t, 32> entry_digest = entry_hasher.final();
  230. std::filesystem::path entry_path =
  231. llvm::formatv("runtimes-{0}-{1}", Version::String,
  232. llvm::toHex(entry_digest, /*LowerCase=*/true))
  233. .str();
  234. Filesystem::Dir entry_dir;
  235. auto open_result = dir_.OpenDir(entry_path, Filesystem::OpenExisting);
  236. if (open_result.ok()) {
  237. entry_dir = *std::move(open_result);
  238. } else {
  239. if (!open_result.error().no_entity()) {
  240. return std::move(open_result).error();
  241. }
  242. // We're going to potentially create a new set of runtimes, prune the
  243. // existing runtimes first to provide a bound on the total size of runtimes.
  244. PruneStaleRuntimes(entry_path);
  245. // Now we can create or open, we don't care if a racing process created the
  246. // same runtime directory.
  247. CARBON_ASSIGN_OR_RETURN(entry_dir,
  248. dir_.OpenDir(entry_path, Filesystem::OpenAlways));
  249. }
  250. CARBON_ASSIGN_OR_RETURN(
  251. auto lock_file, entry_dir.OpenWriteOnly(".lock", Filesystem::OpenAlways));
  252. CARBON_RETURN_IF_ERROR(lock_file.UpdateTimes());
  253. CARBON_ASSIGN_OR_RETURN(
  254. Filesystem::FileLock flock,
  255. lock_file.TryLock(Filesystem::FileLock::Shared, RuntimesLockDeadline,
  256. RuntimesLockPollInterval));
  257. return Runtimes(path_ / entry_path, std::move(entry_dir),
  258. std::move(lock_file), std::move(flock), vlog_stream_);
  259. }
  260. auto Runtimes::Cache::ComputeEntryAges(
  261. llvm::SmallVector<std::filesystem::path> entry_paths)
  262. -> llvm::SmallVector<Entry> {
  263. llvm::SmallVector<Entry> entries;
  264. Filesystem::TimePoint now = Filesystem::Clock::now();
  265. for (auto& path : entry_paths) {
  266. // We use the `mtime` from the lock file in the directory rather than the
  267. // directory itself to avoid any oddities with `mtime` on directories.
  268. //
  269. // Note that we also ignore errors here as if we can't read the stamp file
  270. // we will pick an arbitrary old time stamp, and we want pruning to be
  271. // maximally resilient to partially deleted or corrupted caches in order to
  272. // prune them back into a healthy state.
  273. auto stat_result = dir_.Lstat(path / ".lock");
  274. auto mtime = stat_result.ok()
  275. ? stat_result->mtime()
  276. : Filesystem::TimePoint(Filesystem::Duration(0));
  277. entries.push_back({.path = std::move(path), .age = now - mtime});
  278. }
  279. return entries;
  280. }
  281. auto Runtimes::Cache::PruneStaleRuntimes(
  282. const std::filesystem::path& new_entry_path) -> void {
  283. llvm::SmallVector<std::filesystem::path> dir_entries;
  284. llvm::SmallVector<std::filesystem::path> non_dir_entries;
  285. auto read_result = dir_.AppendEntriesIf(
  286. dir_entries, non_dir_entries,
  287. [](llvm::StringRef name) { return name.starts_with("runtimes-"); });
  288. if (!read_result.ok()) {
  289. CARBON_VLOG("Unable to read cache directory to prune stale entries: {0}",
  290. read_result.error());
  291. return;
  292. }
  293. // Directly attempt to remove non-directory and bad directory entries.
  294. for (const auto& name : non_dir_entries) {
  295. CARBON_VLOG("Unlinking non-directory entry '{0}'", name);
  296. auto result = dir_.Unlink(name);
  297. if (!result.ok()) {
  298. CARBON_VLOG("Error unlinking non-directory entry '{0}': {1}", name,
  299. result.error());
  300. }
  301. }
  302. // If we only have a small number of entries, no need to prune.
  303. if (dir_entries.size() < MinNumEntries) {
  304. return;
  305. }
  306. llvm::SmallVector<Entry> entries = ComputeEntryAges(std::move(dir_entries));
  307. auto rm_entry = [&](const std::filesystem::path& entry_name) {
  308. // Note that we don't propagate errors here because we want to prune as much
  309. // as possible. We do log them.
  310. CARBON_VLOG("Removing cache entry '{0}'", entry_name);
  311. auto rm_result = dir_.Rmtree(entry_name);
  312. if (!rm_result.ok() && !rm_result.error().no_entity()) {
  313. CARBON_VLOG("Unable to remove old runtimes '{0}': {1}", entry_name,
  314. rm_result.error());
  315. return false;
  316. }
  317. return true;
  318. };
  319. // Remove entries older than our max first. We don't need to check for locking
  320. // or other issues here given the age.
  321. llvm::erase_if(entries, [&](const Entry& entry) {
  322. return entry.age > MaxEntryAge && rm_entry(entry.path);
  323. });
  324. // Sort the entries so that the oldest is first.
  325. llvm::sort(entries, [](const Entry& lhs, const Entry& rhs) {
  326. return lhs.age > rhs.age;
  327. });
  328. // Now try to get the number of entries below our max target by removing the
  329. // least-recently used entries that are either more than our max locked age or
  330. // unlocked.
  331. auto rm_unlocked_entry = [&](const std::filesystem::path& name,
  332. Filesystem::Duration age) {
  333. // Past a certain age, bypass the locking for efficiency and to avoid
  334. // retaining entries with stale locks.
  335. if (age > MaxLockedEntryAge) {
  336. return rm_entry(name);
  337. }
  338. CARBON_VLOG("Attempting to lock cache entry '{0}'", name);
  339. auto lock_file_open_result =
  340. dir_.OpenReadOnly(name / ".lock", Filesystem::OpenAlways);
  341. if (!lock_file_open_result.ok()) {
  342. if (lock_file_open_result.error().no_entity() ||
  343. lock_file_open_result.error().not_dir()) {
  344. // The only way these failures should be possible is if something
  345. // removed the cache directory between our read above and here. Assume
  346. // the entry is gone and continue.
  347. return true;
  348. }
  349. // For other errors, assume locked.
  350. CARBON_VLOG("Error opening lock file for cache entry '{0}': {1}", name,
  351. lock_file_open_result.error());
  352. return false;
  353. }
  354. Filesystem::ReadFile lock_file = *std::move(lock_file_open_result);
  355. auto lock_result =
  356. lock_file.TryLock(Filesystem::FileLock::Exclusive, RuntimesLockDeadline,
  357. RuntimesLockPollInterval);
  358. if (!lock_result.ok()) {
  359. // The normal case is when locking would block, log anything else.
  360. if (!lock_result.error().would_block()) {
  361. CARBON_VLOG("Error locking cache entry '{0}': {1}", name,
  362. lock_result.error());
  363. }
  364. // However, don't try to remove it as we didn't acquire the lock.
  365. return false;
  366. }
  367. // The lock is held, remove the entry.
  368. return rm_entry(name);
  369. };
  370. int num_entries = entries.size();
  371. for (const auto& [name, age] : entries) {
  372. if (num_entries < MaxNumEntries) {
  373. break;
  374. }
  375. // Don't prune the currently being built entry. We should only reach here
  376. // when some other process created this entry in a race, and we don't want
  377. // to remove it or trigger rebuilds.
  378. if (name == new_entry_path) {
  379. continue;
  380. }
  381. if (rm_unlocked_entry(name, age)) {
  382. --num_entries;
  383. }
  384. }
  385. if (num_entries >= MaxNumEntries) {
  386. CARBON_VLOG(
  387. "Unable to prune cache to our target size due to held locks on recent "
  388. "cache entries or removal errors, leaving {0} entries in the cache",
  389. num_entries);
  390. }
  391. }
  392. auto Runtimes::Builder::Commit() && -> ErrorOr<std::filesystem::path> {
  393. std::filesystem::path dest_path = runtimes_->base_path() / dest_;
  394. // First, try to do the atomic commit of the built runtimes into the final
  395. // location.
  396. CARBON_CHECK(dir_.path().parent_path() == runtimes_->base_path(),
  397. "Building a temporary directory '{0}' that is not in the "
  398. "runtimes tree '{1}'",
  399. dir_.path(), runtimes_->base_path());
  400. auto rename_result = runtimes_->base_dir().Rename(
  401. dir_.path().filename(), runtimes_->base_dir(), dest_);
  402. // If the rename was successful, then we don't need to remove anything so
  403. // release that state.
  404. if (rename_result.ok()) {
  405. std::move(dir_).Release();
  406. } else if (rename_result.error().not_empty()) {
  407. // Some other runtimes were successfully committed before ours, so we want
  408. // to discard ours. We report errors cleaning up here as we don't want to
  409. // pollute the filesystem excessively.
  410. //
  411. // TODO: Consider instead being more resilient to errors here and just log
  412. // them.
  413. CARBON_VLOG("PID {0} found racily built runtimes in cache path: {1}",
  414. getpid(), dest_path);
  415. CARBON_RETURN_IF_ERROR(std::move(dir_).Remove());
  416. } else {
  417. // An unexpected error occurred, propagate it and let the normal cleanup
  418. // occur.
  419. //
  420. // TODO: It's possible we need to handle `EBUSY` here, likely by ensuring it
  421. // is the *destination* that is busy and an existing, valid directory built
  422. // concurrently.
  423. return std::move(rename_result).error();
  424. }
  425. // Now that we've got a final path in place successfully, clear the flock if
  426. // it is currently held.
  427. ReleaseFileLock();
  428. // Finally, the build is committed so finish putting this into the moved-from
  429. // state by clearing the runtimes pointer.
  430. runtimes_ = nullptr;
  431. return dest_path;
  432. }
  433. auto Runtimes::Builder::ReleaseFileLock() -> void {
  434. CARBON_CHECK(runtimes_ != nullptr);
  435. if (flock_.is_locked()) {
  436. std::filesystem::path dest_path = runtimes_->base_path() / dest_;
  437. CARBON_VLOG("PID {0} releasing lock on cache path: {1}", getpid(),
  438. dest_path);
  439. (void)lock_file_.WriteFileFromString("");
  440. flock_ = {};
  441. (void)std::move(lock_file_).Close();
  442. } else {
  443. CARBON_CHECK(!lock_file_.is_valid());
  444. }
  445. }
  446. auto Runtimes::Builder::Destroy() -> void {
  447. // If the runtimes are null, no in-flight build is owned so nothing to do.
  448. if (runtimes_ == nullptr) {
  449. CARBON_CHECK(
  450. !lock_file_.is_valid() && !flock_.is_locked() && !dir_.is_valid(),
  451. "Builder left in a partially cleared state!");
  452. return;
  453. }
  454. // Otherwise we need to abandon an in-flight build. First release the lock.
  455. ReleaseFileLock();
  456. // The rest of the cleanup is handled by the `RemovingDir` destructor.
  457. }
  458. } // namespace Carbon