runtimes_cache_test.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  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 <gmock/gmock.h>
  6. #include <gtest/gtest.h>
  7. #include <chrono>
  8. #include <filesystem>
  9. #include <fstream>
  10. #include <limits>
  11. #include <mutex>
  12. #include <ratio>
  13. #include <string>
  14. #include <thread>
  15. #include <utility>
  16. #include <variant>
  17. #include "common/check.h"
  18. #include "common/error_test_helpers.h"
  19. #include "common/filesystem.h"
  20. #include "common/ostream.h"
  21. #include "common/raw_string_ostream.h"
  22. #include "common/version.h"
  23. #include "llvm/ADT/ScopeExit.h"
  24. #include "llvm/Support/SHA256.h"
  25. #include "testing/base/capture_std_streams.h"
  26. #include "testing/base/file_helpers.h"
  27. #include "testing/base/global_exe_path.h"
  28. namespace Carbon {
  29. class RuntimesTestPeer {
  30. public:
  31. static auto LockFilePath(Runtimes::Component component) -> std::string {
  32. return llvm::formatv(Runtimes::LockFileFormat,
  33. Runtimes::ComponentPath(component))
  34. .str();
  35. }
  36. static auto BuildImpl(Runtimes& runtimes, Runtimes::Component component,
  37. Filesystem::Duration deadline,
  38. Filesystem::Duration poll_interval)
  39. -> ErrorOr<std::variant<std::filesystem::path, Runtimes::Builder>> {
  40. return runtimes.BuildImpl(component, deadline, poll_interval);
  41. }
  42. static auto CacheMinNumEntries() -> int {
  43. return Runtimes::Cache::MinNumEntries;
  44. }
  45. static auto CacheMaxNumEntries() -> int {
  46. return Runtimes::Cache::MaxNumEntries;
  47. }
  48. };
  49. namespace {
  50. using ::testing::_;
  51. using ::testing::AllOf;
  52. using ::testing::AnyOf;
  53. using ::testing::Eq;
  54. using ::testing::Gt;
  55. using Testing::IsError;
  56. using Testing::IsSuccess;
  57. using ::testing::Lt;
  58. using ::testing::Ne;
  59. using ::testing::Not;
  60. using ::testing::StartsWith;
  61. using ::testing::StrEq;
  62. using ::testing::VariantWith;
  63. class RuntimesCacheTest : public ::testing::Test {
  64. public:
  65. RuntimesCacheTest()
  66. : cache_(*Runtimes::Cache::MakeCustom(install_, tmp_dir_.path())) {}
  67. auto LookupNRuntimes(int n) -> llvm::SmallVector<Runtimes> {
  68. llvm::SmallVector<Runtimes> runtimes;
  69. for (int i : llvm::seq(n)) {
  70. runtimes.push_back(*cache_.Lookup(
  71. {.target = llvm::formatv("aarch64-unknown-unknown{0}", i).str()}));
  72. }
  73. return runtimes;
  74. }
  75. InstallPaths install_ =
  76. InstallPaths::MakeForBazelRunfiles(Testing::GetExePath());
  77. Filesystem::RemovingDir tmp_dir_ = *Filesystem::MakeTmpDir();
  78. std::string cache_key_ = "test cache";
  79. Runtimes::Cache cache_;
  80. };
  81. TEST_F(RuntimesCacheTest, BuildSystemCache) {
  82. // Create an install with a missing digest.
  83. auto bad_install_dir = *tmp_dir_.CreateDirectories("bad_install/lib/carbon");
  84. bad_install_dir.WriteFileFromString("carbon_install.txt", "no digest")
  85. .Check();
  86. InstallPaths bad_install =
  87. InstallPaths::Make((tmp_dir_.path() / "bad_install").native());
  88. // Create directories to use in various environment variables.
  89. auto xdg_dir = *tmp_dir_.CreateDirectories("xdg_cache_home");
  90. std::filesystem::path xdg_path = tmp_dir_.path() / "xdg_cache_home";
  91. auto test_home = *tmp_dir_.CreateDirectories("test_home");
  92. std::filesystem::path home_path = tmp_dir_.path() / "test_home";
  93. auto home_cache_dir = *test_home.CreateDirectories(".cache");
  94. std::filesystem::path home_cache_path = home_path / ".cache";
  95. // Save the environment variables we'll override for testing and restore them
  96. // afterward to avoid test-to-test oddities.
  97. constexpr const char* XdgCacheEnv = "XDG_CACHE_HOME";
  98. constexpr const char* HomeEnv = "HOME";
  99. const char* orig_xdg_cache = getenv(XdgCacheEnv);
  100. const char* orig_home = getenv(HomeEnv);
  101. auto restore_env = llvm::scope_exit([&] {
  102. for (const auto [env, orig] : {std::pair{XdgCacheEnv, orig_xdg_cache},
  103. std::pair{HomeEnv, orig_home}}) {
  104. if (orig) {
  105. setenv(env, orig, /*overwrite*/ true);
  106. } else {
  107. unsetenv(env);
  108. }
  109. }
  110. });
  111. // Begin testing the basic logic of selecting different roots for the cache.
  112. setenv(XdgCacheEnv, xdg_path.c_str(), /*overwrite*/ true);
  113. setenv(HomeEnv, home_path.c_str(), /*overwrite*/ true);
  114. // First check that even with all the environment set up, when we don't have a
  115. // digest file available, we bypass those options and use a temporary cache
  116. // path. This is the only safe approach as without a digest file we can't
  117. // track whether it is correct to reuse a persistently cached entry.
  118. auto result = Runtimes::Cache::MakeSystem(bad_install);
  119. ASSERT_THAT(result, IsSuccess(_));
  120. EXPECT_THAT(result->path(), Not(StartsWith(home_cache_path)));
  121. EXPECT_THAT(result->path(), Not(StartsWith(xdg_path)));
  122. // Once we have a digest, the main XDG cache logic should work.
  123. result = Runtimes::Cache::MakeSystem(install_);
  124. ASSERT_THAT(result, IsSuccess(_));
  125. EXPECT_THAT(result->path(), StartsWith(xdg_path));
  126. // Destruction shouldn't remove system cache directories.
  127. result = Error("nothing");
  128. EXPECT_TRUE(*tmp_dir_.Access("xdg_cache_home"));
  129. // Remove the XDG cache directory, but leave the environment set. We want to
  130. // be robust against this, but it isn't important *how* the fallback occurs,
  131. // it could go to `$HOME/.cache`, or to a temporary directory.
  132. tmp_dir_.Rmtree("xdg_cache_home").Check();
  133. EXPECT_THAT(Runtimes::Cache::MakeSystem(install_), IsSuccess(_));
  134. // Set the XDG environment to the empty string which should trigger using the
  135. // home directory.
  136. setenv(XdgCacheEnv, "", /*overwrite*/ true);
  137. result = Runtimes::Cache::MakeSystem(install_);
  138. ASSERT_THAT(result, IsSuccess(_));
  139. EXPECT_THAT(result->path(), StartsWith(home_cache_path));
  140. // Destruction shouldn't remove system cache directories.
  141. result = Error("nothing");
  142. EXPECT_TRUE(*tmp_dir_.Access("test_home"));
  143. EXPECT_TRUE(*test_home.Access(".cache"));
  144. // Same as with an empty string, but with a relative path instead.
  145. setenv(XdgCacheEnv, "relative/cache/home", /*overwrite*/ true);
  146. result = Runtimes::Cache::MakeSystem(install_);
  147. ASSERT_THAT(result, IsSuccess(_));
  148. EXPECT_THAT(result->path(), StartsWith(home_cache_path));
  149. // Destruction shouldn't remove system cache directories.
  150. result = Error("nothing");
  151. EXPECT_TRUE(*tmp_dir_.Access("test_home"));
  152. EXPECT_TRUE(*test_home.Access(".cache"));
  153. // Same as with an empty string, but this time with an unset environment
  154. // variable.
  155. unsetenv(XdgCacheEnv);
  156. result = Runtimes::Cache::MakeSystem(install_);
  157. ASSERT_THAT(result, IsSuccess(_));
  158. EXPECT_THAT(result->path(), StartsWith(home_cache_path));
  159. // Destruction shouldn't remove system cache directories.
  160. result = Error("nothing");
  161. EXPECT_TRUE(*tmp_dir_.Access("test_home"));
  162. EXPECT_TRUE(*test_home.Access(".cache"));
  163. // Now check a bunch of different failure modes for the home directory
  164. // fallback. These should all end up creating temporary directories which
  165. // we'll test functionally at the end.
  166. setenv(HomeEnv, "", /*overwrite*/ true);
  167. EXPECT_THAT(Runtimes::Cache::MakeSystem(install_), IsSuccess(_));
  168. setenv(HomeEnv, "relative/home", /*overwrite*/ true);
  169. EXPECT_THAT(Runtimes::Cache::MakeSystem(install_), IsSuccess(_));
  170. // Correct the path and make sure it works again.
  171. setenv(HomeEnv, home_path.c_str(), /*overwrite*/ true);
  172. result = Runtimes::Cache::MakeSystem(install_);
  173. ASSERT_THAT(result, IsSuccess(_));
  174. EXPECT_THAT(result->path(), StartsWith(home_cache_path));
  175. // Now try removing directories around home.
  176. test_home.Rmtree(".cache").Check();
  177. EXPECT_THAT(Runtimes::Cache::MakeSystem(install_), IsSuccess(_));
  178. tmp_dir_.Rmtree("test_home").Check();
  179. EXPECT_THAT(Runtimes::Cache::MakeSystem(install_), IsSuccess(_));
  180. // Finally, double check that these temporary caches still produce a writable
  181. // directory.
  182. result = Runtimes::Cache::MakeSystem(install_);
  183. ASSERT_THAT(result, IsSuccess(_));
  184. EXPECT_THAT(result->path(), Not(StartsWith(home_cache_path)));
  185. EXPECT_THAT(result->path(), Not(StartsWith(xdg_path)));
  186. ASSERT_THAT(Filesystem::Cwd().WriteFileFromString(
  187. result->path() / "test_file", "test"),
  188. IsSuccess(_));
  189. ASSERT_THAT(Filesystem::Cwd().ReadFileToString(result->path() / "test_file"),
  190. IsSuccess(StrEq("test")));
  191. }
  192. TEST_F(RuntimesCacheTest, BasicBuild) {
  193. llvm::SmallVector<std::string> targets = {"aarch64-unknown-unknown",
  194. "x86_64-unknown-unknown"};
  195. llvm::SmallVector<std::filesystem::path> built_runtimes_paths;
  196. for (const std::string& target : targets) {
  197. SCOPED_TRACE(target);
  198. auto lookup_result = cache_.Lookup({.target = target});
  199. ASSERT_THAT(lookup_result, IsSuccess(_));
  200. auto runtimes = *std::move(lookup_result);
  201. auto build_result = runtimes.Build(Runtimes::ClangResourceDir);
  202. ASSERT_THAT(build_result, IsSuccess(VariantWith<Runtimes::Builder>(_)));
  203. auto builder = std::get<Runtimes::Builder>(*std::move(build_result));
  204. EXPECT_TRUE(builder.path().is_absolute()) << builder.path();
  205. // Create a file as our "runtime".
  206. builder.dir().WriteFileFromString("runtime_file", target).Check();
  207. // Make sure the builder's path finds this file.
  208. EXPECT_THAT(
  209. Filesystem::Cwd().ReadFileToString(builder.path() / "runtime_file"),
  210. IsSuccess(StrEq(target)));
  211. auto commit_result = std::move(builder).Commit();
  212. ASSERT_THAT(commit_result, IsSuccess(_));
  213. std::filesystem::path clang_runtimes_path = *std::move(commit_result);
  214. EXPECT_THAT(
  215. runtimes.Build(Runtimes::ClangResourceDir),
  216. IsSuccess(VariantWith<std::filesystem::path>(Eq(clang_runtimes_path))));
  217. built_runtimes_paths.push_back(clang_runtimes_path);
  218. }
  219. for (const auto& [target, built_runtimes_path] :
  220. llvm::zip_equal(targets, built_runtimes_paths)) {
  221. SCOPED_TRACE(target);
  222. auto lookup_result = cache_.Lookup({.target = target});
  223. ASSERT_THAT(lookup_result, IsSuccess(_));
  224. auto runtimes = *std::move(lookup_result);
  225. EXPECT_THAT(
  226. runtimes.Build(Runtimes::ClangResourceDir),
  227. IsSuccess(VariantWith<std::filesystem::path>(Eq(built_runtimes_path))));
  228. }
  229. }
  230. TEST_F(RuntimesCacheTest, DifferentKeys) {
  231. const std::string target = "aarch64-unknown-unknown";
  232. auto runtimes1 = *cache_.Lookup({.target = target});
  233. // Build a second cache with a different key but pointing at the same
  234. // directory and target to simulate two versions or builds of the Carbon
  235. // toolchain.
  236. auto custom_install_dir =
  237. *tmp_dir_.CreateDirectories("custom_install/lib/carbon");
  238. custom_install_dir.WriteFileFromString("carbon_install.txt", "diff digest")
  239. .Check();
  240. custom_install_dir.WriteFileFromString("install_digest.txt", "abcd").Check();
  241. InstallPaths install2 =
  242. InstallPaths::Make((tmp_dir_.path() / "custom_install").native());
  243. auto cache2 = *Runtimes::Cache::MakeCustom(install2, tmp_dir_.path());
  244. auto runtimes2 = *cache2.Lookup({.target = target});
  245. // The parent paths of these runtimes should be the same.
  246. EXPECT_THAT(runtimes1.base_path().parent_path(),
  247. Eq(runtimes2.base_path().parent_path()));
  248. // But the base paths for these two runtimes should differ due to cache key
  249. // differences.
  250. EXPECT_THAT(runtimes1.base_path(), Ne(runtimes2.base_path()));
  251. }
  252. TEST_F(RuntimesCacheTest, ConcurrentBuilds) {
  253. const std::string target = "aarch64-unknown-unknown";
  254. auto runtimes1 = *cache_.Lookup({.target = target});
  255. // Build a second cache and runtimes pointing at the same directory and target
  256. // to simulate concurrent processes.
  257. auto cache2 = *Runtimes::Cache::MakeCustom(install_, tmp_dir_.path());
  258. auto runtimes2 = *cache2.Lookup({.target = target});
  259. // Start the first build, this will lock the directory.
  260. auto build_result1 = runtimes1.Build(Runtimes::ClangResourceDir);
  261. ASSERT_THAT(build_result1, IsSuccess(VariantWith<Runtimes::Builder>(_)));
  262. auto builder1 = std::get<Runtimes::Builder>(*std::move(build_result1));
  263. EXPECT_THAT(builder1.dir().WriteFileFromString("runtime_file", "build1"),
  264. IsSuccess(_));
  265. // Start the second build in a separate thread so that it can block while we
  266. // finish the first build. The only result we'll need at the end is the built
  267. // path.
  268. std::filesystem::path build2_path;
  269. auto build2_lambda = [&build2_path, &runtimes2] {
  270. // Typically building here will try to acquire the same file lock acquired
  271. // with the first build. However, the file locking is always _advisory_ and
  272. // may fail. As a consequence we can't make assumptions about whether this
  273. // blocks or not.
  274. auto build_result2 = runtimes2.Build(Runtimes::ClangResourceDir);
  275. ASSERT_THAT(build_result2, IsSuccess(_));
  276. if (std::holds_alternative<std::filesystem::path>(*build_result2)) {
  277. // In the common case, we blocked on a file lock and find the first built
  278. // result directly. Save it.
  279. build2_path = std::get<std::filesystem::path>(*std::move(build_result2));
  280. } else {
  281. // In rare cases, the initial build will fail to acquire the file lock.
  282. // The entire build process is designed specifically to be resilient to
  283. // that so we should still succeed, but now we need to handle building in
  284. // this thread as well. Note that a true failure here may only
  285. // show up intermittently.
  286. auto builder2 = std::get<Runtimes::Builder>(*std::move(build_result2));
  287. builder2.dir().WriteFileFromString("runtime_file", "build2").Check();
  288. auto commit2_result = std::move(builder2).Commit();
  289. ASSERT_THAT(commit2_result, IsSuccess(_));
  290. build2_path = *std::move(commit2_result);
  291. }
  292. };
  293. std::thread build2_thread(build2_lambda);
  294. // Use a scoped join to avoid leaking the thread as some platforms don't have
  295. // `std::jthread`.
  296. auto scoped_join =
  297. llvm::scope_exit([&build2_thread] { build2_thread.join(); });
  298. // Commit the first built runtime.
  299. auto commit_result = std::move(builder1).Commit();
  300. ASSERT_THAT(commit_result, IsSuccess(_));
  301. std::filesystem::path build1_path = *std::move(commit_result);
  302. // Even though there may be is another thread running, we should now get
  303. // non-blocking access directly to the built runtime.
  304. EXPECT_THAT(runtimes1.Build(Runtimes::ClangResourceDir),
  305. IsSuccess(VariantWith<std::filesystem::path>(Eq(build1_path))));
  306. // Now join the second cache's build thread to ensure it completes and verify
  307. // that it produces the same path fully-built path.
  308. build2_thread.join();
  309. scoped_join.release();
  310. EXPECT_THAT(build2_path, Eq(build1_path));
  311. // Note that we don't know which build actually ended up committed here so
  312. // accept either. The first one is much more common, but in rare cases it will
  313. // fail to acquire its file lock and we will have racing builds. In that case
  314. // the second build may commit first.
  315. EXPECT_THAT(*Filesystem::Cwd().ReadFileToString(build1_path / "runtime_file"),
  316. AnyOf(StrEq("build1"), StrEq("build2")));
  317. }
  318. TEST_F(RuntimesCacheTest, ConcurrentBuildsWithFailedLocking) {
  319. // This test is very similar to `ConcurrentBuild` in terms of what can happen.
  320. // But here, we intentionally subvert the file locking and even us
  321. // synchronization to maximize the chance of racing commits.
  322. //
  323. // The goal here is to do two things:
  324. // 1) Provide more direct stress testing of lock-file-failure modes and racing
  325. // commits to catch any consistent bugs that emerge.
  326. // 2) Ensure that a removed lock file specifically is handled gracefully, both
  327. // by a build with the file open and locked, and by a racing build.
  328. const std::string target = "aarch64-unknown-unknown";
  329. auto runtimes1 = *cache_.Lookup({.target = target});
  330. // Build a second cache and runtimes pointing at the same directory and target
  331. // to simulate concurrent processes.
  332. auto cache2 = *Runtimes::Cache::MakeCustom(install_, tmp_dir_.path());
  333. auto runtimes2 = *cache2.Lookup({.target = target});
  334. // Start the first build, this will lock the directory.
  335. auto build_result1 = runtimes1.Build(Runtimes::ClangResourceDir);
  336. ASSERT_THAT(build_result1, IsSuccess(VariantWith<Runtimes::Builder>(_)));
  337. auto builder1 = std::get<Runtimes::Builder>(*std::move(build_result1));
  338. builder1.dir().WriteFileFromString("runtime_file", "build1").Check();
  339. // Now sneakily remove the lock file from the runtimes directory in the cache.
  340. // This is something that could happen, for example from temporary directories
  341. // being cleaned. The cache should be resilient against this and it gives us a
  342. // good way to have two racing builds of the same directory.
  343. std::filesystem::path lock_file_path =
  344. RuntimesTestPeer::LockFilePath(Runtimes::ClangResourceDir);
  345. ASSERT_THAT(runtimes1.base_dir().Unlink(lock_file_path), IsSuccess(_));
  346. // We will synchronize with the thread to ensure we _actually_ have two
  347. // parallel builds rather than accidentally having a fully serial execution.
  348. std::mutex m;
  349. std::condition_variable cv;
  350. bool build_started = false;
  351. // Start the second build in a separate thread. The only result we'll need at
  352. // the end is the built path.
  353. std::filesystem::path build2_path;
  354. auto build2_lambda = [&build2_path, &runtimes2, target, &m, &cv,
  355. &build_started] {
  356. auto build_result2 = runtimes2.Build(Runtimes::ClangResourceDir);
  357. ASSERT_THAT(build_result2, IsSuccess(VariantWith<Runtimes::Builder>(_)));
  358. auto builder2 = std::get<Runtimes::Builder>(*std::move(build_result2));
  359. builder2.dir().WriteFileFromString("runtime_file", "build2").Check();
  360. // Notify the first thread to commit its build and concurrently commit this
  361. // built runtime. The goal is to get as close as we can to having these
  362. // commits actually race so that a failure in that mode would emerge as a
  363. // flake of the test. None of this is providing correctness.
  364. {
  365. std::unique_lock lock(m);
  366. build_started = true;
  367. cv.notify_one();
  368. }
  369. auto commit_result = std::move(builder2).Commit();
  370. ASSERT_THAT(commit_result, IsSuccess(_));
  371. build2_path = *std::move(commit_result);
  372. // Even though there may be another thread running, and even holding a lock
  373. // file, we should now get non-blocking access directly to the built
  374. // runtime. This is mostly added for completeness, a held lock is more
  375. // directly tested in `CurrentBuildsLockTimeout`.
  376. EXPECT_THAT(runtimes2.Build(Runtimes::ClangResourceDir),
  377. IsSuccess(VariantWith<std::filesystem::path>(Eq(build2_path))));
  378. };
  379. std::thread build2_thread(build2_lambda);
  380. // Use a scoped join to avoid leaking the thread as some platforms don't have
  381. // `std::jthread`.
  382. auto scoped_join =
  383. llvm::scope_exit([&build2_thread] { build2_thread.join(); });
  384. // As soon as the second thread notifies that its build is started and ready
  385. // to commit, also commit the first built runtime.
  386. {
  387. std::unique_lock lock(m);
  388. cv.wait(lock, [&build_started] { return build_started; });
  389. }
  390. auto commit_result = std::move(builder1).Commit();
  391. ASSERT_THAT(commit_result, IsSuccess(_));
  392. std::filesystem::path build1_path = *std::move(commit_result);
  393. // Even though there may be another thread running, we should now get
  394. // non-blocking access directly to the built runtime.
  395. EXPECT_THAT(runtimes1.Build(Runtimes::ClangResourceDir),
  396. IsSuccess(VariantWith<std::filesystem::path>(Eq(build1_path))));
  397. // Now join the second cache's build thread to ensure it completes and verify
  398. // that it produces the same path fully-built path.
  399. build2_thread.join();
  400. scoped_join.release();
  401. EXPECT_THAT(build2_path, Eq(build1_path));
  402. // Much like the simple concurrent build, we can't know which build finished
  403. // first so we need to accept either build's runtime file.
  404. EXPECT_THAT(*Filesystem::Cwd().ReadFileToString(build1_path / "runtime_file"),
  405. AnyOf(StrEq("build1"), StrEq("build2")));
  406. }
  407. TEST_F(RuntimesCacheTest, ConcurrentBuildsLockTimeout) {
  408. // Another test designed to be similar to `ConcurrentBuilds` but stressing a
  409. // failure path. Here, we want to reliably exercise the code path where a lock
  410. // file is held when a second build begins and it polls and times out. This
  411. // can happen naturally, even with very large timeouts under sufficient system
  412. // load. Here, we artificially make it as likely as possible for better stress
  413. // testing and easier debugging of problems with this situation.
  414. const std::string target = "aarch64-unknown-unknown";
  415. auto runtimes1 = *cache_.Lookup({.target = target});
  416. // Build a second cache and runtimes pointing at the same directory and target
  417. // to simulate concurrent processes.
  418. auto cache2 = *Runtimes::Cache::MakeCustom(install_, tmp_dir_.path());
  419. auto runtimes2 = *cache2.Lookup({.target = target});
  420. // Start the first build, this will lock the directory.
  421. auto build_result1 = runtimes1.Build(Runtimes::ClangResourceDir);
  422. ASSERT_THAT(build_result1, IsSuccess(VariantWith<Runtimes::Builder>(_)));
  423. auto builder1 = std::get<Runtimes::Builder>(*std::move(build_result1));
  424. builder1.dir().WriteFileFromString("runtime_file", "build1").Check();
  425. // Directly simulate a second thread or process timing out on acquiring the
  426. // file-based advisory lock by giving it an artificially short timeout and
  427. // running it in the same thread. This should only poll for 50ms before
  428. // proceeding without the lock.
  429. //
  430. // However, note that this is not *guaranteed* -- the first build may have
  431. // exhausted the much higher default poll timeout and failed to acquire a file
  432. // lock at all. When that happens, this path may in turn succeed at acquiring
  433. // the file lock. All of that is fine, and the test even remains effective as
  434. // either way we have successfully exercised the code path with lock file
  435. // timeout. The lowered time here just ensures that the test finishes promptly
  436. // relative to the system load.
  437. auto build_result2 = RuntimesTestPeer::BuildImpl(
  438. runtimes2, Runtimes::ClangResourceDir, std::chrono::milliseconds(50),
  439. std::chrono::milliseconds(10));
  440. ASSERT_THAT(build_result2, IsSuccess(VariantWith<Runtimes::Builder>(_)));
  441. auto builder2 = std::get<Runtimes::Builder>(*std::move(build_result2));
  442. builder2.dir().WriteFileFromString("runtime_file", "build2").Check();
  443. // Commit the second runtime, as this one *doesn't* hold any lock. This leaves
  444. // the lock present and held, but creates a valid runtimes directory.
  445. auto commit2_result = std::move(builder2).Commit();
  446. ASSERT_THAT(commit2_result, IsSuccess(_));
  447. std::filesystem::path build2_path = *std::move(commit2_result);
  448. // Now, even though we still have the lock file held, repeatedly building
  449. // proceeds without blocking.
  450. EXPECT_THAT(runtimes2.Build(Runtimes::ClangResourceDir),
  451. IsSuccess(VariantWith<std::filesystem::path>(Eq(build2_path))));
  452. // Finally, commit the lock-holding build to ensure it also succeeds, even
  453. // though it will reliably discard its built cache.
  454. auto commit1_result = std::move(builder1).Commit();
  455. ASSERT_THAT(commit1_result, IsSuccess(_));
  456. std::filesystem::path build1_path = *std::move(commit1_result);
  457. // And ensure that we got the same path and the second build's contents.
  458. EXPECT_THAT(build1_path, Eq(build2_path));
  459. EXPECT_THAT(*Filesystem::Cwd().ReadFileToString(build1_path / "runtime_file"),
  460. StrEq("build2"));
  461. }
  462. TEST_F(RuntimesCacheTest, Lookup) {
  463. // Basic successful lookup of a new runtimes.
  464. auto lookup_result = cache_.Lookup({.target = "aarch64-unknown-unknown"});
  465. ASSERT_THAT(lookup_result, IsSuccess(_));
  466. auto runtimes = *std::move(lookup_result);
  467. auto lock_stat = runtimes.base_dir().Stat(".lock");
  468. ASSERT_THAT(lock_stat, IsSuccess(_));
  469. EXPECT_TRUE(lock_stat->is_file());
  470. // Looking up the same target should return the same runtimes.
  471. lookup_result = cache_.Lookup({.target = "aarch64-unknown-unknown"});
  472. ASSERT_THAT(lookup_result, IsSuccess(_));
  473. auto runtimes2 = *std::move(lookup_result);
  474. EXPECT_THAT(runtimes2.base_path(), Eq(runtimes.base_path()));
  475. EXPECT_THAT(runtimes.base_dir().Stat()->unix_inode(),
  476. Eq(runtimes.base_dir().Stat()->unix_inode()));
  477. }
  478. TEST_F(RuntimesCacheTest, LookupFailsIfCannotCreateDir) {
  479. // Create a read-only directory with the cache in it to cause failures.
  480. std::filesystem::path ro_cache_path = tmp_dir_.path() / "ro_cache";
  481. tmp_dir_.CreateDirectories("ro_cache", /*creation_mode=*/0500).Check();
  482. auto ro_cache = *Runtimes::Cache::MakeCustom(install_, ro_cache_path);
  483. auto lookup_result = ro_cache.Lookup({.target = "aarch64-unknown-unknown"});
  484. EXPECT_THAT(lookup_result, IsError(_));
  485. }
  486. TEST_F(RuntimesCacheTest, LookupWithSmallNumberOfStaleRuntimes) {
  487. // Lookup two runtimes to populate the cache.
  488. auto runtimes1 = *cache_.Lookup({.target = "aarch64-unknown-unknown1"});
  489. auto runtimes2 = *cache_.Lookup({.target = "aarch64-unknown-unknown2"});
  490. // Get the Unix-like inode of the directories so we can check whether
  491. // subsequent lookups create a new directory.
  492. auto runtimes1_inode = runtimes1.base_dir().Stat()->unix_inode();
  493. auto runtimes2_inode = runtimes2.base_dir().Stat()->unix_inode();
  494. // Now adjust their age backwards in time by two years to make them very, very
  495. // stale.
  496. auto now = Filesystem::Clock::now();
  497. auto two_years_ago = now - std::chrono::years(2);
  498. runtimes1.base_dir().UpdateTimes(".lock", two_years_ago).Check();
  499. runtimes2.base_dir().UpdateTimes(".lock", two_years_ago).Check();
  500. // Close the runtimes, releasing any locks.
  501. runtimes1 = {};
  502. runtimes2 = {};
  503. // Lookup a new runtime, potentially pruning stale ones.
  504. auto runtimes3 = *cache_.Lookup({.target = "aarch64-unknown-unknown3"});
  505. // Redo the previous lookups and ensure they found the original directories as
  506. // we don't have enough runtimes to prune.
  507. runtimes1 = *cache_.Lookup({.target = "aarch64-unknown-unknown1"});
  508. runtimes2 = *cache_.Lookup({.target = "aarch64-unknown-unknown2"});
  509. EXPECT_THAT(runtimes1.base_dir().Stat()->unix_inode(), Eq(runtimes1_inode));
  510. EXPECT_THAT(runtimes2.base_dir().Stat()->unix_inode(), Eq(runtimes2_inode));
  511. // The timestamp on the lock file should also be updated. We can't assume the
  512. // filesystem clock is monotonic, so it is possible an adjustment occurs while
  513. // this test is running. We check that the updated time is within 2 days of
  514. // `now` to minimize flake risks, which should be completely fine to detect
  515. // bugs as we set the time to 2 years in the past above.
  516. EXPECT_THAT(
  517. runtimes1.base_dir().Stat(".lock")->mtime(),
  518. AllOf(Gt(now - std::chrono::days(2)), Lt(now + std::chrono::days(2))));
  519. EXPECT_THAT(
  520. runtimes2.base_dir().Stat(".lock")->mtime(),
  521. AllOf(Gt(now - std::chrono::days(2)), Lt(now + std::chrono::days(2))));
  522. }
  523. TEST_F(RuntimesCacheTest, LookupWithManyStaleRuntimes) {
  524. auto runtimes1 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh1"});
  525. auto stale_runtimes = LookupNRuntimes(RuntimesTestPeer::CacheMinNumEntries());
  526. auto runtimes2 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh2"});
  527. // Get the Unix-like inode of the directories so we can check whether
  528. // subsequent lookups create a new directory. For the stale directory, we
  529. // don't just get the inode, we also create an open directory to it. This
  530. // should allow it to be replaced, but prevent the directory's inode from
  531. // being reused.
  532. auto runtimes1_inode = runtimes1.base_dir().Stat()->unix_inode();
  533. auto runtimes2_inode = runtimes2.base_dir().Stat()->unix_inode();
  534. Filesystem::Dir stale_dir =
  535. *Filesystem::Cwd().OpenDir(stale_runtimes[0].base_path());
  536. auto stale_runtimes_0_inode =
  537. stale_runtimes[0].base_dir().Stat()->unix_inode();
  538. // Confirm that our extra open directory points to the some entity as the
  539. // runtimes has open.
  540. ASSERT_THAT(stale_dir.Stat()->unix_inode(), Eq(stale_runtimes_0_inode));
  541. // Now adjust their age backwards in time by two years to make them very, very
  542. // stale.
  543. auto now = Filesystem::Clock::now();
  544. auto two_years_ago = now - std::chrono::years(2);
  545. for (auto& stale_runtime : stale_runtimes) {
  546. stale_runtime.base_dir().UpdateTimes(".lock", two_years_ago).Check();
  547. }
  548. // Close the runtimes, releasing any locks.
  549. runtimes1 = {};
  550. stale_runtimes.clear();
  551. runtimes2 = {};
  552. // Lookup a new runtime, potentially pruning stale ones.
  553. auto runtimes3 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh3"});
  554. // Re-lookup three of the original runtimes.
  555. runtimes1 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh1"});
  556. auto stale_runtimes_0 =
  557. *cache_.Lookup({.target = "aarch64-unknown-unknown0"});
  558. runtimes2 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh2"});
  559. // The first and last should have been preserved as they were not stale.
  560. EXPECT_THAT(runtimes1.base_dir().Stat()->unix_inode(), Eq(runtimes1_inode));
  561. EXPECT_THAT(runtimes2.base_dir().Stat()->unix_inode(), Eq(runtimes2_inode));
  562. // One of the stale runtimes should be freshly created though. Note that this
  563. // is only reliable because `stale_runtimes_0_inode` remains in use with our
  564. // open `stale_dir` above. Without that, the inode could be reused and despite
  565. // being freshly created, the directory would have the same inode.
  566. EXPECT_THAT(stale_runtimes_0.base_dir().Stat()->unix_inode(),
  567. Ne(stale_runtimes_0_inode));
  568. }
  569. TEST_F(RuntimesCacheTest, LookupWithTooManyRuntimes) {
  570. auto runtimes1 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh1"});
  571. auto runtimes2 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh2"});
  572. // Compute the number of runtimes to fill up the cache as the max, minus the
  573. // two created above. Note that it is important to not _overflow_ the cache
  574. // here: because we are holding all of these runtimes open, they all have
  575. // their lock files locked. This will result in an attempt to prune the cache
  576. // that tries (and fails) to acquire a lock on all N runtimes which can be
  577. // very slow.
  578. int n = RuntimesTestPeer::CacheMaxNumEntries() - 2;
  579. auto stale_runtimes = LookupNRuntimes(n);
  580. // Compute stale target strings.
  581. auto stale_runtimes_n_1_target =
  582. llvm::formatv("aarch64-unknown-unknown{0}", n - 1).str();
  583. auto stale_runtimes_n_2_target =
  584. llvm::formatv("aarch64-unknown-unknown{0}", n - 2).str();
  585. // Get the Unix-like inode of the directories so we can check whether
  586. // subsequent lookups create a new directory.
  587. auto runtimes1_inode = runtimes1.base_dir().Stat()->unix_inode();
  588. auto runtimes2_inode = runtimes2.base_dir().Stat()->unix_inode();
  589. auto stale_runtimes_0_inode =
  590. stale_runtimes[0].base_dir().Stat()->unix_inode();
  591. auto stale_runtimes_n_1_inode =
  592. stale_runtimes.back().base_dir().Stat()->unix_inode();
  593. // For the n-2 stale runtime, get the inode but also open the underlying
  594. // directory so that the inode can't be reused even after pruning the runtime
  595. // below.
  596. Runtimes& stale_runtimes_n_2_orig = *std::prev(stale_runtimes.end(), 2);
  597. auto stale_runtimes_n_2_inode =
  598. stale_runtimes_n_2_orig.base_dir().Stat()->unix_inode();
  599. Filesystem::Dir stale_dir =
  600. *Filesystem::Cwd().OpenDir(stale_runtimes_n_2_orig.base_path());
  601. // Confirm that our extra open directory points to the some entity as the
  602. // runtimes has open.
  603. ASSERT_THAT(stale_dir.Stat()->unix_inode(), Eq(stale_runtimes_n_2_inode));
  604. // Now manually set all the timestamps. We do this manually to avoid any
  605. // reliance on the clock behavior or the amount of time passing between lookup
  606. // calls.
  607. auto now = Filesystem::Clock::now();
  608. runtimes1.base_dir().UpdateTimes(".lock", now).Check();
  609. runtimes2.base_dir().UpdateTimes(".lock", now).Check();
  610. // Now set the stale runtimes to times further and further in the past.
  611. now -= std::chrono::milliseconds(1);
  612. for (auto [i, stale_runtime] : llvm::enumerate(stale_runtimes)) {
  613. stale_runtime.base_dir()
  614. .UpdateTimes(".lock", now - std::chrono::milliseconds(i * i))
  615. .Check();
  616. }
  617. // Close most of the runtimes to release the locks, but keep the oldest stale
  618. // runtime locked along with a fresh one to exercise the locking path.
  619. runtimes1 = {};
  620. auto stale_runtime_n_orig = stale_runtimes.pop_back_val();
  621. stale_runtimes.clear();
  622. // Lookup a new runtime, potentially pruning stale ones.
  623. auto runtimes3 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh3"});
  624. // Re-lookup three of the original runtimes.
  625. runtimes1 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh1"});
  626. runtimes2 = *cache_.Lookup({.target = "aarch64-unknown-unknown-fresh2"});
  627. auto stale_runtimes_0 =
  628. *cache_.Lookup({.target = "aarch64-unknown-unknown0"});
  629. auto stale_runtimes_n_1 =
  630. *cache_.Lookup({.target = stale_runtimes_n_1_target});
  631. auto stale_runtimes_n_2 =
  632. *cache_.Lookup({.target = stale_runtimes_n_2_target});
  633. // The fresh runtimes should be preserved.
  634. EXPECT_THAT(runtimes1.base_dir().Stat()->unix_inode(), Eq(runtimes1_inode));
  635. EXPECT_THAT(runtimes2.base_dir().Stat()->unix_inode(), Eq(runtimes2_inode));
  636. EXPECT_THAT(stale_runtimes_0.base_dir().Stat()->unix_inode(),
  637. Eq(stale_runtimes_0_inode));
  638. // THe last stale runtime should have been locked and so should remain.
  639. EXPECT_THAT(stale_runtimes_n_1.base_dir().Stat()->unix_inode(),
  640. Eq(stale_runtimes_n_1_inode));
  641. // The next to last should have been pruned and re-created though.
  642. EXPECT_THAT(stale_runtimes_n_2.base_dir().Stat()->unix_inode(),
  643. Ne(stale_runtimes_n_2_inode));
  644. }
  645. } // namespace
  646. } // namespace Carbon