diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/buildtool/build_engine/target_map/TARGETS | 1 | ||||
-rw-r--r-- | src/buildtool/build_engine/target_map/target_cache.cpp | 20 | ||||
-rw-r--r-- | src/buildtool/build_engine/target_map/target_cache_entry.cpp | 5 | ||||
-rw-r--r-- | src/buildtool/build_engine/target_map/target_cache_entry.hpp | 4 | ||||
-rw-r--r-- | src/buildtool/execution_api/local/TARGETS | 26 | ||||
-rw-r--r-- | src/buildtool/execution_api/local/garbage_collector.cpp | 567 | ||||
-rw-r--r-- | src/buildtool/execution_api/local/garbage_collector.hpp | 82 | ||||
-rw-r--r-- | src/buildtool/execution_api/local/local_ac.hpp | 19 | ||||
-rw-r--r-- | src/buildtool/execution_api/local/local_cas.hpp | 51 | ||||
-rw-r--r-- | src/buildtool/file_system/file_storage.hpp | 34 | ||||
-rw-r--r-- | src/buildtool/main/main.cpp | 6 |
11 files changed, 777 insertions, 38 deletions
diff --git a/src/buildtool/build_engine/target_map/TARGETS b/src/buildtool/build_engine/target_map/TARGETS index abf20ccc..a599b6ee 100644 --- a/src/buildtool/build_engine/target_map/TARGETS +++ b/src/buildtool/build_engine/target_map/TARGETS @@ -101,6 +101,7 @@ , "private-deps": [ ["@", "fmt", "", "fmt"] , ["src/buildtool/execution_api/local", "config"] + , ["src/buildtool/execution_api/local", "garbage_collector"] , ["src/buildtool/file_system", "file_system_manager"] , ["src/buildtool/logging", "log_level"] ] diff --git a/src/buildtool/build_engine/target_map/target_cache.cpp b/src/buildtool/build_engine/target_map/target_cache.cpp index 493b7505..48e761fd 100644 --- a/src/buildtool/build_engine/target_map/target_cache.cpp +++ b/src/buildtool/build_engine/target_map/target_cache.cpp @@ -23,6 +23,9 @@ #include "src/buildtool/execution_api/local/config.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/logging/log_level.hpp" +#ifndef BOOTSTRAP_BUILD_TOOL +#include "src/buildtool/execution_api/local/garbage_collector.hpp" +#endif auto TargetCache::Store(TargetCacheKey const& key, TargetCacheEntry const& value) const noexcept -> bool { @@ -46,16 +49,23 @@ auto TargetCache::Store(TargetCacheKey const& key, } auto TargetCache::Read(TargetCacheKey const& key) const noexcept - -> std::optional<std::pair<TargetCacheEntry, Artifact::ObjectInfo>> { - auto entry_path = file_store_.GetPath(key.Id().digest.hash()); - auto const entry = - FileSystemManager::ReadFile(entry_path, ObjectType::File); - if (not entry.has_value()) { + -> std::optional<std::pair<TargetCacheEntry, Artifact::ObjectInfo> > { + auto id = key.Id().digest.hash(); + auto entry_path = file_store_.GetPath(id); +#ifndef BOOTSTRAP_BUILD_TOOL + // Try to find target-cache entry in CAS generations and uplink if required. + auto found = GarbageCollector::FindAndUplinkTargetCacheEntry(id); +#else + auto found = FileSystemManager::IsFile(entry_path); +#endif + if (not found) { logger_.Emit(LogLevel::Debug, "Cache miss, entry not found {}", entry_path.string()); return std::nullopt; } + auto const entry = + FileSystemManager::ReadFile(entry_path, ObjectType::File); if (auto info = Artifact::ObjectInfo::FromString(*entry)) { if (auto path = CAS().BlobPath(info->digest)) { if (auto value = FileSystemManager::ReadFile(*path)) { diff --git a/src/buildtool/build_engine/target_map/target_cache_entry.cpp b/src/buildtool/build_engine/target_map/target_cache_entry.cpp index 45815056..139c706b 100644 --- a/src/buildtool/build_engine/target_map/target_cache_entry.cpp +++ b/src/buildtool/build_engine/target_map/target_cache_entry.cpp @@ -33,6 +33,11 @@ auto TargetCacheEntry::FromTarget( return std::nullopt; } +auto TargetCacheEntry::FromJson(nlohmann::json desc) noexcept + -> TargetCacheEntry { + return TargetCacheEntry(std::move(desc)); +} + auto TargetCacheEntry::ToResult() const noexcept -> std::optional<TargetResult> { return TargetResult::FromJson(desc_); diff --git a/src/buildtool/build_engine/target_map/target_cache_entry.hpp b/src/buildtool/build_engine/target_map/target_cache_entry.hpp index b6d1351c..1e54a003 100644 --- a/src/buildtool/build_engine/target_map/target_cache_entry.hpp +++ b/src/buildtool/build_engine/target_map/target_cache_entry.hpp @@ -40,6 +40,10 @@ class TargetCacheEntry { std::unordered_map<ArtifactDescription, Artifact::ObjectInfo> const& replacements) noexcept -> std::optional<TargetCacheEntry>; + // Create a target-cache entry from a json description. + [[nodiscard]] static auto FromJson(nlohmann::json desc) noexcept + -> TargetCacheEntry; + // Obtain TargetResult from cache entry. [[nodiscard]] auto ToResult() const noexcept -> std::optional<TargetResult>; diff --git a/src/buildtool/execution_api/local/TARGETS b/src/buildtool/execution_api/local/TARGETS index ecf6b9ca..31f2c655 100644 --- a/src/buildtool/execution_api/local/TARGETS +++ b/src/buildtool/execution_api/local/TARGETS @@ -30,6 +30,7 @@ , "srcs": ["local_action.cpp", "local_storage.cpp"] , "deps": [ "config" + , "garbage_collector" , ["@", "fmt", "", "fmt"] , ["@", "gsl-lite", "", "gsl-lite"] , ["src/buildtool/execution_api/common", "common"] @@ -38,7 +39,6 @@ , ["src/buildtool/file_system", "file_storage"] , ["src/buildtool/file_system", "file_system_manager"] , ["src/buildtool/compatibility", "compatibility"] - , ["src/buildtool/logging", "logging"] , ["src/buildtool/common", "common"] , ["src/buildtool/common", "bazel_types"] , ["src/buildtool/execution_api/bazel_msg", "bazel_msg"] @@ -50,4 +50,28 @@ , ["src/buildtool/system", "system_command"] ] } +, "garbage_collector": + { "type": ["@", "rules", "CC", "library"] + , "name": ["garbage_collector"] + , "hdrs": ["garbage_collector.hpp"] + , "srcs": ["garbage_collector.cpp"] + , "deps": [["src/utils/cpp", "file_locking"]] + , "stage": ["src", "buildtool", "execution_api", "local"] + , "private-deps": + [ "config" + , ["@", "json", "", "json"] + , ["src/buildtool/common", "common"] + , ["src/buildtool/common", "bazel_types"] + , ["src/buildtool/compatibility", "compatibility"] + , ["src/buildtool/build_engine/target_map", "target_cache_entry"] + , ["src/buildtool/execution_api/common", "common"] + , ["src/buildtool/file_system", "file_storage"] + , ["src/buildtool/file_system", "file_system_manager"] + , ["src/buildtool/file_system", "git_repo"] + , ["src/buildtool/file_system", "object_type"] + , ["src/buildtool/logging", "logging"] + , ["src/buildtool/logging", "log_level"] + , ["src/utils/cpp", "hex_string"] + ] + } } diff --git a/src/buildtool/execution_api/local/garbage_collector.cpp b/src/buildtool/execution_api/local/garbage_collector.cpp new file mode 100644 index 00000000..31af07e5 --- /dev/null +++ b/src/buildtool/execution_api/local/garbage_collector.cpp @@ -0,0 +1,567 @@ +// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "src/buildtool/execution_api/local/garbage_collector.hpp" + +#include <vector> + +#include <nlohmann/json.hpp> + +#include "src/buildtool/build_engine/target_map/target_cache_entry.hpp" +#include "src/buildtool/common/artifact.hpp" +#include "src/buildtool/common/bazel_types.hpp" +#include "src/buildtool/compatibility/compatibility.hpp" +#include "src/buildtool/compatibility/native_support.hpp" +#include "src/buildtool/execution_api/common/execution_common.hpp" +#include "src/buildtool/execution_api/local/config.hpp" +#include "src/buildtool/file_system/file_storage.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/buildtool/file_system/git_repo.hpp" +#include "src/buildtool/file_system/object_type.hpp" +#include "src/buildtool/logging/log_level.hpp" +#include "src/buildtool/logging/logger.hpp" +#include "src/utils/cpp/hex_string.hpp" + +auto GarbageCollector::FindAndUplinkBlob(std::string const& id, + bool is_executable) noexcept -> bool { + // Try to find blob in all generations. + for (int i = 0; i < LocalExecutionConfig::NumGenerations(); i++) { + if (UplinkBlob(i, id, is_executable)) { + return true; + } + } + return false; +} + +auto GarbageCollector::FindAndUplinkTree(std::string const& id) noexcept + -> bool { + // Try to find tree in all generations. + for (int i = 0; i < LocalExecutionConfig::NumGenerations(); i++) { + if (UplinkTree(i, id)) { + return true; + } + } + return false; +} + +auto GarbageCollector::FindAndUplinkActionCacheEntry( + std::string const& id) noexcept -> bool { + // Try to find action-cache entry in all generations. + for (int i = 0; i < LocalExecutionConfig::NumGenerations(); i++) { + if (UplinkActionCacheEntry(i, id)) { + return true; + } + } + return false; +} + +auto GarbageCollector::FindAndUplinkTargetCacheEntry( + std::string const& id) noexcept -> bool { + // Try to find target-cache entry in all generations. + for (int i = 0; i < LocalExecutionConfig::NumGenerations(); i++) { + if (UplinkTargetCacheEntry(i, id)) { + return true; + } + } + return false; +} + +auto GarbageCollector::SharedLock() noexcept -> std::optional<LockFile> { + return LockFile::Acquire(LockFilePath(), /*is_shared=*/true); +} + +auto GarbageCollector::ExclusiveLock() noexcept -> std::optional<LockFile> { + return LockFile::Acquire(LockFilePath(), /*is_shared=*/false); +} + +auto GarbageCollector::LockFilePath() noexcept -> std::filesystem::path { + return LocalExecutionConfig::CacheRoot() / "gc.lock"; +} + +auto GarbageCollector::TriggerGarbageCollection() noexcept -> bool { + auto pid = CreateProcessUniqueId(); + if (not pid) { + return false; + } + auto remove_me = std::string{"remove-me-"} + *pid; + auto remove_me_dir = LocalExecutionConfig::CacheRoot() / remove_me; + if (FileSystemManager::IsDirectory(remove_me_dir)) { + if (not FileSystemManager::RemoveDirectory(remove_me_dir, + /*recursively=*/true)) { + Logger::Log(LogLevel::Error, + "Failed to remove directory {}", + remove_me_dir.string()); + return false; + } + } + { // Create scope for critical renaming section protected by advisory lock. + auto lock = ExclusiveLock(); + if (not lock) { + Logger::Log(LogLevel::Error, + "Failed to exclusively lock the local build root"); + return false; + } + for (int i = LocalExecutionConfig::NumGenerations() - 1; i >= 0; i--) { + auto cache_root = LocalExecutionConfig::CacheRoot(i); + if (FileSystemManager::IsDirectory(cache_root)) { + auto new_cache_root = + (i == LocalExecutionConfig::NumGenerations() - 1) + ? remove_me_dir + : LocalExecutionConfig::CacheRoot(i + 1); + if (not FileSystemManager::Rename(cache_root, new_cache_root)) { + Logger::Log(LogLevel::Error, + "Failed to rename {} to {}.", + cache_root.string(), + new_cache_root.string()); + return false; + } + } + } + } + if (FileSystemManager::IsDirectory(remove_me_dir)) { + if (not FileSystemManager::RemoveDirectory(remove_me_dir, + /*recursively=*/true)) { + Logger::Log(LogLevel::Warning, + "Failed to remove directory {}", + remove_me_dir.string()); + return false; + } + } + return true; +} + +auto GarbageCollector::UplinkBlob(int index, + std::string const& id, + bool is_executable) noexcept -> bool { + // Determine blob path of given generation. + auto root = + is_executable + ? LocalExecutionConfig::CASDir<ObjectType::Executable>(index) + : LocalExecutionConfig::CASDir<ObjectType::File>(index); + auto blob_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(blob_path)) { + return false; + } + + // Determine blob path in latest generation. + auto root_latest = + is_executable ? LocalExecutionConfig::CASDir<ObjectType::Executable>(0) + : LocalExecutionConfig::CASDir<ObjectType::File>(0); + auto blob_path_latest = GetStoragePath(root_latest, id); + if (not FileSystemManager::IsFile(blob_path_latest)) { + // Uplink blob from older generation to the latest generation. + if (not FileSystemManager::CreateDirectory( + blob_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + blob_path, + blob_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(blob_path_latest)) { + return false; + } + } + return true; +} + +// NOLINTNEXTLINE(misc-no-recursion) +auto GarbageCollector::UplinkTree(int index, std::string const& id) noexcept + -> bool { + // Determine tree path of given generation. + auto root = LocalExecutionConfig::CASDir<ObjectType::Tree>(index); + auto tree_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(tree_path)) { + return false; + } + + // Determine tree path in latest generation. + auto root_latest = LocalExecutionConfig::CASDir<ObjectType::Tree>(0); + auto tree_path_latest = GetStoragePath(root_latest, id); + if (not FileSystemManager::IsFile(tree_path_latest)) { + // Determine tree entries. + auto content = FileSystemManager::ReadFile(tree_path); + auto tree_entries = GitRepo::ReadTreeData(*content, + id, + /*is_hex_id=*/true); + if (not tree_entries) { + return false; + } + + // Uplink tree entries. + for (auto const& [raw_id, entry_vector] : *tree_entries) { + // Process only first entry from 'entry_vector' since all entries + // represent the same blob, just with different names. + auto entry = entry_vector.front(); + auto hash = ToHexString(raw_id); + if (entry.type == ObjectType::Tree) { + if (not UplinkTree(index, hash)) { + return false; + } + } + else { + if (not UplinkBlob( + index, hash, entry.type == ObjectType::Executable)) { + return false; + } + } + } + + // Uplink tree from older generation to the latest generation. + if (not FileSystemManager::CreateDirectory( + tree_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + tree_path, + tree_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(tree_path_latest)) { + return false; + } + } + return true; +} + +auto GarbageCollector::UplinkBazelTree(int index, + std::string const& id) noexcept -> bool { + // Determine bazel tree path of given generation. + auto root = LocalExecutionConfig::CASDir<ObjectType::File>(index); + auto tree_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(tree_path)) { + return false; + } + + // Determine bazel tree entries. + auto content = FileSystemManager::ReadFile(tree_path); + bazel_re::Tree tree{}; + if (not tree.ParseFromString(*content)) { + return false; + } + + // Uplink bazel tree entries. + auto dir = tree.root(); + for (auto const& file : dir.files()) { + if (not UplinkBlob(index, + NativeSupport::Unprefix(file.digest().hash()), + file.is_executable())) { + return false; + } + } + for (auto const& directory : dir.directories()) { + if (not UplinkBazelDirectory( + index, NativeSupport::Unprefix(directory.digest().hash()))) { + return false; + } + } + + // Determine bazel tree path in latest generation. + auto root_latest = LocalExecutionConfig::CASDir<ObjectType::File>(0); + auto tree_path_latest = GetStoragePath(root_latest, id); + + // Uplink bazel tree from older generation to the latest generation. + if (not FileSystemManager::IsFile(tree_path_latest)) { + if (not FileSystemManager::CreateDirectory( + tree_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + tree_path, + tree_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(tree_path_latest)) { + return false; + } + } + return true; +} + +// NOLINTNEXTLINE(misc-no-recursion) +auto GarbageCollector::UplinkBazelDirectory(int index, + std::string const& id) noexcept + -> bool { + // Determine bazel directory path of given generation. + auto root = LocalExecutionConfig::CASDir<ObjectType::File>(index); + auto dir_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(dir_path)) { + return false; + } + + // Determine bazel directory entries. + auto content = FileSystemManager::ReadFile(dir_path); + bazel_re::Directory dir{}; + if (not dir.ParseFromString(*content)) { + return false; + } + + // Uplink bazel directory entries. + for (auto const& file : dir.files()) { + if (not UplinkBlob(index, + NativeSupport::Unprefix(file.digest().hash()), + file.is_executable())) { + return false; + } + } + for (auto const& directory : dir.directories()) { + if (not UplinkBazelDirectory( + index, NativeSupport::Unprefix(directory.digest().hash()))) { + return false; + } + } + + // Determine bazel directory path in latest generation. + auto root_latest = LocalExecutionConfig::CASDir<ObjectType::File>(0); + auto dir_path_latest = GetStoragePath(root_latest, id); + + // Uplink bazel directory from older generation to the latest generation. + if (not FileSystemManager::IsFile(dir_path_latest)) { + if (not FileSystemManager::CreateDirectory( + dir_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + dir_path, + dir_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(dir_path_latest)) { + return false; + } + } + return true; +} + +auto GarbageCollector::UplinkActionCacheEntry(int index, + std::string const& id) noexcept + -> bool { + // Determine action-cache entry path of given generation. + auto root = LocalExecutionConfig::ActionCacheDir(index); + auto entry_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(entry_path)) { + return false; + } + + // Determine action-cache entry location. + auto content = FileSystemManager::ReadFile(entry_path, ObjectType::File); + bazel_re::Digest digest{}; + if (not digest.ParseFromString(*content)) { + return false; + } + + // Uplink action-cache entry blob. + if (not UplinkActionCacheEntryBlob( + index, NativeSupport::Unprefix(digest.hash()))) { + return false; + } + + // Determine action-cache entry path in latest generation. + auto root_latest = LocalExecutionConfig::ActionCacheDir(0); + auto entry_path_latest = GetStoragePath(root_latest, id); + + // Uplink action-cache entry from older generation to the latest + // generation. + if (not FileSystemManager::IsFile(entry_path_latest)) { + if (not FileSystemManager::CreateDirectory( + entry_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + entry_path, + entry_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(entry_path_latest)) { + return false; + } + } + return true; +} + +auto GarbageCollector::UplinkActionCacheEntryBlob( + int index, + std::string const& id) noexcept -> bool { + + // Determine action-cache entry blob path of given generation. + auto root = LocalExecutionConfig::CASDir<ObjectType::File>(index); + auto entry_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(entry_path)) { + return false; + } + + // Determine artifacts referenced by action-cache entry. + auto content = FileSystemManager::ReadFile(entry_path); + bazel_re::ActionResult result{}; + if (not result.ParseFromString(*content)) { + return false; + } + + // Uplink referenced artifacts. + for (auto const& file : result.output_files()) { + if (not UplinkBlob(index, + NativeSupport::Unprefix(file.digest().hash()), + file.is_executable())) { + return false; + } + } + for (auto const& directory : result.output_directories()) { + if (Compatibility::IsCompatible()) { + if (not UplinkBazelTree( + index, + NativeSupport::Unprefix(directory.tree_digest().hash()))) { + return false; + } + } + else { + if (not UplinkTree( + index, + NativeSupport::Unprefix(directory.tree_digest().hash()))) { + return false; + } + } + } + + // Determine action-cache entry blob path in latest generation. + auto root_latest = LocalExecutionConfig::CASDir<ObjectType::File>(0); + auto entry_path_latest = GetStoragePath(root_latest, id); + + // Uplink action-cache entry blob from older generation to the latest + // generation. + if (not FileSystemManager::IsFile(entry_path_latest)) { + if (not FileSystemManager::CreateDirectory( + entry_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + entry_path, + entry_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(entry_path_latest)) { + return false; + } + } + return true; +} + +auto GarbageCollector::UplinkTargetCacheEntry(int index, + std::string const& id) noexcept + -> bool { + + // Determine target-cache entry path of given generation. + auto root = LocalExecutionConfig::TargetCacheDir(index); + auto entry_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(entry_path)) { + return false; + } + + // Determine target-cache entry location. + auto content = FileSystemManager::ReadFile(entry_path); + auto info = Artifact::ObjectInfo::FromString(*content); + if (not info) { + return false; + } + + // Uplink target-cache entry blob. + if (not UplinkTargetCacheEntryBlob(index, info->digest.hash())) { + return false; + } + + // Determine target-cache entry path in latest generation. + auto root_latest = LocalExecutionConfig::TargetCacheDir(0); + auto entry_path_latest = GetStoragePath(root_latest, id); + + // Uplink target-cache entry from older generation to the latest + // generation. + if (not FileSystemManager::IsFile(entry_path_latest)) { + if (not FileSystemManager::CreateDirectory( + entry_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + entry_path, + entry_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(entry_path_latest)) { + return false; + } + } + return true; +} + +auto GarbageCollector::UplinkTargetCacheEntryBlob( + int index, + std::string const& id) noexcept -> bool { + + // Determine target-cache entry blob path of given generation. + auto root = LocalExecutionConfig::CASDir<ObjectType::File>(index); + auto entry_path = GetStoragePath(root, id); + if (not FileSystemManager::IsFile(entry_path)) { + return false; + } + + // Determine artifacts referenced by target-cache entry. + auto content = FileSystemManager::ReadFile(entry_path); + nlohmann::json json_desc{}; + try { + json_desc = nlohmann::json::parse(*content); + } catch (std::exception const& ex) { + return false; + } + auto entry = TargetCacheEntry::FromJson(json_desc); + std::vector<Artifact::ObjectInfo> artifacts_info; + if (not entry.ToArtifacts(&artifacts_info)) { + return false; + } + + // Uplink referenced artifacts. + for (auto const& info : artifacts_info) { + auto hash = info.digest.hash(); + if (info.type == ObjectType::Tree) { + if (Compatibility::IsCompatible()) { + if (not UplinkBazelDirectory(index, hash)) { + return false; + } + } + else { + if (not UplinkTree(index, hash)) { + return false; + } + } + } + else { + if (not UplinkBlob( + index, hash, info.type == ObjectType::Executable)) { + return false; + } + } + } + + // Determine target-cache entry blob path in latest generation. + auto root_latest = LocalExecutionConfig::CASDir<ObjectType::File>(0); + auto entry_path_latest = GetStoragePath(root_latest, id); + + // Uplink target-cache entry blob from older generation to the latest + // generation. + if (not FileSystemManager::IsFile(entry_path_latest)) { + if (not FileSystemManager::CreateDirectory( + entry_path_latest.parent_path())) { + return false; + } + if (not FileSystemManager::CreateFileHardlink( + entry_path, + entry_path_latest, + /*log_failure_at=*/LogLevel::Debug) and + not FileSystemManager::IsFile(entry_path_latest)) { + return false; + } + } + return true; +} diff --git a/src/buildtool/execution_api/local/garbage_collector.hpp b/src/buildtool/execution_api/local/garbage_collector.hpp new file mode 100644 index 00000000..405c8900 --- /dev/null +++ b/src/buildtool/execution_api/local/garbage_collector.hpp @@ -0,0 +1,82 @@ +// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef INCLUDED_SRC_BUILDTOOL_EXECUTION_API_LOCAL_GARBAGE_COLLECTOR_HPP +#define INCLUDED_SRC_BUILDTOOL_EXECUTION_API_LOCAL_GARBAGE_COLLECTOR_HPP + +#include <optional> +#include <string> + +#include "src/utils/cpp/file_locking.hpp" + +/// \brief +class GarbageCollector { + public: + [[nodiscard]] auto static FindAndUplinkBlob(std::string const& id, + bool is_executable) noexcept + -> bool; + + [[nodiscard]] auto static FindAndUplinkTree(std::string const& id) noexcept + -> bool; + + [[nodiscard]] auto static FindAndUplinkActionCacheEntry( + std::string const& id) noexcept -> bool; + + [[nodiscard]] auto static FindAndUplinkTargetCacheEntry( + std::string const& id) noexcept -> bool; + + [[nodiscard]] auto static TriggerGarbageCollection() noexcept -> bool; + + [[nodiscard]] auto static SharedLock() noexcept -> std::optional<LockFile>; + + [[nodiscard]] auto static ExclusiveLock() noexcept + -> std::optional<LockFile>; + + private: + [[nodiscard]] auto static LockFilePath() noexcept -> std::filesystem::path; + + [[nodiscard]] auto static UplinkBlob(int index, + std::string const& id, + bool is_executable) noexcept -> bool; + + [[nodiscard]] auto static UplinkTree(int index, + std::string const& id) noexcept + -> bool; + + [[nodiscard]] auto static UplinkBazelTree(int index, + std::string const& id) noexcept + -> bool; + + [[nodiscard]] auto static UplinkBazelDirectory( + int index, + std::string const& id) noexcept -> bool; + + [[nodiscard]] auto static UplinkActionCacheEntry( + int index, + std::string const& id) noexcept -> bool; + + [[nodiscard]] auto static UplinkActionCacheEntryBlob( + int index, + std::string const& id) noexcept -> bool; + + [[nodiscard]] auto static UplinkTargetCacheEntry( + int index, + std::string const& id) noexcept -> bool; + + [[nodiscard]] auto static UplinkTargetCacheEntryBlob( + int index, + std::string const& id) noexcept -> bool; +}; + +#endif // INCLUDED_SRC_BUILDTOOL_EXECUTION_API_LOCAL_GARBAGE_COLLECTOR_HPP diff --git a/src/buildtool/execution_api/local/local_ac.hpp b/src/buildtool/execution_api/local/local_ac.hpp index 03945d16..423282be 100644 --- a/src/buildtool/execution_api/local/local_ac.hpp +++ b/src/buildtool/execution_api/local/local_ac.hpp @@ -19,6 +19,7 @@ #include "src/buildtool/common/bazel_types.hpp" #include "src/buildtool/execution_api/common/execution_common.hpp" #include "src/buildtool/execution_api/local/config.hpp" +#include "src/buildtool/execution_api/local/garbage_collector.hpp" #include "src/buildtool/execution_api/local/local_cas.hpp" #include "src/buildtool/file_system/file_storage.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" @@ -47,21 +48,23 @@ class LocalAC { [[nodiscard]] auto CachedResult(bazel_re::Digest const& action_id) const noexcept -> std::optional<bazel_re::ActionResult> { - auto entry_path = - file_store_.GetPath(NativeSupport::Unprefix(action_id.hash())); - bazel_re::Digest digest{}; - auto const entry = - FileSystemManager::ReadFile(entry_path, ObjectType::File); - if (not entry.has_value()) { + auto id = NativeSupport::Unprefix(action_id.hash()); + auto entry_path = file_store_.GetPath(id); + // Try to find action-cache entry in CAS generations and uplink if + // required. + if (not GarbageCollector::FindAndUplinkActionCacheEntry(id)) { logger_.Emit(LogLevel::Debug, "Cache miss, entry not found {}", entry_path.string()); return std::nullopt; } + auto const entry = + FileSystemManager::ReadFile(entry_path, ObjectType::File); + bazel_re::Digest digest{}; if (not digest.ParseFromString(*entry)) { logger_.Emit(LogLevel::Warning, "Parsing cache entry failed for action {}", - NativeSupport::Unprefix(action_id.hash())); + id); return std::nullopt; } auto src_path = cas_->BlobPath(digest); @@ -74,7 +77,7 @@ class LocalAC { } logger_.Emit(LogLevel::Warning, "Parsing action result failed for action {}", - NativeSupport::Unprefix(action_id.hash())); + id); return std::nullopt; } diff --git a/src/buildtool/execution_api/local/local_cas.hpp b/src/buildtool/execution_api/local/local_cas.hpp index 2f181ca8..afda71ed 100644 --- a/src/buildtool/execution_api/local/local_cas.hpp +++ b/src/buildtool/execution_api/local/local_cas.hpp @@ -25,6 +25,9 @@ #include "src/buildtool/file_system/file_storage.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/logging/logger.hpp" +#ifndef BOOTSTRAP_BUILD_TOOL +#include "src/buildtool/execution_api/local/garbage_collector.hpp" +#endif template <ObjectType kType = ObjectType::File> class LocalCAS { @@ -42,6 +45,13 @@ class LocalCAS { return instance; } + auto Reset() noexcept -> void { + file_store_ = FileStorage<kStorageType, + StoreMode::FirstWins, + /*kSetEpochTime=*/true>{ + LocalExecutionConfig::CASDir<kType>(0)}; + } + [[nodiscard]] auto StoreBlobFromBytes(std::string const& bytes) const noexcept -> std::optional<bazel_re::Digest> { return StoreBlob(bytes, /*is_owner=*/true); @@ -57,11 +67,17 @@ class LocalCAS { -> std::optional<std::filesystem::path> { auto id = NativeSupport::Unprefix(digest.hash()); auto blob_path = file_store_.GetPath(id); - if (FileSystemManager::IsFile(blob_path)) { - return blob_path; +#ifndef BOOTSTRAP_BUILD_TOOL + // Try to find blob in CAS generations and uplink if required. + auto found = FindAndUplinkBlob(id); +#else + auto found = FileSystemManager::IsFile(blob_path); +#endif + if (not found) { + logger_.Emit(LogLevel::Debug, "Blob not found {}", id); + return std::nullopt; } - logger_.Emit(LogLevel::Debug, "Blob not found {}", id); - return std::nullopt; + return blob_path; } private: @@ -109,16 +125,35 @@ class LocalCAS { -> std::optional<bazel_re::Digest> { auto digest = CreateDigest(data); if (digest) { - if (StoreBlobData( - NativeSupport::Unprefix(digest->hash()), data, is_owner)) { + auto id = NativeSupport::Unprefix(digest->hash()); +#ifndef BOOTSTRAP_BUILD_TOOL + if (FindAndUplinkBlob(id)) { + return digest; + } +#endif + if (StoreBlobData(id, data, is_owner)) { return digest; } - logger_.Emit( - LogLevel::Debug, "Failed to store blob {}.", digest->hash()); + logger_.Emit(LogLevel::Debug, "Failed to store blob {}.", id); } logger_.Emit(LogLevel::Debug, "Failed to create digest."); return std::nullopt; } + +#ifndef BOOTSTRAP_BUILD_TOOL + [[nodiscard]] auto FindAndUplinkBlob(std::string const& id) const noexcept + -> bool { + if (Compatibility::IsCompatible()) { + return GarbageCollector::FindAndUplinkBlob( + id, kType == ObjectType::Executable); + } + if (kType == ObjectType::Tree) { + return GarbageCollector::FindAndUplinkTree(id); + } + return GarbageCollector::FindAndUplinkBlob( + id, kType == ObjectType::Executable); + } +#endif }; #endif // INCLUDED_SRC_BUILDTOOL_EXECUTION_API_LOCAL_LOCAL_CAS_HPP diff --git a/src/buildtool/file_system/file_storage.hpp b/src/buildtool/file_system/file_storage.hpp index f320375d..c8b30005 100644 --- a/src/buildtool/file_system/file_storage.hpp +++ b/src/buildtool/file_system/file_storage.hpp @@ -21,6 +21,20 @@ #include "src/buildtool/execution_api/common/execution_common.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" +/// \brief Determines the storage path of a blob identified by a hash value. The +/// same sharding technique as used in git is applied, meaning, the hash value +/// is separated into a directory part and file part. Two characters are used +/// for the directory part, the rest for the file, which results in 256 possible +/// directories. +/// \param root The root path of the storage. +/// \param id The hash value of the blob. +/// \returns The sharded file path. +[[nodiscard]] static inline auto GetStoragePath( + std::filesystem::path const& root, + std::string const& id) noexcept -> std::filesystem::path { + return root / id.substr(0, 2) / id.substr(2, id.size() - 2); +} + enum class StoreMode { // First thread to write conflicting file wins. FirstWins, @@ -56,12 +70,12 @@ class FileStorage { [[nodiscard]] auto GetPath(std::string const& id) const noexcept -> std::filesystem::path { - return GetShardedPath(id); + return GetStoragePath(storage_root_, id); } private: static constexpr bool kFdLess{kType == ObjectType::Executable}; - std::filesystem::path const storage_root_{}; + std::filesystem::path storage_root_{}; /// \brief Add file to storage from file path via link or copy and rename. /// If a race-condition occurs, the winning thread will be the one @@ -72,7 +86,7 @@ class FileStorage { [[nodiscard]] auto AtomicAddFromFile(std::string const& id, std::filesystem::path const& path, bool is_owner) const noexcept -> bool { - auto file_path = GetShardedPath(id); + auto file_path = GetPath(id); if ((kMode == StoreMode::LastWins or not FileSystemManager::Exists(file_path)) and FileSystemManager::CreateDirectory(file_path.parent_path())) { @@ -115,7 +129,7 @@ class FileStorage { [[nodiscard]] auto AtomicAddFromBytes( std::string const& id, std::string const& bytes) const noexcept -> bool { - auto file_path = GetShardedPath(id); + auto file_path = GetPath(id); if (kMode == StoreMode::LastWins or not FileSystemManager::Exists(file_path)) { auto unique_path = CreateUniquePath(file_path); @@ -131,18 +145,6 @@ class FileStorage { return FileSystemManager::IsFile(file_path); } - /// \brief Determines the storage path of a blob identified by a hash value. - /// The same sharding technique as git is used, meaning, the hash value is - /// separated into a directory part and file part. Two characters are used - /// for the directory part, the rest for the file, which results in 256 - /// possible directories. - /// \param id The hash value. - /// \returns The sharded file path. - [[nodiscard]] auto GetShardedPath(std::string const& id) const noexcept - -> std::filesystem::path { - return storage_root_ / id.substr(0, 2) / id.substr(2, id.size() - 2); - } - /// \brief Create file from file path. [[nodiscard]] static auto CreateFileFromPath( std::filesystem::path const& file_path, diff --git a/src/buildtool/main/main.cpp b/src/buildtool/main/main.cpp index 5bb707f5..5fab02f6 100644 --- a/src/buildtool/main/main.cpp +++ b/src/buildtool/main/main.cpp @@ -39,6 +39,7 @@ #include "src/buildtool/main/install_cas.hpp" #ifndef BOOTSTRAP_BUILD_TOOL #include "src/buildtool/auth/authentication.hpp" +#include "src/buildtool/execution_api/local/garbage_collector.hpp" #include "src/buildtool/graph_traverser/graph_traverser.hpp" #include "src/buildtool/progress_reporting/base_progress_reporter.hpp" #endif @@ -1176,6 +1177,11 @@ auto main(int argc, char* argv[]) -> int { DetermineRoots(arguments.common, arguments.analysis); #ifndef BOOTSTRAP_BUILD_TOOL + auto lock = GarbageCollector::SharedLock(); + if (not lock) { + return kExitFailure; + } + if (arguments.cmd == SubCommand::kTraverse) { if (arguments.graph.git_cas) { if (Compatibility::IsCompatible()) { |