From 83129498a86ccfc60c8783a04ae102366c8a2f68 Mon Sep 17 00:00:00 2001 From: Riley W Date: Wed, 11 Dec 2024 16:18:57 +0800 Subject: [PATCH 01/14] optimize batch hold/release (#394) Co-authored-by: NamelessOIer <70872016+NamelessOIer@users.noreply.github.com> --- src/CraneCtld/CtldGrpcServer.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/CraneCtld/CtldGrpcServer.cpp b/src/CraneCtld/CtldGrpcServer.cpp index 2449741e3..7bbc072a3 100644 --- a/src/CraneCtld/CtldGrpcServer.cpp +++ b/src/CraneCtld/CtldGrpcServer.cpp @@ -201,8 +201,14 @@ grpc::Status CraneCtldServiceImpl::ModifyTask( } } else if (request->attribute() == ModifyTaskRequest::Hold) { int64_t secs = request->hold_seconds(); + std::vector>> results; + results.reserve(request->task_ids().size()); for (auto task_id : request->task_ids()) { - err = g_task_scheduler->HoldReleaseTaskAsync(task_id, secs).get(); + results.emplace_back( + task_id, g_task_scheduler->HoldReleaseTaskAsync(task_id, secs)); + } + for (auto &[task_id, res] : results) { + err = res.get(); if (err == CraneErr::kOk) { response->add_modified_tasks(task_id); } else if (err == CraneErr::kNonExistent) { From aec0cf92da95592c31ad78c767b6e796e27d9872 Mon Sep 17 00:00:00 2001 From: Riley W Date: Wed, 11 Dec 2024 16:19:17 +0800 Subject: [PATCH 02/14] Chore/result (#393) * chore: Remove result.hpp Signed-off-by: Li Junlin * chore: Optimize include Signed-off-by: Li Junlin --------- Signed-off-by: Li Junlin Co-authored-by: Li Junlin --- dependencies/cmake/CMakeLists.txt | 2 - dependencies/cmake/LibEvent/CMakeLists.txt | 39 - .../cmake/mariadb-connector-c/CMakeLists.txt | 23 - dependencies/pre_installed/CMakeLists.txt | 1 - .../pre_installed/result/CMakeLists.txt | 3 - .../result/include/result/result.hpp | 5513 ----------------- src/CraneCtld/AccountManager.cpp | 28 +- src/CraneCtld/AccountManager.h | 6 +- src/CraneCtld/CMakeLists.txt | 1 - src/CraneCtld/CtldGrpcServer.cpp | 40 +- src/CraneCtld/CtldGrpcServer.h | 8 +- src/CraneCtld/CtldPreCompiledHeader.h | 3 - src/CraneCtld/CtldPublicDefs.h | 2 - src/CraneCtld/EmbeddedDbClient.cpp | 124 +- src/CraneCtld/EmbeddedDbClient.h | 165 +- src/CraneCtld/TaskScheduler.cpp | 4 +- src/Craned/CMakeLists.txt | 1 - src/Craned/CforedClient.cpp | 11 +- src/Craned/CforedClient.h | 20 +- src/Craned/CranedPreCompiledHeader.h | 14 +- src/Craned/CranedPublicDefs.h | 1 - src/Craned/TaskManager.h | 2 - test/CMakeLists.txt | 5 +- test/CraneCtld/CMakeLists.txt | 19 - test/CraneCtld/NodeSelectionAlgoTest.cpp | 208 - test/CraneCtld/PeventsTest.cpp | 47 - test/CraneCtld/XdNodeKeeper_test.cpp | 747 --- test/CraneCtld/server_keep_alive_test.cpp | 280 - test/Craned/CMakeLists.txt | 28 - test/Craned/TaskManager_test.cpp | 255 - test/Misc/CMakeLists.txt | 69 - test/Misc/Misc_test.cpp | 94 - test/Misc/grpc_example_test.cpp | 715 --- test/Misc/libaio_test.cpp | 221 - test/Misc/libevent_example_test.cpp | 258 - test/Misc/libuv_test.cpp | 55 - test/Misc/mariadb_connector_c_test.cpp | 351 -- test/Misc/yaml_cpp_test.cpp | 38 - test/SharedTestImpl/CMakeLists.txt | 6 - test/SharedTestImpl/greeter_service_impl.cpp | 54 - .../include/SharedTestImpl/GlobalDefs.h | 32 - .../SharedTestImpl/greeter_service_impl.h | 47 - test/protos/CMakeLists.txt | 26 - test/protos/greeter.proto | 47 - test/protos/math.proto | 34 - 45 files changed, 206 insertions(+), 9441 deletions(-) delete mode 100644 dependencies/cmake/LibEvent/CMakeLists.txt delete mode 100644 dependencies/cmake/mariadb-connector-c/CMakeLists.txt delete mode 100644 dependencies/pre_installed/result/CMakeLists.txt delete mode 100644 dependencies/pre_installed/result/include/result/result.hpp delete mode 100644 test/CraneCtld/NodeSelectionAlgoTest.cpp delete mode 100644 test/CraneCtld/PeventsTest.cpp delete mode 100644 test/CraneCtld/XdNodeKeeper_test.cpp delete mode 100644 test/CraneCtld/server_keep_alive_test.cpp delete mode 100644 test/Craned/CMakeLists.txt delete mode 100644 test/Craned/TaskManager_test.cpp delete mode 100644 test/Misc/Misc_test.cpp delete mode 100644 test/Misc/grpc_example_test.cpp delete mode 100644 test/Misc/libaio_test.cpp delete mode 100644 test/Misc/libevent_example_test.cpp delete mode 100644 test/Misc/libuv_test.cpp delete mode 100644 test/Misc/mariadb_connector_c_test.cpp delete mode 100644 test/Misc/yaml_cpp_test.cpp delete mode 100644 test/SharedTestImpl/CMakeLists.txt delete mode 100644 test/SharedTestImpl/greeter_service_impl.cpp delete mode 100644 test/SharedTestImpl/include/SharedTestImpl/GlobalDefs.h delete mode 100644 test/SharedTestImpl/include/SharedTestImpl/greeter_service_impl.h delete mode 100644 test/protos/CMakeLists.txt delete mode 100644 test/protos/greeter.proto delete mode 100644 test/protos/math.proto diff --git a/dependencies/cmake/CMakeLists.txt b/dependencies/cmake/CMakeLists.txt index c1ccd32fe..0be910b79 100644 --- a/dependencies/cmake/CMakeLists.txt +++ b/dependencies/cmake/CMakeLists.txt @@ -18,7 +18,6 @@ add_subdirectory(yaml-cpp) add_subdirectory(fmt) add_subdirectory(googletest) add_subdirectory(spdlog) -#add_subdirectory(LibEvent) add_subdirectory(cxxopts) add_subdirectory(grpc) add_subdirectory(libcgroup) @@ -29,7 +28,6 @@ add_subdirectory(ranges-v3) add_subdirectory(backward-cpp) add_subdirectory(fpm) -#add_subdirectory(mariadb-connector-c) include(${CMAKE_SOURCE_DIR}/CMakeModule/SuppressHeaderWarning.cmake) suppress_header_warning() diff --git a/dependencies/cmake/LibEvent/CMakeLists.txt b/dependencies/cmake/LibEvent/CMakeLists.txt deleted file mode 100644 index 6dd90d235..000000000 --- a/dependencies/cmake/LibEvent/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -include(FetchContent) - -if (BUILD_SHARED_LIBS) - set(EVENT__LIBRARY_TYPE "SHARED") -else () - set(EVENT__LIBRARY_TYPE "STATIC") -endif () - -message(STATUS "LibEvent is set to ${EVENT__LIBRARY_TYPE} mode") - -set(EVENT__DISABLE_TESTS ON) -set(EVENT__DISABLE_BENCHMARK ON) -set(EVENT__DISABLE_REGRESS ON) -set(EVENT__DISABLE_SAMPLES ON) - -# Disable as we don't use network functions in libevent -set(EVENT__DISABLE_OPENSSL ON) -set(EVENT__DISABLE_MBEDTLS ON) - -if (CRANE_USE_GITEE_SOURCE) - set(LIBEVENT_SRC_URL "https://gitee.com/zenglingbo/crane-sched-deps/raw/master/libevent-2.1.12-stable.tar.gz") -else () - set(LIBEVENT_SRC_URL "https://github.com/libevent/libevent/releases/download/release-2.1.12-stable/libevent-2.1.12-stable.tar.gz") -endif () - -FetchContent_Declare(libevent_repo - URL ${LIBEVENT_SRC_URL} - URL_HASH SHA256=92e6de1be9ec176428fd2367677e61ceffc2ee1cb119035037a27d346b0403bb - INACTIVITY_TIMEOUT 5 - ) -FetchContent_MakeAvailable(libevent_repo) - -if (BUILD_SHARED_LIBS) - add_library(dev_event_core ALIAS event_core_shared) - add_library(dev_event_pthreads ALIAS event_pthreads_shared) -else () - add_library(dev_event_core ALIAS event_core_static) - add_library(dev_event_pthreads ALIAS event_pthreads_static) -endif () \ No newline at end of file diff --git a/dependencies/cmake/mariadb-connector-c/CMakeLists.txt b/dependencies/cmake/mariadb-connector-c/CMakeLists.txt deleted file mode 100644 index 75ddebe21..000000000 --- a/dependencies/cmake/mariadb-connector-c/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -include(FetchContent) - -if (CRANE_USE_GITEE_SOURCE) - set(MARIADB_CONNECTOR_C_SRC_URL "https://gitee.com/zenglingbo/crane-sched-deps/raw/master/mariadb-connector-c-3.3.2.zip") -else () - set(MARIADB_CONNECTOR_C_SRC_URL "https://github.com/mariadb-corporation/mariadb-connector-c/archive/refs/tags/v3.3.2.zip") -endif () - -FetchContent_Declare(mariadb_connector_c_repo - URL ${MARIADB_CONNECTOR_C_SRC_URL} - URL_HASH SHA256=9d3a76aefec32ff75e62a311095557d1718547dbd9de6963feda1e4874a65ba5 - INACTIVITY_TIMEOUT 5 - ) - -SET(INSTALL_PLUGINDIR ${CMAKE_BINARY_DIR}) -FetchContent_GetProperties(mariadb_connector_c_repo) - -if (NOT mariadb_connector_c_repo_POPULATED) - FetchContent_Populate(mariadb_connector_c_repo) - add_subdirectory(${mariadb_connector_c_repo_SOURCE_DIR} ${mariadb_connector_c_repo_BINARY_DIR} EXCLUDE_FROM_ALL) - set_target_properties(mariadbclient PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${mariadb_connector_c_repo_BINARY_DIR}/include;${mariadb_connector_c_repo_SOURCE_DIR}/include") -endif () \ No newline at end of file diff --git a/dependencies/pre_installed/CMakeLists.txt b/dependencies/pre_installed/CMakeLists.txt index 6f2c74ab0..7b8fcee6d 100644 --- a/dependencies/pre_installed/CMakeLists.txt +++ b/dependencies/pre_installed/CMakeLists.txt @@ -3,7 +3,6 @@ add_compile_options(-w) add_subdirectory(concurrentqueue) add_subdirectory(pevents-1.22.11) -add_subdirectory(result) include(${CMAKE_SOURCE_DIR}/CMakeModule/SuppressHeaderWarning.cmake) suppress_header_warning() diff --git a/dependencies/pre_installed/result/CMakeLists.txt b/dependencies/pre_installed/result/CMakeLists.txt deleted file mode 100644 index f5aa9becd..000000000 --- a/dependencies/pre_installed/result/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -add_library(result INTERFACE) -target_include_directories(result INTERFACE include) -target_compile_definitions(result INTERFACE -DRESULT_NAMESPACE=cpp_result) \ No newline at end of file diff --git a/dependencies/pre_installed/result/include/result/result.hpp b/dependencies/pre_installed/result/include/result/result.hpp deleted file mode 100644 index 41f220400..000000000 --- a/dependencies/pre_installed/result/include/result/result.hpp +++ /dev/null @@ -1,5513 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -/// \file result.hpp -/// -/// \brief This header contains the 'result' monadic type for indicating -/// possible error conditions -//////////////////////////////////////////////////////////////////////////////// - -/* - The MIT License (MIT) - - Copyright (c) 2017-2021 Matthew Rodusek All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ - -#ifndef RESULT_RESULT_HPP -#define RESULT_RESULT_HPP - -#include // std::size_t -#include // std::reference_wrapper, std::invoke -#include // std::initializer_list -#include // std::address_of -#include // placement-new -#include // std::string (for exception message) -#include // std::enable_if, std::is_constructible, etc -#include // std::in_place_t, std::forward - -#if defined(RESULT_EXCEPTIONS_DISABLED) -# include // std::fprintf, stderr -#else -# include // std::logic_error -#endif - -#if __cplusplus >= 201402L -# define RESULT_CPP14_CONSTEXPR constexpr -#else -# define RESULT_CPP14_CONSTEXPR -#endif - -#if __cplusplus >= 201703L -# define RESULT_CPP17_INLINE inline -#else -# define RESULT_CPP17_INLINE -#endif - -#if defined(__clang__) && defined(_MSC_VER) -# define RESULT_INLINE_VISIBILITY __attribute__((visibility("hidden"))) -#elif defined(__clang__) || defined(__GNUC__) -# define RESULT_INLINE_VISIBILITY \ - __attribute__((visibility("hidden"), always_inline)) -#elif defined(_MSC_VER) -# define RESULT_INLINE_VISIBILITY __forceinline -#else -# define RESULT_INLINE_VISIBILITY -#endif - -// [[clang::warn_unused_result]] is more full-featured than gcc's variant, since -// it supports being applied to class objects. -#if __cplusplus >= 201703L -# define RESULT_NODISCARD [[nodiscard]] -# define RESULT_WARN_UNUSED [[nodiscard]] -#elif defined(__clang__) && \ - ((__clang_major__ > 3) || \ - ((__clang_major__ == 3) && (__clang_minor__ >= 9))) -# define RESULT_NODISCARD [[clang::warn_unused_result]] -# define RESULT_WARN_UNUSED [[clang::warn_unused_result]] -#elif defined(__GNUC__) -# define RESULT_NODISCARD -# define RESULT_WARN_UNUSED [[gnu::warn_unused_result]] -#else -# define RESULT_WARN_UNUSED -# define RESULT_NODISCARD -#endif - -#if defined(RESULT_NAMESPACE) -# define RESULT_NAMESPACE_INTERNAL RESULT_NAMESPACE -#else -# define RESULT_NAMESPACE_INTERNAL cpp -#endif -#define RESULT_NS_IMPL RESULT_NAMESPACE_INTERNAL::bitwizeshift - -// clang's `-Wdocumentation-unknown-command` flag is bugged and does not -// understand `\copydoc` tags, despite this being a valid doxygen tag. -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -#endif - -namespace RESULT_NAMESPACE_INTERNAL { -inline namespace bitwizeshift { - -//=========================================================================== -// utilities : constexpr forward -//=========================================================================== - -// std::forward is not constexpr until C++14 -namespace detail { -#if __cplusplus >= 201402L -using std::forward; -#else -template -inline RESULT_INLINE_VISIBILITY constexpr auto forward( - typename std::remove_reference::type& t) noexcept -> T&& { - return static_cast(t); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto forward( - typename std::remove_reference::type&& t) noexcept -> T&& { - return static_cast(t); -} -#endif -} // namespace detail - -//=========================================================================== -// utilities : invoke / invoke_result -//=========================================================================== - -// std::invoke was introduced in C++17 - -namespace detail { -#if __cplusplus >= 201703L -using std::invoke; -using std::invoke_result; -using std::invoke_result_t; -#else -template -struct is_reference_wrapper : std::false_type {}; - -template -struct is_reference_wrapper> : std::true_type {}; - -//------------------------------------------------------------------------- - -template < - typename Base, typename T, typename Derived, typename... Args, - typename = typename std::enable_if< - std::is_function::value && - std::is_base_of::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto -invoke(T Base::*pmf, Derived&& ref, Args&&... args) noexcept( - noexcept((::RESULT_NS_IMPL::detail::forward(ref).* - pmf)(::RESULT_NS_IMPL::detail::forward(args)...))) - -> decltype((::RESULT_NS_IMPL::detail::forward(ref).* - pmf)(::RESULT_NS_IMPL::detail::forward(args)...)) { - return (RESULT_NS_IMPL::detail::forward(ref).* - pmf)(RESULT_NS_IMPL::detail::forward(args)...); -} - -template < - typename Base, typename T, typename RefWrap, typename... Args, - typename = typename std::enable_if< - std::is_function::value && - is_reference_wrapper::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto invoke( - T Base::*pmf, RefWrap&& ref, - Args&&... args) noexcept(noexcept((ref.get().* - pmf)(std::forward(args)...))) - -> decltype((ref.get().* - pmf)(RESULT_NS_IMPL::detail::forward(args)...)) { - return (ref.get().*pmf)(RESULT_NS_IMPL::detail::forward(args)...); -} - -template < - typename Base, typename T, typename Pointer, typename... Args, - typename = typename std::enable_if< - std::is_function::value && - !is_reference_wrapper::type>::value && - !std::is_base_of::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto invoke( - T Base::*pmf, Pointer&& ptr, - Args&&... args) noexcept(noexcept(((*std::forward(ptr)).* - pmf)(std::forward(args)...))) - -> decltype(((*RESULT_NS_IMPL::detail::forward(ptr)).* - pmf)(RESULT_NS_IMPL::detail::forward(args)...)) { - return ((*RESULT_NS_IMPL::detail::forward(ptr)).* - pmf)(RESULT_NS_IMPL::detail::forward(args)...); -} - -template < - typename Base, typename T, typename Derived, - typename = typename std::enable_if< - !std::is_function::value && - std::is_base_of::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto invoke( - T Base::*pmd, - Derived&& ref) noexcept(noexcept(std::forward(ref).*pmd)) - -> decltype(RESULT_NS_IMPL::detail::forward(ref).*pmd) { - return RESULT_NS_IMPL::detail::forward(ref).*pmd; -} - -template < - typename Base, typename T, typename RefWrap, - typename = typename std::enable_if< - !std::is_function::value && - is_reference_wrapper::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto invoke( - T Base::*pmd, RefWrap&& ref) noexcept(noexcept(ref.get().*pmd)) - -> decltype(ref.get().*pmd) { - return ref.get().*pmd; -} - -template < - typename Base, typename T, typename Pointer, - typename = typename std::enable_if< - !std::is_function::value && - !is_reference_wrapper::type>::value && - !std::is_base_of::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto invoke( - T Base::*pmd, - Pointer&& ptr) noexcept(noexcept((*std::forward(ptr)).*pmd)) - -> decltype((*RESULT_NS_IMPL::detail::forward(ptr)).*pmd) { - return (*RESULT_NS_IMPL::detail::forward(ptr)).*pmd; -} - -template ::type>::value>::type> -inline RESULT_INLINE_VISIBILITY constexpr auto -invoke(F&& f, Args&&... args) noexcept( - noexcept(std::forward(f)(std::forward(args)...))) - -> decltype(RESULT_NS_IMPL::detail::forward(f)( - RESULT_NS_IMPL::detail::forward(args)...)) { - return RESULT_NS_IMPL::detail::forward(f)( - RESULT_NS_IMPL::detail::forward(args)...); -} - -template -struct is_invocable { - template - static auto test(Fn2&&, Args2&&...) - -> decltype(invoke(std::declval(), std::declval()...), - std::true_type{}); - - static auto test(...) -> std::false_type; - - using type = decltype(test(std::declval(), std::declval()...)); - static constexpr bool value = type::value; -}; - -template -struct invoke_result_impl { - using type = decltype(RESULT_NS_IMPL::detail::invoke( - std::declval(), std::declval()...)); -}; -template -struct invoke_result_impl {}; - -template -struct invoke_result - : invoke_result_impl::value, Fn, Args...> {}; - -template -using invoke_result_t = typename invoke_result::type; -#endif -} // namespace detail - -//=========================================================================== -// struct : in_place_t -//=========================================================================== - -#if __cplusplus >= 201703L -using std::in_place; -using std::in_place_t; -#else -/// \brief A structure for representing in-place construction -struct in_place_t { - explicit in_place_t() = default; -}; -RESULT_CPP17_INLINE constexpr auto in_place = in_place_t{}; -#endif - -//=========================================================================== -// struct : in_place_t -//=========================================================================== - -/// \brief A structure for representing in-place construction of an error type -struct in_place_error_t { - explicit in_place_error_t() = default; -}; - -RESULT_CPP17_INLINE constexpr auto in_place_error = in_place_error_t{}; - -//=========================================================================== -// forward-declarations -//=========================================================================== - -template -class failure; - -template -class result; - -template -class bad_result_access; - -//=========================================================================== -// traits -//=========================================================================== - -template -struct is_failure : std::false_type {}; -template -struct is_failure> : std::true_type {}; - -template -struct is_result : std::false_type {}; -template -struct is_result> : std::true_type {}; - -//=========================================================================== -// trait : detail::wrapped_result_type -//=========================================================================== - -namespace detail { - -template -using wrapped_result_type = typename std::conditional< - std::is_lvalue_reference::value, - std::reference_wrapper::type>, - typename std::remove_const::type>::type; - -} // namespace detail - -#if !defined(RESULT_DISABLE_EXCEPTIONS) - -//=========================================================================== -// class : bad_result_access -//=========================================================================== - -///////////////////////////////////////////////////////////////////////////// -/// \brief An exception thrown when result::value is accessed without -/// a contained value -///////////////////////////////////////////////////////////////////////////// -template -class bad_result_access : public std::logic_error { - //------------------------------------------------------------------------- - // Constructor / Assignment - //------------------------------------------------------------------------- - public: - /// \brief Constructs this exception using the underlying error type for - /// the error type - /// - /// \param error the underlying error - template ::value>::type> - explicit bad_result_access(E2&& error); - - /// \{ - /// \brief Constructs this exception using the underlying error type for - /// the error and a message - /// - /// \param what_arg the message for the failure - /// \param error the underlying error - template ::value>::type> - bad_result_access(const char* what_arg, E2&& error); - template ::value>::type> - bad_result_access(const std::string& what_arg, E2&& error); - /// \} - - bad_result_access(const bad_result_access& other) = default; - bad_result_access(bad_result_access&& other) = default; - - //------------------------------------------------------------------------- - - auto operator=(const bad_result_access& other) - -> bad_result_access& = default; - auto operator=(bad_result_access&& other) -> bad_result_access& = default; - - /// \{ - /// \brief Gets the underlying error - /// - /// \return the error - auto error() & noexcept -> E&; - auto error() && noexcept -> E&&; - auto error() const& noexcept -> const E&; - auto error() const&& noexcept -> const E&&; - /// \} - - //------------------------------------------------------------------------- - // Private Members - //------------------------------------------------------------------------- - private: - E m_error; -}; - -#endif - -namespace detail { - -template -using failure_is_value_convertible = std::integral_constant< - bool, (std::is_constructible::value && - !std::is_same::type, in_place_t>::value && - !is_failure::type>::value && - !is_result::type>::value)>; - -template -using failure_is_explicit_value_convertible = - std::integral_constant::value && - !std::is_convertible::value)>; - -template -using failure_is_implicit_value_convertible = - std::integral_constant::value && - std::is_convertible::value)>; - -template -using failure_is_value_assignable = std::integral_constant< - bool, (!is_result::type>::value && - !is_failure::type>::value && - std::is_assignable&, E2>::value)>; - -} // namespace detail - -//=========================================================================== -// class : failure_type -//=========================================================================== - -////////////////////////////////////////////////////////////////////////////// -/// \brief A semantic type used for distinguishing failure values in an -/// API that returns result types -/// -/// \tparam E the error type -////////////////////////////////////////////////////////////////////////////// -template -class failure { - static_assert(!is_result::type>::value, - "A (possibly CV-qualified) result 'E' type is ill-formed."); - static_assert(!is_failure::type>::value, - "A (possibly CV-qualified) failure 'E' type is ill-formed."); - static_assert(!std::is_void::type>::value, - "A (possibly CV-qualified) 'void' 'E' type is ill-formed."); - static_assert(!std::is_rvalue_reference::value, - "rvalue references for 'E' type is ill-formed. " - "Only lvalue references are valid."); - - //------------------------------------------------------------------------- - // Public Member Types - //------------------------------------------------------------------------- - public: - using error_type = E; - - //------------------------------------------------------------------------- - // Constructors / Assignment - //------------------------------------------------------------------------- - public: - /// \brief Constructs a failure via default construction - failure() = default; - - /// \brief Constructs a failure by delegating construction to the - /// underlying constructor - /// - /// \param args the arguments to forward to E's constructor - template ::value>::type> - constexpr failure(in_place_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs a failure by delegating construction to the - /// underlying constructor - /// - /// \param ilist the initializer list - /// \param args the arguments to forward to E's constructor - template , Args...>::value>::type> - constexpr failure( - in_place_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - E, std::initializer_list, - Args...>::value); - - /// \{ - /// \brief Constructs a failure from the given error - /// - /// \param error the error to create a failure from - template ::value, - int>::type = 0> - constexpr failure(E2&& error) noexcept( - std::is_nothrow_constructible::value); - template ::value, - int>::type = 0> - constexpr explicit failure(E2&& error) noexcept( - std::is_nothrow_constructible::value); - /// \} - - /// \brief Constructs this failure by copying the contents of an existing - /// one - /// - /// \param other the other failure to copy - /* implicit */ failure(const failure& other) = default; - - /// \brief Constructs this failure by moving the contents of an existing - /// one - /// - /// \param other the other failure to move - /* implicit */ failure(failure&& other) = default; - - /// \brief Constructs this failure by copy-converting \p other - /// - /// \param other the other failure to copy - template ::value>::type> - constexpr /* implicit */ failure(const failure& other) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs this failure by move-converting \p other - /// - /// \param other the other failure to copy - template ::value>::type> - constexpr /* implicit */ failure(failure&& other) noexcept( - std::is_nothrow_constructible::value); - - //-------------------------------------------------------------------------- - - /// \brief Assigns the value of \p error to this failure through - /// move-assignment - /// - /// \param error the value to assign - /// \return reference to `(*this)` - template ::value>::type> - RESULT_CPP14_CONSTEXPR auto operator=(E2&& error) noexcept( - std::is_nothrow_assignable::value || - std::is_lvalue_reference::value) -> failure&; - - /// \brief Assigns the contents of \p other to this by copy-assignment - /// - /// \param other the other failure to copy - /// \return reference to `(*this)` - auto operator=(const failure& other) -> failure& = default; - - /// \brief Assigns the contents of \p other to this by move-assignment - /// - /// \param other the other failure to move - /// \return reference to `(*this)` - auto operator=(failure&& other) -> failure& = default; - - /// \brief Assigns the contents of \p other to this by copy conversion - /// - /// \param other the other failure to copy-convert - /// \return reference to `(*this)` - template ::value>::type> - RESULT_CPP14_CONSTEXPR auto operator=(const failure& other) noexcept( - std::is_nothrow_assignable::value) -> failure&; - - /// \brief Assigns the contents of \p other to this by move conversion - /// - /// \param other the other failure to move-convert - /// \return reference to `(*this)` - template ::value>::type> - RESULT_CPP14_CONSTEXPR auto operator=(failure&& other) noexcept( - std::is_nothrow_assignable::value) -> failure&; - - //-------------------------------------------------------------------------- - // Observers - //-------------------------------------------------------------------------- - public: - /// \{ - /// \brief Gets the underlying error - /// - /// \return the underlying error - RESULT_CPP14_CONSTEXPR - auto error() & noexcept -> typename std::add_lvalue_reference::type; - RESULT_CPP14_CONSTEXPR - auto error() && noexcept -> typename std::add_rvalue_reference::type; - constexpr auto error() const& noexcept -> typename std::add_lvalue_reference< - typename std::add_const::type>::type; - constexpr auto error() const&& noexcept -> typename std::add_rvalue_reference< - typename std::add_const::type>::type; - /// \} - - //------------------------------------------------------------------------- - // Private Member Types - //------------------------------------------------------------------------- - private: - using underlying_type = detail::wrapped_result_type; - - //------------------------------------------------------------------------- - // Private Members - //------------------------------------------------------------------------- - private: - underlying_type m_failure; -}; - -#if __cplusplus >= 201703L -template -failure(std::reference_wrapper) -> failure; - -template -failure(T&&) -> failure::type>; -#endif - -//=========================================================================== -// non-member functions : class : failure -//=========================================================================== - -//--------------------------------------------------------------------------- -// Comparison -//--------------------------------------------------------------------------- - -template -constexpr auto operator==(const failure& lhs, - const failure& rhs) noexcept -> bool; -template -constexpr auto operator!=(const failure& lhs, - const failure& rhs) noexcept -> bool; -template -constexpr auto operator<(const failure& lhs, - const failure& rhs) noexcept -> bool; -template -constexpr auto operator>(const failure& lhs, - const failure& rhs) noexcept -> bool; -template -constexpr auto operator<=(const failure& lhs, - const failure& rhs) noexcept -> bool; -template -constexpr auto operator>=(const failure& lhs, - const failure& rhs) noexcept -> bool; - -//--------------------------------------------------------------------------- -// Utilities -//--------------------------------------------------------------------------- - -/// \brief Deduces and constructs a failure type from \p e -/// -/// \param e the failure value -/// \return a constructed failure value -template -RESULT_WARN_UNUSED constexpr auto fail(E&& e) noexcept( - std::is_nothrow_constructible::type, E>::value) - -> failure::type>; - -/// \brief Deduces a failure reference from a reverence_wrapper -/// -/// \param e the failure value -/// \return a constructed failure reference -template -RESULT_WARN_UNUSED constexpr auto fail(std::reference_wrapper e) noexcept - -> failure; - -/// \brief Constructs a failure type from a series of arguments -/// -/// \tparam E the failure type -/// \param args the arguments to forward to E's constructor -/// \return a constructed failure type -template ::value>::type> -RESULT_WARN_UNUSED constexpr auto fail(Args&&... args) noexcept( - std::is_nothrow_constructible::value) -> failure; - -/// \brief Constructs a failure type from an initializer list and series of -/// arguments -/// -/// \tparam E the failure type -/// \param args the arguments to forward to E's constructor -/// \return a constructed failure type -template , Args...>::value>::type> -RESULT_WARN_UNUSED constexpr auto -fail(std::initializer_list ilist, Args&&... args) noexcept( - std::is_nothrow_constructible, Args...>::value) - -> failure; - -/// \brief Swaps the contents of two failure values -/// -/// \param lhs the left failure -/// \param rhs the right failure -template -auto swap(failure& lhs, failure& rhs) -#if __cplusplus >= 201703L - noexcept(std::is_nothrow_swappable::value) -> void; -#else - noexcept(std::is_nothrow_move_constructible::value) -> void; -#endif - -namespace detail { - -//========================================================================= -// class : unit -//========================================================================= - -/// \brief A standalone monostate object (effectively std::monostate). This -/// exists to allow for `void` specializations -struct unit {}; - -//========================================================================= -// non-member functions : class : unit -//========================================================================= - -constexpr auto operator==(unit, unit) noexcept -> bool { return true; } -constexpr auto operator!=(unit, unit) noexcept -> bool { return false; } -constexpr auto operator<(unit, unit) noexcept -> bool { return false; } -constexpr auto operator>(unit, unit) noexcept -> bool { return false; } -constexpr auto operator<=(unit, unit) noexcept -> bool { return true; } -constexpr auto operator>=(unit, unit) noexcept -> bool { return true; } - -//========================================================================= -// class : detail::result_union -//========================================================================= - -/////////////////////////////////////////////////////////////////////////// -/// \brief A basic utility that acts as a union containing the T and E -/// types -/// -/// This is specialized on the case that both T and E are trivial, in which -/// case `result_union` is also trivial -/// -/// \tparam T the value type result to be returned -/// \tparam E the error type returned on failure -/// \tparam IsTrivial Whether or not both T and E are trivial -/////////////////////////////////////////////////////////////////////////// -template ::value&& - std::is_trivially_destructible::value> -struct result_union { - //----------------------------------------------------------------------- - // Public Member Types - //----------------------------------------------------------------------- - - using underlying_value_type = wrapped_result_type; - using underlying_error_type = E; - - //----------------------------------------------------------------------- - // Constructors / Assignment - //----------------------------------------------------------------------- - - /// \brief Constructs an empty object - /// - /// This is for use with conversion constructors, since it allows a - /// temporary unused object to be set - result_union(unit) noexcept; - - /// \brief Constructs the underlying value from the specified \p args - /// - /// \param args the arguments to forward to T's constructor - template - constexpr result_union(in_place_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs the underlying error from the specified \p args - /// - /// \param args the arguments to forward to E's constructor - template - constexpr result_union(in_place_error_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - result_union(const result_union&) = default; - result_union(result_union&&) = default; - - //----------------------------------------------------------------------- - - auto operator=(const result_union&) -> result_union& = default; - auto operator=(result_union&&) -> result_union& = default; - - //----------------------------------------------------------------------- - // Modifiers - //----------------------------------------------------------------------- - - /// \brief A no-op for trivial types - auto destroy() const noexcept -> void; - - //----------------------------------------------------------------------- - // Public Members - //----------------------------------------------------------------------- - - union { - underlying_value_type m_value; - underlying_error_type m_error; - unit m_empty; - }; - bool m_has_value; -}; - -//------------------------------------------------------------------------- - -template -struct result_union { - //----------------------------------------------------------------------- - // Public Member Types - //----------------------------------------------------------------------- - - using underlying_value_type = wrapped_result_type; - using underlying_error_type = E; - - //----------------------------------------------------------------------- - // Constructors / Assignment / Destructor - //----------------------------------------------------------------------- - - /// \brief Constructs an empty object - /// - /// This is for use with conversion constructors, since it allows a - /// temporary unused object to be set - result_union(unit) noexcept; - - /// \brief Constructs the underlying value from the specified \p args - /// - /// \param args the arguments to forward to T's constructor - template - constexpr result_union(in_place_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs the underlying error from the specified \p args - /// - /// \param args the arguments to forward to E's constructor - template - constexpr result_union(in_place_error_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - result_union(const result_union&) = default; - result_union(result_union&&) = default; - - //----------------------------------------------------------------------- - - /// \brief Destroys the underlying stored object - ~result_union() noexcept(std::is_nothrow_destructible::value&& - std::is_nothrow_destructible::value); - - //----------------------------------------------------------------------- - - auto operator=(const result_union&) -> result_union& = default; - auto operator=(result_union&&) -> result_union& = default; - - //----------------------------------------------------------------------- - // Modifiers - //----------------------------------------------------------------------- - - /// \brief Destroys the underlying stored object - auto destroy() -> void; - - //----------------------------------------------------------------------- - // Public Members - //----------------------------------------------------------------------- - - union { - underlying_value_type m_value; - underlying_error_type m_error; - unit m_empty; - }; - bool m_has_value; -}; - -//========================================================================= -// class : result_construct_base -//========================================================================= - -/////////////////////////////////////////////////////////////////////////// -/// \brief Base class of assignment to enable construction and assignment -/// -/// This class is used with several pieces of construction to ensure -/// trivial constructibility and assignability: -/// -/// * `result_trivial_copy_ctor_base` -/// * `result_trivial_move_ctor_base` -/// * `result_copy_assign_base` -/// * `result_move_assign_base` -/// -/// \tparam T the value type -/// \tparam E the error type -/////////////////////////////////////////////////////////////////////////// -template -struct result_construct_base { - //----------------------------------------------------------------------- - // Constructors / Assignment - //----------------------------------------------------------------------- - - /// \brief Constructs an empty object - /// - /// This is for use with conversion constructors, since it allows a - /// temporary unused object to be set - result_construct_base(unit) noexcept; - - /// \brief Constructs the underlying value from the specified \p args - /// - /// \param args the arguments to forward to T's constructor - template - constexpr result_construct_base(in_place_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs the underlying error from the specified \p args - /// - /// \param args the arguments to forward to E's constructor - template - constexpr result_construct_base(in_place_error_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - result_construct_base(const result_construct_base&) = default; - result_construct_base(result_construct_base&&) = default; - - auto operator=(const result_construct_base&) - -> result_construct_base& = default; - auto operator=(result_construct_base&&) -> result_construct_base& = default; - - //----------------------------------------------------------------------- - // Construction / Assignment - //----------------------------------------------------------------------- - - /// \brief Constructs the value type from \p args - /// - /// \note This is an implementation detail only meant to be used during - /// construction - /// - /// \pre there is no contained value or error at the time of construction - /// - /// \param args the arguments to forward to T's constructor - template - auto construct_value(Args&&... args) noexcept( - std::is_nothrow_constructible::value) -> void; - - /// \brief Constructs the error type from \p args - /// - /// \note This is an implementation detail only meant to be used during - /// construction - /// - /// \pre there is no contained value or error at the time of construction - /// - /// \param args the arguments to forward to E's constructor - template - auto construct_error(Args&&... args) noexcept( - std::is_nothrow_constructible::value) -> void; - - /// \brief Constructs the underlying error from the \p other result - /// - /// If \p other contains a value, then the T type will be - /// default-constructed. - /// - /// \note This is an implementation detail only meant to be used during - /// construction of `result` types - /// - /// \pre there is no contained value or error at the time of construction - /// - /// \param other the other result to construct - template - auto construct_error_from_result(Result&& other) -> void; - - /// \brief Constructs the underlying type from a result object - /// - /// \note This is an implementation detail only meant to be used during - /// construction - /// - /// \pre there is no contained value or error at the time of construction - /// - /// \param other the other result to construct - template - auto construct_from_result(Result&& other) -> void; - - //----------------------------------------------------------------------- - - template - auto assign_value(Value&& value) noexcept( - std::is_nothrow_assignable::value) -> void; - - template - auto assign_error(Error&& error) noexcept( - std::is_nothrow_assignable::value) -> void; - - template - auto assign_from_result(Result&& other) -> void; - - //----------------------------------------------------------------------- - - template - auto construct_value_from_result_impl(std::true_type, - ReferenceWrapper&& reference) noexcept - -> void; - - template - auto construct_value_from_result_impl( - std::false_type, - Value&& value) noexcept(std::is_nothrow_constructible::value) - -> void; - - template - auto assign_value_from_result_impl(std::true_type, Result&& other) -> void; - - template - auto assign_value_from_result_impl(std::false_type, Result&& other) -> void; - - //----------------------------------------------------------------------- - // Public Members - //----------------------------------------------------------------------- - - using storage_type = result_union; - - storage_type storage; -}; - -//========================================================================= -// class : result_trivial_copy_ctor_base -//========================================================================= - -template -struct result_trivial_copy_ctor_base_impl : result_construct_base { - using base_type = result_construct_base; - using base_type::base_type; - - result_trivial_copy_ctor_base_impl( - const result_trivial_copy_ctor_base_impl& - other) noexcept(std::is_nothrow_copy_constructible::value&& - std::is_nothrow_copy_constructible::value); - result_trivial_copy_ctor_base_impl( - result_trivial_copy_ctor_base_impl&& other) = default; - - auto operator=(const result_trivial_copy_ctor_base_impl& other) - -> result_trivial_copy_ctor_base_impl& = default; - auto operator=(result_trivial_copy_ctor_base_impl&& other) - -> result_trivial_copy_ctor_base_impl& = default; -}; - -template -using conditionally_nest_type = - typename std::conditional::type; - -template -using result_trivial_copy_ctor_base = - conditionally_nest_type::value && - std::is_trivially_copy_constructible::value, - result_trivial_copy_ctor_base_impl>; - -//========================================================================= -// class : result_trivial_move_ctor_base -//========================================================================= - -template -struct result_trivial_move_ctor_base_impl - : result_trivial_copy_ctor_base { - using base_type = result_trivial_copy_ctor_base; - using base_type::base_type; - - result_trivial_move_ctor_base_impl( - const result_trivial_move_ctor_base_impl& other) = default; - result_trivial_move_ctor_base_impl( - result_trivial_move_ctor_base_impl&& - other) noexcept(std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_constructible::value); - - auto operator=(const result_trivial_move_ctor_base_impl& other) - -> result_trivial_move_ctor_base_impl& = default; - auto operator=(result_trivial_move_ctor_base_impl&& other) - -> result_trivial_move_ctor_base_impl& = default; -}; - -template -using result_trivial_move_ctor_base = - conditionally_nest_type::value && - std::is_trivially_move_constructible::value, - result_trivial_move_ctor_base_impl>; - -//========================================================================= -// class : result_trivial_copy_assign_base -//========================================================================= - -template -struct result_trivial_copy_assign_base_impl - : result_trivial_move_ctor_base { - using base_type = result_trivial_move_ctor_base; - using base_type::base_type; - - result_trivial_copy_assign_base_impl( - const result_trivial_copy_assign_base_impl& other) = default; - result_trivial_copy_assign_base_impl( - result_trivial_copy_assign_base_impl&& other) = default; - - auto operator=(const result_trivial_copy_assign_base_impl& other) noexcept( - std::is_nothrow_copy_constructible::value&& - std::is_nothrow_copy_constructible::value&& - std::is_nothrow_copy_assignable::value&& - std::is_nothrow_copy_assignable::value) - -> result_trivial_copy_assign_base_impl&; - auto operator=(result_trivial_copy_assign_base_impl&& other) - -> result_trivial_copy_assign_base_impl& = default; -}; - -template -using result_trivial_copy_assign_base = conditionally_nest_type< - std::is_trivially_copy_constructible::value && - std::is_trivially_copy_constructible::value && - std::is_trivially_copy_assignable::value && - std::is_trivially_copy_assignable::value && - std::is_trivially_destructible::value && - std::is_trivially_destructible::value, - result_trivial_copy_assign_base_impl>; - -//========================================================================= -// class : result_trivial_move_assign_base -//========================================================================= - -template -struct result_trivial_move_assign_base_impl - : result_trivial_copy_assign_base { - using base_type = result_trivial_copy_assign_base; - using base_type::base_type; - - result_trivial_move_assign_base_impl( - const result_trivial_move_assign_base_impl& other) = default; - result_trivial_move_assign_base_impl( - result_trivial_move_assign_base_impl&& other) = default; - - auto operator=(const result_trivial_move_assign_base_impl& other) - -> result_trivial_move_assign_base_impl& = default; - auto operator=(result_trivial_move_assign_base_impl&& other) noexcept( - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value&& - std::is_nothrow_move_assignable::value) - -> result_trivial_move_assign_base_impl&; -}; - -template -using result_trivial_move_assign_base = conditionally_nest_type< - std::is_trivially_move_constructible::value && - std::is_trivially_move_constructible::value && - std::is_trivially_move_assignable::value && - std::is_trivially_move_assignable::value && - std::is_trivially_destructible::value && - std::is_trivially_destructible::value, - result_trivial_move_assign_base_impl>; - -//========================================================================= -// class : disable_copy_ctor -//========================================================================= - -template -struct disable_copy_ctor : result_trivial_move_assign_base { - using base_type = result_trivial_move_assign_base; - using base_type::base_type; - - disable_copy_ctor(const disable_copy_ctor& other) = delete; - disable_copy_ctor(disable_copy_ctor&& other) = default; - - auto operator=(const disable_copy_ctor& other) - -> disable_copy_ctor& = default; - auto operator=(disable_copy_ctor&& other) -> disable_copy_ctor& = default; -}; - -template -using result_copy_ctor_base = - conditionally_nest_type::value && - std::is_copy_constructible::value, - disable_copy_ctor>; - -//========================================================================= -// class : disable_move_ctor -//========================================================================= - -template -struct disable_move_ctor : result_copy_ctor_base { - using base_type = result_copy_ctor_base; - using base_type::base_type; - - disable_move_ctor(const disable_move_ctor& other) = default; - disable_move_ctor(disable_move_ctor&& other) = delete; - - auto operator=(const disable_move_ctor& other) - -> disable_move_ctor& = default; - auto operator=(disable_move_ctor&& other) -> disable_move_ctor& = default; -}; - -template -using result_move_ctor_base = - conditionally_nest_type::value && - std::is_move_constructible::value, - disable_move_ctor>; - -//========================================================================= -// class : disable_move_assignment -//========================================================================= - -template -struct disable_move_assignment : result_move_ctor_base { - using base_type = result_move_ctor_base; - using base_type::base_type; - - disable_move_assignment(const disable_move_assignment& other) = default; - disable_move_assignment(disable_move_assignment&& other) = default; - - auto operator=(const disable_move_assignment& other) - -> disable_move_assignment& = delete; - auto operator=(disable_move_assignment&& other) - -> disable_move_assignment& = default; -}; - -template -using result_copy_assign_base = conditionally_nest_type< - std::is_nothrow_copy_constructible::value && - std::is_nothrow_copy_constructible::value && - std::is_copy_assignable>::value && - std::is_copy_assignable::value, - disable_move_assignment>; - -//========================================================================= -// class : disable_copy_assignment -//========================================================================= - -template -struct disable_copy_assignment : result_copy_assign_base { - using base_type = result_copy_assign_base; - using base_type::base_type; - - disable_copy_assignment(const disable_copy_assignment& other) = default; - disable_copy_assignment(disable_copy_assignment&& other) = default; - - auto operator=(const disable_copy_assignment& other) - -> disable_copy_assignment& = default; - auto operator=(disable_copy_assignment&& other) - -> disable_copy_assignment& = delete; -}; - -template -using result_move_assign_base = conditionally_nest_type< - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_constructible::value && - std::is_move_assignable>::value && - std::is_move_assignable::value, - disable_copy_assignment>; - -//========================================================================= -// alias : result_storage -//========================================================================= - -template -using result_storage = result_move_assign_base; - -//========================================================================= -// traits : result -//========================================================================= - -template -using result_is_convertible = std::integral_constant< - bool, ( - // T1 constructible from result - std::is_constructible&>::value || - std::is_constructible&>::value || - std::is_constructible&&>::value || - std::is_constructible&&>::value || - - // E1 constructible from result - std::is_constructible&>::value || - std::is_constructible&>::value || - std::is_constructible&&>::value || - std::is_constructible&&>::value || - - // result convertible to T1 - std::is_convertible&, T1>::value || - std::is_convertible&, T1>::value || - std::is_convertible&&, T1>::value || - std::is_convertible&&, T1>::value || - - // result convertible to E2 - std::is_convertible&, E1>::value || - std::is_convertible&, E1>::value || - std::is_convertible&&, E1>::value || - std::is_convertible&&, E1>::value)>; - -//------------------------------------------------------------------------- - -template -using result_is_copy_convertible = - std::integral_constant::value && - std::is_constructible::value && - std::is_constructible::value)>; - -template -using result_is_implicit_copy_convertible = - std::integral_constant::value && - std::is_convertible::value && - std::is_convertible::value)>; - -template -using result_is_explicit_copy_convertible = - std::integral_constant::value && - (!std::is_convertible::value || - !std::is_convertible::value))>; - -//------------------------------------------------------------------------- - -template -using result_is_move_convertible = - std::integral_constant::value && - std::is_constructible::value && - std::is_constructible::value)>; - -template -using result_is_implicit_move_convertible = - std::integral_constant::value && - std::is_convertible::value && - std::is_convertible::value)>; - -template -using result_is_explicit_move_convertible = - std::integral_constant::value && - (!std::is_convertible::value || - !std::is_convertible::value))>; - -//------------------------------------------------------------------------- - -template -using result_is_value_convertible = std::integral_constant< - bool, - (std::is_constructible::value && - !std::is_same::type, in_place_t>::value && - !std::is_same::type, in_place_error_t>::value && - !is_result::type>::value)>; - -template -using result_is_explicit_value_convertible = - std::integral_constant::value && - !std::is_convertible::value)>; - -template -using result_is_implicit_value_convertible = - std::integral_constant::value && - std::is_convertible::value)>; - -//------------------------------------------------------------------------- - -template -using result_is_convert_assignable = std::integral_constant< - bool, (result_is_convertible::value && - - std::is_assignable&>::value && - std::is_assignable&>::value && - std::is_assignable&&>::value && - std::is_assignable&&>::value && - - std::is_assignable&>::value && - std::is_assignable&>::value && - std::is_assignable&&>::value && - std::is_assignable&&>::value)>; - -template -using result_is_copy_convert_assignable = std::integral_constant< - bool, (!result_is_convert_assignable::value && - - std::is_nothrow_constructible::value && - std::is_assignable&, const T2&>::value && - std::is_nothrow_constructible::value && - std::is_assignable::value)>; - -template -using result_is_move_convert_assignable = std::integral_constant< - bool, (!result_is_convert_assignable::value && - - std::is_nothrow_constructible::value && - std::is_assignable::value && - std::is_nothrow_constructible::value && - std::is_assignable::value)>; - -//------------------------------------------------------------------------- - -template -using result_is_value_assignable = std::integral_constant< - bool, (!is_result::type>::value && - !is_failure::type>::value && - std::is_nothrow_constructible::value && - std::is_assignable&, U>::value && - (!std::is_same::type, - typename std::decay::type>::value || - !std::is_scalar::value))>; - -template -using result_is_failure_assignable = - std::integral_constant::value && - std::is_assignable::value)>; - -// Friending 'extract_error" below was causing some compilers to incorrectly -// identify `exp.m_storage.m_error` as being an access violation despite the -// friendship. Using a type name instead seems to be ubiquitous across -// compilers -struct result_error_extractor { - template - static constexpr auto get(const result& exp) noexcept -> const E&; - template - static constexpr auto get(result& exp) noexcept -> E&; -}; - -template -constexpr auto extract_error(const result& exp) noexcept -> const E&; - -template -[[noreturn]] auto throw_bad_result_access(E&& error) -> void; - -template -[[noreturn]] auto throw_bad_result_access_message(String&& message, E&& error) - -> void; - -} // namespace detail - -///////////////////////////////////////////////////////////////////////////// -/// \brief The class template `result` manages result results from APIs, -/// while encoding possible failure conditions. -/// -/// A common use-case for result is the return value of a function that -/// may fail. As opposed to other approaches, such as `std::pair` -/// or `std::optional`, `result` more accurately conveys the intent of the -/// user along with the failure condition to the caller. This effectively -/// produces an orthogonal error handling mechanism that allows for exception -/// safety while also allowing discrete testability of the return type. -/// -/// `result` types may contain a `T` value, which signifies that an -/// operation succeeded in producing the result value of type `T`. If an -/// `result` does not contain a `T` value, it will always contain an `E` -/// error condition instead. -/// -/// An `result` can always be queried for a possible error case by -/// calling the `error()` function, even if it contains a value. -/// In the case that a `result` contains a value object, this will -/// simply return an `E` object constructed through default aggregate -/// construction, as if through the expression `E{}`, which is assumed to be -/// a "valid" (no-error) state for an `E` type. -/// For example: -/// -/// * `std::error_code{}` produces a default-construct error-code, which is -/// the "no error" state, -/// * integral (or enum) error codes produce a `0` value (no error), thanks to -/// zero-initialization, -/// * `std::exception_ptr{}` produces a null-pointer, -/// * `std::string{}` produces an empty string `""`, -/// * etc. -/// -/// When a `result` contains either a value or error, the storage for -/// that object is guaranteed to be allocated as part of the result -/// object's footprint, i.e. no dynamic memory allocation ever takes place. -/// Thus, a result object models an object, not a pointer, even though the -/// `operator*()` and `operator->()` are defined. -/// -/// When an object of type `result` is contextually converted to -/// `bool`, the conversion returns `true` if the object contains a value and -/// `false` if it contains an error. -/// -/// `result` objects do not have a "valueless" state like `variant`s do. -/// Once a `result` has been constructed with a value or error, the -/// active underlying type can only be changed through assignment which may -/// is only enabled if construction is guaranteed to be *non-throwing*. This -/// ensures that a valueless state cannot occur naturally. -/// -/// Example Use: -/// \code -/// auto to_string(int x) -> result -/// { -/// try { -/// return std::stoi(x); -/// } catch (const std::invalid_argument&) { -/// return fail(std::errc::invalid_argument); -/// } catch (const std::std::out_of_range&) { -/// return fail(std::errc::result_out_of_range); -/// } -/// } -/// \endcode -/// -/// \note If using C++17 or above, `fail` can be replaced with -/// `failure{...}` thanks to CTAD. -/// -/// \tparam T the underlying value type -/// \tparam E the underlying error type -/////////////////////////////////////////////////////////////////////////// -template -class RESULT_NODISCARD result { - // Type requirements - - static_assert(!std::is_abstract::value, - "It is ill-formed for T to be abstract type"); - static_assert( - !std::is_same::type, in_place_t>::value, - "It is ill-formed for T to be a (possibly CV-qualified) in_place_t type"); - static_assert( - !is_result::type>::value, - "It is ill-formed for T to be a (possibly CV-qualified) 'result' type"); - static_assert( - !is_failure::type>::value, - "It is ill-formed for T to be a (possibly CV-qualified) 'failure' type"); - static_assert(!std::is_rvalue_reference::value, - "It is ill-formed for T to be an rvalue 'result type. " - "Only lvalue references are valid."); - - static_assert(!std::is_abstract::value, - "It is ill-formed for E to be abstract type"); - static_assert( - !std::is_void::type>::value, - "It is ill-formed for E to be (possibly CV-qualified) void type"); - static_assert( - !is_result::type>::value, - "It is ill-formed for E to be a (possibly CV-qualified) 'result' type"); - static_assert( - !is_failure::type>::value, - "It is ill-formed for E to be a (possibly CV-qualified) 'failure' type"); - static_assert( - !std::is_same::type, in_place_t>::value, - "It is ill-formed for E to be a (possibly CV-qualified) in_place_t type"); - static_assert(!std::is_reference::value, - "It is ill-formed for E to be a reference type. " - "Only T types may be lvalue references"); - - // Friendship - - friend detail::result_error_extractor; - - template - friend class result; - - //------------------------------------------------------------------------- - // Public Member Types - //------------------------------------------------------------------------- - public: - using value_type = T; ///< The value type of this result - using error_type = E; ///< The error type of this result - using failure_type = failure; ///< The failure type - - template - using rebind = result; ///< Rebinds the result type - - //------------------------------------------------------------------------- - // Constructor / Destructor / Assignment - //------------------------------------------------------------------------- - public: - /// \brief Default-constructs a result with the underlying value type - /// active - /// - /// This constructor is only enabled if `T` is default-constructible - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// assert(cpp::result{} == std::string{}); - /// ``` - template ::value>::type> - constexpr result() noexcept(std::is_nothrow_constructible::value); - - /// \brief Copy constructs this result - /// - /// If \p other contains a value, initializes the contained value as if - /// direct-initializing (but not direct-list-initializing) an object of - /// type `T` with the expression *other. - /// - /// If other contains an error, constructs an object that contains a copy - /// of that error. - /// - /// \note This constructor is defined as deleted if - /// `std::is_copy_constructible::value` or - /// `std::is_copy_constructible::value` is `false` - /// - /// \note This constructor is defined as trivial if both - /// `std::is_trivially_copy_constructible::value` and - /// `std::is_trivially_copy_constructible::value` are `true` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// const auto r = cpp::result{42}; - /// const auto s = r; - /// - /// assert(r == s); - /// ``` - /// - /// \param other the result to copy - constexpr result(const result& other) = default; - - /// \brief Move constructs a result - /// - /// If other contains a value, initializes the contained value as if - /// direct-initializing (but not direct-list-initializing) an object - /// of type T with the expression `std::move(*other)` and does not make - /// other empty: a moved-from result still contains a value, but the - /// value itself is moved from. - /// - /// If other contains an error, move-constructs this result from that - /// error. - /// - /// \note This constructor is defined as deleted if - /// `std::is_move_constructible::value` or - /// `std::is_move_constructible::value` is `false` - /// - /// \note This constructor is defined as trivial if both - /// `std::is_trivially_move_constructible::value` and - /// `std::is_trivially_move_constructible::value` are `true` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{"hello world"}; - /// auto s = std::move(r); - /// - /// assert(s == "hello world"); - /// ``` - /// - /// \param other the result to move - constexpr result(result&& other) = default; - - /// \{ - /// \brief Converting copy constructor - /// - /// If \p other contains a value, constructs a result object - /// that contains a value, initialized as if direct-initializing - /// (but not direct-list-initializing) an object of type `T` with the - /// expression `*other`. - /// - /// If \p other contains an error, constructs a result object that - /// contains an error, initialized as if direct-initializing - /// (but not direct-list-initializing) an object of type `E`. - /// - /// \note This constructor does not participate in overload resolution - /// unless the following conditions are met: - /// - `std::is_constructible_v` is `true` - /// - T is not constructible or convertible from any expression - /// of type (possibly const) `result` - /// - E is not constructible or convertible from any expression - /// of type (possible const) `result` - /// - /// \note This constructor is explicit if and only if - /// `std::is_convertible_v` or - /// `std::is_convertible_v` is `false` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// const auto r = cpp::result{42}; - /// const auto s = cpp::result{r}; - /// - /// assert(r == s); - /// ``` - /// - /// \param other the other type to convert - template ::value, - int>::type = 0> - result(const result& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value); - template ::value, - int>::type = 0> - explicit result(const result& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value); - /// \} - - /// \{ - /// \brief Converting move constructor - /// - /// If \p other contains a value, constructs a result object - /// that contains a value, initialized as if direct-initializing - /// (but not direct-list-initializing) an object of type T with the - /// expression `std::move(*other)`. - /// - /// If \p other contains an error, constructs a result object that - /// contains an error, initialized as if direct-initializing - /// (but not direct-list-initializing) an object of type E&&. - /// - /// \note This constructor does not participate in overload resolution - /// unless the following conditions are met: - /// - `std::is_constructible_v` is `true` - /// - T is not constructible or convertible from any expression - /// of type (possibly const) `result` - /// - E is not constructible or convertible from any expression - /// of type (possible const) `result` - /// - /// \note This constructor is explicit if and only if - /// `std::is_convertible_v` or - /// `std::is_convertible_v` is `false` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result,int>{ - /// std::make_unique() - /// }; - /// const auto s = cpp::result,long>{ - /// std::move(r) - /// }; - /// ``` - /// - /// \param other the other type to convert - template ::value, - int>::type = 0> - result(result&& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value); - template ::value, - int>::type = 0> - explicit result(result&& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value); - /// \} - - //------------------------------------------------------------------------- - - /// \brief Constructs a result object that contains a value - /// - /// The value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type `T` from the arguments - /// `std::forward(args)...` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// cpp::in_place, "Hello world" - /// }; - /// ``` - /// - /// \param args the arguments to pass to T's constructor - template ::value>::type> - constexpr explicit result(in_place_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs a result object that contains a value - /// - /// The value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type `T` from the arguments - /// `std::forward>(ilist)`, - /// `std::forward(args)...` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// cpp::in_place, {'H','e','l','l','o'} - /// }; - /// ``` - /// - /// \param ilist An initializer list of entries to forward - /// \param args the arguments to pass to T's constructor - template &, Args...>::value>::type> - constexpr explicit result( - in_place_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - T, std::initializer_list, - Args...>::value); - - //------------------------------------------------------------------------- - - /// \brief Constructs a result object that contains an error - /// - /// the value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type `E` from the arguments - /// `std::forward(args)...` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// cpp::in_place_error, "Hello world" - /// }; - /// ``` - /// - /// \param args the arguments to pass to E's constructor - template ::value>::type> - constexpr explicit result(in_place_error_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs a result object that contains an error - /// - /// The value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type `E` from the arguments - /// `std::forward>(ilist)`, - /// `std::forward(args)...` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// cpp::in_place_error, {'H','e','l','l','o'} - /// }; - /// ``` - /// - /// \param ilist An initializer list of entries to forward - /// \param args the arguments to pass to Es constructor - template &, Args...>::value>::type> - constexpr explicit result( - in_place_error_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - E, std::initializer_list, - Args...>::value); - - //------------------------------------------------------------------------- - - /// \{ - /// \brief Constructs the underlying error of this result - /// - /// \note This constructor only participates in overload resolution if - /// `E` is constructible from \p e - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// cpp::result r = cpp::fail(42); - /// - /// auto get_error_result() -> cpp::result { - /// return cpp::fail("hello world!"); - /// } - /// ``` - /// - /// \param e the failure error - template ::value>::type> - constexpr /* implicit */ result(const failure& e) noexcept( - std::is_nothrow_constructible::value); - template ::value>::type> - constexpr /* implicit */ result(failure&& e) noexcept( - std::is_nothrow_constructible::value); - /// \} - - /// \{ - /// \brief Constructs a result object that contains a value - /// - /// The value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type T with the expression - /// value. - /// - /// \note This constructor is constexpr if the constructor of T - /// selected by direct-initialization is constexpr - /// - /// \note This constructor does not participate in overload - /// resolution unless `std::is_constructible_v` is true - /// and `decay_t` is neither `in_place_t`, `in_place_error_t`, - /// nor a `result` type. - /// - /// \note This constructor is explicit if and only if - /// `std::is_convertible_v` is `false` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// cpp::result r = 42; - /// - /// auto get_value() -> cpp::result { - /// return "hello world!"; // implicit conversion - /// } - /// ``` - /// - /// \param value the value to copy - template ::value, - int>::type = 0> - constexpr explicit result(U&& value) noexcept( - std::is_nothrow_constructible::value); - template ::value, - int>::type = 0> - constexpr /* implicit */ result(U&& value) noexcept( - std::is_nothrow_constructible::value); - /// \} - - //------------------------------------------------------------------------- - - /// \brief Copy assigns the result stored in \p other - /// - /// \note This assignment operator only participates in overload resolution - /// if the following conditions are met: - /// - `std::is_nothrow_copy_constructible_v` is `true`, and - /// - `std::is_nothrow_copy_constructible_v` is `true` - /// this restriction guarantees that no ' - /// - /// \note This assignment operator is defined as trivial if the following - /// conditions are all `true`: - /// - `std::is_trivially_copy_constructible::value` - /// - `std::is_trivially_copy_constructible::value` - /// - `std::is_trivially_copy_assignable::value` - /// - `std::is_trivially_copy_assignable::value` - /// - `std::is_trivially_destructible::value` - /// - `std::is_trivially_destructible::value` - /// - /// \param other the other result to copy - auto operator=(const result& other) -> result& = default; - - /// \brief Move assigns the result stored in \p other - /// - /// \note This assignment operator only participates in overload resolution - /// if the following conditions are met: - /// - `std::is_nothrow_copy_constructible_v` is `true`, and - /// - `std::is_nothrow_copy_constructible_v` is `true` - /// this restriction guarantees that no 'valueless_by_exception` state - /// may occur. - /// - /// \note This assignment operator is defined as trivial if the following - /// conditions are all `true`: - /// - `std::is_trivially_move_constructible::value` - /// - `std::is_trivially_move_constructible::value` - /// - `std::is_trivially_move_assignable::value` - /// - `std::is_trivially_move_assignable::value` - /// - `std::is_trivially_destructible::value` - /// - `std::is_trivially_destructible::value` - /// - /// \param other the other result to copy - auto operator=(result&& other) -> result& = default; - - /// \brief Copy-converts the state of \p other - /// - /// If both `*this` and \p other contain either values or errors, the - /// underlying value is constructed as if through assignment. - /// - /// Otherwise if `*this` contains a value, but \p other contains an error, - /// then the contained value is destroyed by calling its destructor. `*this` - /// will no longer contain a value after the call, and will now contain `E` - /// constructed as if direct-initializing (but not direct-list-initializing) - /// an object with an argument of `const E2&`. - /// - /// If \p other contains a value and `*this` contains an error, then the - /// contained error is destroyed by calling its destructor. `*this` now - /// contains a value constructed as if direct-initializing (but not - /// direct-list-initializing) an object with an argument of `const T2&`. - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_constructible_v`, - /// `std::is_assignable_v`, - /// `std::is_nothrow_constructible_v`, - /// `std::is_assignable_v` are all true. - /// - T is not constructible, convertible, or assignable from any - /// expression of type (possibly const) `result` - /// - /// \param other the other result object to convert - /// \return reference to `(*this)` - template < - typename T2, typename E2, - typename = typename std::enable_if< - detail::result_is_copy_convert_assignable::value>::type> - auto operator=(const result& other) noexcept( - std::is_nothrow_assignable::value&& - std::is_nothrow_assignable::value) -> result&; - - /// \brief Move-converts the state of \p other - /// - /// If both `*this` and \p other contain either values or errors, the - /// underlying value is constructed as if through move-assignment. - /// - /// Otherwise if `*this` contains a value, but \p other contains an error, - /// then the contained value is destroyed by calling its destructor. `*this` - /// will no longer contain a value after the call, and will now contain `E` - /// constructed as if direct-initializing (but not direct-list-initializing) - /// an object with an argument of `E2&&`. - /// - /// If \p other contains a value and `*this` contains an error, then the - /// contained error is destroyed by calling its destructor. `*this` now - /// contains a value constructed as if direct-initializing (but not - /// direct-list-initializing) an object with an argument of `T2&&`. - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_constructible_v`, - /// `std::is_assignable_v`, - /// `std::is_nothrow_constructible_v`, - /// `std::is_assignable_v` are all true. - /// - T is not constructible, convertible, or assignable from any - /// expression of type (possibly const) `result` - /// - /// \param other the other result object to convert - /// \return reference to `(*this)` - template < - typename T2, typename E2, - typename = typename std::enable_if< - detail::result_is_move_convert_assignable::value>::type> - auto operator=(result&& other) noexcept( - std::is_nothrow_assignable::value&& - std::is_nothrow_assignable::value) -> result&; - - /// \brief Perfect-forwarded assignment - /// - /// Depending on whether `*this` contains a value before the call, the - /// contained value is either direct-initialized from std::forward(value) - /// or assigned from std::forward(value). - /// - /// \note The function does not participate in overload resolution unless - /// - `std::decay_t` is not a result type, - /// - `std::decay_t` is not a failure type - /// - `std::is_nothrow_constructible_v` is `true` - /// - `std::is_assignable_v` is `true` - /// - and at least one of the following is `true`: - /// - `T` is not a scalar type; - /// - `std::decay_t` is not `T`. - /// - /// \param value to assign to the contained value - /// \return reference to `(*this)` - template ::value>::type> - auto operator=(U&& value) noexcept(std::is_nothrow_assignable::value) - -> result&; - - /// \{ - /// \brief Perfect-forwarded assignment - /// - /// Depending on whether `*this` contains an error before the call, the - /// contained error is either direct-initialized via forwarding the error, - /// or assigned from forwarding the error - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_constructible_v` is `true`, and - /// - `std::is_assignable_v` is `true` - /// - /// \param other the failure value to assign to this - /// \return reference to `(*this)` - template < - typename E2, - typename = typename std::enable_if< - detail::result_is_failure_assignable::value>::type> - auto operator=(const failure& other) noexcept( - std::is_nothrow_assignable::value) -> result&; - template ::value>::type> - auto operator=(failure&& other) noexcept( - std::is_nothrow_assignable::value) -> result&; - /// \} - - //------------------------------------------------------------------------- - // Observers - //------------------------------------------------------------------------- - public: - /// \{ - /// \brief Retrieves a pointer to the contained value - /// - /// This operator exists to give `result` an `optional`-like API for cases - /// where it's known that the `result` already contains a value. - /// - /// Care must be taken to ensure that this is only used in safe contexts - /// where a `T` value is active. - /// - /// \note The behavior is undefined if `*this` does not contain a value. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// make_widget() - /// }; - /// - /// r->do_something(); - /// ``` - /// - /// \return a pointer to the contained value - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto operator->() noexcept -> - typename std::remove_reference::type*; - RESULT_WARN_UNUSED - constexpr auto operator->() const noexcept -> - typename std::remove_reference::type>::type*; - /// \} - - /// \{ - /// \brief Retrieves a reference to the contained value - /// - /// This operator exists to give `result` an `optional`-like API for cases - /// where it's known that the `result` already contains a value. - /// - /// Care must be taken to ensure that this is only used in safe contexts - /// where a `T` value is active. - /// - /// \note The behaviour is undefined if `*this` does not contain a value - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// make_widget() - /// }; - /// - /// (*r).do_something(); - /// - /// consume(*r); - /// ``` - /// - /// \return a reference to the contained value - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto operator*() & noexcept -> - typename std::add_lvalue_reference::type; - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto operator*() && noexcept -> - typename std::add_rvalue_reference::type; - RESULT_WARN_UNUSED - constexpr auto operator*() const& noexcept -> - typename std::add_lvalue_reference< - typename std::add_const::type>::type; - RESULT_WARN_UNUSED - constexpr auto operator*() const&& noexcept -> - typename std::add_rvalue_reference< - typename std::add_const::type>::type; - /// \} - - //------------------------------------------------------------------------- - - /// \brief Contextually convertible to `true` if `*this` contains a value - /// - /// This function exists to allow for simple, terse checks for containing - /// a value. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto get_result() -> cpp::result; - /// auto r = get_result(); - /// if (r) { ... } - /// - /// assert(static_cast(cpp::result{42})); - /// - /// assert(!static_cast(cpp::result{cpp::fail(42)})); - /// ``` - /// - /// \return `true` if `*this` contains a value, `false` if `*this` - /// does not contain a value - RESULT_WARN_UNUSED - constexpr explicit operator bool() const noexcept; - - /// \brief Returns `true` if `*this` contains a value - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto get_result() -> cpp::result; - /// auto r = get_result(); - /// if (r.has_value()) { ... } - /// - /// assert(cpp::result{42}.has_value()); - /// - /// assert(!cpp::result{cpp::fail(42)}.has_value()); - /// ``` - /// - /// \return `true` if `*this` contains a value, `false` if `*this` - /// contains an error - RESULT_WARN_UNUSED - constexpr auto has_value() const noexcept -> bool; - - /// \brief Returns `true` if `*this` contains an error - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto get_result() -> cpp::result; - /// - /// auto r = get_result(); - /// if (r.has_error()) { ... } - /// - /// assert(!cpp::result{42}.has_error()); - /// - /// assert(cpp::result{cpp::fail(42)}.has_error()); - /// ``` - /// - /// \return `true` if `*this` contains an error, `false` if `*this` - /// contains a value - RESULT_WARN_UNUSED - constexpr auto has_error() const noexcept -> bool; - - //------------------------------------------------------------------------- - - /// \{ - /// \brief Returns a reference to the contained value - /// - /// This function provides checked (throwing) access to the underlying - /// value. The constness and refness of this result is propagated to the - /// underlying reference. - /// - /// If this contains an error, an exception is thrown containing the - /// underlying error. The error is consumed propagating the same constness - /// and refness of this result. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// assert(cpp::result{42}.value() == 42); - /// - /// auto r = cpp::result,int>{ - /// std::make_unique(42) - /// }; - /// auto s = std::move(r).value(); - /// - /// try { - /// auto r = cpp::result{ cpp::fail(42) }; - /// auto v = r.value(); - /// } catch (const cpp::bad_result_access& e) { - /// assert(e.error() == 42); - /// } - /// ``` - /// - /// \throws bad_result_access if `*this` does not contain a value. - /// - /// \return the value of `*this` - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto value() & -> - typename std::add_lvalue_reference::type; - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto value() && -> - typename std::add_rvalue_reference::type; - RESULT_WARN_UNUSED - constexpr auto value() const& -> typename std::add_lvalue_reference< - typename std::add_const::type>::type; - RESULT_WARN_UNUSED - constexpr auto value() const&& -> typename std::add_rvalue_reference< - typename std::add_const::type>::type; - /// \} - - /// \{ - /// \brief Returns the contained error, if one exists, or a - /// default-constructed error value - /// - /// The `error()` function will not throw any exceptions if `E` does not - /// throw any exceptions for the copy or move construction. - /// - /// This is to limit the possible scope for exceptions, and to allow the - /// error type to be treated as a "status"-like type, where the - /// default-constructed case is considered the "good" state. - /// - /// If this function is invoked on an rvalue of a result, the error is - /// returned via move-construction - /// - /// ### Requires - /// - /// * `std::is_default_constructible::value` is `true` - /// * `std::is_copy_constructible::value` or - /// `std::is_move_constructible::value` is `true` - /// * `E{}` represents the "good" (non-error) state - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ 42 }; - /// assert(r.error() == std::error_code{}); - /// - /// auto r = cpp::result{ - /// cpp::fail(std::io_errc::stream) - /// }; - /// - /// assert(r.error() == std::io_errc::stream); - /// ``` - /// - /// \return the error or a default-constructed error value - RESULT_WARN_UNUSED - constexpr auto error() const& noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_copy_constructible::value) -> E; - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto error() && noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_move_constructible::value) -> E; - /// } - - /// \{ - /// \brief Asserts an expectation that this result contains an error, - /// throwing a bad_result_access on failure - /// - /// If this function is invoked from an rvalue of `result`, then this will - /// consume the underlying error first, if there is one. - /// - /// \note This function exists as a means to allow for results to be marked - /// `used` without requiring directly inspecting the underlying value. - /// This is, in effect, equivalent to `assert(res.has_value())`, - /// however it uses exceptions to ensure the stack can be unwound, and - /// exceptions invoked. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto start_service() -> cpp::result; - /// - /// start_service().expect("Service failed to start!"); - /// ``` - /// - /// \param message the message to provide to this expectation - template ::value && - std::is_copy_constructible::value)>::type> - RESULT_CPP14_CONSTEXPR auto expect(String&& message) const& -> void; - template ::value && - std::is_move_constructible::value)>::type> - RESULT_CPP14_CONSTEXPR auto expect(String&& message) && -> void; - /// \} - - //------------------------------------------------------------------------- - // Monadic Functionalities - //------------------------------------------------------------------------- - public: - /// \{ - /// \brief Returns the contained value if `*this` has a value, - /// otherwise returns \p default_value. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{42}; - /// assert(r.value_or(0) == 42); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.value_or(0) == 0); - /// ``` - /// - /// \param default_value the value to use in case `*this` contains an error - /// \return the contained value or \p default_value - template - RESULT_WARN_UNUSED constexpr auto value_or(U&& default_value) const& -> - typename std::remove_reference::type; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto value_or( - U&& default_value) && -> typename std::remove_reference::type; - /// \} - - /// \{ - /// \brief Returns the contained error if `*this` has an error, - /// otherwise returns \p default_error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{42}; - /// assert(r.error_or(0) == cpp::fail(0)); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.error_or(0) == cpp::fail(42)); - /// ``` - /// - /// \param default_error the error to use in case `*this` is empty - /// \return the contained value or \p default_error - template - RESULT_WARN_UNUSED constexpr auto error_or( - U&& default_error) const& -> error_type; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto error_or( - U&& default_error) && -> error_type; - /// \} - - //------------------------------------------------------------------------- - - /// \brief Returns a result containing \p value if this result contains - /// a value, otherwise returns a result containing the current - /// error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{42}; - /// assert(r.and_then(100) == 100); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.and_then(100) == cpp::fail(42)); - /// ``` - /// - /// \param value the value to return as a result - /// \return a result of \p value if this contains a value - template - RESULT_WARN_UNUSED constexpr auto and_then(U&& value) const - -> result::type, E>; - - /// \{ - /// \brief Invokes the function \p fn with the value of this result as - /// the argument - /// - /// If this result contains an error, a result of the error is returned - /// - /// The function being called must return a `result` type or the program - /// is ill-formed - /// - /// If this is called on an rvalue of `result` which contains an error, - /// the returned `result` is constructed from an rvalue of that error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto to_string(int) -> cpp::result; - /// auto r = cpp::result{42}; - /// assert(r.flat_map(to_string) == "42"); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.flat_map(to_string) == cpp::fail(42)); - /// ``` - /// - /// \param fn the function to invoke with this - /// \return The result of the function being called - template - RESULT_WARN_UNUSED constexpr auto flat_map( - Fn&& fn) const& -> detail::invoke_result_t; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto flat_map( - Fn&& fn) && -> detail::invoke_result_t; - /// \} - - /// \{ - /// \brief Invokes the function \p fn with the value of this result as - /// the argument - /// - /// If this result is an error, the result of this function is that - /// error. Otherwise this function wraps the result and returns it as an - /// result. - /// - /// If this is called on an rvalue of `result` which contains an error, - /// the returned `result` is constructed from an rvalue of that error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto to_string(int) -> std::string; - /// auto r = cpp::result{42}; - /// assert(r.map(to_string) == "42"); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.map(to_string) == cpp::fail(42)); - /// ``` - /// - /// \param fn the function to invoke with this - /// \return The result result of the function invoked - template - RESULT_WARN_UNUSED constexpr auto map( - Fn&& fn) const& -> result, E>; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto map( - Fn&& fn) && -> result, E>; - /// \} - - /// \{ - /// \brief Invokes the function \p fn with the error of this result as - /// the argument - /// - /// If this result contains a value, the result of this function is that - /// value. Otherwise the function is called with that error and returns the - /// result as a result. - /// - /// If this is called on an rvalue of `result` which contains a value, - /// the returned `result` is constructed from an rvalue of that value. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto to_string(int) -> std::string; - /// auto r = cpp::result{42}; - /// assert(r.map_error(to_string) == 42); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.map_error(to_string) == cpp::fail("42")); - /// - /// auto r = cpp::result{}; - /// auto s = r.map(std::string::size); // 's' contains 'result' - /// ``` - /// - /// \param fn the function to invoke with this - /// \return The result result of the function invoked - template - RESULT_WARN_UNUSED constexpr auto map_error( - Fn&& fn) const& -> result>; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto map_error( - Fn&& fn) && -> result>; - /// \} - - /// \{ - /// \brief Invokes the function \p fn with the error of this result as - /// the argument - /// - /// If this result contains a value, a result of the value is returned - /// - /// The function being called must return a `result` type or the program - /// is ill-formed - /// - /// If this is called on an rvalue of `result` which contains an error, - /// the returned `result` is constructed from an rvalue of that error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto to_string(int) -> cpp::result; - /// auto r = cpp::result{42}; - /// assert(r.flat_map(to_string) == 42); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.flat_map(to_string) == cpp::fail("42")); - /// ``` - /// - /// \param fn the function to invoke with this - /// \return The result of the function being called - template - RESULT_WARN_UNUSED constexpr auto flat_map_error( - Fn&& fn) const& -> detail::invoke_result_t; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto flat_map_error( - Fn&& fn) && -> detail::invoke_result_t; - /// \} - - //------------------------------------------------------------------------- - // Private Members - //------------------------------------------------------------------------- - private: - detail::result_storage m_storage; - - //------------------------------------------------------------------------- - // Private Monadic Functions - //------------------------------------------------------------------------- - private: - /// \{ - /// \brief Map implementations for void and non-void functions - /// - /// \param fn the function - template - constexpr auto map_impl(std::true_type, Fn&& fn) const& -> result; - template - constexpr auto map_impl(std::false_type, Fn&& fn) - const& -> result, E>; - template - RESULT_CPP14_CONSTEXPR auto map_impl(std::true_type, - Fn&& fn) && -> result; - template - RESULT_CPP14_CONSTEXPR auto map_impl( - std::false_type, - Fn&& fn) && -> result, E>; - /// \} -}; - -//=========================================================================== -// class : result -//=========================================================================== - -///////////////////////////////////////////////////////////////////////////// -/// \brief Partial specialization of `result` -/// -/// \tparam E the underlying error type -///////////////////////////////////////////////////////////////////////////// -template -class RESULT_NODISCARD result { - // Type requirements - - static_assert( - !std::is_void::type>::value, - "It is ill-formed for E to be (possibly CV-qualified) void type"); - static_assert(!std::is_abstract::value, - "It is ill-formed for E to be abstract type"); - static_assert( - !is_failure::type>::value, - "It is ill-formed for E to be a (possibly CV-qualified) 'failure' type"); - static_assert(!std::is_reference::value, - "It is ill-formed for E to be a reference type. " - "Only T types may be lvalue references"); - - // Friendship - - friend detail::result_error_extractor; - - template - friend class result; - - //------------------------------------------------------------------------- - // Public Member Types - //------------------------------------------------------------------------- - public: - using value_type = void; ///< The value type of this result - using error_type = E; ///< The error type of this result - using failure_type = failure; ///< The failure type - - template - using rebind = result; ///< Rebinds the result type - - //------------------------------------------------------------------------- - // Constructor / Assignment - //------------------------------------------------------------------------- - public: - /// \brief Constructs a `result` object in a value state - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{}; - /// ``` - constexpr result() noexcept; - - /// \brief Copy constructs this result - /// - /// If other contains an error, constructs an object that contains a copy - /// of that error. - /// - /// \note This constructor is defined as deleted if - /// `std::is_copy_constructible::value` is `false` - /// - /// \note This constructor is defined as trivial if both - /// `std::is_trivially_copy_constructible::value` are `true` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// const auto r = cpp::result{}; - /// const auto s = r; - /// ``` - /// - /// \param other the result to copy - constexpr result(const result& other) = default; - - /// \brief Move constructs a result - /// - /// If other contains an error, move-constructs this result from that - /// error. - /// - /// \note This constructor is defined as deleted if - /// `std::is_move_constructible::value` is `false` - /// - /// \note This constructor is defined as trivial if both - /// `std::is_trivially_move_constructible::value` are `true` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{}; - /// auto s = std::move(r); - /// ``` - /// - /// \param other the result to move - constexpr result(result&& other) = default; - - /// \brief Converting copy constructor - /// - /// If \p other contains a value, constructs a result object that is not - /// in an error state -- ignoring the value. - /// - /// If \p other contains an error, constructs a result object that - /// contains an error, initialized as if direct-initializing - /// (but not direct-list-initializing) an object of type `E`. - /// - /// \note This constructor does not participate in overload resolution - /// unless the following conditions are met: - /// - `std::is_constructible_v` is `true` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// const auto r = cpp::result{42}; - /// const auto s = cpp::result{r}; - /// ``` - /// - /// \param other the other type to convert - template ::value>::type> - explicit result(const result& other) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Converting move constructor - /// - /// If \p other contains an error, constructs a result object that - /// contains an error, initialized as if direct-initializing - /// (but not direct-list-initializing) an object of type E&&. - /// - /// \note This constructor does not participate in overload resolution - /// unless the following conditions are met: - /// - `std::is_constructible_v` is `true` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{42}; - /// auto s = cpp::result{ - /// std::move(r) - /// }; - /// ``` - /// - /// \param other the other type to convert - template ::value>::type> - explicit result(result&& other) noexcept( - std::is_nothrow_constructible::value); - - //------------------------------------------------------------------------- - - /// \brief Constructs a result object in a value state - /// - /// This constructor exists primarily for symmetry with the `result` - /// constructor. Unlike the `T` overload, no variadic arguments may be - /// supplied. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{cpp::in_place}; - /// ``` - constexpr explicit result(in_place_t) noexcept; - - /// \brief Constructs a result object that contains an error - /// - /// the value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type `E` from the arguments - /// `std::forward(args)...` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// cpp::in_place_error, "Hello world" - /// }; - /// ``` - /// - /// \param args the arguments to pass to `E`'s constructor - template ::value>::type> - constexpr explicit result(in_place_error_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value); - - /// \brief Constructs a result object that contains an error - /// - /// The value is initialized as if direct-initializing (but not - /// direct-list-initializing) an object of type `E` from the arguments - /// `std::forward>(ilist)`, - /// `std::forward(args)...` - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto r = cpp::result{ - /// cpp::in_place_error, {'H','e','l','l','o'} - /// }; - /// ``` - /// - /// \param ilist An initializer list of entries to forward - /// \param args the arguments to pass to Es constructor - template &, Args...>::value>::type> - constexpr explicit result( - in_place_error_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - E, std::initializer_list, - Args...>::value); - - //------------------------------------------------------------------------- - - /// \{ - /// \brief Constructs the underlying error of this result - /// - /// \note This constructor only participates in overload resolution if - /// `E` is constructible from \p e - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// cpp::result r = cpp::fail(42); - /// - /// auto get_error_result() -> cpp::result { - /// return cpp::fail("hello world!"); - /// } - /// ``` - /// - /// \param e the failure error - template ::value>::type> - constexpr /* implicit */ result(const failure& e) noexcept( - std::is_nothrow_constructible::value); - template ::value>::type> - constexpr /* implicit */ result(failure&& e) noexcept( - std::is_nothrow_constructible::value); - /// \} - - //------------------------------------------------------------------------- - - /// \brief Copy assigns the result stored in \p other - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_copy_constructible_v` is `true` - /// this restriction guarantees that no 'valueless_by_exception` state - /// may occur. - /// - /// \note This assignment operator is defined as trivial if the following - /// conditions are all `true`: - /// - `std::is_trivially_copy_constructible::value` - /// - `std::is_trivially_copy_assignable::value` - /// - `std::is_trivially_destructible::value` - /// - /// \param other the other result to copy - auto operator=(const result& other) -> result& = default; - - /// \brief Move assigns the result stored in \p other - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_copy_constructible_v` is `true` - /// this restriction guarantees that no 'valueless_by_exception` state - /// may occur. - /// - /// \note This assignment operator is defined as trivial if the following - /// conditions are all `true`: - /// - `std::is_trivially_move_constructible::value` - /// - `std::is_trivially_move_assignable::value` - /// - `std::is_trivially_destructible::value` - /// - /// \param other the other result to copy - auto operator=(result&& other) -> result& = default; - - /// \brief Copy-converts the state of \p other - /// - /// If both this and \p other contain an error, the underlying error is - /// assigned through copy-assignment. - /// - /// If \p other contains a value state, this result is constructed in a - /// value state. - /// - /// If \p other contans an error state, and this contains a value state, - /// the underlying error is constructed through copy-construction. - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_constructible_v`, - /// `std::is_assignable_v` are all `true`. - /// - /// \param other the other result object to convert - /// \return reference to `(*this)` - template ::value && - std::is_assignable::value>::type> - auto operator=(const result& other) noexcept( - std::is_nothrow_assignable::value) -> result&; - - /// \brief Move-converts the state of \p other - /// - /// If both this and \p other contain an error, the underlying error is - /// assigned through move-assignment. - /// - /// If \p other contains a value state, this result is constructed in a - /// value state. - /// - /// If \p other contans an error state, and this contains a value state, - /// the underlying error is constructed through move-construction. - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_constructible_v`, - /// `std::is_assignable_v` are all `true`. - /// - /// \param other the other result object to convert - /// \return reference to `(*this)` - template ::value && - std::is_assignable::value>::type> - auto operator=(result&& other) noexcept( - std::is_nothrow_assignable::value) -> result&; - - /// \{ - /// \brief Perfect-forwarded assignment - /// - /// Depending on whether `*this` contains an error before the call, the - /// contained error is either direct-initialized via forwarding the error, - /// or assigned from forwarding the error - /// - /// \note The function does not participate in overload resolution unless - /// - `std::is_nothrow_constructible_v` is `true`, and - /// - `std::is_assignable_v` is `true` - /// - /// \param other the failure value to assign to this - /// \return reference to `(*this)` - template < - typename E2, - typename = typename std::enable_if< - detail::result_is_failure_assignable::value>::type> - auto operator=(const failure& other) noexcept( - std::is_nothrow_assignable::value) -> result&; - template ::value>::type> - auto operator=(failure&& other) noexcept( - std::is_nothrow_assignable::value) -> result&; - /// \} - - //------------------------------------------------------------------------- - // Observers - //------------------------------------------------------------------------- - public: - /// \brief Contextually convertible to `true` if `*this` does not contain - /// an error - /// - /// This function exists to allow for simple, terse checks for containing - /// a value. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto get_result() -> cpp::result; - /// auto r = get_result(); - /// if (r) { ... } - /// - /// assert(static_cast(cpp::result{})); - /// - /// assert(!static_cast(cpp::result{cpp::fail(42)})); - /// ``` - /// - /// \return `true` if `*this` contains a value, `false` if `*this` - /// does not contain a value - RESULT_WARN_UNUSED - constexpr explicit operator bool() const noexcept; - - /// \copydoc result::has_value - RESULT_WARN_UNUSED - constexpr auto has_value() const noexcept -> bool; - - /// \copydoc result::has_error - RESULT_WARN_UNUSED - constexpr auto has_error() const noexcept -> bool; - - //------------------------------------------------------------------------- - - /// \{ - /// \brief Throws an exception if `(*this)` is in an error state - /// - /// This function exists for symmetry with `cpp::result` objects where - /// `T` contains a value. - /// - /// If this contains an error, an exception is thrown containing the - /// underlying error. The error is consumed propagating the same constness - /// and refness of this result. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// cpp::result{}.value(); // no exception - /// - /// auto r = cpp::result>{ - /// cpp::fail(std::make_unique(42)) - /// }; - /// std::move(r).value(); // throws bad_result_access> - /// - /// try { - /// auto r = cpp::result{ cpp::fail(42) }.value(); - /// } catch (const cpp::bad_result_access& e) { - /// assert(e.error() == 42); - /// } - /// ``` - /// - /// \throws bad_result_access if `*this` does not contain a value. - RESULT_CPP14_CONSTEXPR auto value() && -> void; - RESULT_CPP14_CONSTEXPR auto value() const& -> void; - /// \} - - /// \{ - /// \copydoc result::error - RESULT_WARN_UNUSED - constexpr auto error() const& noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_copy_constructible::value) -> E; - RESULT_WARN_UNUSED - RESULT_CPP14_CONSTEXPR auto error() && noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_copy_constructible::value) -> E; - /// \} - - /// \{ - /// \copydoc result::expect - template ::value && - std::is_copy_constructible::value)>::type> - RESULT_CPP14_CONSTEXPR auto expect(String&& message) const& -> void; - template ::value && - std::is_move_constructible::value)>::type> - RESULT_CPP14_CONSTEXPR auto expect(String&& message) && -> void; - /// \} - - //------------------------------------------------------------------------- - // Monadic Functionalities - //------------------------------------------------------------------------- - public: - /// \{ - /// \copydoc result::error_or - template - RESULT_WARN_UNUSED constexpr auto error_or( - U&& default_error) const& -> error_type; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto error_or( - U&& default_error) && -> error_type; - /// \} - - //------------------------------------------------------------------------- - - /// \copydoc result::and_then - template - RESULT_WARN_UNUSED constexpr auto and_then(U&& value) const - -> result::type, E>; - - /// \{ - /// \brief Invokes the function \p fn if `(*this)` contains no value - /// - /// If this result contains an error, a result of the error is returned - /// - /// The function being called must return a `result` type or the program - /// is ill-formed - /// - /// If this is called on an rvalue of `result` which contains an error, - /// the returned `result` is constructed from an rvalue of that error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto generate_int() -> cpp::result { return 42; } - /// auto r = cpp::result{}; - /// assert(r.flat_map(generate_int) == 42); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.flat_map(generate_int) == cpp::fail(42)); - /// ``` - /// - /// \param fn the function to invoke with this - /// \return The result of the function being called - template - RESULT_WARN_UNUSED constexpr auto flat_map( - Fn&& fn) const& -> detail::invoke_result_t; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto flat_map( - Fn&& fn) && -> detail::invoke_result_t; - /// \} - - /// \{ - /// \brief Invokes the function \p fn if `(*this)` contains no value - /// - /// If this result is an error, the result of this function is that - /// error. Otherwise this function wraps the result and returns it as an - /// result. - /// - /// If this is called on an rvalue of `result` which contains an error, - /// the returned `result` is constructed from an rvalue of that error. - /// - /// ### Examples - /// - /// Basic Usage: - /// - /// ```cpp - /// auto generate_int() -> int { return 42; } - /// auto r = cpp::result{}; - /// assert(r.map(generate_int) == 42); - /// - /// auto r = cpp::result{cpp::fail(42)}; - /// assert(r.map(generate_int) == cpp::fail(42)); - /// ``` - /// - /// \param fn the function to invoke with this - /// \return The result result of the function invoked - template - RESULT_WARN_UNUSED constexpr auto map( - Fn&& fn) const& -> result, E>; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto map( - Fn&& fn) && -> result, E>; - /// \} - - /// \{ - /// \copydoc result::map_error - template - constexpr auto map_error( - Fn&& fn) const& -> result>; - template - RESULT_CPP14_CONSTEXPR auto map_error( - Fn&& fn) && -> result>; - /// \} - - /// \{ - /// \copydoc result::flat_map_error - template - RESULT_WARN_UNUSED constexpr auto flat_map_error( - Fn&& fn) const& -> detail::invoke_result_t; - template - RESULT_WARN_UNUSED RESULT_CPP14_CONSTEXPR auto flat_map_error( - Fn&& fn) && -> detail::invoke_result_t; - /// \} - - //------------------------------------------------------------------------- - // Private Members - //------------------------------------------------------------------------- - private: - detail::result_storage m_storage; - - //------------------------------------------------------------------------- - // Private Monadic Functions - //------------------------------------------------------------------------- - private: - /// \{ - /// \brief Map implementations for void and non-void functions - /// - /// \param fn the function - template - constexpr auto map_impl(std::true_type, Fn&& fn) const& -> result; - template - constexpr auto map_impl(std::false_type, Fn&& fn) - const& -> result, E>; - template - RESULT_CPP14_CONSTEXPR auto map_impl(std::true_type, - Fn&& fn) && -> result; - template - RESULT_CPP14_CONSTEXPR auto map_impl( - std::false_type, Fn&& fn) && -> result, E>; - /// \} -}; - -//=========================================================================== -// non-member functions : class : result -//=========================================================================== - -//--------------------------------------------------------------------------- -// Comparison -//--------------------------------------------------------------------------- - -template -constexpr auto operator==(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator!=(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator>=(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator<=(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator>(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator<(const result& lhs, - const result& rhs) noexcept -> bool; - -//--------------------------------------------------------------------------- - -template -constexpr auto operator==(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator!=(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator>=(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator<=(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator>(const result& lhs, - const result& rhs) noexcept -> bool; -template -constexpr auto operator<(const result& lhs, - const result& rhs) noexcept -> bool; - -//--------------------------------------------------------------------------- - -template < - typename T, typename E, typename U, - typename = typename std::enable_if::value>::type> -constexpr auto operator==(const result& exp, const U& value) noexcept - -> bool; -template < - typename T, typename U, typename E, - typename = typename std::enable_if::value>::type> -constexpr auto operator==(const T& value, const result& exp) noexcept - -> bool; -template < - typename T, typename E, typename U, - typename = typename std::enable_if::value>::type> -constexpr auto operator!=(const result& exp, const U& value) noexcept - -> bool; -template < - typename T, typename U, typename E, - typename = typename std::enable_if::value>::type> -constexpr auto operator!=(const T& value, const result& exp) noexcept - -> bool; -template < - typename T, typename E, typename U, - typename = typename std::enable_if::value>::type> -constexpr auto operator<=(const result& exp, const U& value) noexcept - -> bool; -template < - typename T, typename U, typename E, - typename = typename std::enable_if::value>::type> -constexpr auto operator<=(const T& value, const result& exp) noexcept - -> bool; -template < - typename T, typename E, typename U, - typename = typename std::enable_if::value>::type> -constexpr auto operator>=(const result& exp, const U& value) noexcept - -> bool; -template < - typename T, typename U, typename E, - typename = typename std::enable_if::value>::type> -constexpr auto operator>=(const T& value, const result& exp) noexcept - -> bool; -template < - typename T, typename E, typename U, - typename = typename std::enable_if::value>::type> -constexpr auto operator<(const result& exp, const U& value) noexcept - -> bool; -template < - typename T, typename U, typename E, - typename = typename std::enable_if::value>::type> -constexpr auto operator<(const T& value, const result& exp) noexcept - -> bool; -template < - typename T, typename E, typename U, - typename = typename std::enable_if::value>::type> -constexpr auto operator>(const result& exp, const U& value) noexcept - -> bool; -template < - typename T, typename U, typename E, - typename = typename std::enable_if::value>::type> -constexpr auto operator>(const T& value, const result& exp) noexcept - -> bool; - -//--------------------------------------------------------------------------- - -template -constexpr auto operator==(const result& exp, - const failure& value) noexcept -> bool; -template -constexpr auto operator==(const failure& value, - const result& exp) noexcept -> bool; -template -constexpr auto operator!=(const result& exp, - const failure& value) noexcept -> bool; -template -constexpr auto operator!=(const failure& value, - const result& exp) noexcept -> bool; -template -constexpr auto operator<=(const result& exp, - const failure& value) noexcept -> bool; -template -constexpr auto operator<=(const failure& value, - const result& exp) noexcept -> bool; -template -constexpr auto operator>=(const result& exp, - const failure& value) noexcept -> bool; -template -constexpr auto operator>=(const failure& value, - const result& exp) noexcept -> bool; -template -constexpr auto operator<(const result& exp, - const failure& value) noexcept -> bool; -template -constexpr auto operator<(const failure& value, - const result& exp) noexcept -> bool; -template -constexpr auto operator>(const result& exp, - const failure& value) noexcept -> bool; -template -constexpr auto operator>(const failure& value, - const result& exp) noexcept -> bool; - -//--------------------------------------------------------------------------- -// Utilities -//--------------------------------------------------------------------------- - -/// \{ -/// \brief Swaps the contents of \p lhs with \p rhs -/// -/// \param lhs the left result -/// \param rhs the right result -template -auto swap(result& lhs, result& rhs) -#if __cplusplus >= 201703L - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value&& - std::is_nothrow_swappable::value&& - std::is_nothrow_swappable::value) -#else - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value) -#endif - -> void; -template -auto swap(result& lhs, result& rhs) -#if __cplusplus >= 201703L - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value&& - std::is_nothrow_swappable::value) -#else - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value) -#endif - -> void; -/// \} - -} // namespace bitwizeshift -} // namespace RESULT_NAMESPACE_INTERNAL - -namespace std { - -template -struct hash<::RESULT_NS_IMPL::result> { - auto operator()(const RESULT_NS_IMPL::result& x) const -> std::size_t { - if (x.has_value()) { - return std::hash{}(*x) + - 1; // add '1' to differentiate from error case - } - return std::hash{}(::RESULT_NS_IMPL::detail::extract_error(x)); - } -}; - -template -struct hash<::RESULT_NS_IMPL::result> { - auto operator()(const RESULT_NS_IMPL::result& x) const - -> std::size_t { - if (x.has_value()) { - return 0; - } - return std::hash{}(::RESULT_NS_IMPL::detail::extract_error(x)); - } -}; - -} // namespace std - -#if !defined(RESULT_DISABLE_EXCEPTIONS) - -//============================================================================= -// class : bad_result_access -//============================================================================= - -//----------------------------------------------------------------------------- -// Constructors -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::bad_result_access::bad_result_access(E2&& error) - : logic_error{"error attempting to access value from result containing error"}, - m_error(detail::forward(error)) -{ - -} - -template -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::bad_result_access::bad_result_access(const char* what_arg, - E2&& error) - : logic_error{what_arg}, m_error(detail::forward(error)) {} - -template -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::bad_result_access::bad_result_access( - const std::string& what_arg, E2&& error) - : logic_error{what_arg}, m_error(detail::forward(error)) {} - -//----------------------------------------------------------------------------- -// Observers -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::bad_result_access::error() & noexcept -> E& { - return m_error; -} - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::bad_result_access::error() && noexcept -> E&& { - return static_cast(m_error); -} - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::bad_result_access::error() const& noexcept -> const E& { - return m_error; -} - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::bad_result_access::error() const&& noexcept -> const E&& { - return static_cast(m_error); -} - -#endif - -//============================================================================= -// class : failure -//============================================================================= - -//----------------------------------------------------------------------------- -// Constructors -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::failure::failure( - in_place_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_failure(detail::forward(args)...) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::failure::failure( - in_place_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - E, std::initializer_list, - Args...>::value) - : m_failure(ilist, detail::forward(args)...) {} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::failure::failure( - E2&& error) noexcept(std::is_nothrow_constructible::value) - : m_failure(detail::forward(error)) {} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::failure::failure( - E2&& error) noexcept(std::is_nothrow_constructible::value) - : m_failure(detail::forward(error)) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::failure::failure( - const failure& - other) noexcept(std::is_nothrow_constructible::value) - : m_failure(other.error()) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::failure::failure( - failure&& other) noexcept(std::is_nothrow_constructible::value) - : m_failure(static_cast&&>(other).error()) {} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::failure::operator=(E2&& error) noexcept( - std::is_nothrow_assignable::value || - std::is_lvalue_reference::value) -> failure& { - m_failure = detail::forward(error); - - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::failure::operator=(const failure& other) noexcept( - std::is_nothrow_assignable::value) -> failure& { - m_failure = other.error(); - - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::failure::operator=(failure&& other) noexcept( - std::is_nothrow_assignable::value) -> failure& { - m_failure = static_cast&&>(other).error(); - - return (*this); -} - -//----------------------------------------------------------------------------- -// Observers -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::failure::error() & noexcept -> - typename std::add_lvalue_reference::type { - return m_failure; -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::failure::error() && noexcept -> - typename std::add_rvalue_reference::type { - using reference = typename std::add_rvalue_reference::type; - - return static_cast(m_failure); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::failure::error() const& noexcept -> - typename std::add_lvalue_reference::type>::type { - return m_failure; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::failure::error() const&& noexcept -> - typename std::add_rvalue_reference::type>::type { - using reference = typename std::add_rvalue_reference< - typename std::add_const::type>::type; - - return static_cast(m_failure); -} - -//============================================================================= -// non-member functions : class : failure -//============================================================================= - -//----------------------------------------------------------------------------- -// Comparison -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const failure& lhs, const failure& rhs) noexcept -> bool { - return lhs.error() == rhs.error(); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const failure& lhs, const failure& rhs) noexcept -> bool { - return lhs.error() != rhs.error(); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const failure& lhs, const failure& rhs) noexcept -> bool { - return lhs.error() < rhs.error(); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const failure& lhs, const failure& rhs) noexcept -> bool { - return lhs.error() > rhs.error(); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const failure& lhs, const failure& rhs) noexcept -> bool { - return lhs.error() <= rhs.error(); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const failure& lhs, const failure& rhs) noexcept -> bool { - return lhs.error() >= rhs.error(); -} - -//----------------------------------------------------------------------------- -// Utilities -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::fail(E&& e) noexcept( - std::is_nothrow_constructible::type, E>::value) - -> failure::type> { - using result_type = failure::type>; - - return result_type(detail::forward(e)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::fail( - std::reference_wrapper e) noexcept -> failure { - using result_type = failure; - - return result_type{e.get()}; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::fail( - Args&&... args) noexcept(std::is_nothrow_constructible::value) - -> failure { - return failure(in_place, detail::forward(args)...); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::fail(std::initializer_list ilist, Args&&... args) noexcept( - std::is_nothrow_constructible, Args...>::value) - -> failure { - return failure(in_place, ilist, detail::forward(args)...); -} - -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::swap(failure& lhs, - failure& rhs) -#if __cplusplus >= 201703L - noexcept(std::is_nothrow_swappable::value) -> void -#else - noexcept(std::is_nothrow_move_constructible::value) -> void -#endif -{ - using std::swap; - - swap(lhs.error(), rhs.error()); -} - -//============================================================================= -// class : detail::result_union -//============================================================================= - -//----------------------------------------------------------------------------- -// Constructors / Assignment -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_union::result_union( - unit) noexcept - : m_empty{} { - // m_has_value intentionally not set -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::detail:: - result_union::result_union( - in_place_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_value(detail::forward(args)...), m_has_value{true} {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::detail:: - result_union::result_union( - in_place_error_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_error(detail::forward(args)...), m_has_value{false} {} - -//----------------------------------------------------------------------------- -// Modifiers -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_union::destroy() const noexcept - -> void { - // do nothing -} - -//============================================================================= -// class : detail::result_union -//============================================================================= - -//----------------------------------------------------------------------------- -// Constructors / Destructor / Assignment -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_union::result_union(unit) noexcept - : m_empty{} { - // m_has_value intentionally not set -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::detail:: - result_union::result_union( - in_place_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_value(detail::forward(args)...), m_has_value{true} {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::detail:: - result_union::result_union( - in_place_error_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_error(detail::forward(args)...), m_has_value{false} {} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY - RESULT_NS_IMPL::detail::result_union::~result_union() noexcept( - std::is_nothrow_destructible::value&& - std::is_nothrow_destructible::value) { - destroy(); -} - -//----------------------------------------------------------------------------- -// Modifiers -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_union::destroy() -> void { - if (m_has_value) { - m_value.~underlying_value_type(); - } else { - m_error.~underlying_error_type(); - } -} - -//============================================================================= -// class : result_construct_base -//============================================================================= - -//----------------------------------------------------------------------------- -// Constructors / Assignment -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_construct_base::result_construct_base( - unit) noexcept - : storage{unit{}} {} - -template -template -inline constexpr RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_construct_base::result_construct_base( - in_place_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : storage{in_place, detail::forward(args)...} {} - -template -template -inline constexpr RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_construct_base::result_construct_base( - in_place_error_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : storage(in_place_error, detail::forward(args)...) {} - -//----------------------------------------------------------------------------- -// Construction / Assignment -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_construct_base::construct_value( - Args&&... args) noexcept(std::is_nothrow_constructible::value) - -> void { - using value_type = typename storage_type::underlying_value_type; - - auto* p = static_cast(std::addressof(storage.m_value)); - new (p) value_type(detail::forward(args)...); - storage.m_has_value = true; -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_construct_base::construct_error( - Args&&... args) noexcept(std::is_nothrow_constructible::value) - -> void { - using error_type = typename storage_type::underlying_error_type; - - auto* p = static_cast(std::addressof(storage.m_error)); - new (p) error_type(detail::forward(args)...); - storage.m_has_value = false; -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::detail:: - result_construct_base::construct_error_from_result(Result&& other) - -> void { - if (other.storage.m_has_value) { - construct_value(); - } else { - construct_error(detail::forward(other).storage.m_error); - } -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_construct_base::construct_from_result( - Result&& other) -> void { - if (other.storage.m_has_value) { - construct_value_from_result_impl( - std::is_lvalue_reference{}, - detail::forward(other).storage.m_value); - } else { - construct_error(detail::forward(other).storage.m_error); - } -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_construct_base::assign_value( - Value&& value) noexcept(std::is_nothrow_assignable::value) - -> void { - if (!storage.m_has_value) { - storage.destroy(); - construct_value(detail::forward(value)); - } else { - storage.m_value = detail::forward(value); - } -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_construct_base::assign_error( - Error&& error) noexcept(std::is_nothrow_assignable::value) - -> void { - if (storage.m_has_value) { - storage.destroy(); - construct_error(detail::forward(error)); - } else { - storage.m_error = detail::forward(error); - } -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_construct_base::assign_from_result( - Result&& other) -> void { - if (other.storage.m_has_value != storage.m_has_value) { - storage.destroy(); - construct_from_result(detail::forward(other)); - } else if (storage.m_has_value) { - assign_value_from_result_impl(std::is_lvalue_reference{}, - detail::forward(other)); - } else { - storage.m_error = detail::forward(other).storage.m_error; - } -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::detail:: - result_construct_base::construct_value_from_result_impl( - std::true_type, ReferenceWrapper&& reference) noexcept -> void { - using value_type = typename storage_type::underlying_value_type; - - auto* p = static_cast(std::addressof(storage.m_value)); - new (p) value_type(reference.get()); - storage.m_has_value = true; -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::detail:: - result_construct_base::construct_value_from_result_impl( - std::false_type, - Value&& value) noexcept(std::is_nothrow_constructible::value) - -> void { - using value_type = typename storage_type::underlying_value_type; - - auto* p = static_cast(std::addressof(storage.m_value)); - new (p) value_type(detail::forward(value)); - storage.m_has_value = true; -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::detail:: - result_construct_base::assign_value_from_result_impl(std::true_type, - Result&& other) - -> void { - // T is a reference; unwrap it - storage.m_value = other.storage.m_value.get(); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::detail:: - result_construct_base::assign_value_from_result_impl(std::false_type, - Result&& other) - -> void { - storage.m_value = detail::forward(other).storage.m_value; -} - -//============================================================================= -// class : result_trivial_copy_ctor_base_impl -//============================================================================= - -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_trivial_copy_ctor_base_impl:: - result_trivial_copy_ctor_base_impl( - const result_trivial_copy_ctor_base_impl& - other) noexcept(std::is_nothrow_copy_constructible::value&& - std::is_nothrow_copy_constructible::value) - : base_type(unit{}) { - using ctor_base = result_construct_base; - - ctor_base::construct_from_result(static_cast(other)); -} - -//============================================================================= -// class : result_trivial_move_ctor_base -//============================================================================= - -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::detail::result_trivial_move_ctor_base_impl:: - result_trivial_move_ctor_base_impl( - result_trivial_move_ctor_base_impl&& - other) noexcept(std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_constructible::value) - : base_type(unit{}) { - using ctor_base = result_construct_base; - - ctor_base::construct_from_result(static_cast(other)); -} - -//============================================================================= -// class : result_copy_assign_base -//============================================================================= - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_trivial_copy_assign_base_impl::operator=( - const result_trivial_copy_assign_base_impl& - other) noexcept(std::is_nothrow_copy_constructible::value&& - std::is_nothrow_copy_constructible::value&& - std::is_nothrow_copy_assignable::value&& - std::is_nothrow_copy_assignable::value) - -> result_trivial_copy_assign_base_impl& { - using ctor_base = result_construct_base; - - ctor_base::assign_from_result(static_cast(other)); - return (*this); -} - -//========================================================================= -// class : result_move_assign_base -//========================================================================= - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::result_trivial_move_assign_base_impl::operator=( - result_trivial_move_assign_base_impl&& - other) noexcept(std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_constructible::value&& - std::is_nothrow_move_assignable::value&& - std::is_nothrow_move_assignable::value) - -> result_trivial_move_assign_base_impl& { - using ctor_base = result_construct_base; - - ctor_base::assign_from_result(static_cast(other)); - return (*this); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::detail::result_error_extractor::get( - const result& exp) noexcept -> const E& { - return exp.m_storage.storage.m_error; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::detail::result_error_extractor::get(result& exp) noexcept - -> E& { - return exp.m_storage.storage.m_error; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::detail::extract_error(const result& exp) noexcept - -> const E& { - return result_error_extractor::get(exp); -} - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::throw_bad_result_access(E&& error) -> void { -#if defined(RESULT_DISABLE_EXCEPTIONS) - std::fprintf( - stderr, - "error attempting to access value from result containing error\n"); - std::abort(); -#else - using exception_type = bad_result_access::type>::type>; - - throw exception_type{detail::forward(error)}; -#endif -} - -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::detail::throw_bad_result_access_message(String&& message, - E&& error) -> void { -#if defined(RESULT_DISABLE_EXCEPTIONS) - const auto message_string = std::string{detail::forward(message)}; - std::fprintf(stderr, "%s\n", message_string.c_str()); - std::abort(); -#else - using exception_type = bad_result_access::type>::type>; - - throw exception_type{detail::forward(message), - detail::forward(error)}; -#endif -} - -//============================================================================= -// class : result -//============================================================================= - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result< - T, E>::result() noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place) {} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::result::result(const result& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value) - : m_storage(detail::unit{}) { - m_storage.construct_from_result( - static_cast&>(other).m_storage); -} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::result::result(const result& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value) - : m_storage(detail::unit{}) { - m_storage.construct_from_result( - static_cast&>(other).m_storage); -} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::result::result(result&& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value) - : m_storage(detail::unit{}) { - m_storage.construct_from_result( - static_cast&&>(other).m_storage); -} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::result::result(result&& other) noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_constructible::value) - : m_storage(detail::unit{}) { - m_storage.construct_from_result( - static_cast&&>(other).m_storage); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - in_place_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place, detail::forward(args)...) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result:: - result(in_place_t, std::initializer_list ilist, Args&&... args) noexcept( - std::is_nothrow_constructible, - Args...>::value) - : m_storage(in_place, ilist, detail::forward(args)...) {} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - in_place_error_t, - Args&&... args) noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place_error, detail::forward(args)...) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - in_place_error_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - E, std::initializer_list, - Args...>::value) - : m_storage(in_place_error, ilist, detail::forward(args)...) {} - -//------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - const failure& - e) noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place_error, e.error()) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - failure&& e) noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place_error, static_cast(e.error())) {} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - U&& value) noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place, detail::forward(value)) {} - -template -template ::value, - int>::type> -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result::result( - U&& value) noexcept(std::is_nothrow_constructible::value) - : m_storage(in_place, detail::forward(value)) {} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::result::operator=(const result& other) noexcept( - std::is_nothrow_assignable::value&& - std::is_nothrow_assignable::value) -> result& { - m_storage.assign_from_result( - static_cast&>(other).m_storage); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::result::operator=(result&& other) noexcept( - std::is_nothrow_assignable::value&& - std::is_nothrow_assignable::value) -> result& { - m_storage.assign_from_result(static_cast&&>(other).m_storage); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::result::operator=( - U&& value) noexcept(std::is_nothrow_assignable::value) -> result& { - m_storage.assign_value(detail::forward(value)); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::result::operator=(const failure& other) noexcept( - std::is_nothrow_assignable::value) -> result& { - m_storage.assign_error(other.error()); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::result::operator=( - failure&& other) noexcept(std::is_nothrow_assignable::value) - -> result& { - m_storage.assign_error(static_cast(other.error())); - return (*this); -} - -//----------------------------------------------------------------------------- -// Observers -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::operator->() noexcept -> - typename std::remove_reference::type* { - // Prior to C++17, std::addressof was not `constexpr`. - // Since `addressof` fixes a relatively obscure issue where users define a - // custom `operator&`, the pre-C++17 implementation has been defined to be - // `&(**this)` so that it may exist in constexpr contexts. -#if __cplusplus >= 201703L - return std::addressof(**this); -#else - return &(**this); -#endif -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::operator->() const noexcept -> - typename std::remove_reference::type>::type* { -#if __cplusplus >= 201703L - return std::addressof(**this); -#else - return &(**this); -#endif -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::operator*() & noexcept -> - typename std::add_lvalue_reference::type { - return m_storage.storage.m_value; -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::operator*() && noexcept -> - typename std::add_rvalue_reference::type { - using reference = typename std::add_rvalue_reference::type; - - return static_cast(m_storage.storage.m_value); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::operator*() const& noexcept -> - typename std::add_lvalue_reference::type>::type { - return m_storage.storage.m_value; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::operator*() const&& noexcept -> - typename std::add_rvalue_reference::type>::type { - using reference = typename std::add_rvalue_reference< - typename std::add_const::type>::type; - - return static_cast(m_storage.storage.m_value); -} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result< - T, E>::operator bool() const noexcept { - return m_storage.storage.m_has_value; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::has_value() const noexcept -> bool { - return m_storage.storage.m_has_value; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::has_error() const noexcept -> bool { - return !m_storage.storage.m_has_value; -} - -//----------------------------------------------------------------------------- - -// The `has_value()` expression below is incorrectly identified as an unused -// value, which results in the `-Wunused-value` warning. This is suppressed -// to prevent false-positives -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wunused-value" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-value" -#elif defined(_MSC_VER) -// Older MSVC versions incorrectly warn on returning a reference to a temporary. -// This has been suppressed -# pragma warning(push) -# pragma warning(disable : 4172) -#endif - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::value() & -> - typename std::add_lvalue_reference::type { - return ( - has_value() || - (detail::throw_bad_result_access(m_storage.storage.m_error), false), - m_storage.storage.m_value); -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::value() && -> - typename std::add_rvalue_reference::type { - using reference = typename std::add_rvalue_reference::type; - - return (has_value() || (detail::throw_bad_result_access( - static_cast(m_storage.storage.m_error)), - true), - static_cast(m_storage.storage.m_value)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::value() const& -> - typename std::add_lvalue_reference::type>::type { - return ( - has_value() || - (detail::throw_bad_result_access(m_storage.storage.m_error), true), - m_storage.storage.m_value); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::value() const&& -> - typename std::add_rvalue_reference::type>::type { - using reference = typename std::add_rvalue_reference< - typename std::add_const::type>::type; - - return ( - has_value() || (detail::throw_bad_result_access( - static_cast(m_storage.storage.m_error)), - true), - (static_cast(m_storage.storage.m_value))); -} - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#elif defined(_MSC_VER) -# pragma warning(pop) -#endif - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::error() const& noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_copy_constructible::value) -> E { - static_assert( - std::is_default_constructible::value, - "E must be default-constructible if 'error()' checks are used. " - "This is to allow for default-constructed error states to represent the " - "'good' state"); - - return m_storage.storage.m_has_value ? E{} : m_storage.storage.m_error; -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::error() && noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_move_constructible::value) -> E { - static_assert( - std::is_default_constructible::value, - "E must be default-constructible if 'error()' checks are used. " - "This is to allow for default-constructed error states to represent the " - "'good' state"); - - return m_storage.storage.m_has_value - ? E{} - : static_cast(m_storage.storage.m_error); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_CPP14_CONSTEXPR auto RESULT_NS_IMPL::result::expect( - String&& message) const& -> void { - if (has_error()) { - detail::throw_bad_result_access_message(detail::forward(message), - m_storage.storage.m_error); - } -} - -template -template -inline RESULT_CPP14_CONSTEXPR auto RESULT_NS_IMPL::result::expect( - String&& message) && -> void { - if (has_error()) { - detail::throw_bad_result_access_message( - detail::forward(message), - static_cast(m_storage.storage.m_error)); - } -} - -//----------------------------------------------------------------------------- -// Monadic Functionalities -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::value_or(U&& default_value) const& -> - typename std::remove_reference::type { - return m_storage.storage.m_has_value ? m_storage.storage.m_value - : detail::forward(default_value); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::value_or(U&& default_value) && -> - typename std::remove_reference::type { - return m_storage.storage.m_has_value ? static_cast(**this) - : detail::forward(default_value); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::error_or(U&& default_error) const& -> error_type { - return m_storage.storage.m_has_value ? detail::forward(default_error) - : m_storage.storage.m_error; -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::error_or(U&& default_error) && -> error_type { - return m_storage.storage.m_has_value - ? detail::forward(default_error) - : static_cast(m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::and_then(U&& value) const - -> result::type, E> { - return map([&value](const T&) { return detail::forward(value); }); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::flat_map( - Fn&& fn) const& -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map must return a result type or the program is ill-formed"); - - return has_value() ? detail::invoke(detail::forward(fn), - m_storage.storage.m_value) - : result_type(in_place_error, m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::flat_map( - Fn&& fn) && -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map must return a result type or the program is ill-formed"); - - return has_value() - ? detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_value)) - : result_type(in_place_error, - static_cast(m_storage.storage.m_error)); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map( - Fn&& fn) const& -> result, E> { - using result_type = detail::invoke_result_t; - - return map_impl(std::is_void{}, detail::forward(fn)); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map( - Fn&& fn) && -> result, E> { - using result_type = detail::invoke_result_t; - - return static_cast&&>(*this).map_impl( - std::is_void{}, detail::forward(fn)); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map_error( - Fn&& fn) const& -> result> { - using result_type = result>; - - return has_error() ? result_type(in_place_error, - detail::invoke(detail::forward(fn), - m_storage.storage.m_error)) - : result_type(in_place, m_storage.storage.m_value); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map_error( - Fn&& fn) && -> result> { - using result_type = result>; - - return has_error() - ? result_type( - in_place_error, - detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_error))) - : result_type(static_cast(m_storage.storage.m_value)); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::flat_map_error( - Fn&& fn) const& -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map_error must return a result type or the program is ill-formed"); - - return has_value() ? result_type(in_place, m_storage.storage.m_value) - : detail::invoke(detail::forward(fn), - m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::flat_map_error( - Fn&& fn) && -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map_error must return a result type or the program is ill-formed"); - - return has_value() - ? result_type(in_place, - static_cast(m_storage.storage.m_value)) - : detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_error)); -} - -//----------------------------------------------------------------------------- -// Private Monadic Functions -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map_impl(std::true_type, - Fn&& fn) const& -> result { - using result_type = result; - - return has_value() ? (detail::invoke(detail::forward(fn), - m_storage.storage.m_value), - result_type{}) - : result_type(in_place_error, m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map_impl(std::false_type, Fn&& fn) - const& -> result, E> { - using invoke_result_type = detail::invoke_result_t; - using result_type = result; - - return has_value() - ? result_type(in_place, detail::invoke(detail::forward(fn), - m_storage.storage.m_value)) - : result_type(in_place_error, m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map_impl(std::true_type, - Fn&& fn) && -> result { - using result_type = result; - - return has_value() - ? (detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_value)), - result_type{}) - : result_type(in_place_error, - static_cast(m_storage.storage.m_error)); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map_impl( - std::false_type, - Fn&& fn) && -> result, E> { - using invoke_result_type = detail::invoke_result_t; - using result_type = result; - - return has_value() - ? result_type( - in_place, - detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_value))) - : result_type(in_place_error, - static_cast(m_storage.storage.m_error)); -} - -//============================================================================= -// class : result -//============================================================================= - -//----------------------------------------------------------------------------- -// Constructor / Assignment -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result< - void, E>::result() noexcept - : m_storage(in_place) {} - -template -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::result::result(const result& other) noexcept( - std::is_nothrow_constructible::value) - : m_storage(detail::unit{}) { - m_storage.construct_error_from_result( - static_cast&>(other).m_storage); -} - -template -template -inline RESULT_INLINE_VISIBILITY -RESULT_NS_IMPL::result::result(result&& other) noexcept( - std::is_nothrow_constructible::value) - : m_storage(detail::unit{}) { - m_storage.construct_error_from_result( - static_cast&&>(other).m_storage); -} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result< - void, E>::result(in_place_t) noexcept - : m_storage(in_place) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result:: - result(in_place_error_t, Args&&... args) noexcept( - std::is_nothrow_constructible::value) - : m_storage(in_place_error, detail::forward(args)...) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result:: - result(in_place_error_t, std::initializer_list ilist, - Args&&... args) noexcept(std:: - is_nothrow_constructible< - E, std::initializer_list, - Args...>::value) - : m_storage(in_place_error, ilist, detail::forward(args)...) {} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result:: - result(const failure& e) noexcept( - std::is_nothrow_constructible::value) - : m_storage(in_place_error, e.error()) {} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result:: - result(failure&& e) noexcept( - std::is_nothrow_constructible::value) - : m_storage(in_place_error, static_cast(e.error())) {} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::result::operator=( - const result& - other) noexcept(std::is_nothrow_assignable::value) - -> result& { - m_storage.assign_from_result(other.m_storage); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::result::operator=(result&& other) noexcept( - std::is_nothrow_assignable::value) -> result& { - m_storage.assign_from_result( - static_cast&&>(other).m_storage); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto -RESULT_NS_IMPL::result::operator=(const failure& other) noexcept( - std::is_nothrow_assignable::value) -> result& { - m_storage.assign_error(other.error()); - return (*this); -} - -template -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::result::operator=( - failure&& other) noexcept(std::is_nothrow_assignable::value) - -> result& { - m_storage.assign_error(static_cast(other.error())); - return (*this); -} - -//----------------------------------------------------------------------------- -// Observers -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr RESULT_NS_IMPL::result< - void, E>::operator bool() const noexcept { - return has_value(); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::has_value() const noexcept -> bool { - return m_storage.storage.m_has_value; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::has_error() const noexcept -> bool { - return !has_value(); -} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::value() const& -> void { - static_cast( - has_value() || - (detail::throw_bad_result_access(m_storage.storage.m_error), true)); -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::value() && -> void { - static_cast(has_value() || - (detail::throw_bad_result_access( - static_cast(m_storage.storage.m_error)), - true)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::error() const& noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_copy_constructible::value) -> E { - return has_value() ? E{} : m_storage.storage.m_error; -} - -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::error() && noexcept( - std::is_nothrow_constructible::value&& - std::is_nothrow_copy_constructible::value) -> E { - return has_value() ? E{} : static_cast(m_storage.storage.m_error); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_CPP14_CONSTEXPR auto RESULT_NS_IMPL::result::expect( - String&& message) const& -> void { - if (has_error()) { - detail::throw_bad_result_access_message(detail::forward(message), - m_storage.storage.m_error); - } -} - -template -template -inline RESULT_CPP14_CONSTEXPR auto RESULT_NS_IMPL::result::expect( - String&& message) && -> void { - if (has_error()) { - detail::throw_bad_result_access_message( - detail::forward(message), - static_cast(m_storage.storage.m_error)); - } -} - -//----------------------------------------------------------------------------- -// Monadic Functionalities -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::error_or( - U&& default_error) const& -> error_type { - return has_value() ? detail::forward(default_error) - : m_storage.storage.m_error; -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::error_or(U&& default_error) && -> error_type { - return has_value() ? detail::forward(default_error) - : static_cast(m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::and_then(U&& value) const - -> result::type, E> { - return map([&value] { return detail::forward(value); }); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::flat_map( - Fn&& fn) const& -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map must return a result type or the program is ill-formed"); - - return has_value() ? detail::invoke(detail::forward(fn)) - : result_type(in_place_error, m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::flat_map( - Fn&& fn) && -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map must return a result type or the program is ill-formed"); - - return has_value() ? detail::invoke(detail::forward(fn)) - : result_type(in_place_error, - static_cast(m_storage.storage.m_error)); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map( - Fn&& fn) const& -> result, E> { - using result_type = detail::invoke_result_t; - - return map_impl(std::is_void{}, detail::forward(fn)); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map( - Fn&& fn) && -> result, E> { - using result_type = detail::invoke_result_t; - - return static_cast&&>(*this).map_impl( - std::is_void{}, detail::forward(fn)); -} - -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map_error( - Fn&& fn) const& -> result> { - using result_type = result>; - - return has_value() ? result_type{} - : result_type(in_place_error, - detail::invoke(detail::forward(fn), - m_storage.storage.m_error)); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map_error( - Fn&& fn) && -> result> { - using result_type = result>; - - return has_value() - ? result_type{} - : result_type( - in_place_error, - detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_error))); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::flat_map_error( - Fn&& fn) const& -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map_error must return a result type or the program is ill-formed"); - static_assert( - std::is_default_constructible::value, - "flat_map_error for result requires the new T type to be default-" - "constructible"); - - return has_value() ? result_type{} - : detail::invoke(detail::forward(fn), - m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::flat_map_error( - Fn&& fn) && -> detail::invoke_result_t { - using result_type = detail::invoke_result_t; - - static_assert( - is_result::value, - "flat_map_error must return a result type or the program is ill-formed"); - static_assert( - std::is_default_constructible::value, - "flat_map_error for result requires the new T type to be default-" - "constructible"); - - return has_value() - ? result_type{} - : detail::invoke(detail::forward(fn), - static_cast(m_storage.storage.m_error)); -} - -//----------------------------------------------------------------------------- -// Private Monadic Functions -//----------------------------------------------------------------------------- - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map_impl(std::true_type, - Fn&& fn) const& -> result { - using result_type = result; - - return has_value() ? (detail::invoke(detail::forward(fn)), result_type{}) - : result_type(in_place_error, m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY constexpr auto -RESULT_NS_IMPL::result::map_impl( - std::false_type, Fn&& fn) const& -> result, E> { - using invoke_result_type = detail::invoke_result_t; - using result_type = result; - - return has_value() - ? result_type(in_place, detail::invoke(detail::forward(fn))) - : result_type(in_place_error, m_storage.storage.m_error); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map_impl(std::true_type, - Fn&& fn) && -> result { - using result_type = result; - - return has_value() ? (detail::invoke(detail::forward(fn)), result_type{}) - : result_type(in_place_error, - static_cast(m_storage.storage.m_error)); -} - -template -template -inline RESULT_INLINE_VISIBILITY RESULT_CPP14_CONSTEXPR auto -RESULT_NS_IMPL::result::map_impl( - std::false_type, Fn&& fn) && -> result, E> { - using invoke_result_type = detail::invoke_result_t; - using result_type = result; - - return has_value() - ? result_type(in_place, detail::invoke(detail::forward(fn))) - : result_type(in_place_error, - static_cast(m_storage.storage.m_error)); -} - -//============================================================================= -// non-member functions : class : result -//============================================================================= - -//----------------------------------------------------------------------------- -// Comparison -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const result& lhs, const result& rhs) noexcept -> bool { - return (lhs.has_value() == rhs.has_value()) - ? (lhs.has_value() - ? *lhs == *rhs - : detail::extract_error(lhs) == detail::extract_error(rhs)) - : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const result& lhs, const result& rhs) noexcept -> bool { - return (lhs.has_value() == rhs.has_value()) - ? (lhs.has_value() - ? *lhs != *rhs - : detail::extract_error(lhs) != detail::extract_error(rhs)) - : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const result& lhs, const result& rhs) noexcept -> bool { - return (lhs.has_value() == rhs.has_value()) - ? (lhs.has_value() - ? *lhs >= *rhs - : detail::extract_error(lhs) >= detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) >= - static_cast(static_cast(rhs)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const result& lhs, const result& rhs) noexcept -> bool { - return (lhs.has_value() == rhs.has_value()) - ? (lhs.has_value() - ? *lhs <= *rhs - : detail::extract_error(lhs) <= detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) <= - static_cast(static_cast(rhs)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const result& lhs, const result& rhs) noexcept -> bool { - return (lhs.has_value() == rhs.has_value()) - ? (lhs.has_value() - ? *lhs > *rhs - : detail::extract_error(lhs) > detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) > - static_cast(static_cast(rhs)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const result& lhs, const result& rhs) noexcept -> bool { - return (lhs.has_value() == rhs.has_value()) - ? (lhs.has_value() - ? *lhs < *rhs - : detail::extract_error(lhs) < detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) < - static_cast(static_cast(rhs)); -} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const result& lhs, const result& rhs) noexcept -> bool { - return lhs.has_value() == rhs.has_value() - ? (lhs.has_value() - ? true - : detail::extract_error(lhs) == detail::extract_error(rhs)) - : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const result& lhs, const result& rhs) noexcept -> bool { - return lhs.has_value() == rhs.has_value() - ? (lhs.has_value() - ? false - : detail::extract_error(lhs) != detail::extract_error(rhs)) - : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const result& lhs, const result& rhs) noexcept -> bool { - return lhs.has_value() == rhs.has_value() - ? (lhs.has_value() - ? true - : detail::extract_error(lhs) >= detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) >= - static_cast(static_cast(rhs)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const result& lhs, const result& rhs) noexcept -> bool { - return lhs.has_value() == rhs.has_value() - ? (lhs.has_value() - ? true - : detail::extract_error(lhs) <= detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) <= - static_cast(static_cast(rhs)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const result& lhs, const result& rhs) noexcept -> bool { - return lhs.has_value() == rhs.has_value() - ? (lhs.has_value() - ? false - : detail::extract_error(lhs) > detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) > - static_cast(static_cast(rhs)); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const result& lhs, const result& rhs) noexcept -> bool { - return lhs.has_value() == rhs.has_value() - ? (lhs.has_value() - ? false - : detail::extract_error(lhs) < detail::extract_error(rhs)) - : static_cast(static_cast(lhs)) < - static_cast(static_cast(rhs)); -} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const result& exp, const U& value) noexcept -> bool { - return (exp.has_value() && *exp == value); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const T& value, const result& exp) noexcept -> bool { - return (exp.has_value() && *exp == value); -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const result& exp, const U& value) noexcept -> bool { - return exp.has_value() ? *exp != value : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const T& value, const result& exp) noexcept -> bool { - return exp.has_value() ? value != *exp : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const result& exp, const U& value) noexcept -> bool { - return exp.has_value() ? *exp <= value : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const T& value, const result& exp) noexcept -> bool { - return exp.has_value() ? value <= *exp : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const result& exp, const U& value) noexcept -> bool { - return exp.has_value() ? *exp >= value : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const T& value, const result& exp) noexcept -> bool { - return exp.has_value() ? value >= *exp : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const result& exp, const U& value) noexcept -> bool { - return exp.has_value() ? *exp < value : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const T& value, const result& exp) noexcept -> bool { - return exp.has_value() ? value < *exp : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const result& exp, const U& value) noexcept -> bool { - return exp.has_value() ? *exp > value : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const T& value, const result& exp) noexcept -> bool { - return exp.has_value() ? value > *exp : true; -} - -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const result& exp, const failure& error) noexcept -> bool { - return exp.has_error() ? detail::extract_error(exp) == error.error() : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator==( - const failure& error, const result& exp) noexcept -> bool { - return exp.has_error() ? error.error() == detail::extract_error(exp) : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const result& exp, const failure& error) noexcept -> bool { - return exp.has_error() ? detail::extract_error(exp) != error.error() : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator!=( - const failure& error, const result& exp) noexcept -> bool { - return exp.has_error() ? error.error() != detail::extract_error(exp) : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const result& exp, const failure& error) noexcept -> bool { - return exp.has_error() ? detail::extract_error(exp) <= error.error() : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<=( - const failure& error, const result& exp) noexcept -> bool { - return exp.has_error() ? error.error() <= detail::extract_error(exp) : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const result& exp, const failure& error) noexcept -> bool { - return exp.has_error() ? detail::extract_error(exp) >= error.error() : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>=( - const failure& error, const result& exp) noexcept -> bool { - return exp.has_error() ? error.error() >= detail::extract_error(exp) : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const result& exp, const failure& error) noexcept -> bool { - return exp.has_error() ? detail::extract_error(exp) < error.error() : true; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator<( - const failure& error, const result& exp) noexcept -> bool { - return exp.has_error() ? error.error() < detail::extract_error(exp) : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const result& exp, const failure& error) noexcept -> bool { - return exp.has_error() ? detail::extract_error(exp) > error.error() : false; -} - -template -inline RESULT_INLINE_VISIBILITY constexpr auto RESULT_NS_IMPL::operator>( - const failure& error, const result& exp) noexcept -> bool { - return exp.has_error() ? error.error() > detail::extract_error(exp) : true; -} - -//----------------------------------------------------------------------------- -// Utilities -//----------------------------------------------------------------------------- - -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::swap(result& lhs, - result& rhs) -#if __cplusplus >= 201703L - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value&& - std::is_nothrow_swappable::value&& - std::is_nothrow_swappable::value) -#else - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value) -#endif - -> void { - using std::swap; - - if (lhs.has_value() == rhs.has_value()) { - if (lhs.has_value()) { - swap(*lhs, *rhs); - } else { - auto& lhs_error = detail::result_error_extractor::get(lhs); - auto& rhs_error = detail::result_error_extractor::get(rhs); - - swap(lhs_error, rhs_error); - } - // If both `result`s contain values, do nothing - } else { - auto temp = static_cast&&>(lhs); - lhs = static_cast&&>(rhs); - rhs = static_cast&&>(temp); - } -} - -template -inline RESULT_INLINE_VISIBILITY auto RESULT_NS_IMPL::swap(result& lhs, - result& rhs) -#if __cplusplus >= 201703L - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value&& - std::is_nothrow_swappable::value) -#else - noexcept(std::is_nothrow_move_constructible>::value&& - std::is_nothrow_move_assignable>::value) -#endif - -> void { - using std::swap; - - if (lhs.has_value() == rhs.has_value()) { - if (lhs.has_error()) { - auto& lhs_error = detail::result_error_extractor::get(lhs); - auto& rhs_error = detail::result_error_extractor::get(rhs); - - swap(lhs_error, rhs_error); - } - // If both `result`s contain values, do nothing - } else { - auto temp = static_cast&&>(lhs); - lhs = static_cast&&>(rhs); - rhs = static_cast&&>(temp); - } -} - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -#undef RESULT_NAMESPACE_INTERNAL -#undef RESULT_NS_IMPL -#undef RESULT_CPP14_CONSTEXPR -#undef RESULT_CPP17_INLINE -#undef RESULT_INLINE_VISIBILITY -#undef RESULT_NODISCARD -#undef RESULT_WARN_UNUSED - -#endif /* RESULT_RESULT_HPP */ diff --git a/src/CraneCtld/AccountManager.cpp b/src/CraneCtld/AccountManager.cpp index 1d46a1204..fd4e8a239 100644 --- a/src/CraneCtld/AccountManager.cpp +++ b/src/CraneCtld/AccountManager.cpp @@ -898,7 +898,7 @@ bool AccountManager::CheckUserPermissionToPartition( return false; } -result::result AccountManager::CheckIfUserOfAccountIsEnabled( +std::expected AccountManager::CheckIfUserOfAccountIsEnabled( const std::string& user, const std::string& account) { util::read_lock_guard user_guard(m_rw_user_mutex_); util::read_lock_guard account_guard(m_rw_account_mutex_); @@ -907,7 +907,7 @@ result::result AccountManager::CheckIfUserOfAccountIsEnabled( do { const Account* account_ptr = GetExistedAccountInfoNoLock_(account_name); if (account_ptr->blocked) { - return result::fail( + return std::unexpected( fmt::format("Ancestor account '{}' is blocked", account_ptr->name)); } account_name = account_ptr->parent_account; @@ -915,19 +915,19 @@ result::result AccountManager::CheckIfUserOfAccountIsEnabled( const User* user_ptr = GetExistedUserInfoNoLock_(user); if (user_ptr->account_to_attrs_map.at(account).blocked) { - return result::fail(fmt::format("User '{}' is blocked", user_ptr->name)); + return std::unexpected(fmt::format("User '{}' is blocked", user_ptr->name)); } return {}; } -result::result AccountManager::CheckAndApplyQosLimitOnTask( +std::expected AccountManager::CheckAndApplyQosLimitOnTask( const std::string& user, const std::string& account, TaskInCtld* task) { util::read_lock_guard user_guard(m_rw_user_mutex_); util::read_lock_guard qos_guard(m_rw_qos_mutex_); const User* user_share_ptr = GetExistedUserInfoNoLock_(user); if (!user_share_ptr) { - return result::fail(fmt::format("Unknown user '{}'", user)); + return std::unexpected(fmt::format("Unknown user '{}'", user)); } if (task->uid != 0) { @@ -935,20 +935,20 @@ result::result AccountManager::CheckAndApplyQosLimitOnTask( .allowed_partition_qos_map.find(task->partition_id); if (partition_it == user_share_ptr->account_to_attrs_map.at(account) .allowed_partition_qos_map.end()) - return result::fail("Partition is not allowed for this user."); + return std::unexpected("Partition is not allowed for this user."); if (task->qos.empty()) { // Default qos task->qos = partition_it->second.first; if (task->qos.empty()) - return result::fail( + return std::unexpected( fmt::format("The user '{}' has no QOS available for this partition " "'{}' to be used", task->Username(), task->partition_id)); } else { // Check whether task.qos in the qos list if (!ranges::contains(partition_it->second.second, task->qos)) - return result::fail(fmt::format( + return std::unexpected(fmt::format( "The qos '{}' you set is not in partition's allowed qos list", task->qos)); } @@ -960,34 +960,34 @@ result::result AccountManager::CheckAndApplyQosLimitOnTask( const Qos* qos_share_ptr = GetExistedQosInfoNoLock_(task->qos); if (!qos_share_ptr) - return result::fail(fmt::format("Unknown QOS '{}'", task->qos)); + return std::unexpected(fmt::format("Unknown QOS '{}'", task->qos)); task->qos_priority = qos_share_ptr->priority; if (task->time_limit >= absl::Seconds(kTaskMaxTimeLimitSec)) { task->time_limit = qos_share_ptr->max_time_limit_per_task; } else if (task->time_limit > qos_share_ptr->max_time_limit_per_task) - return result::fail("time-limit reached the user's limit."); + return std::unexpected("time-limit reached the user's limit."); if (static_cast(task->cpus_per_task) > qos_share_ptr->max_cpus_per_user) - return result::fail("cpus-per-task reached the user's limit."); + return std::unexpected("cpus-per-task reached the user's limit."); return {}; } -result::result AccountManager::CheckUidIsAdmin( +std::expected AccountManager::CheckUidIsAdmin( uint32_t uid) { util::read_lock_guard user_guard(m_rw_user_mutex_); auto user_result = GetUserInfoByUidNoLock_(uid); if (!user_result) { - return result::failure("User is not a user of Crane."); + return std::unexpected("User is not a user of Crane."); } const User* user_ptr = user_result.value(); if (user_ptr->admin_level >= User::Operator) return {}; - return result::failure("User has insufficient privilege."); + return std::unexpected("User has insufficient privilege."); } AccountManager::CraneExpected AccountManager::CheckIfUidHasPermOnUser( diff --git a/src/CraneCtld/AccountManager.h b/src/CraneCtld/AccountManager.h index 62d251a9d..812acc5a5 100644 --- a/src/CraneCtld/AccountManager.h +++ b/src/CraneCtld/AccountManager.h @@ -129,13 +129,13 @@ class AccountManager { const std::string& account, const std::string& partition); - result::result CheckIfUserOfAccountIsEnabled( + std::expected CheckIfUserOfAccountIsEnabled( const std::string& user, const std::string& account); - result::result CheckAndApplyQosLimitOnTask( + std::expected CheckAndApplyQosLimitOnTask( const std::string& user, const std::string& account, TaskInCtld* task); - result::result CheckUidIsAdmin(uint32_t uid); + std::expected CheckUidIsAdmin(uint32_t uid); CraneExpected CheckIfUidHasPermOnUser(uint32_t uid, const std::string& username, diff --git a/src/CraneCtld/CMakeLists.txt b/src/CraneCtld/CMakeLists.txt index f030eb8e2..1f6ceeb8e 100644 --- a/src/CraneCtld/CMakeLists.txt +++ b/src/CraneCtld/CMakeLists.txt @@ -46,7 +46,6 @@ target_link_libraries(cranectld PRIVATE mongocxx_static range-v3::range-v3 - result Backward::Interface ) diff --git a/src/CraneCtld/CtldGrpcServer.cpp b/src/CraneCtld/CtldGrpcServer.cpp index 7bbc072a3..a27cae29d 100644 --- a/src/CraneCtld/CtldGrpcServer.cpp +++ b/src/CraneCtld/CtldGrpcServer.cpp @@ -57,7 +57,7 @@ grpc::Status CraneCtldServiceImpl::SubmitBatchTasks( grpc::ServerContext *context, const crane::grpc::SubmitBatchTasksRequest *request, crane::grpc::SubmitBatchTasksReply *response) { - std::vector, std::string>> results; + std::vector, std::string>> results; uint32_t task_count = request->count(); const auto &task_to_ctld = request->task(); @@ -71,11 +71,11 @@ grpc::Status CraneCtldServiceImpl::SubmitBatchTasks( results.emplace_back(std::move(result)); } - for (auto &res : results) { + for (auto& res : results) { if (res.has_value()) response->mutable_task_id_list()->Add(res.value().get()); else - response->mutable_reason_list()->Add(res.error()); + response->mutable_reason_list()->Add(std::move(res.error())); } return grpc::Status::OK; @@ -154,7 +154,7 @@ grpc::Status CraneCtldServiceImpl::ModifyTask( using ModifyTaskRequest = crane::grpc::ModifyTaskRequest; auto res = g_account_manager->CheckUidIsAdmin(request->uid()); - if (res.has_error()) { + if (!res) { for (auto task_id : request->task_ids()) { response->add_not_modified_tasks(task_id); response->add_not_modified_reasons(res.error()); @@ -802,12 +802,12 @@ grpc::Status CraneCtldServiceImpl::CforedStream( auto submit_result = m_ctld_server_->SubmitTaskToScheduler(std::move(task)); - result::result result; + std::expected result; if (submit_result.has_value()) { - result = result::result{ + result = std::expected{ submit_result.value().get()}; } else { - result = result::fail(submit_result.error()); + result = std::unexpected(submit_result.error()); } ok = stream_writer->WriteTaskIdReply(payload.pid(), result); @@ -928,12 +928,12 @@ CtldServer::CtldServer(const Config::CraneCtldListenConf &listen_conf) { signal(SIGINT, &CtldServer::signal_handler_func); } -result::result, std::string> +std::expected, std::string> CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { CraneErr err; if (!task->password_entry->Valid()) { - return result::fail( + return std::unexpected( fmt::format("Uid {} not found on the controller node", task->uid)); } task->SetUsername(task->password_entry->Username()); @@ -942,7 +942,7 @@ CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { auto user_scoped_ptr = g_account_manager->GetExistedUserInfo(task->Username()); if (!user_scoped_ptr) { - return result::fail(fmt::format( + return std::unexpected(fmt::format( "User '{}' not found in the account database", task->Username())); } @@ -951,7 +951,7 @@ CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { task->MutableTaskToCtld()->set_account(user_scoped_ptr->default_account); } else { if (!user_scoped_ptr->account_to_attrs_map.contains(task->account)) { - return result::fail(fmt::format( + return std::unexpected(fmt::format( "Account '{}' is not in your account list", task->account)); } } @@ -959,7 +959,7 @@ CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { if (!g_account_manager->CheckUserPermissionToPartition( task->Username(), task->account, task->partition_id)) { - return result::fail( + return std::unexpected( fmt::format("User '{}' doesn't have permission to use partition '{}' " "when using account '{}'", task->Username(), task->partition_id, task->account)); @@ -968,8 +968,8 @@ CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { auto enable_res = g_account_manager->CheckIfUserOfAccountIsEnabled( task->Username(), task->account); - if (enable_res.has_error()) { - return result::fail(enable_res.error()); + if (!enable_res) { + return std::unexpected(enable_res.error()); } err = g_task_scheduler->AcquireTaskAttributes(task.get()); @@ -986,32 +986,32 @@ CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { if (err == CraneErr::kNonExistent) { CRANE_DEBUG("Task submission failed. Reason: Partition doesn't exist!"); - return result::fail("Partition doesn't exist!"); + return std::unexpected("Partition doesn't exist!"); } else if (err == CraneErr::kInvalidNodeNum) { CRANE_DEBUG( "Task submission failed. Reason: --node is either invalid or greater " "than the number of nodes in its partition."); - return result::fail( + return std::unexpected( "--node is either invalid or greater than the number of nodes in its " "partition."); } else if (err == CraneErr::kNoResource) { CRANE_DEBUG( "Task submission failed. " "Reason: The resources of the partition are insufficient."); - return result::fail("The resources of the partition are insufficient"); + return std::unexpected("The resources of the partition are insufficient"); } else if (err == CraneErr::kNoAvailNode) { CRANE_DEBUG( "Task submission failed. " "Reason: Nodes satisfying the requirements of task are insufficient"); - return result::fail( + return std::unexpected( "Nodes satisfying the requirements of task are insufficient."); } else if (err == CraneErr::kInvalidParam) { CRANE_DEBUG( "Task submission failed. " "Reason: The param of task is invalid."); - return result::fail("The param of task is invalid."); + return std::unexpected("The param of task is invalid."); } - return result::fail(CraneErrStr(err)); + return std::unexpected(CraneErrStr(err)); } } // namespace Ctld diff --git a/src/CraneCtld/CtldGrpcServer.h b/src/CraneCtld/CtldGrpcServer.h index 2099602c2..fdc24d0ac 100644 --- a/src/CraneCtld/CtldGrpcServer.h +++ b/src/CraneCtld/CtldGrpcServer.h @@ -45,7 +45,7 @@ class CforedStreamWriter { : m_stream_(stream), m_valid_(true) {} bool WriteTaskIdReply(pid_t calloc_pid, - result::result res) { + std::expected res) { LockGuard guard(&m_stream_mtx_); if (!m_valid_) return false; @@ -67,7 +67,7 @@ class CforedStreamWriter { bool WriteTaskResAllocReply( task_id_t task_id, - result::result>, + std::expected>, std::string> res) { LockGuard guard(&m_stream_mtx_); @@ -122,7 +122,7 @@ class CforedStreamWriter { } bool WriteCforedRegistrationAck( - const result::result &res) { + const std::expected &res) { LockGuard guard(&m_stream_mtx_); if (!m_valid_) return false; @@ -308,7 +308,7 @@ class CtldServer { inline void Wait() { m_server_->Wait(); } - result::result, std::string> SubmitTaskToScheduler( + std::expected, std::string> SubmitTaskToScheduler( std::unique_ptr task); private: diff --git a/src/CraneCtld/CtldPreCompiledHeader.h b/src/CraneCtld/CtldPreCompiledHeader.h index 25e8fc6fe..43cc9c35f 100644 --- a/src/CraneCtld/CtldPreCompiledHeader.h +++ b/src/CraneCtld/CtldPreCompiledHeader.h @@ -87,9 +87,6 @@ // fpm #include -// result -#include - #include "crane/Logger.h" // Logger.h must be the first diff --git a/src/CraneCtld/CtldPublicDefs.h b/src/CraneCtld/CtldPublicDefs.h index 6b3213069..2e3eeb252 100644 --- a/src/CraneCtld/CtldPublicDefs.h +++ b/src/CraneCtld/CtldPublicDefs.h @@ -153,8 +153,6 @@ inline Ctld::Config g_config; namespace Ctld { -namespace result = cpp_result; - /** * The static information on a Craned (the static part of CranedMeta). This * structure is provided when a new Craned node is to be registered in diff --git a/src/CraneCtld/EmbeddedDbClient.cpp b/src/CraneCtld/EmbeddedDbClient.cpp index 3300288bf..53307f931 100644 --- a/src/CraneCtld/EmbeddedDbClient.cpp +++ b/src/CraneCtld/EmbeddedDbClient.cpp @@ -22,7 +22,7 @@ namespace Ctld { #ifdef CRANE_HAVE_UNQLITE -result::result UnqliteDb::Init(const std::string& path) { +std::expected UnqliteDb::Init(const std::string& path) { int rc; m_db_path_ = path; @@ -32,7 +32,7 @@ result::result UnqliteDb::Init(const std::string& path) { m_db_ = nullptr; CRANE_ERROR("Failed to open unqlite db file {}: {}", m_db_path_, GetInternalErrorStr_()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } // Unqlite does not roll back and clear WAL after crashing. @@ -43,13 +43,13 @@ result::result UnqliteDb::Init(const std::string& path) { m_db_ = nullptr; CRANE_ERROR("Failed to rollback the undone transaction: {}", GetInternalErrorStr_()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } return {}; } -result::result UnqliteDb::Close() { +std::expected UnqliteDb::Close() { int rc; if (m_db_ != nullptr) { CRANE_TRACE("Closing unqlite..."); @@ -58,14 +58,14 @@ result::result UnqliteDb::Close() { if (rc != UNQLITE_OK) { CRANE_ERROR("Failed to close unqlite: {}", GetInternalErrorStr_()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } return {}; } -result::result UnqliteDb::Store(txn_id_t txn_id, +std::expected UnqliteDb::Store(txn_id_t txn_id, const std::string& key, const void* data, size_t len) { @@ -81,11 +81,11 @@ result::result UnqliteDb::Store(txn_id_t txn_id, CRANE_ERROR("Failed to store key {} into db: {}", key, GetInternalErrorStr_()); if (rc != UNQLITE_NOTIMPLEMENTED) unqlite_rollback(m_db_); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } -result::result UnqliteDb::Fetch(txn_id_t txn_id, +std::expected UnqliteDb::Fetch(txn_id_t txn_id, const std::string& key, void* buf, size_t* len) { int rc; @@ -101,12 +101,12 @@ result::result UnqliteDb::Fetch(txn_id_t txn_id, std::this_thread::yield(); continue; } - if (rc == UNQLITE_NOTFOUND) return result::failure(DbErrorCode::kNotFound); + if (rc == UNQLITE_NOTFOUND) return std::unexpected(DbErrorCode::kNotFound); CRANE_ERROR("Failed to get value size for key {}: {}", key, GetInternalErrorStr_()); unqlite_rollback(m_db_); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } if (*len == 0) { @@ -114,13 +114,13 @@ result::result UnqliteDb::Fetch(txn_id_t txn_id, return {0}; } else if (*len < n_bytes) { *len = n_bytes; - return result::failure(DbErrorCode::kBufferSmall); + return std::unexpected(DbErrorCode::kBufferSmall); } return {n_bytes}; } -result::result UnqliteDb::Delete(txn_id_t txn_id, +std::expected UnqliteDb::Delete(txn_id_t txn_id, const std::string& key) { int rc; while (true) { @@ -130,17 +130,17 @@ result::result UnqliteDb::Delete(txn_id_t txn_id, std::this_thread::yield(); continue; } - if (rc == UNQLITE_NOTFOUND) return result::failure(DbErrorCode::kNotFound); + if (rc == UNQLITE_NOTFOUND) return std::unexpected(DbErrorCode::kNotFound); CRANE_ERROR("Failed to delete key {} from db: {}", key, GetInternalErrorStr_()); if (rc != UNQLITE_NOTIMPLEMENTED) unqlite_rollback(m_db_); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } -result::result UnqliteDb::Begin() { +std::expected UnqliteDb::Begin() { int rc; while (true) { rc = unqlite_begin(m_db_); @@ -150,11 +150,11 @@ result::result UnqliteDb::Begin() { continue; } CRANE_ERROR("Failed to begin transaction: {}", GetInternalErrorStr_()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } -result::result UnqliteDb::Commit(txn_id_t txn_id) { +std::expected UnqliteDb::Commit(txn_id_t txn_id) { if (txn_id <= 0 || txn_id > s_fixed_txn_id_) return {}; int rc; @@ -166,11 +166,11 @@ result::result UnqliteDb::Commit(txn_id_t txn_id) { continue; } CRANE_ERROR("Failed to commit: {}", GetInternalErrorStr_()); - return result::failure(kOther); + return std::unexpected(kOther); } } -result::result UnqliteDb::Abort(txn_id_t txn_id) { +std::expected UnqliteDb::Abort(txn_id_t txn_id) { if (txn_id <= 0 || txn_id > s_fixed_txn_id_) return {}; int rc; @@ -182,15 +182,15 @@ result::result UnqliteDb::Abort(txn_id_t txn_id) { continue; } CRANE_ERROR("Failed to abort: {}", GetInternalErrorStr_()); - return result::failure(kOther); + return std::unexpected(kOther); } } -result::result UnqliteDb::IterateAllKv(KvIterFunc func) { +std::expected UnqliteDb::IterateAllKv(KvIterFunc func) { int rc; unqlite_kv_cursor* cursor; rc = unqlite_kv_cursor_init(m_db_, &cursor); - if (rc != UNQLITE_OK) return result::failure(kOther); + if (rc != UNQLITE_OK) return std::unexpected(kOther); for (unqlite_kv_cursor_first_entry(cursor); unqlite_kv_cursor_valid_entry(cursor); @@ -234,7 +234,7 @@ std::string UnqliteDb::GetInternalErrorStr_() { #ifdef CRANE_HAVE_BERKELEY_DB -result::result BerkeleyDb::Init(const std::string& path) { +std::expected BerkeleyDb::Init(const std::string& path) { try { std::filesystem::path db_dir{path}; std::filesystem::path env_dir{path + "Env"}; @@ -245,7 +245,7 @@ result::result BerkeleyDb::Init(const std::string& path) { } catch (const std::exception& e) { CRANE_CRITICAL("Invalid berkeley db env home path {}: {}", m_env_home_, e.what()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } m_db_path_ = path; @@ -286,16 +286,16 @@ result::result BerkeleyDb::Init(const std::string& path) { CRANE_ERROR("Failed to init berkeley db: {}", e.what()); } - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } catch (std::exception& e) { CRANE_ERROR("Failed to init berkeley db: {}", e.what()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } return {}; } -result::result BerkeleyDb::Close() { +std::expected BerkeleyDb::Close() { if (m_db_ != nullptr && m_env_ != nullptr) { CRANE_TRACE("Closing berkeley db..."); try { @@ -305,14 +305,14 @@ result::result BerkeleyDb::Close() { } catch (DbException& e) { CRANE_ERROR("Failed to close berkeley db and environment: {}, {}, {}", m_db_path_, m_env_home_, e.what()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } return {}; } -result::result BerkeleyDb::Store(txn_id_t txn_id, +std::expected BerkeleyDb::Store(txn_id_t txn_id, const std::string& key, const void* data, size_t len) { @@ -326,13 +326,13 @@ result::result BerkeleyDb::Store(txn_id_t txn_id, } catch (DbException& e) { CRANE_ERROR("Failed to store key {} into db: {}", key, e.what()); std::ignore = Abort(txn_id); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } return {}; } -result::result BerkeleyDb::Fetch(txn_id_t txn_id, +std::expected BerkeleyDb::Fetch(txn_id_t txn_id, const std::string& key, void* buf, size_t* len) { int rc; @@ -352,27 +352,27 @@ result::result BerkeleyDb::Fetch(txn_id_t txn_id, } catch (DbException& e) { if (e.get_errno() == DB_BUFFER_SMALL) { *len = data_dbt.get_size(); - return result::failure(DbErrorCode::kBufferSmall); + return std::unexpected(DbErrorCode::kBufferSmall); } else { CRANE_ERROR("Failed to get value size for key {}. {}", key, e.what()); std::ignore = Abort(txn_id); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } if (rc != 0) { if (rc == DB_NOTFOUND) - return result::failure(DbErrorCode::kNotFound); + return std::unexpected(DbErrorCode::kNotFound); else { std::ignore = Abort(txn_id); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } } return {data_dbt.get_size()}; } -result::result BerkeleyDb::Delete(txn_id_t txn_id, +std::expected BerkeleyDb::Delete(txn_id_t txn_id, const std::string& key) { DbTxn* txn = GetDbTxnFromId_(txn_id); @@ -381,15 +381,15 @@ result::result BerkeleyDb::Delete(txn_id_t txn_id, int rc = m_db_->del(txn, &key_dbt, 0); if (rc != 0) { CRANE_ERROR("Failed to delete key {} from db.", key); - if (rc == DB_NOTFOUND) return result::failure(DbErrorCode::kNotFound); + if (rc == DB_NOTFOUND) return std::unexpected(DbErrorCode::kNotFound); std::ignore = Abort(txn_id); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } return {}; } -result::result BerkeleyDb::Begin() { +std::expected BerkeleyDb::Begin() { DbTxn* txn = nullptr; try { @@ -399,14 +399,14 @@ result::result BerkeleyDb::Begin() { ); } catch (DbException& e) { CRANE_ERROR("Failed to begin a transaction: {}", e.what()); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } m_txn_map_[txn->id()] = txn; return {txn->id()}; } -result::result BerkeleyDb::Commit(txn_id_t txn_id) { +std::expected BerkeleyDb::Commit(txn_id_t txn_id) { DbTxn* txn = GetDbTxnFromId_(txn_id); try { @@ -414,14 +414,14 @@ result::result BerkeleyDb::Commit(txn_id_t txn_id) { } catch (DbException& e) { CRANE_ERROR("Failed to commit a transaction: {}", e.what()); std::ignore = Abort(txn_id); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } m_txn_map_.erase(txn_id); return {}; } -result::result BerkeleyDb::Abort(txn_id_t txn_id) { +std::expected BerkeleyDb::Abort(txn_id_t txn_id) { DbTxn* txn = GetDbTxnFromId_(txn_id); try { @@ -429,18 +429,18 @@ result::result BerkeleyDb::Abort(txn_id_t txn_id) { } catch (DbException& e) { CRANE_ERROR("Failed to abort a transaction: {}", e.what()); m_txn_map_.erase(txn_id); - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } m_txn_map_.erase(txn_id); return {}; } -result::result BerkeleyDb::IterateAllKv(KvIterFunc func) { +std::expected BerkeleyDb::IterateAllKv(KvIterFunc func) { int rc; Dbc* cursor; if ((rc = m_db_->cursor(nullptr, &cursor, 0)) != 0) { - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); } Dbt key, value; @@ -458,7 +458,7 @@ result::result BerkeleyDb::IterateAllKv(KvIterFunc func) { } rc = cursor->close(); - if (rc) return result::failure(DbErrorCode::kOther); + if (rc) return std::unexpected(DbErrorCode::kOther); return {}; } @@ -481,14 +481,14 @@ DbTxn* BerkeleyDb::GetDbTxnFromId_(txn_id_t txn_id) { EmbeddedDbClient::~EmbeddedDbClient() { if (m_variable_db_) { auto result = m_variable_db_->Close(); - if (result.has_error()) + if (!result) CRANE_ERROR( "Error occurred when closing the embedded db of variable data!"); } if (m_fixed_db_) { auto result = m_fixed_db_->Close(); - if (result.has_error()) + if (!result) CRANE_ERROR("Error occurred when closing the embedded db of fixed data!"); } } @@ -521,9 +521,9 @@ bool EmbeddedDbClient::Init(const std::string& db_path) { } auto result = m_variable_db_->Init(db_path + "var"); - if (result.has_error()) return false; + if (!result) return false; result = m_fixed_db_->Init(db_path + "fix"); - if (result.has_error()) return false; + if (!result) return false; bool ok; @@ -544,7 +544,7 @@ bool EmbeddedDbClient::RetrieveLastSnapshot(DbSnapshot* snapshot) { using TaskStatus = crane::grpc::TaskStatus; using RuntimeAttr = crane::grpc::RuntimeAttrOfTask; - result::result result; + std::expected result; std::unordered_map db_id_runtime_attr_map; result = m_variable_db_->IterateAllKv( @@ -564,7 +564,7 @@ bool EmbeddedDbClient::RetrieveLastSnapshot(DbSnapshot* snapshot) { return true; }); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Failed to restore the variable data into queues"); return false; } @@ -600,7 +600,7 @@ bool EmbeddedDbClient::RetrieveLastSnapshot(DbSnapshot* snapshot) { return true; }); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Failed to restore fixed data into queues!"); return false; } @@ -611,7 +611,7 @@ bool EmbeddedDbClient::RetrieveLastSnapshot(DbSnapshot* snapshot) { bool EmbeddedDbClient::AppendTasksToPendingAndAdvanceTaskIds( const std::vector& tasks) { txn_id_t txn_id; - result::result result; + std::expected result; // Note: In current implementation, this function is called by only // one single thread and the lock here is actually useless. @@ -631,7 +631,7 @@ bool EmbeddedDbClient::AppendTasksToPendingAndAdvanceTaskIds( result = StoreTypeIntoDb_(m_fixed_db_.get(), txn_id, GetFixedDbEntryName_(task->TaskDbId()), &task->TaskToCtld()); - if (result.has_error()) { + if (!result) { CRANE_ERROR( "Failed to store the fixed data of task id: {} / task db id: {}.", task->TaskId(), task->TaskDbId()); @@ -649,7 +649,7 @@ bool EmbeddedDbClient::AppendTasksToPendingAndAdvanceTaskIds( result = StoreTypeIntoDb_(m_variable_db_.get(), txn_id, GetVariableDbEntryName_(task->TaskDbId()), &task->RuntimeAttr()); - if (result.has_error()) { + if (!result) { CRANE_ERROR( "Failed to store the variable data of " "task id: {} / task db id: {}.", @@ -660,14 +660,14 @@ bool EmbeddedDbClient::AppendTasksToPendingAndAdvanceTaskIds( result = StoreTypeIntoDb_(m_variable_db_.get(), txn_id, s_next_task_id_str_, &task_id); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Failed to store next_task_id."); return false; } result = StoreTypeIntoDb_(m_variable_db_.get(), txn_id, s_next_task_db_id_str_, &task_db_id); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Failed to store next_task_db_id."); return false; } @@ -686,12 +686,12 @@ bool EmbeddedDbClient::PurgeEndedTasks(const std::vector& db_ids) { // 2. when erasing task data, fixed data db is erased after variable db; txn_id_t txn_id; - result::result res; + std::expected res; if (!BeginDbTransaction_(m_variable_db_.get(), &txn_id)) return false; for (const auto& id : db_ids) { res = m_variable_db_->Delete(txn_id, GetVariableDbEntryName_(id)); - if (res.has_error()) { + if (!res) { CRANE_ERROR( "Failed to delete embedded variable data entry. Error code: {}", int(res.error())); @@ -703,7 +703,7 @@ bool EmbeddedDbClient::PurgeEndedTasks(const std::vector& db_ids) { if (!BeginDbTransaction_(m_fixed_db_.get(), &txn_id)) return false; for (const auto& id : db_ids) { res = m_fixed_db_->Delete(txn_id, GetFixedDbEntryName_(id)); - if (res.has_error()) { + if (!res) { CRANE_ERROR("Failed to delete embedded fixed data entry. Error code: {}", int(res.error())); return false; diff --git a/src/CraneCtld/EmbeddedDbClient.h b/src/CraneCtld/EmbeddedDbClient.h index 735449d0c..349c58c11 100644 --- a/src/CraneCtld/EmbeddedDbClient.h +++ b/src/CraneCtld/EmbeddedDbClient.h @@ -46,34 +46,34 @@ class IEmbeddedDb { public: virtual ~IEmbeddedDb() = default; - virtual result::result Init(std::string const& path) = 0; + virtual std::expected Init(std::string const& path) = 0; - virtual result::result Close() = 0; + virtual std::expected Close() = 0; - virtual result::result Store(txn_id_t txn_id, - std::string const& key, - const void* data, - size_t len) = 0; + virtual std::expected Store(txn_id_t txn_id, + std::string const& key, + const void* data, + size_t len) = 0; - virtual result::result Fetch(txn_id_t txn_id, - std::string const& key, - void* buf, size_t* len) = 0; + virtual std::expected Fetch(txn_id_t txn_id, + std::string const& key, + void* buf, size_t* len) = 0; - virtual result::result Delete(txn_id_t txn_id, - std::string const& key) = 0; + virtual std::expected Delete(txn_id_t txn_id, + std::string const& key) = 0; - virtual result::result Begin() = 0; + virtual std::expected Begin() = 0; - virtual result::result Commit(txn_id_t txn_id) = 0; + virtual std::expected Commit(txn_id_t txn_id) = 0; - virtual result::result Abort(txn_id_t txn_id) = 0; + virtual std::expected Abort(txn_id_t txn_id) = 0; using KvIterFunc = std::function&& value)>; /// @param func if the return value of func is true, continue to next KV. /// Otherwise, continue to next KV and delete current KV. - virtual result::result IterateAllKv(KvIterFunc func) = 0; + virtual std::expected IterateAllKv(KvIterFunc func) = 0; virtual std::string const& DbPath() = 0; }; @@ -82,29 +82,28 @@ class IEmbeddedDb { class UnqliteDb : public IEmbeddedDb { public: - result::result Init(const std::string& path) override; + std::expected Init(const std::string& path) override; - result::result Close() override; + std::expected Close() override; - result::result Store(txn_id_t txn_id, - const std::string& key, - const void* data, - size_t len) override; + std::expected Store(txn_id_t txn_id, + const std::string& key, + const void* data, size_t len) override; - result::result Fetch(txn_id_t txn_id, - const std::string& key, void* buf, - size_t* len) override; + std::expected Fetch(txn_id_t txn_id, + const std::string& key, void* buf, + size_t* len) override; - result::result Delete(txn_id_t txn_id, - const std::string& key) override; + std::expected Delete(txn_id_t txn_id, + const std::string& key) override; - result::result Begin() override; + std::expected Begin() override; - result::result Commit(txn_id_t txn_id) override; + std::expected Commit(txn_id_t txn_id) override; - result::result Abort(txn_id_t txn_id) override; + std::expected Abort(txn_id_t txn_id) override; - result::result IterateAllKv(KvIterFunc func) override; + std::expected IterateAllKv(KvIterFunc func) override; const std::string& DbPath() override { return m_db_path_; }; @@ -123,29 +122,28 @@ class UnqliteDb : public IEmbeddedDb { class BerkeleyDb : public IEmbeddedDb { public: - result::result Init(const std::string& path) override; + std::expected Init(const std::string& path) override; - result::result Close() override; + std::expected Close() override; - result::result Store(txn_id_t txn_id, - const std::string& key, - const void* data, - size_t len) override; + std::expected Store(txn_id_t txn_id, + const std::string& key, + const void* data, size_t len) override; - result::result Fetch(txn_id_t txn_id, - const std::string& key, void* buf, - size_t* len) override; + std::expected Fetch(txn_id_t txn_id, + const std::string& key, void* buf, + size_t* len) override; - result::result Delete(txn_id_t txn_id, - const std::string& key) override; + std::expected Delete(txn_id_t txn_id, + const std::string& key) override; - result::result Begin() override; + std::expected Begin() override; - result::result Commit(txn_id_t txn_id) override; + std::expected Commit(txn_id_t txn_id) override; - result::result Abort(txn_id_t txn_id) override; + std::expected Abort(txn_id_t txn_id) override; - result::result IterateAllKv(KvIterFunc func) override; + std::expected IterateAllKv(KvIterFunc func) override; const std::string& DbPath() override { return m_db_path_; }; @@ -225,14 +223,15 @@ class EmbeddedDbClient { txn_id_t txn_id, db_id_t db_id, crane::grpc::RuntimeAttrOfTask const& runtime_attr) { return StoreTypeIntoDbIfExists_(m_variable_db_.get(), txn_id, - GetVariableDbEntryName_(db_id), &runtime_attr) + GetVariableDbEntryName_(db_id), + &runtime_attr) .has_value(); } bool UpdateTaskToCtldIfExists(txn_id_t txn_id, db_id_t db_id, - crane::grpc::TaskToCtld const& task_to_ctld) { + crane::grpc::TaskToCtld const& task_to_ctld) { return StoreTypeIntoDbIfExists_(m_fixed_db_.get(), txn_id, - GetFixedDbEntryName_(db_id), &task_to_ctld) + GetFixedDbEntryName_(db_id), &task_to_ctld) .has_value(); } @@ -282,12 +281,12 @@ class EmbeddedDbClient { // Helper functions for basic embedded db operations - inline result::result FetchTaskDataInDbAtomic_( + inline std::expected FetchTaskDataInDbAtomic_( txn_id_t txn_id, db_id_t db_id, TaskInEmbeddedDb* task_in_db) { auto result = FetchTypeFromDb_(m_fixed_db_.get(), txn_id, GetFixedDbEntryName_(db_id), task_in_db->mutable_task_to_ctld()); - if (result.has_error()) return result; + if (!result) return result; return FetchTypeFromDb_(m_variable_db_.get(), txn_id, GetVariableDbEntryName_(db_id), @@ -298,7 +297,7 @@ class EmbeddedDbClient { bool FetchTypeFromVarDbOrInitWithValueNoLockAndTxn_(txn_id_t txn_id, std::string const& key, T* buf, T value) { - result::result fetch_result = + std::expected fetch_result = FetchTypeFromDb_(m_variable_db_.get(), txn_id, key, buf); if (fetch_result.has_value()) return true; @@ -307,9 +306,9 @@ class EmbeddedDbClient { "Key {} not found in embedded db. Initialize it with value {}", key, value); - result::result store_result = + std::expected store_result = StoreTypeIntoDb_(m_variable_db_.get(), txn_id, key, &value); - if (store_result.has_error()) { + if (!store_result) { CRANE_ERROR("Failed to init key '{}' in db.", key); return false; } @@ -322,13 +321,13 @@ class EmbeddedDbClient { } } - result::result FetchTypeFromDb_( + std::expected FetchTypeFromDb_( txn_id_t txn_id, const std::shared_ptr& db, std::string const& key, std::string* buf) { size_t n_bytes{0}; auto result = db->Fetch(txn_id, key, nullptr, &n_bytes); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Unexpected error when fetching the size of string key '{}'", key); return result; @@ -336,7 +335,7 @@ class EmbeddedDbClient { buf->resize(n_bytes); result = db->Fetch(txn_id, key, buf->data(), &n_bytes); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Unexpected error when fetching the data of string key '{}'", key); return result; @@ -345,14 +344,14 @@ class EmbeddedDbClient { return {n_bytes}; } - result::result FetchTypeFromDb_( + std::expected FetchTypeFromDb_( IEmbeddedDb* db, txn_id_t txn_id, std::string const& key, google::protobuf::MessageLite* value) { size_t n_bytes{0}; std::string buf; auto result = db->Fetch(txn_id, key, nullptr, &n_bytes); - if (result.has_error() && result.error() != kBufferSmall) { + if (!result && result.error() != kBufferSmall) { CRANE_ERROR("Unexpected error when fetching the size of proto key '{}'", key); return result; @@ -360,7 +359,7 @@ class EmbeddedDbClient { buf.resize(n_bytes); result = db->Fetch(txn_id, key, buf.data(), &n_bytes); - if (result.has_error()) { + if (!result) { CRANE_ERROR("Unexpected error when fetching the data of proto key '{}'", key); return result; @@ -369,29 +368,29 @@ class EmbeddedDbClient { bool ok = value->ParseFromArray(buf.data(), n_bytes); if (!ok) { CRANE_ERROR("Failed to parse protobuf data of key {}", key); - return result::failure(DbErrorCode::kParsingError); + return std::unexpected(DbErrorCode::kParsingError); } return {n_bytes}; } template - result::result FetchTypeFromDb_(IEmbeddedDb* db, - txn_id_t txn_id, - std::string const& key, - T* buf) { + std::expected FetchTypeFromDb_(IEmbeddedDb* db, + txn_id_t txn_id, + std::string const& key, + T* buf) { size_t n_bytes{sizeof(T)}; auto result = db->Fetch(txn_id, key, buf, &n_bytes); - if (result.has_error() && result.error() != DbErrorCode::kNotFound) + if (!result && result.error() != DbErrorCode::kNotFound) CRANE_ERROR("Unexpected error when fetching scalar key '{}'.", key); return result; } template - result::result StoreTypeIntoDb_(IEmbeddedDb* db, - txn_id_t txn_id, - std::string const& key, - const T* value) + std::expected StoreTypeIntoDb_(IEmbeddedDb* db, + txn_id_t txn_id, + std::string const& key, + const T* value) requires std::derived_from { using google::protobuf::io::CodedOutputStream; @@ -408,15 +407,15 @@ class EmbeddedDbClient { } template - result::result StoreTypeIntoDb_(IEmbeddedDb* db, - txn_id_t txn_id, - std::string const& key, - const T* value) { + std::expected StoreTypeIntoDb_(IEmbeddedDb* db, + txn_id_t txn_id, + std::string const& key, + const T* value) { return db->Store(txn_id, key, value, sizeof(T)); } template - result::result StoreTypeIntoDbIfExists_( + std::expected StoreTypeIntoDbIfExists_( IEmbeddedDb* db, txn_id_t txn_id, const std::string& key, const T* value) requires std::derived_from { @@ -431,44 +430,44 @@ class EmbeddedDbClient { value->SerializeToCodedStream(&codedOutputStream); if (!BeginDbTransaction_(db, &txn_id)) - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); size_t len = 0; auto fetch_result = db->Fetch(txn_id, key, nullptr, &len); - if (fetch_result.has_error()) { + if (!fetch_result) { if (fetch_result.error() == DbErrorCode::kNotFound) { CommitDbTransaction_(db, txn_id); return {}; } - return result::failure(fetch_result.error()); + return std::unexpected(fetch_result.error()); } auto store_result = StoreTypeIntoDb_(db, txn_id, key, value); - if (store_result.has_error()) return result::failure(store_result.error()); + if (!store_result) return std::unexpected(store_result.error()); CommitDbTransaction_(db, txn_id); return {}; } template - result::result StoreTypeIntoDbIfExists_( + std::expected StoreTypeIntoDbIfExists_( IEmbeddedDb* db, txn_id_t txn_id, const std::string& key, const T* value) { if (!BeginDbTransaction_(db, &txn_id)) - return result::failure(DbErrorCode::kOther); + return std::unexpected(DbErrorCode::kOther); size_t len = 0; auto fetch_result = db->Fetch(txn_id, key, nullptr, &len); - if (fetch_result.has_error()) { + if (!fetch_result) { if (fetch_result.error() == DbErrorCode::kNotFound) { CommitDbTransaction_(db, txn_id); return {}; } - return result::failure(fetch_result.error()); + return std::unexpected(fetch_result.error()); } auto store_result = StoreTypeIntoDb_(db, txn_id, key, value); - if (store_result.has_error()) return result::failure(store_result.error()); + if (!store_result) return std::unexpected(store_result.error()); CommitDbTransaction_(db, txn_id); return {}; diff --git a/src/CraneCtld/TaskScheduler.cpp b/src/CraneCtld/TaskScheduler.cpp index e713fd7dc..ffa3025e7 100644 --- a/src/CraneCtld/TaskScheduler.cpp +++ b/src/CraneCtld/TaskScheduler.cpp @@ -1176,7 +1176,7 @@ crane::grpc::CancelTaskReply TaskScheduler::CancelPendingOrRunningTask( // to their own jobs. std::string filter_uname = request.filter_username(); if (filter_uname.empty() && - g_account_manager->CheckUidIsAdmin(operator_uid).has_error()) { + !g_account_manager->CheckUidIsAdmin(operator_uid)) { PasswordEntry entry(operator_uid); filter_uname = entry.Username(); } @@ -2748,7 +2748,7 @@ CraneErr TaskScheduler::AcquireTaskAttributes(TaskInCtld* task) { auto check_qos_result = g_account_manager->CheckAndApplyQosLimitOnTask( task->Username(), task->account, task); - if (check_qos_result.has_error()) { + if (!check_qos_result) { CRANE_ERROR("Failed to call CheckAndApplyQosLimitOnTask: {}", check_qos_result.error()); return CraneErr::kInvalidParam; diff --git a/src/Craned/CMakeLists.txt b/src/Craned/CMakeLists.txt index f8eb17c11..f7d3902bc 100644 --- a/src/Craned/CMakeLists.txt +++ b/src/Craned/CMakeLists.txt @@ -52,7 +52,6 @@ target_link_libraries(craned uvw yaml-cpp - result Backward::Interface diff --git a/src/Craned/CforedClient.cpp b/src/Craned/CforedClient.cpp index 274aa630b..2818c1e62 100644 --- a/src/Craned/CforedClient.cpp +++ b/src/Craned/CforedClient.cpp @@ -21,6 +21,9 @@ #include "crane/String.h" namespace Craned { +using crane::grpc::StreamCforedTaskIOReply; +using crane::grpc::StreamCforedTaskIORequest; + CforedClient::CforedClient() : m_stopped_(false){}; CforedClient::~CforedClient() { @@ -46,14 +49,14 @@ void CforedClient::InitChannelAndStub(const std::string& cfored_name) { } // std::unique_ptr will automatically release the dangling stub. - m_stub_ = CraneForeD::NewStub(m_cfored_channel_); + m_stub_ = crane::grpc::CraneForeD::NewStub(m_cfored_channel_); m_fwd_thread_ = std::thread([this] { AsyncSendRecvThread_(); }); } void CforedClient::CleanOutputQueueAndWriteToStreamThread_( - ClientAsyncReaderWriter* - stream, + grpc::ClientAsyncReaderWriter* stream, std::atomic* write_pending) { CRANE_TRACE("CleanOutputQueueThread started."); std::pair output; @@ -117,7 +120,7 @@ void CforedClient::AsyncSendRecvThread_() { // CRANE_TRACE("NextStatus: {}, ok: {}, Tag received: {}, state: {}", // int(next_status), ok, intptr_t(tag), int(state)); - if (next_status == CompletionQueue::SHUTDOWN) break; + if (next_status == grpc::CompletionQueue::SHUTDOWN) break; // TIMEOUT is like the Idle event in libuv and // thus a context switch of state machine. diff --git a/src/Craned/CforedClient.h b/src/Craned/CforedClient.h index 634dee88a..d61a0ce98 100644 --- a/src/Craned/CforedClient.h +++ b/src/Craned/CforedClient.h @@ -21,21 +21,10 @@ #include "CranedPublicDefs.h" // Precompiled header comes first. -#include - #include "TaskManager.h" namespace Craned { -// Todo: Move using into classes or functions! -using grpc::Channel; -using grpc::ClientAsyncReaderWriter; -using grpc::CompletionQueue; - -using crane::grpc::CraneForeD; -using crane::grpc::StreamCforedTaskIOReply; -using crane::grpc::StreamCforedTaskIORequest; - class CforedClient { template using ConcurrentQueue = moodycamel::ConcurrentQueue; @@ -66,8 +55,9 @@ class CforedClient { }; void CleanOutputQueueAndWriteToStreamThread_( - ClientAsyncReaderWriter* stream, + grpc::ClientAsyncReaderWriter* + stream, std::atomic* write_pending); ConcurrentQueue> m_output_queue_; @@ -75,8 +65,8 @@ class CforedClient { std::atomic m_stopped_{false}; std::string m_cfored_name_; - std::shared_ptr m_cfored_channel_; - std::unique_ptr m_stub_; + std::shared_ptr m_cfored_channel_; + std::unique_ptr m_stub_; // Tag MUST have the same size of void* !!!! enum class Tag : intptr_t { Prepare = 0, Read = 1, Write = 2 }; diff --git a/src/Craned/CranedPreCompiledHeader.h b/src/Craned/CranedPreCompiledHeader.h index 2d318701b..e8a431b5f 100644 --- a/src/Craned/CranedPreCompiledHeader.h +++ b/src/Craned/CranedPreCompiledHeader.h @@ -20,16 +20,13 @@ // Standard Libraries #include -#include #include #include #include #include #include -#include #include #include -#include #include #include #include @@ -44,7 +41,6 @@ #include #include #include -#include #include #include @@ -68,16 +64,18 @@ // Thread pool #include -// Concurrent queue -#include - // gRPC -#include #include +// UVW +#include + // fpm #include +// Concurrent queue +#include + // Include the header which defines the static log level #include "crane/Logger.h" diff --git a/src/Craned/CranedPublicDefs.h b/src/Craned/CranedPublicDefs.h index a2563cd1d..3674aff39 100644 --- a/src/Craned/CranedPublicDefs.h +++ b/src/Craned/CranedPublicDefs.h @@ -23,7 +23,6 @@ #include "crane/OS.h" - namespace Craned { inline constexpr uint64_t kEvSigChldResendMs = 500; diff --git a/src/Craned/TaskManager.h b/src/Craned/TaskManager.h index bee386bc8..2632db685 100644 --- a/src/Craned/TaskManager.h +++ b/src/Craned/TaskManager.h @@ -23,8 +23,6 @@ #include -#include - #include "CgroupManager.h" #include "crane/PasswordEntry.h" #include "protos/Crane.grpc.pb.h" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1a45111a8..7d9f15855 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,3 @@ add_subdirectory(CraneCtld) -add_subdirectory(Craned) -add_subdirectory(SharedTestImpl) add_subdirectory(Utilities) -add_subdirectory(Misc) -add_subdirectory(protos) \ No newline at end of file +add_subdirectory(Misc) \ No newline at end of file diff --git a/test/CraneCtld/CMakeLists.txt b/test/CraneCtld/CMakeLists.txt index b03820762..d0c21ffec 100644 --- a/test/CraneCtld/CMakeLists.txt +++ b/test/CraneCtld/CMakeLists.txt @@ -5,19 +5,6 @@ endif () include(GoogleTest) -add_executable(server_keep_alive_test - server_keep_alive_test.cpp - ) -target_link_libraries(server_keep_alive_test - GTest::gtest GTest::gtest_main - - Utility_PublicHeader - - test_proto - shared_test_impl_lib - ) -gtest_discover_tests(server_keep_alive_test) - add_executable(embedded_db_client_test ${PROJECT_SOURCE_DIR}/src/CraneCtld/CtldPublicDefs.h ${PROJECT_SOURCE_DIR}/src/CraneCtld/EmbeddedDbClient.h @@ -40,9 +27,3 @@ target_link_libraries(embedded_db_client_test ) target_include_directories(embedded_db_client_test PUBLIC ${PROJECT_SOURCE_DIR}/src/CraneCtld) gtest_discover_tests(embedded_db_client_test) - -add_executable(pevents_test PeventsTest.cpp) -target_link_libraries(pevents_test - GTest::gtest GTest::gtest_main - Utility_PublicHeader - pevents) \ No newline at end of file diff --git a/test/CraneCtld/NodeSelectionAlgoTest.cpp b/test/CraneCtld/NodeSelectionAlgoTest.cpp deleted file mode 100644 index cfe6860ed..000000000 --- a/test/CraneCtld/NodeSelectionAlgoTest.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include - -#include "TaskScheduler.h" - -/** Outdated test code. - * Keep it for reference. - -TEST(NodeSelectionAlgo, MinLoadFirst) { // NOLINT(cert-err58-cpp) - using namespace Ctld; - - g_meta_container = std::make_unique(); - const std::string partition_name{"Part1"}; - - uint32_t part_id = g_meta_container->AllocPartitionId(partition_name); - ASSERT_EQ(part_id, 0); - - uint32_t node_index = g_meta_container->AllocNodeIndexInPartition(part_id); - ASSERT_EQ(node_index, 0); - - AllocatableResource node_resource{}; - node_resource.cpu_count = 16; - // unlimited memory for convenience - // Subtract 1000 because we have running tasks. - node_resource.memory_bytes = std::numeric_limits::max() - 1000; - node_resource.memory_sw_bytes = std::numeric_limits::max() - 1000; - - CranedStaticMeta node_static{.craned_index = node_index, - .ipv4_addr = "", - .port = 0, - .node_name = "0", - .partition_id = part_id, - .partition_name = partition_name, - .res = {.allocatable_resource = node_resource}}; - g_meta_container->CranedUp(node_static); - - AllocatableResource allocatable_resource_r1; - allocatable_resource_r1.cpu_count = 3; - Resources resources_r1{allocatable_resource_r1}; - - AllocatableResource allocatable_resource_r2; - allocatable_resource_r2.cpu_count = 3; - Resources resources_r2{allocatable_resource_r2}; - - AllocatableResource allocatable_resource_r3; - allocatable_resource_r3.cpu_count = 2; - Resources resources_r3{allocatable_resource_r3}; - - g_meta_container->MallocResourceFromNode({part_id, node_index}, 1, - resources_r1); - g_meta_container->MallocResourceFromNode({part_id, node_index}, 2, - resources_r2); - g_meta_container->MallocResourceFromNode({part_id, node_index}, 3, - resources_r3); - - auto all_part_meta = g_meta_container->GetAllPartitionsMetaMapPtr(); - - absl::flat_hash_map> running_tasks; - absl::btree_map> pending_tasks; - INodeSelectionAlgo::NodeSelectionResult selection_result; - - auto task_r1 = std::make_unique(); - auto task_r2 = std::make_unique(); - auto task_r3 = std::make_unique(); - - absl::Time now = absl::FromUnixSeconds(ToUnixSeconds(absl::Now())); - task_r1->start_time = now - absl::Seconds(100); - task_r1->time_limit = absl::Seconds(100 + 20); - task_r1->resources = resources_r1; - - task_r2->start_time = now - absl::Seconds(100); - task_r2->time_limit = absl::Seconds(100 + 40); - task_r2->resources = resources_r2; - - task_r3->start_time = now - absl::Seconds(100); - task_r3->time_limit = absl::Seconds(100 + 40); - task_r3->resources = resources_r3; - - task_r1->task_id = 1; - task_r2->task_id = 2; - task_r3->task_id = 3; - task_r1->partition_id = part_id; - task_r2->partition_id = part_id; - task_r3->partition_id = part_id; - running_tasks.emplace(1, std::move(task_r1)); - running_tasks.emplace(2, std::move(task_r2)); - running_tasks.emplace(3, std::move(task_r3)); - - auto task_p1 = std::make_unique(); - auto task_p2 = std::make_unique(); - auto task_p3 = std::make_unique(); - auto task_p4 = std::make_unique(); - auto task_p5 = std::make_unique(); - auto task_p6 = std::make_unique(); - auto task_p7 = std::make_unique(); - auto task_p8 = std::make_unique(); - - AllocatableResource allocatable_resource_p1; - allocatable_resource_p1.cpu_count = 11; - Resources resources_p1{allocatable_resource_p1}; - task_p1->resources = resources_p1; - - AllocatableResource allocatable_resource_p2; - allocatable_resource_p2.cpu_count = 3; - Resources resources_p2{allocatable_resource_p2}; - task_p2->resources = resources_p2; - - AllocatableResource allocatable_resource_p3; - allocatable_resource_p3.cpu_count = 2; - Resources resources_p3{allocatable_resource_p3}; - task_p3->resources = resources_p3; - - AllocatableResource allocatable_resource_p4; - allocatable_resource_p4.cpu_count = 2; - Resources resources_p4{allocatable_resource_p4}; - task_p4->resources = resources_p4; - - AllocatableResource allocatable_resource_p5; - allocatable_resource_p5.cpu_count = 3; - Resources resources_p5{allocatable_resource_p5}; - task_p5->resources = resources_p5; - - AllocatableResource allocatable_resource_p6; - allocatable_resource_p6.cpu_count = 14; - Resources resources_p6{allocatable_resource_p6}; - task_p6->resources = resources_p6; - - AllocatableResource allocatable_resource_p7; - allocatable_resource_p7.cpu_count = 6; - Resources resources_p7{allocatable_resource_p7}; - task_p7->resources = resources_p7; - - AllocatableResource allocatable_resource_p8; - allocatable_resource_p8.cpu_count = 2; - Resources resources_p8{allocatable_resource_p8}; - task_p8->resources = resources_p8; - - task_p1->time_limit = absl::Seconds(29); - task_p2->time_limit = absl::Seconds(100); - task_p3->time_limit = absl::Seconds(120); - task_p4->time_limit = absl::Seconds(23); - task_p5->time_limit = absl::Seconds(10); - task_p6->time_limit = absl::Seconds(20); - task_p7->time_limit = absl::Seconds(9); - task_p8->time_limit = absl::Seconds(20); - - task_p1->task_id = 11; - task_p2->task_id = 12; - task_p3->task_id = 13; - task_p4->task_id = 14; - task_p5->task_id = 15; - task_p6->task_id = 16; - task_p7->task_id = 17; - task_p8->task_id = 18; - task_p1->partition_id = part_id; - task_p2->partition_id = part_id; - task_p3->partition_id = part_id; - task_p4->partition_id = part_id; - task_p5->partition_id = part_id; - task_p6->partition_id = part_id; - task_p7->partition_id = part_id; - task_p8->partition_id = part_id; - pending_tasks.emplace(11, std::move(task_p1)); - pending_tasks.emplace(12, std::move(task_p2)); - pending_tasks.emplace(13, std::move(task_p3)); - pending_tasks.emplace(14, std::move(task_p4)); - pending_tasks.emplace(15, std::move(task_p5)); - pending_tasks.emplace(16, std::move(task_p6)); - pending_tasks.emplace(17, std::move(task_p7)); - pending_tasks.emplace(18, std::move(task_p8)); - - std::unique_ptr algo = std::make_unique(); - std::list result; - algo->NodeSelect(*all_part_meta, running_tasks, &pending_tasks, &result); - - // Two runnable pending task: P5 and P8 - ASSERT_EQ(result.size(), 2); - - auto iter = result.begin(); - ASSERT_EQ(iter->first->task_id, 15); - ASSERT_EQ(iter->first->start_time - now, absl::Seconds(0)); - - std::advance(iter, 1); - ASSERT_EQ(iter->first->task_id, 18); - ASSERT_EQ(iter->first->start_time - now, absl::Seconds(0)); -} - - */ \ No newline at end of file diff --git a/test/CraneCtld/PeventsTest.cpp b/test/CraneCtld/PeventsTest.cpp deleted file mode 100644 index cecc4ef5f..000000000 --- a/test/CraneCtld/PeventsTest.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include -#include - -namespace pevents { -using namespace neosmart; - -using event_t = neosmart_event_t; -} // namespace pevents - -TEST(PEVENTS, Simple) { - pevents::event_t ev_a = pevents::CreateEvent(true); - pevents::event_t ev_b = pevents::CreateEvent(); - std::array evs{ev_a, ev_b}; - - pevents::SetEvent(ev_a); - - int index; - int i = 0; - while (true) { - pevents::WaitForMultipleEvents(evs.data(), evs.size(), false, - pevents::WAIT_INFINITE, index); - GTEST_LOG_(INFO) << fmt::format("Event #{} triggered!", index); - if (++i >= 3) break; - } -} \ No newline at end of file diff --git a/test/CraneCtld/XdNodeKeeper_test.cpp b/test/CraneCtld/XdNodeKeeper_test.cpp deleted file mode 100644 index 258771fec..000000000 --- a/test/CraneCtld/XdNodeKeeper_test.cpp +++ /dev/null @@ -1,747 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "CranedKeeper.h" -#include "CranedServer.h" -#include "SharedTestImpl/GlobalDefs.h" -#include "crane/OS.h" - -using testing::_; -using testing::AnyOf; -using testing::AtLeast; -using testing::Invoke; -using testing::InvokeWithoutArgs; -using testing::Sequence; - -namespace bi = boost::interprocess; - -#define RED "\033[0;31m" -#define RESET "\033[0m" - -/** Outdated test code. - * Keep it for reference. - -class MockCtldServer { - public: - MOCK_METHOD(void, CranedIsUpCb, (CranedId, void*)); - MOCK_METHOD(void, CranedIsDownCb, (CranedId, void*)); - MOCK_METHOD(void, CranedIsTempDownCb, (CranedId, void*)); - MOCK_METHOD(void, CranedRecFromTempFailureCb, (CranedId, void*)); -}; - -class CranedKeeperTest : public ::testing::Test { - public: - void SetUp() override { - g_task_mgr = std::make_unique(); - - m_mock_ctld_server_ = std::make_unique(); - - m_keeper_ = std::make_unique(); - m_keeper_->SetCranedIsUpCb( - std::bind(&MockCtldServer::CranedIsUpCb, m_mock_ctld_server_.get(), - std::placeholders::_1, std::placeholders::_2)); - m_keeper_->SetCranedIsDownCb( - std::bind(&MockCtldServer::CranedIsDownCb, m_mock_ctld_server_.get(), - std::placeholders::_1, std::placeholders::_2)); - m_keeper_->SetCranedTempDownCb(std::bind( - &MockCtldServer::CranedIsTempDownCb, m_mock_ctld_server_.get(), - std::placeholders::_1, std::placeholders::_2)); - m_keeper_->SetCranedRecFromTempFailureCb( - std::bind(&MockCtldServer::CranedRecFromTempFailureCb, - m_mock_ctld_server_.get(), std::placeholders::_1, - std::placeholders::_2)); - } - - void TearDown() override { - if (m_keeper_) m_keeper_.reset(); - m_mock_ctld_server_.reset(); - g_task_mgr->Shutdown(); - g_task_mgr.reset(); - } - - std::unique_ptr m_mock_ctld_server_; - std::unique_ptr m_keeper_; -}; - -TEST_F(CranedKeeperTest, FailToConnect) { - std::string server_addr{"127.0.0.1:50011"}; - - AllocatableResource res; - res.cpu_count = 10; - res.memory_bytes = 1024 * 1024 * 1024; - res.memory_sw_bytes = 1024 * 1024 * 1024; - - auto result_future = - m_keeper_->RegisterCraned(server_addr, CranedId{0, 0}, &res, nullptr); - Ctld::RegisterNodeResult result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), false); -} - -TEST_F(CranedKeeperTest, OneStub_OneAbortedServer) { - using Craned::CranedServer; - using namespace std::chrono_literals; - - std::string server_addr{"127.0.0.1:50011"}; - - AllocatableResource res; - res.cpu_count = 10; - res.memory_bytes = 1024 * 1024 * 1024; - res.memory_sw_bytes = 1024 * 1024 * 1024; - - std::atomic_bool has_exited = false; - - auto xd_server = std::make_unique(server_addr); - std::this_thread::sleep_for(1s); - - ON_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Up", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Temp Down", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Down", node_id); - has_exited = true; - })); - - Sequence seq; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - - auto result_future = - m_keeper_->RegisterCraned(server_addr, CranedId{0, 0}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - Ctld::RegisterNodeResult result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 0})); - } - - std::this_thread::sleep_for(3s); - xd_server->Shutdown(); - xd_server.reset(); - - while (!has_exited) { - std::this_thread::yield(); - } -} - -// Note: we use sleep() here to provide synchronization. However, valgrind may -// slow the execution down too much and cause the test to fail. -// -// Todo: Consider to use barrier or condition variable. -// -// The server is gracefully shut down. In such case, a GOAWAY message will be -// send. READY -> IDLE transition is expected. -// e.g. Shutdown by the user command. -TEST_F(CranedKeeperTest, OneStub_OneTempDownServer) { - using Craned::CranedServer; - using namespace std::chrono_literals; - - std::atomic_bool has_exited = false; - - std::string server_addr{"127.0.0.1:50011"}; - - AllocatableResource res; - res.cpu_count = 10; - res.memory_bytes = 1024 * 1024 * 1024; - res.memory_sw_bytes = 1024 * 1024 * 1024; - - ON_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Up", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Temp Down", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), - CranedRecFromTempFailureCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} recovered from temporary failure", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Down", node_id); - has_exited = true; - })); - - Sequence seq; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), - CranedRecFromTempFailureCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - - auto xd_server = std::make_unique(server_addr); - std::this_thread::sleep_for(1s); - - auto result_future = - m_keeper_->RegisterCraned(server_addr, CranedId{0, 0}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - Ctld::RegisterNodeResult result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 0})); - } - - std::this_thread::sleep_for(2s); - xd_server->Shutdown(); - xd_server.reset(); - - xd_server = std::make_unique(server_addr); - std::this_thread::sleep_for(2s); - xd_server->Shutdown(); - xd_server.reset(); - - while (!has_exited) { - std::this_thread::yield(); - } -} - -// Note: Valgrind will conflict with pthread_barrier in such an abort case. -// It may be caused by the bugs of pthread library or this test. -// This test SHOULD be disabled when using valgrind. -// See https://valgrind.org/docs/manual/hg-manual.html -// -// Todo: Check whether this case is buggy on pthread_barrier misuse. (using -// Helgrind) -// -// The server is aborted. In such case, a GOAWAY message will never be -// send. -// e.g. Shutdown by power failure. -TEST_F(CranedKeeperTest, OneStub_OneTempAbortedServer) { - using Craned::CranedServer; - using namespace std::chrono_literals; - - util::SetCloseOnExecFromFd(STDERR_FILENO + 1); - - std::atomic_bool has_exited = false; - - boost::fibers::barrier xd_rec_barrier(2); - - ON_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Up", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Temp Down", node_id); - })); - - ON_CALL((*m_mock_ctld_server_), - CranedRecFromTempFailureCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} recovered from temporary failure", node_id); - xd_rec_barrier.wait(); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE("Node {} is Down", node_id); - has_exited = true; - })); - - Sequence seq; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), - CranedRecFromTempFailureCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq); - - std::string server_addr{"127.0.0.1:50011"}; - - AllocatableResource res; - res.cpu_count = 10; - res.memory_bytes = 1024 * 1024 * 1024; - res.memory_sw_bytes = 1024 * 1024 * 1024; - - // Start server and client connects it. - { - struct TestIpc { - TestIpc() : init_barrier(2), terminate_barrier(2) {} - - bi::barrier init_barrier; - bi::barrier terminate_barrier; - }; - - bi::mapped_region region(bi::anonymous_shared_memory(sizeof(TestIpc))); - TestIpc* ipc = new (region.get_address()) TestIpc; - - pid_t child_pid = fork(); - if (child_pid == 0) { // Child - auto xd_server = std::make_unique(server_addr); - ipc->init_barrier.wait(); - - ipc->terminate_barrier.wait(); - std::terminate(); - } else { - ipc->init_barrier.wait(); - auto result_future = - m_keeper_->RegisterCraned(server_addr, CranedId{0, 0}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - Ctld::RegisterNodeResult result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 0})); - } - - ipc->terminate_barrier.wait(); - int stat; - wait(&stat); - } - // ipc is destructed here. - } - - // Restart server and wait for the client to reconnect. - { - struct TestIpc { - TestIpc() : terminate_barrier(2) {} - - bi::barrier terminate_barrier; - }; - - bi::mapped_region region(bi::anonymous_shared_memory(sizeof(TestIpc))); - TestIpc* ipc = new (region.get_address()) TestIpc; - - pid_t child_pid = fork(); - if (child_pid == 0) { // Child - auto xd_server = std::make_unique(server_addr); - - (void)xd_server.get(); - ipc->terminate_barrier.wait(); - std::terminate(); - } else { - // Wait for the client to reconnect the server. - xd_rec_barrier.wait(); - - // Let child process terminate. - ipc->terminate_barrier.wait(); - - int stat; - wait(&stat); - } - // ipc is destructed here. - } - - while (!has_exited) { - std::this_thread::yield(); - } -} - -TEST_F(CranedKeeperTest, TwoStubs_TwoTempDownServers) { - using Craned::CranedServer; - using namespace std::chrono_literals; - using testing::AnyOf; - - std::string server_addr_0{"127.0.0.1:50011"}; - std::string server_addr_1{"127.0.0.1:50012"}; - - AllocatableResource res; - res.cpu_count = 10; - res.memory_bytes = 1024 * 1024 * 1024; - res.memory_sw_bytes = 1024 * 1024 * 1024; - - std::unique_ptr init_barrier_0; - std::unique_ptr terminate_barrier_0; - std::unique_ptr terminate_barrier_1; - std::unique_ptr exit_barrier_all; - - std::atomic_uint disconnected_count = 0; - std::atomic_uint exit_count = 0; - - init_barrier_0 = std::make_unique(2); - terminate_barrier_0 = std::make_unique(2); - terminate_barrier_1 = std::make_unique(2); - - ON_CALL((*m_mock_ctld_server_), - CranedIsUpCb(AnyOf(CranedId{0, 0}, CranedId{0, 1}), _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE(RED "Node {} is Up" RESET, node_id); - - if (node_id == CranedId{0, 0}) { - terminate_barrier_0->wait(); - } else { - terminate_barrier_1->wait(); - } - })); - - ON_CALL((*m_mock_ctld_server_), - CranedIsTempDownCb(AnyOf(CranedId{0, 0}, CranedId{0, 1}), _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE(RED "Node {} is Temp Down" RESET, node_id); - disconnected_count++; - })); - - ON_CALL((*m_mock_ctld_server_), - CranedRecFromTempFailureCb(CranedId{0, 0}, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE(RED "Node {} recovered from temporary failure" RESET, - node_id); - terminate_barrier_0->wait(); - })); - - ON_CALL((*m_mock_ctld_server_), - CranedIsDownCb(AnyOf(CranedId{0, 0}, CranedId{0, 1}), _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - CRANE_TRACE(RED "Node {} is Down" RESET, node_id); - exit_count++; - })); - - Sequence seq_0; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - EXPECT_CALL((*m_mock_ctld_server_), - CranedRecFromTempFailureCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - - Sequence seq_1; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 1}, _)) - .Times(1) - .InSequence(seq_1); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 1}, _)) - .Times(1) - .InSequence(seq_1); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 1}, _)) - .Times(1) - .InSequence(seq_1); - - std::thread t0([&] { - auto xd_server = std::make_unique(server_addr_0); - init_barrier_0->wait(); - - // Wait for client stub 0 to connect. - terminate_barrier_0->wait(); - xd_server->Shutdown(); - }); - - // Wait for server 0 initialization. - init_barrier_0->wait(); - - auto result_future = - m_keeper_->RegisterCraned(server_addr_0, CranedId{0, 0}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - // Wait for Xd Node 0 registration result. - Ctld::RegisterNodeResult result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 0})); - } - - t0.join(); - - std::thread t1([&] { - auto xd_server = std::make_unique(server_addr_1); - - // Wait for client stub 1 to connect. - terminate_barrier_1->wait(); - xd_server->Shutdown(); - }); - - result_future = - m_keeper_->RegisterCraned(server_addr_1, CranedId{0, 1}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - // Wait for Xd Node 1 registration result. - result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 1})); - } - - t1.join(); - - // Wait for Xd Node 0,1 to encounter temporary failure. - while (disconnected_count < 2) std::this_thread::yield(); - terminate_barrier_0 = std::make_unique(2); - - std::thread t0_restart([&] { - auto xd_server = std::make_unique(server_addr_0); - - // Wait for client stub 0 to re-connect. - terminate_barrier_0->wait(); - xd_server->Shutdown(); - }); - t0_restart.join(); - - while (exit_count < 2) std::this_thread::yield(); -} - -TEST_F(CranedKeeperTest, CheckReuseOfSlot) { - using Craned::CranedServer; - using namespace std::chrono_literals; - - std::string server_addr_0{"127.0.0.1:50011"}; - std::string server_addr_1{"127.0.0.1:50012"}; - std::string server_addr_2{"127.0.0.1:50013"}; - - AllocatableResource res; - res.cpu_count = 10; - res.memory_bytes = 1024 * 1024 * 1024; - res.memory_sw_bytes = 1024 * 1024 * 1024; - - std::vector> terminate_barriers; - for (int i = 0; i < 3; i++) - terminate_barriers.emplace_back( - std::make_unique(2)); - - auto restart_barrier_1 = std::make_unique(2); - bool has_restarted_1 = false; - uint start_count = 0; - - std::atomic_uint exit_count = 0; - - ON_CALL((*m_mock_ctld_server_), CranedIsUpCb(_, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - ASSERT_THAT(node_id, - AnyOf(CranedId{0, 0}, CranedId{0, 1}, CranedId{0, 2})); - - CRANE_TRACE(RED "Node {} is Up" RESET, node_id); - - start_count++; - if (start_count >= 3 && !has_restarted_1) { - CRANE_TRACE(RED "Terminate Node #1 ..." RESET); - terminate_barriers[1]->wait(); - } else if (has_restarted_1) { - for (auto&& i : {0, 1, 2}) terminate_barriers[i]->wait(); - } - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(_, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - ASSERT_THAT(node_id, - AnyOf(CranedId{0, 0}, CranedId{0, 1}, CranedId{0, 2})); - CRANE_TRACE(RED "Node {} is Temp Down" RESET, node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedRecFromTempFailureCb(_, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - ASSERT_THAT(node_id, - AnyOf(CranedId{0, 0}, CranedId{0, 1}, CranedId{0, 2})); - CRANE_TRACE(RED "Node {} recovered from temporary failure" RESET, - node_id); - })); - - ON_CALL((*m_mock_ctld_server_), CranedIsDownCb(_, _)) - .WillByDefault(Invoke([&](CranedId node_id, void*) { - ASSERT_THAT(node_id, - AnyOf(CranedId{0, 0}, CranedId{0, 1}, CranedId{0, 2})); - CRANE_TRACE(RED "Node #{} is Down" RESET, node_id); - if (!has_restarted_1 && node_id == CranedId{0, 1}) { - CRANE_TRACE(RED "Restarting Node (0,1) ..." RESET); - restart_barrier_1->wait(); - has_restarted_1 = true; - } else if (has_restarted_1) { - exit_count++; - } - })); - - Sequence seq_0; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 0}, _)) - .Times(1) - .InSequence(seq_0); - - Sequence seq_2; - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 2}, _)) - .Times(1) - .InSequence(seq_2); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 2}, _)) - .Times(1) - .InSequence(seq_2); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 2}, _)) - .Times(1) - .InSequence(seq_2); - - Sequence seq_1; - for (int i = 0; i < 2; i++) { - EXPECT_CALL((*m_mock_ctld_server_), CranedIsUpCb(CranedId{0, 1}, _)) - .Times(1) - .InSequence(seq_1); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsTempDownCb(CranedId{0, 1}, _)) - .Times(1) - .InSequence(seq_1); - EXPECT_CALL((*m_mock_ctld_server_), CranedIsDownCb(CranedId{0, 1}, _)) - .Times(1) - .InSequence(seq_1); - } - - std::thread t0([&] { - auto xd_server = std::make_unique(server_addr_0); - terminate_barriers[0]->wait(); - xd_server->Shutdown(); - }); - - // Server 0 and 2 serve as the slot occupier and they will be shut down at the - // same time. - std::thread t2([&] { - auto xd_server = std::make_unique(server_addr_2); - terminate_barriers[2]->wait(); - xd_server->Shutdown(); - }); - - std::thread t1; - t1 = std::thread([&] { - auto xd_server = std::make_unique(server_addr_1); - terminate_barriers[1]->wait(); - xd_server->Shutdown(); - }); - - std::future result_future; - Ctld::RegisterNodeResult result; - - result_future = - m_keeper_->RegisterCraned(server_addr_0, CranedId{0, 0}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 0})); - } - - result_future = - m_keeper_->RegisterCraned(server_addr_1, CranedId{0, 1}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 1})); - } - - result_future = - m_keeper_->RegisterCraned(server_addr_2, CranedId{0, 2}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 2})); - } - - t1.join(); - restart_barrier_1->wait(); - - terminate_barriers[1] = std::make_unique(2); - t1 = std::thread([&] { - auto xd_server = std::make_unique(server_addr_1); - terminate_barriers[1]->wait(); - xd_server->Shutdown(); - }); - - result_future = - m_keeper_->RegisterCraned(server_addr_1, CranedId{0, 1}, &res, nullptr); - result_future.wait(); - ASSERT_EQ(result_future.valid(), true); - - result = result_future.get(); - EXPECT_EQ(result.node_id.has_value(), true); - if (result.node_id.has_value()) { - EXPECT_EQ(result.node_id.value(), (CranedId{0, 1})); - } - - while (exit_count < 3) { - std::this_thread::yield(); - } - - t0.join(); - t1.join(); - t2.join(); -} - - */ \ No newline at end of file diff --git a/test/CraneCtld/server_keep_alive_test.cpp b/test/CraneCtld/server_keep_alive_test.cpp deleted file mode 100644 index ba0c92255..000000000 --- a/test/CraneCtld/server_keep_alive_test.cpp +++ /dev/null @@ -1,280 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "SharedTestImpl/greeter_service_impl.h" -#include "crane/OS.h" -#include "crane/PublicHeader.h" -#include "protos/greeter.grpc.pb.h" -#include "protos/greeter.pb.h" - -using grpc::Channel; - -using grpc_example::Greeter; -using grpc_example::HelloReply; -using grpc_example::HelloRequest; - -using namespace std::chrono_literals; - -std::string_view GrpcConnStateStr(grpc_connectivity_state state) { - switch (state) { - case grpc_connectivity_state::GRPC_CHANNEL_IDLE: - return "IDLE"; - case grpc_connectivity_state::GRPC_CHANNEL_CONNECTING: - return "CONNECTING"; - case grpc_connectivity_state::GRPC_CHANNEL_READY: - return "READY"; - case grpc_connectivity_state::GRPC_CHANNEL_TRANSIENT_FAILURE: - return "TRANSIENT_FAILURE"; - case grpc_connectivity_state::GRPC_CHANNEL_SHUTDOWN: - return "SHUTDOWN"; - default: - return "UNKNOWN"; - } -} - -/** - * A simple class as the demo of a node state machine. - */ -class KeepAliveGreeterClient { - public: - KeepAliveGreeterClient( - const std::string& server_addr, - std::function - state_change_cb) - : m_cq_closed_(false), - m_state_change_cb_(state_change_cb), - m_failure_retry_times_(0) { - // These KeepAlive args seem useless right now, so we do not merge the - // following lines into the actual code. - grpc::ChannelArguments channel_args; - channel_args.SetInt(GRPC_ARG_KEEPALIVE_TIME_MS, 5 /*s*/ * 1000 /*ms*/); - channel_args.SetInt(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 10 /*s*/ * 1000 /*ms*/); - channel_args.SetInt(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1 /*true*/); - - m_channel_ = grpc::CreateCustomChannel( - server_addr, grpc::InsecureChannelCredentials(), channel_args); - - m_last_grpc_conn_state_ = m_channel_->GetState(true); - CRANE_INFO("Channel initial conn state: {}", - GrpcConnStateStr(m_last_grpc_conn_state_)); - - using namespace std::chrono_literals; - m_channel_->NotifyOnStateChange(m_last_grpc_conn_state_, - std::chrono::system_clock::now() + 3s, - &m_cq_, nullptr); - - m_stub_ = Greeter::NewStub(m_channel_); - - m_state_monitor_thread_ = - std::thread(&KeepAliveGreeterClient::StateMonitorThreadFunc, this); - } - - std::string SayHello(const std::string& user) { - HelloRequest request; - request.set_name(user); - - HelloReply reply; - - grpc::ClientContext context; - - grpc::Status status = m_stub_->SayHello(&context, request, &reply); - - if (status.ok()) { - return reply.message(); - } else { - CRANE_INFO("{}:{}", status.error_code(), status.error_message()); - return "RPC failed"; - } - } - - ~KeepAliveGreeterClient() { - m_cq_mtx_.lock(); - - m_cq_.Shutdown(); - m_cq_closed_ = true; - - m_cq_mtx_.unlock(); - - m_state_monitor_thread_.join(); - } - - private: - void StateMonitorThreadFunc() { - CRANE_INFO("StateMonitorThread started..."); - - bool ok; - void* tag; - - while (true) { - if (m_cq_.Next(&tag, &ok)) { - grpc_connectivity_state new_state = m_channel_->GetState(true); - - if (m_last_grpc_conn_state_ != new_state) { - if (m_state_change_cb_) { - m_state_change_cb_(m_last_grpc_conn_state_, new_state); - } - m_last_grpc_conn_state_ = new_state; - } - - m_cq_mtx_.lock(); - if (!m_cq_closed_) // When cq is closed, do not register more callbacks - // on it. - m_channel_->NotifyOnStateChange(m_last_grpc_conn_state_, - std::chrono::system_clock::now() + 3s, - &m_cq_, nullptr); - m_cq_mtx_.unlock(); - } else - break; - } - - CRANE_INFO("StateMonitorThread is exiting..."); - } - - grpc_connectivity_state m_last_grpc_conn_state_; - - grpc::CompletionQueue m_cq_; - std::mutex m_cq_mtx_; - bool m_cq_closed_; - - uint32_t m_failure_retry_times_; - - std::thread m_state_monitor_thread_; - - std::function - m_state_change_cb_; - - std::shared_ptr m_channel_; - std::unique_ptr m_stub_; -}; - -TEST(KeepAlive, ServerNormallyExit) { - std::string server_addr("localhost:50022"); - - auto state_change_cb = [](grpc_connectivity_state old_state, - grpc_connectivity_state new_state) { - CRANE_INFO("Underlying conn state changed: {} -> {}", - GrpcConnStateStr(old_state), GrpcConnStateStr(new_state)); - }; - - auto server = std::make_unique(server_addr); - auto client = - std::make_unique(server_addr, state_change_cb); - - std::string reply = client->SayHello("Riley"); - CRANE_INFO("SayHello(Riley) -> {}", reply); - - std::this_thread::sleep_for(10s); - - server->Shutdown(); - server->Wait(); - - std::this_thread::sleep_for(20s); - - client.reset(); -} - -TEST(KeepAlive, ServerAborts) { - std::string server_addr("localhost:50022"); - - pid_t pid = fork(); - - if (pid > 0) { - auto state_change_cb = [](grpc_connectivity_state old_state, - grpc_connectivity_state new_state) { - CRANE_INFO("Underlying conn state changed: {} -> {}", - GrpcConnStateStr(old_state), GrpcConnStateStr(new_state)); - }; - - auto client = - std::make_unique(server_addr, state_change_cb); - - std::this_thread::sleep_for(6s); - - std::string reply = client->SayHello("Riley"); - CRANE_INFO("SayHello(Riley) -> {}", reply); - - EXPECT_EQ(reply, "RPC failed"); - - std::this_thread::sleep_for(6s); - - client.reset(); - - int stat, wait_pid; - wait_pid = wait(&stat); - - EXPECT_EQ(WIFSIGNALED(stat), 1); - EXPECT_EQ(wait_pid, pid); - } else { - auto server = std::make_unique(server_addr); - - std::this_thread::sleep_for(3s); - - std::abort(); - } -} - -using grpc::Channel; -using grpc_example::Greeter; - -TEST(KeepAlive, FreeStubWhenCallingRpc) { - using namespace std::chrono_literals; - - std::string server_addr("localhost:50011"); - - pid_t child_pid = fork(); - if (child_pid == 0) { - auto greeter_server = std::make_unique(server_addr); - - std::this_thread::sleep_for(3s); - - (void)greeter_server.get(); - util::CloseFdFrom(0); - - std::terminate(); - } else { - auto channel = - grpc::CreateChannel(server_addr, grpc::InsecureChannelCredentials()); - auto stub = Greeter::NewStub(channel); - - using grpc::ClientContext; - using grpc_example::SleepReply; - using grpc_example::SleepRequest; - - ClientContext context; - SleepRequest request; - SleepReply reply; - - request.set_seconds(10); - grpc::Status status = stub->SleepSeconds(&context, request, &reply); - - EXPECT_EQ(status.error_code(), grpc::StatusCode::UNAVAILABLE); - - int stat; - wait(&stat); - } -} diff --git a/test/Craned/CMakeLists.txt b/test/Craned/CMakeLists.txt deleted file mode 100644 index 7afbac1bc..000000000 --- a/test/Craned/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -add_executable(craned_test - ${CMAKE_SOURCE_DIR}/src/Craned/TaskManager.cpp - ${CMAKE_SOURCE_DIR}/src/Craned/CgroupManager.cpp - ${CMAKE_SOURCE_DIR}/src/Craned/CtldClient.cpp - TaskManager_test.cpp) -target_link_libraries(craned_test - GTest::gtest - GTest::gtest_main - concurrentqueue - spdlog::spdlog - event_core_static - event_pthreads_static - cxxopts - bs_thread_pool - - Threads::Threads - - Utility_cgroup - Utility_PublicHeader - Utility_AnonymousPipe - crane_proto_lib - - test_proto - shared_test_impl_lib -) - -include(GoogleTest) -gtest_discover_tests(craned_test) \ No newline at end of file diff --git a/test/Craned/TaskManager_test.cpp b/test/Craned/TaskManager_test.cpp deleted file mode 100644 index 222ad91dc..000000000 --- a/test/Craned/TaskManager_test.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include "../../src/Craned/TaskManager.h" - -#include - -#include "SharedTestImpl/GlobalDefs.h" -#include "gtest/gtest.h" -#include "crane/PublicHeader.h" - -using namespace Craned; - -static std::string RandomFileNameStr() { - static std::random_device rd; - static std::mt19937 mt(rd()); - static std::uniform_int_distribution dist(100000, 999999); - - return std::to_string(dist(mt)); -} - -static std::string GenerateTestProg(const std::string& prog_text) { - std::string test_prog_path = "/tmp/craned_test_" + RandomFileNameStr(); - std::string cmd; - - cmd = fmt::format(R"(bash -c 'echo -e '"'"'{}'"'" | g++ -xc++ -o {} -)", - prog_text, test_prog_path); - system(cmd.c_str()); - - return test_prog_path; -} - -static void RemoveTestProg(const std::string& test_prog_path) { - // Cleanup - if (remove(test_prog_path.c_str()) != 0) - CRANE_ERROR("Error removing test_prog:", strerror(errno)); -} - -class TaskManagerTest : public testing::Test { - public: - void SetUp() override { - g_task_mgr = std::make_unique(); - } - - void TearDown() override { g_task_mgr.reset(); } -}; - -/** Outdated test code. - * Keep it for reference. - -TEST_F(TaskManagerTest, NormalExit) { - spdlog::set_level(spdlog::level::trace); - std::string prog_text = - "#include \\n" - "#include \\n" - "#include \\n" - "int main() { std::cout<<\"Hello World!\";" - "return 1;" - "}"; - - std::string test_prog_path = GenerateTestProg(prog_text); - - auto output_callback = [&](std::string&& buf) { - CRANE_DEBUG("Output from callback: {}", buf); - - EXPECT_EQ(buf, "Hello World!"); - }; - - auto finish_callback = [&](bool is_terminated_by_signal, int value) { - CRANE_DEBUG("Task ended. Normal exit: {}. Value: {}", - !is_terminated_by_signal, value); - - EXPECT_EQ(is_terminated_by_signal, false); - EXPECT_EQ(value, 1); - }; - - Craned::TaskInitInfo info{ - "RileyTest", test_prog_path, {}, - {.cpu_core_limit = 2}, output_callback, finish_callback, - }; - - CraneErr err = g_task_mgr->DeprecatedAddTaskAsync__(std::move(info)); - CRANE_TRACE("err value: {}, reason: {}", uint64_t(err), CraneErrStr(err)); - - using namespace std::chrono_literals; - std::this_thread::sleep_for(2s); - - // Emulate ctrl+C - kill(getpid(), SIGINT); - - g_task_mgr->Wait(); - - CRANE_TRACE("Exiting test..."); - - RemoveTestProg(test_prog_path); -} - -TEST_F(TaskManagerTest, SigintTermination) { - spdlog::set_level(spdlog::level::trace); - std::string prog_text = - "#include \\n" - "#include \\n" - "#include \\n" - "int main() { std::cout<<\"Hello World!\";" - "std::this_thread::sleep_for(std::chrono::seconds(10));" - "return 1;" - "}"; - - CraneErr err; - - std::string test_prog_path = GenerateTestProg(prog_text); - - auto output_callback = [&](std::string&& buf) { - CRANE_DEBUG("Output from callback: {}", buf); - - EXPECT_EQ(buf, "Hello World!"); - }; - - auto finish_callback = [&](bool is_terminated_by_signal, int value) { - CRANE_DEBUG("Task ended. Normal exit: {}. Value: {}", - !is_terminated_by_signal, value); - - // Kill by SIGINT - EXPECT_EQ(is_terminated_by_signal, true); - - // signum of SIGINT is 2 - EXPECT_EQ(value, 2); - }; - - Craned::TaskInitInfo info_1{ - "RileyTest", test_prog_path, {}, - {.cpu_core_limit = 2}, output_callback, finish_callback, - }; - - err = g_task_mgr->DeprecatedAddTaskAsync__(std::move(info_1)); - EXPECT_EQ(err, CraneErr::kOk); - - Craned::TaskInitInfo info_2{ - "RileyTest", test_prog_path, {}, - {.cpu_core_limit = 2}, output_callback, finish_callback, - }; - err = g_task_mgr->DeprecatedAddTaskAsync__(std::move(info_2)); - EXPECT_EQ(err, CraneErr::kExistingTask); - - Craned::TaskInitInfo info_3{ - "RileyTest_2", test_prog_path, {}, - {.cpu_core_limit = 2}, output_callback, finish_callback, - }; - err = g_task_mgr->DeprecatedAddTaskAsync__(std::move(info_3)); - EXPECT_EQ(err, CraneErr::kOk); - - using namespace std::chrono_literals; - std::this_thread::sleep_for(2s); - - // Emulate ctrl+C. - // This call will trigger the SIGINT handler in TaskManager. - // We expect that all task will be terminated. - kill(getpid(), SIGINT); - - g_task_mgr->Wait(); - - CRANE_TRACE("Exiting test..."); - - RemoveTestProg(test_prog_path); -} - -TEST_F(TaskManagerTest, LsOutput) { - spdlog::set_level(spdlog::level::trace); - CraneErr err; - - auto output_callback = [&](std::string&& buf) { - CRANE_DEBUG("Output from callback: {}", buf); - }; - - auto finish_callback = [&](bool is_terminated_by_signal, int value) { - CRANE_DEBUG("Task ended. Normal exit: {}. Value: {}", - !is_terminated_by_signal, value); - - // Kill by SIGINT - EXPECT_EQ(is_terminated_by_signal, false); - - // signum of SIGINT is 2 - EXPECT_EQ(value, 0); - }; - - Craned::TaskInitInfo info_1{ - "RileyTest", "/bin/ls", {"/"}, - {.cpu_core_limit = 2}, output_callback, finish_callback, - }; - - err = g_task_mgr->DeprecatedAddTaskAsync__(std::move(info_1)); - EXPECT_EQ(err, CraneErr::kOk); - - using namespace std::chrono_literals; - std::this_thread::sleep_for(2s); - - // Emulate ctrl+C. - // This call will trigger the SIGINT handler in TaskManager. - // We expect that all task will be terminated. - kill(getpid(), SIGINT); - - g_task_mgr->Wait(); - - CRANE_TRACE("Exiting test..."); -} - -TEST_F(TaskManagerTest, Shutdown) { - g_task_mgr->Shutdown(); - g_task_mgr->Wait(); -} - -TEST_F(TaskManagerTest, TaskMultiIndexSet) { - g_task_mgr->Shutdown(); - - TaskMultiIndexSet indexSet; - - auto task1 = std::make_unique(); - task1->init_info.name = "Task1"; - task1->root_pid = 1; - - auto task2 = std::make_unique(); - task2->init_info.name = "Task2"; - task2->root_pid = 2; - - indexSet.Insert(std::move(task1)); - indexSet.Insert(std::move(task2)); - - const TaskInstance* p; - p = indexSet.FindByTaskId("Task1"); - ASSERT_NE(p, nullptr); - EXPECT_EQ(p->root_pid, 1); - - EXPECT_EQ(p, indexSet.FindByPid(1)); - - p = indexSet.FindByPid(2); - ASSERT_NE(p, nullptr); - EXPECT_EQ(p->init_info.name, "Task2"); -} - -*/ \ No newline at end of file diff --git a/test/Misc/CMakeLists.txt b/test/Misc/CMakeLists.txt index 13fcbb85e..98c9addbb 100644 --- a/test/Misc/CMakeLists.txt +++ b/test/Misc/CMakeLists.txt @@ -5,55 +5,6 @@ endif () include(GoogleTest) -add_executable(misc_grpc_example_test grpc_example_test.cpp) -target_link_libraries(misc_grpc_example_test - GTest::gtest GTest::gtest_main - concurrentqueue - shared_test_impl_lib) -gtest_discover_tests(misc_grpc_example_test) - -add_executable(misc_libevent_example_test libevent_example_test.cpp) -target_link_libraries(misc_libevent_example_test - GTest::gtest GTest::gtest_main - Utility_AnonymousPipe - event_core_static - event_pthreads_static - Threads::Threads - spdlog::spdlog -) -gtest_discover_tests(misc_libevent_example_test) - -add_executable(misc_libuv_test libuv_test.cpp) -target_link_libraries(misc_libuv_test - GTest::gtest GTest::gtest_main - uvw - Threads::Threads -) -gtest_discover_tests(misc_libuv_test) - -add_executable(misc_Misc_test Misc_test.cpp) -target_link_libraries(misc_Misc_test - GTest::gtest GTest::gtest_main - Threads::Threads - absl::btree - absl::synchronization - spdlog::spdlog -) -gtest_discover_tests(misc_Misc_test) - - -add_executable(misc_mariadb_c_test mariadb_connector_c_test.cpp) -target_link_libraries(misc_mariadb_c_test - GTest::gtest GTest::gtest_main - Threads::Threads - absl::strings - mariadbclient - Utility_PublicHeader - spdlog::spdlog -) -target_include_directories(misc_mariadb_c_test PRIVATE ${CMAKE_SOURCE_DIR}/src/CraneCtld) -gtest_discover_tests(misc_mariadb_c_test) - add_executable(misc_mongodb_test mongodb_test.cpp) target_link_libraries(misc_mongodb_test GTest::gtest GTest::gtest_main @@ -66,15 +17,6 @@ target_link_libraries(misc_mongodb_test target_include_directories(misc_mongodb_test PRIVATE ${CMAKE_SOURCE_DIR}/src/CraneCtld) gtest_discover_tests(misc_mongodb_test) -add_executable(misc_yaml_test yaml_cpp_test.cpp) -target_link_libraries(misc_yaml_test - GTest::gtest GTest::gtest_main - Threads::Threads - spdlog::spdlog - yaml-cpp -) -gtest_discover_tests(misc_yaml_test) - add_executable(misc_unqlite_test unqlite_test.cpp) target_link_libraries(misc_unqlite_test GTest::gtest GTest::gtest_main @@ -84,14 +26,3 @@ target_link_libraries(misc_unqlite_test test_proto ) gtest_discover_tests(misc_unqlite_test) - -add_executable(misc_libaio_test libaio_test.cpp) -target_include_directories(misc_libaio_test PUBLIC ${LIBAIO_INCLUDE_DIRS}) -target_link_libraries(misc_libaio_test PUBLIC - GTest::gtest GTest::gtest_main - Threads::Threads - spdlog::spdlog - test_proto - ${LIBAIO_LIBRARIES} -) -gtest_discover_tests(misc_libaio_test) diff --git a/test/Misc/Misc_test.cpp b/test/Misc/Misc_test.cpp deleted file mode 100644 index 47e336221..000000000 --- a/test/Misc/Misc_test.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include - -#include -#include -#include - -TEST(Misc, AbseilBtreeMap) { - absl::btree_map map; - map.emplace(1, 2); - map.emplace(2, 3); - map.emplace(3, 4); - - auto iter = map.begin(); - for (int i = 1; i <= 3; i++, iter++) { - EXPECT_EQ(iter->first, i); - } - - iter = map.find(3); - map.emplace(4, 5); - EXPECT_EQ((++iter)->first, 4); - map.emplace(5, 6); - EXPECT_EQ((++iter)->second, 6); -} - -TEST(Misc, AbseilMutexCondition) { - absl::Mutex mtx; - std::queue int_queue; - std::atomic_bool stop{false}; - std::thread t; - absl::BlockingCounter counter(1); - - absl::Condition cond( - +[](decltype(int_queue)* queue) { return !queue->empty(); }, &int_queue); - - t = std::thread([&] { - int cnt = 0; - int val; - - while (true) { - if (stop) break; - bool has_msg = mtx.LockWhenWithTimeout(cond, absl::Milliseconds(300)); - if (!has_msg) { - mtx.Unlock(); - continue; - } - - val = int_queue.front(); - EXPECT_EQ(cnt++, val); - GTEST_LOG_(INFO) << fmt::format("Popped val: {}", val); - - int_queue.pop(); - mtx.Unlock(); - - if (cnt == 3) counter.DecrementCount(); - } - }); - - int_queue.push(0); - int_queue.push(1); - int_queue.push(2); - - counter.Wait(); - stop = true; - - t.join(); -} - -TEST(Misc, AbslTime) { - absl::Time now = absl::Now(); - absl::Time Inf = now + absl::InfiniteDuration(); - EXPECT_TRUE(now < Inf); -} \ No newline at end of file diff --git a/test/Misc/grpc_example_test.cpp b/test/Misc/grpc_example_test.cpp deleted file mode 100644 index 49e0892b0..000000000 --- a/test/Misc/grpc_example_test.cpp +++ /dev/null @@ -1,715 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "SharedTestImpl/greeter_service_impl.h" -#include "concurrentqueue/concurrentqueue.h" -#include "protos/math.grpc.pb.h" -#include "protos/math.pb.h" -#include "crane/PublicHeader.h" - -using grpc::Server; -using grpc::ServerBuilder; -using grpc::ServerContext; - -using grpc::Channel; -using grpc::ClientContext; -using grpc::Status; - -using grpc_example::Greeter; -using grpc_example::HelloReply; -using grpc_example::HelloRequest; - -class GreeterClient { - public: - explicit GreeterClient(std::shared_ptr channel) - : stub_(Greeter::NewStub(channel)) {} - - // Assembles the client's payload, sends it and presents the response back - // from the server. - std::string SayHello(const std::string& user) { - // Data we are sending to the server. - HelloRequest request; - request.set_name(user); - - // Container for the data we expect from the server. - HelloReply reply; - - // Context for the client. It could be used to convey extra information to - // the server and/or tweak certain RPC behaviors. - ClientContext context; - - // The actual RPC. - Status status = stub_->SayHello(&context, request, &reply); - - // Act upon its status. - if (status.ok()) { - return reply.message(); - } else { - std::cout << status.error_code() << ": " << status.error_message() - << std::endl; - return "RPC failed"; - } - } - - private: - std::unique_ptr stub_; -}; - -TEST(GrpcExample, Simple) { - GreeterSyncServer server("localhost:50051"); - - // Instantiate the client. It requires a channel, out of which the actual RPCs - // are created. This channel models a connection to an endpoint (in this case, - // localhost at port 50051). We indicate that the channel isn't authenticated - // (use of InsecureChannelCredentials()). - GreeterClient greeter(grpc::CreateChannel( - "localhost:50051", grpc::InsecureChannelCredentials())); - std::string user("world"); - std::string reply = greeter.SayHello(user); - SPDLOG_INFO("Greeter received: {}", reply); - - EXPECT_EQ(reply, "Hello world"); - - // This method is thread-safe. - server.Shutdown(); - - // Wait for the server to shutdown. Note that some other thread must be - // responsible for shutting down the server for this call to ever return. - server.Wait(); -} - -using grpc::CompletionQueue; -using grpc::ServerAsyncReaderWriter; -using grpc::ServerCompletionQueue; -using grpc_example::Math; -using grpc_example::MaxRequest; -using grpc_example::MaxResponse; - -// NOTE: This is a complex example for an asynchronous, bidirectional streaming -// server. - -// Most of the logic is similar to AsyncBidiGreeterClient, so follow that class -// for detailed comments. Two main differences between the server and the client -// are: (a) Server cannot initiate a connection, so it first waits for a -// 'connection'. (b) Server can handle multiple streams at the same time, so -// the completion queue/server have a longer lifetime than the client(s). -class AsyncBidiMathServer { - public: - AsyncBidiMathServer(std::string server_address) - : m_server_address_(std::move(server_address)) { - // In general avoid setting up the server in the main thread (specifically, - // in a constructor-like function such as this). We ignore this in the - // context of an example. - ServerBuilder builder; - builder.AddListeningPort(m_server_address_, - grpc::InsecureServerCredentials()); - builder.RegisterService(&m_async_serv_); - m_server_cq_ = builder.AddCompletionQueue(); - m_server_ = builder.BuildAndStart(); - - // Call RequestMax in the constructor. - auto new_conn = std::make_unique( - &m_async_serv_, m_server_cq_.get(), m_next_client_index_, - &m_to_reap_conn_queue_); - m_client_conns_.emplace(m_next_client_index_, std::move(new_conn)); - m_next_client_index_++; - - m_server_cq_thread_ = - std::thread(&AsyncBidiMathServer::m_server_cq_func_, this); - - m_conn_reaping_thread_ = - std::thread(&AsyncBidiMathServer::m_conn_reap_func_, this); - } - - void WaitStop() { - m_thread_should_stop_.store(true, std::memory_order_release); - - m_server_->Shutdown(); - - // Always shutdown the completion queue after the server. - m_server_cq_->Shutdown(); - - m_server_cq_thread_.join(); - m_conn_reaping_thread_.join(); - } - - enum class cq_tag_t : uint8_t { - NEW_RPC_ESTAB = 0, - WRITE, - READ, - SHUTDOWN, - }; - - private: - class ClientConn; - - static constexpr size_t cq_tag_n_bit = 3; - - void m_conn_reap_func_() { - SPDLOG_INFO("[Server] reap thread started."); - uint64_t index; - while (!m_thread_should_stop_.load(std::memory_order_acquire)) { - if (!m_to_reap_conn_queue_.try_dequeue(index)) - std::this_thread::yield(); - else { - SPDLOG_INFO("[Server] Reaping conn: {}", index); - std::lock_guard guard(m_conn_map_lock_); - m_client_conns_.erase(index); - } - } - SPDLOG_INFO("[Server] reap thread ended."); - } - - void m_server_cq_func_() { - SPDLOG_INFO("m_server_cq_func_ started."); - - while (true) { - void* got_tag = nullptr; - bool ok = false; - if (!m_server_cq_->Next(&got_tag, &ok)) { - SPDLOG_INFO( - "[ServerCq] Server Completion Queue closed. Thread for server_cq " - "is " - "ending..."); - break; - } - - if (ok) { - uint64_t index = index_from_tag(got_tag); - cq_tag_t status = status_from_tag(got_tag); - - SPDLOG_INFO( - "[ServerCq] Client {} | Status: {}", index, - (status == cq_tag_t::READ) - ? "READ" - : ((status == cq_tag_t::WRITE) - ? "WRITE" - : (status == cq_tag_t::NEW_RPC_ESTAB ? "NEW_RPC_ESTAB" - : "DONE"))); - - GPR_ASSERT(status == cq_tag_t::WRITE || status == cq_tag_t::READ || - status == cq_tag_t::SHUTDOWN || - status == cq_tag_t::NEW_RPC_ESTAB); - - auto iter = m_client_conns_.find(index); - if (GPR_UNLIKELY(iter == m_client_conns_.end())) { - SPDLOG_ERROR("[ServerCq] Client {} doesn't exist!", index); - } else { - ClientConn* conn = iter->second.get(); - - if (status == cq_tag_t::NEW_RPC_ESTAB) { - GPR_ASSERT(index_from_tag(got_tag) == m_next_client_index_ - 1); - GPR_ASSERT(status_from_tag(got_tag) == cq_tag_t::NEW_RPC_ESTAB); - - SPDLOG_INFO( - "[ServerCq] RPC for client {} established. Requesting read...", - m_next_client_index_ - 1); - conn->RequestRead(); - conn->ConnEstablished(); - - // Prepare next incoming RPC. - auto new_conn = std::make_unique( - &m_async_serv_, m_server_cq_.get(), m_next_client_index_, - &m_to_reap_conn_queue_); - - { - std::lock_guard guard(m_conn_map_lock_); - m_client_conns_.emplace(m_next_client_index_, - std::move(new_conn)); - } - - m_next_client_index_++; - } else { - SPDLOG_ERROR("[ServerCq] Unexpected status {} of Client {}!", - (uint8_t)status, index); - } - } - } else { - SPDLOG_ERROR("[ServerCq] server_cq_.Next() returned with ok false!"); - } - } - } - - const std::string m_server_address_; - std::unique_ptr m_server_; - - Math::AsyncService m_async_serv_ = {}; - - std::unique_ptr m_server_cq_; - - std::mutex m_conn_map_lock_; - std::unordered_map> m_client_conns_; - - std::thread m_server_cq_thread_; - - moodycamel::ConcurrentQueue m_to_reap_conn_queue_; - - std::thread m_conn_reaping_thread_; - - std::atomic_bool m_thread_should_stop_ = false; - - uint64_t m_next_client_index_ = 0; - - public: - static enum cq_tag_t status_from_tag(void* tag) { - constexpr std::uintptr_t zero{}; - constexpr size_t pointer_n_bit = sizeof(std::uintptr_t) * 8; - constexpr std::uintptr_t status_mask = (~zero) - << (pointer_n_bit - cq_tag_n_bit); - return static_cast( - (reinterpret_cast(tag) & status_mask) >> - (pointer_n_bit - cq_tag_n_bit)); - } - - static uint64_t index_from_tag(void* tag) { - constexpr std::uintptr_t zero{}; - constexpr std::uintptr_t index_mask = (~zero) >> cq_tag_n_bit; - return reinterpret_cast(tag) & index_mask; - } - - static void* tag_from_index_status(uint64_t index, cq_tag_t status) { - constexpr std::uintptr_t zero{}; - constexpr size_t pointer_n_bit = sizeof(std::uintptr_t) * 8; - std::uintptr_t status_part = zero | (static_cast(status) - << (pointer_n_bit - cq_tag_n_bit)); - std::uintptr_t index_part = - static_cast(index) & ((~zero) >> cq_tag_n_bit); - - return reinterpret_cast(status_part | index_part); - } - - private: - class ClientConn { - public: - // Take in the "service" instance (in this case representing an asynchronous - // server) and the completion queue "cq" used for asynchronous communication - // with the gRPC runtime. - ClientConn(Math::AsyncService* service, ServerCompletionQueue* server_cq, - uint64_t index, - moodycamel::ConcurrentQueue* to_reap_conn_queue_) - : m_stream_(&m_rpc_ctx_), - m_index_(index), - m_to_reap_conn_queue_(to_reap_conn_queue_), - m_initialized(false) { - service->RequestMax( - &m_rpc_ctx_, &m_stream_, &m_conn_cq_, server_cq, - tag_from_index_status(index, cq_tag_t::NEW_RPC_ESTAB)); - - // This is important as the server should know when the client is done. - m_rpc_ctx_.AsyncNotifyWhenDone( - tag_from_index_status(index, cq_tag_t::SHUTDOWN)); - - m_conn_cq_thread_ = std::thread(&ClientConn::m_conn_cq_func_, this); - } - - ~ClientConn() { - // For those who has established the connection with the client, exit only - // after all the pending writes have flushed. - // - // Also, the established connection may be ended from the client side. - // In such case, conn_cq_thread_ will receive a SHUTDOWN tag and will call - // m_conn_cq_.Shutdown(). The destructor shall not call any API on - // m_stream_ and m_conn_cq_ and should wait for the conn_cq_thread_ to - // quit directly. - // - // For those who hasn't established the connection, shutdown the - // completion queue directly (m_stream is still not associated with the - // completion queue) - if (m_initialized && !m_cq_shutdown_called_) { - MarkConnEnd(); - while (m_is_writing_) { - // Wait for pending writes to be flushed. - std::this_thread::yield(); - } - - MaxResponse resp; - if (m_write_queue_.try_dequeue(resp)) { - // Flush the possible trailing pending writes. See WriteFinished(). - m_stream_.Write(resp, - tag_from_index_status(m_index_, cq_tag_t::WRITE)); - m_is_writing_ = true; - } - while (m_is_writing_) { - // Wait for pending writes to be flushed. - std::this_thread::yield(); - } - - // Inform the conn_cq_thread_ to shutdown the queue. - m_stream_.Finish(Status::OK, - tag_from_index_status(m_index_, cq_tag_t::SHUTDOWN)); - } else { - m_conn_cq_.Shutdown(); - } - m_conn_cq_thread_.join(); - } - - void ConnEstablished() { m_initialized = true; } - - void RequestRead() { - if (!m_end_conn_) - m_stream_.Read(&m_req_, - tag_from_index_status(m_index_, cq_tag_t::READ)); - } - - const MaxRequest& GetRequest() const { return m_req_; } - - // This function is thread-safe - void RequestWrite(MaxResponse&& resp) { - bool expected = false; - if (!m_end_conn_) { - if (m_is_writing_ || - !m_is_writing_.compare_exchange_strong(expected, true)) { - m_write_queue_.enqueue(std::forward(resp)); - } else { - // Nobody is writing and nobody is trying to read at the same time. - m_stream_.Write(resp, - tag_from_index_status(m_index_, cq_tag_t::WRITE)); - } - } - } - - // It's ok to call this function from multiple thread more than one time. - void MarkConnEnd() { m_end_conn_ = true; } - - private: - void WriteFinished() { - MaxResponse resp; - if (m_write_queue_.try_dequeue(resp)) { - m_stream_.Write(resp, tag_from_index_status(m_index_, cq_tag_t::WRITE)); - } else { - // A slight chance that new pending write in enqueued before - // is_writing is set to false. The trailing writes are handle in - // destructor. - m_is_writing_ = false; - } - } - - void m_conn_cq_func_() { - SPDLOG_INFO("[Server | Client {}] conn_cq_thread started.", m_index_); - - while (true) { - void* got_tag = nullptr; - bool ok = false; - if (!m_conn_cq_.Next(&got_tag, &ok)) { - SPDLOG_INFO( - "[Server | Client {}] Completion Queue has been shutdown. " - "Exiting " - "conn_cq_ thread...", - m_index_); - break; - } - - if (ok) { - uint64_t index = index_from_tag(got_tag); - cq_tag_t status = status_from_tag(got_tag); - - SPDLOG_INFO( - "[Server | Client {}] Completion Queue Received: {}", index, - (status == cq_tag_t::READ) - ? "READ" - : ((status == cq_tag_t::WRITE) - ? "WRITE" - : (status == cq_tag_t::NEW_RPC_ESTAB ? "NEW_RPC_ESTAB" - : "SHUTDOWN"))); - - if (status == cq_tag_t::READ) { - const MaxRequest& req = GetRequest(); - - if (req.a() != 0 || req.b() != 0) { - SPDLOG_INFO( - "[Server | Client {}] Receive Request MAX({},{}) from ", - index, req.a(), req.b()); - - MaxResponse resp; - resp.set_result(std::max(req.a(), req.b())); - - RequestWrite(std::move(resp)); - - // Request Next Read. - RequestRead(); - } else { - MarkConnEnd(); - m_to_reap_conn_queue_->enqueue(m_index_); - } - } else if (status == cq_tag_t::WRITE) { - SPDLOG_INFO("[Server | Client {}] Write response to successfully.", - index); - WriteFinished(); - } else if (status == cq_tag_t::SHUTDOWN) { - SPDLOG_INFO( - "[Server | Client {}] SHUTDOWN is called from the destructor. " - "Stopping " - "the completion queue...", - index); - m_cq_shutdown_called_ = true; - m_conn_cq_.Shutdown(); - } else { - SPDLOG_ERROR("[Server | Client {}] Unexpected status {}!", index, - (uint8_t)status); - } - - } else { - SPDLOG_ERROR( - "[Server | Client {}] CompletionQueue.Next() returned with " - "\"ok\": " - "false!"); - } - } - - SPDLOG_INFO("[Server | Client {}] conn_cq_thread ended.", m_index_); - } - - std::atomic_bool m_initialized; - uint64_t m_index_; - - MaxRequest m_req_; - - // Context for the rpc, allowing to tweak aspects of it such as the use - // of compression, authentication, as well as to send metadata back to the - // client. - ServerContext m_rpc_ctx_; - - // The context, request and m_stream_ are ready once the tag is retrieved - // from m_cq_->Next(). - ServerAsyncReaderWriter m_stream_; - - CompletionQueue m_conn_cq_; - - std::thread m_conn_cq_thread_; - - moodycamel::ConcurrentQueue* m_to_reap_conn_queue_; - - // When set true, the ClientConn class will not submit any more write - // requests and will keep flushing all pending writes. - std::atomic_bool m_end_conn_ = false; - - // Indicate whether m_conn_cq.Shutdown() has been called. When set true, - // NO MORE operations should be carried out on m_conn_cq_ and m_stream_. - std::atomic_bool m_cq_shutdown_called_ = false; - - moodycamel::ConcurrentQueue m_write_queue_; - std::atomic_bool m_is_writing_ = false; - }; -}; - -class BidiMathClient { - public: - BidiMathClient(std::shared_ptr channel, uint64_t index) - : m_stub_(Math::NewStub(channel)), m_index_(index) { - m_stream_ = m_stub_->Max(&m_ctx_); - m_recv_thread_ = std::thread(&BidiMathClient::ReceiveRespThread, this); - } - - ~BidiMathClient() { Wait(); } - - void Wait() { - if (m_recv_thread_.joinable()) m_recv_thread_.join(); - } - - void ReceiveRespThread() { - SPDLOG_INFO("[Client {}] Recv Thread Started...", m_index_); - - MaxResponse resp; - while (m_stream_->Read(&resp)) { - SPDLOG_INFO("[Client {}] Received the response: {}", m_index_, - resp.result()); - } - - SPDLOG_INFO("[Client {}] Recv Thread Exiting...", m_index_); - } - - void FindMultipleMax(const std::vector>& pairs) { - for (auto&& pair : pairs) { - MaxRequest req; - req.set_a(pair.first); - req.set_b(pair.second); - m_stream_->Write(req); - } - - m_stream_->Finish(); - } - - private: - ClientContext m_ctx_; - std::unique_ptr> m_stream_; - std::unique_ptr m_stub_; - std::thread m_recv_thread_; - uint64_t m_index_; -}; - -TEST(GrpcExample, BidirectionalStream) { - using tag_t = AsyncBidiMathServer::cq_tag_t; - - void* tag = AsyncBidiMathServer::tag_from_index_status(3, tag_t::READ); - ASSERT_EQ(AsyncBidiMathServer::index_from_tag(tag), 3); - ASSERT_EQ(AsyncBidiMathServer::status_from_tag(tag), tag_t::READ); - - std::string server_address{"localhost:50051"}; - AsyncBidiMathServer server{server_address}; - - BidiMathClient client0( - grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()), - 0); - - BidiMathClient client1( - grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()), - 1); - - BidiMathClient client2( - grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()), - 2); - - std::vector> reqs_1{{1, 2}, - {3, 4}, - {5, 6}, - {7, 8}, - {0, 0}}; - - client0.FindMultipleMax(reqs_1); - client1.FindMultipleMax(reqs_1); - client2.FindMultipleMax(reqs_1); - - client0.Wait(); - client1.Wait(); - client2.Wait(); - - server.WaitStop(); -} - -TEST(GrpcExample, BidirectionalStream_ClientAbort) { - std::string server_address{"localhost:50051"}; - - signal(SIGCHLD, SIG_IGN); - pid_t child_pid = fork(); - if (child_pid == 0) { - int devNull = open("/dev/null", O_WRONLY); - - dup2(devNull, STDOUT_FILENO); - dup2(devNull, STDERR_FILENO); - - BidiMathClient client0( - grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials()), - 0); - - abort(); - } else { - AsyncBidiMathServer server{server_address}; - std::this_thread::sleep_for(std::chrono::seconds(1)); - server.WaitStop(); - } -} - -TEST(Protobuf, InterprocessPipe) { - using google::protobuf::io::FileInputStream; - using google::protobuf::io::FileOutputStream; - using google::protobuf::util::ParseDelimitedFromZeroCopyStream; - using google::protobuf::util::SerializeDelimitedToZeroCopyStream; - - int socket_pair[2]; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, socket_pair) != 0) { - FAIL() << fmt::format("Failed to create socket pair: {}", strerror(errno)); - } - signal(SIGCHLD, SIG_IGN); - - std::thread t1([=] { - int fd = socket_pair[1]; - bool ok; - - FileInputStream istream(fd); - FileOutputStream ostream(fd); - - HelloRequest request; - ok = ParseDelimitedFromZeroCopyStream(&request, &istream, nullptr); - ASSERT_TRUE(ok) << "request.ParseFromZeroCopyStream(&istream)"; - GTEST_LOG_(INFO) << fmt::format("Child receive HelloRequest: {}", - request.name()); - - HelloReply reply; - reply.set_message("OK"); - ok = SerializeDelimitedToZeroCopyStream(reply, &ostream); - ASSERT_TRUE(ok) << "reply.SerializeToZeroCopyStream(&ostream)"; - ostream.Flush(); - ASSERT_TRUE(ok) << "ostream.Flush()"; - }); - - { - int fd = socket_pair[0]; - bool ok; - - FileInputStream istream(fd); - FileOutputStream ostream(fd); - - HelloRequest request; - request.set_name("Parent"); - ok = SerializeDelimitedToZeroCopyStream(request, &ostream); - GTEST_LOG_(INFO) << fmt::format("ostream.ByteCount(): {}", - ostream.ByteCount()); - ASSERT_TRUE(ok) << "request.SerializeToZeroCopyStream(&ostream)"; - ok = ostream.Flush(); - ASSERT_TRUE(ok) << "ostream.Flush()"; - - HelloReply reply; - ok = ParseDelimitedFromZeroCopyStream(&reply, &istream, nullptr); - ASSERT_TRUE(ok) << "reply.ParseFromZeroCopyStream(&istream)"; - GTEST_LOG_(INFO) << fmt::format("Parent receive HelloReply: {}", - reply.message()); - } - - t1.join(); -} - -TEST(GrpcExample, UnixSocket) { - std::filesystem::create_directories(kDefaultCraneBaseDir); - - std::string server_address = - fmt::format("{}{}", "unix://", kDefaultCranedUnixSockPath); - SPDLOG_INFO("Unix Server Address: {}", server_address); - - GreeterSyncServer server(server_address); - - GreeterClient greeter( - grpc::CreateChannel(server_address, grpc::InsecureChannelCredentials())); - std::string user("world"); - std::string reply = greeter.SayHello(user); - SPDLOG_INFO("Greeter received: {}", reply); - - EXPECT_EQ(reply, "Hello world"); - - // This method is thread-safe. - server.Shutdown(); - - // Wait for the server to shutdown. Note that some other thread must be - // responsible for shutting down the server for this call to ever return. - server.Wait(); -} \ No newline at end of file diff --git a/test/Misc/libaio_test.cpp b/test/Misc/libaio_test.cpp deleted file mode 100644 index 00911c680..000000000 --- a/test/Misc/libaio_test.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include - -#include - -std::string path = "/tmp/testfile"; // Path to the file to manipulate -constexpr int32_t file_size = 1000; // Length of file in 4k blocks -constexpr int32_t concurrent_requests = 100; // Number of concurrent requests -constexpr int32_t min_nr = 1; -constexpr int32_t max_nr = 1; - -// The size of operation that will occur on the device -static const int kPageSize = 4096; - -class AIORequest { - public: - int* buffer_; - - virtual void Complete(int res) = 0; - - AIORequest() { AllocBuffer(); } - - void AllocBuffer() { - int ret = posix_memalign(reinterpret_cast(&buffer_), kPageSize, - kPageSize); - ASSERT_EQ(ret, 0); - } - - virtual ~AIORequest() { free(buffer_); } -}; - -class Adder { - public: - virtual void Add(int amount) = 0; - - virtual ~Adder(){}; -}; - -class AIOReadRequest : public AIORequest { - private: - Adder* adder_; - - public: - AIOReadRequest(Adder* adder) : AIORequest(), adder_(adder) {} - - virtual void Complete(int res) { - ASSERT_EQ(res, kPageSize) << "Read incomplete or error " << res; - int value = buffer_[0]; - GTEST_LOG_(INFO) << "Read of " << value << " completed"; - adder_->Add(value); - } -}; - -class AIOWriteRequest : public AIORequest { - private: - int value_; - - public: - AIOWriteRequest(int value) : AIORequest(), value_(value) { - buffer_[0] = value; - } - - virtual void Complete(int res) { - ASSERT_EQ(res, kPageSize) << "Write incomplete or error " << res; - GTEST_LOG_(INFO) << "Write of " << value_ << " completed"; - } -}; - -class AIOAdder : public Adder { - public: - int fd_; - io_context_t ioctx_; - int counter_; - int reap_counter_; - int sum_; - int length_; - - AIOAdder(int length) - : ioctx_(0), counter_(0), reap_counter_(0), sum_(0), length_(length) {} - - void Init() { - GTEST_LOG_(INFO) << "Opening file"; - fd_ = open(path.c_str(), O_RDWR | O_DIRECT | O_CREAT, 0644); - ASSERT_TRUE(fd_ >= 0) << "Error opening file"; - GTEST_LOG_(INFO) << "Allocating enough space for the sum"; - ASSERT_TRUE(fallocate(fd_, 0, 0, kPageSize * length_) >= 0) - << "Error in fallocate"; - GTEST_LOG_(INFO) << "Setting up the io context"; - ASSERT_TRUE(io_setup(100, &ioctx_) >= 0) << "Error in io_setup"; - } - - virtual void Add(int amount) { - sum_ += amount; - GTEST_LOG_(INFO) << "Adding " << amount << " for a total of " << sum_; - } - - void SubmitWrite() { - GTEST_LOG_(INFO) << "Submitting a write to " << counter_; - struct iocb iocb; - struct iocb* iocbs = &iocb; - AIORequest* req = new AIOWriteRequest(counter_); - io_prep_pwrite(&iocb, fd_, req->buffer_, kPageSize, counter_ * kPageSize); - iocb.data = req; - int res = io_submit(ioctx_, 1, &iocbs); - ASSERT_EQ(res, 1); - } - - void WriteFile() { - reap_counter_ = 0; - for (counter_ = 0; counter_ < length_; counter_++) { - SubmitWrite(); - Reap(); - } - ReapRemaining(); - } - - void SubmitRead() { - GTEST_LOG_(INFO) << "Submitting a read from " << counter_; - struct iocb iocb; - struct iocb* iocbs = &iocb; - AIORequest* req = new AIOReadRequest(this); - io_prep_pread(&iocb, fd_, req->buffer_, kPageSize, counter_ * kPageSize); - iocb.data = req; - int res = io_submit(ioctx_, 1, &iocbs); - ASSERT_EQ(res, 1); - } - - void ReadFile() { - reap_counter_ = 0; - for (counter_ = 0; counter_ < length_; counter_++) { - SubmitRead(); - Reap(); - } - ReapRemaining(); - } - - int DoReap(int min_nr) { - GTEST_LOG_(INFO) << "Reaping between " << min_nr << " and " << max_nr - << " io_events"; - struct io_event* events = new io_event[max_nr]; - struct timespec timeout; - timeout.tv_sec = 0; - timeout.tv_nsec = 100000000; - int num_events; - GTEST_LOG_(INFO) << "Calling io_getevents"; - num_events = io_getevents(ioctx_, min_nr, max_nr, events, &timeout); - GTEST_LOG_(INFO) << "Calling completion function on results"; - for (int i = 0; i < num_events; i++) { - struct io_event event = events[i]; - AIORequest* req = static_cast(event.data); - req->Complete(event.res); - delete req; - } - delete[] events; - - GTEST_LOG_(INFO) << "Reaped " << num_events << " io_events"; - reap_counter_ += num_events; - return num_events; - } - - void Reap() { - if (counter_ >= min_nr) { - DoReap(min_nr); - } - } - - void ReapRemaining() { - while (reap_counter_ < length_) { - DoReap(1); - } - } - - ~AIOAdder() { - GTEST_LOG_(INFO) << "Closing AIO context and file"; - io_destroy(ioctx_); - close(fd_); - } - - int Sum() { - GTEST_LOG_(INFO) << "Writing consecutive integers to file"; - WriteFile(); - GTEST_LOG_(INFO) << "Reading consecutive integers from file"; - ReadFile(); - return sum_; - } -}; - -TEST(LibAIO, Simple) { - AIOAdder adder(file_size); - adder.Init(); - int sum = adder.Sum(); - int expected = (file_size * (file_size - 1)) / 2; - GTEST_LOG_(INFO) << "AIO is complete"; - ASSERT_EQ(sum, expected) << "Expected " << expected << " Got " << sum; - printf( - "Successfully calculated that the sum of integers from 0 to %d is %d\n", - file_size - 1, sum); -} \ No newline at end of file diff --git a/test/Misc/libevent_example_test.cpp b/test/Misc/libevent_example_test.cpp deleted file mode 100644 index 5185d2a82..000000000 --- a/test/Misc/libevent_example_test.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include -#include - -#include "AnonymousPipe.h" -#include "event2/bufferevent.h" -#include "event2/event.h" -#include "event2/thread.h" -#include "event2/util.h" -#include "gtest/gtest.h" -#include "spdlog/spdlog.h" - -static void log_cb(int severity, const char *msg) { - switch (severity) { - case _EVENT_LOG_DEBUG: - SPDLOG_DEBUG("{}", msg); - break; - case _EVENT_LOG_MSG: - SPDLOG_INFO("{}", msg); - break; - case _EVENT_LOG_WARN: - SPDLOG_WARN("{}", msg); - break; - case _EVENT_LOG_ERR: - SPDLOG_ERROR("{}", msg); - break; - default: - break; /* never reached */ - } -} - -class STATIC_FUNC { - STATIC_FUNC() { - event_enable_debug_mode(); - event_enable_debug_logging(EVENT_DBG_ALL); - event_set_log_callback(log_cb); - } -}; - -static STATIC_FUNC static_func_(); - -// NOTICE: The test result indicates that this handler -// must be installed before any fork(). -static void sigchld_handler(int sig) { - pid_t pid; - int child_status; - pid = wait(&child_status); - if (pid < 0) { - SPDLOG_ERROR("SIGCHLD received too early. Error:{}", strerror(errno)); - } else - SPDLOG_INFO("SIGCHLD received too early. PID: {}", pid); -} - -struct LibEventTest : testing::Test { - LibEventTest() - : m_ev_sigchld_(nullptr), m_ev_base_(nullptr), testing::Test() { - evthread_use_pthreads(); - } - - void SetUp() override { - signal(SIGCHLD, sigchld_handler); - m_ev_base_ = event_base_new(); - if (!m_ev_base_) { - FAIL() << "Could not initialize libevent!"; - } - } - - void TearDown() override { - if (m_ev_base_) event_base_free(m_ev_base_); - } - - static std::string RandomFileNameStr() { - static std::random_device rd; - static std::mt19937 mt(rd()); - static std::uniform_int_distribution dist(100000, 999999); - - return std::to_string(dist(mt)); - } - - static void read_uv_stream(struct bufferevent *bev, void *user_data) { - auto *this_ = reinterpret_cast(user_data); - - size_t buf_len = evbuffer_get_length(bev->input); - SPDLOG_INFO("Trying to read buffer(len: {})...", buf_len); - EXPECT_GT(buf_len, 0); - - char str_[buf_len + 1]; - int n_copy = evbuffer_remove(bev->input, str_, buf_len); - str_[buf_len] = '\0'; - std::string_view str(str_); - - SPDLOG_INFO("Read from child(Copied: {} bytes): {}", n_copy, str); - EXPECT_EQ(str, this_->m_expected_str_); - } - - static void sigchld_handler_func(evutil_socket_t sig, short events, - void *user_data) { - auto *this_ = reinterpret_cast(user_data); - - SPDLOG_INFO("SIGCHLD received..."); - int status; - wait(&status); - - EXPECT_TRUE(WIFEXITED(status)); - EXPECT_EQ(WEXITSTATUS(status), 1); - - timeval delay{0, 0}; - event_base_loopexit(this_->m_ev_base_, &delay); - } - - struct event_base *m_ev_base_; - event *m_ev_sigchld_; - - std::string m_expected_str_; -}; - -TEST_F(LibEventTest, IoRedirectAndDynamicTaskAdding) { - std::string test_prog_path = "/tmp/craned_test_" + RandomFileNameStr(); - std::string prog_text = - "#include \\n" - "#include \\n" - "#include \\n" - "int main() { std::cout<<\"Hello World!\";" - //"std::this_thread::sleep_for(std::chrono::seconds(1));" - "return 1;" - "}"; - - m_expected_str_ = "Hello World!"; - - std::string cmd; - - cmd = fmt::format(R"(bash -c 'echo -e '"'"'{}'"'" | g++ -xc++ -o {} -)", - prog_text, test_prog_path); - SPDLOG_TRACE("Cmd: {}", cmd); - system(cmd.c_str()); - - std::vector args{test_prog_path.c_str(), nullptr}; - - u_char val{}; - bool _; - AnonymousPipe anon_pipe; - - pid_t child_pid = fork(); - if (child_pid == 0) { // Child - anon_pipe.CloseParentEnd(); - - dup2(anon_pipe.GetChildEndFd(), 1); // stdout -> pipe - dup2(anon_pipe.GetChildEndFd(), 2); // stderr -> pipe - - _ = anon_pipe.ReadIntegerFromParent(&val); - anon_pipe.CloseChildEnd(); // This descriptor is no longer needed. - - std::vector argv{test_prog_path.c_str(), nullptr}; - - execvp(test_prog_path.c_str(), const_cast(argv.data())); - } else { // Parent - anon_pipe.CloseChildEnd(); - - struct bufferevent *ev_buf_event; - ev_buf_event = - bufferevent_socket_new(m_ev_base_, anon_pipe.GetParentEndFd(), 0); - if (!ev_buf_event) { - FAIL() << "Error constructing bufferevent!"; - } - bufferevent_setcb(ev_buf_event, read_uv_stream, nullptr, nullptr, this); - bufferevent_enable(ev_buf_event, EV_READ); - bufferevent_disable(ev_buf_event, EV_WRITE); - - _ = anon_pipe.WriteIntegerToChild(val); - - // Persistent event. No need to reactivate it. - this->m_ev_sigchld_ = - evsignal_new(this->m_ev_base_, SIGCHLD, sigchld_handler_func, this); - if (!this->m_ev_sigchld_) { - FAIL() << "Could not create a signal event!"; - } - - if (event_add(this->m_ev_sigchld_, nullptr) < 0) { - FAIL() << "Could not add a signal event!"; - } - - event_base_dispatch(m_ev_base_); - bufferevent_free(ev_buf_event); - if (m_ev_sigchld_) event_free(m_ev_sigchld_); - } - if (remove(test_prog_path.c_str()) != 0) - SPDLOG_ERROR("Error removing test_prog:", strerror(errno)); -} - -static void CustomEventCb(int sock, short which, void *arg) { - static int times = 0; - std::this_thread::sleep_for(std::chrono::seconds(3)); - SPDLOG_INFO("CustomEventCb Called"); - times++; - if (times == 2) { - timeval val{0, 0}; - event_base_loopexit(reinterpret_cast(arg), &val); - } -} - -// Just a code example. -TEST_F(LibEventTest, CustomEvent) { - struct event *ev = event_new(m_ev_base_, -1, EV_READ | EV_PERSIST, - CustomEventCb, m_ev_base_); - event_add(ev, nullptr); - - std::thread t([=] { - std::this_thread::sleep_for(std::chrono::seconds(1)); - event_active(ev, 0, 0); - std::this_thread::sleep_for(std::chrono::seconds(1)); - event_active(ev, 0, 0); - }); - - event_base_dispatch(m_ev_base_); - - SPDLOG_INFO("Loop Exit"); - - t.join(); - std::this_thread::sleep_for(std::chrono::seconds(1)); - event_free(ev); -} - -static void onTimer(int, short, void *arg) { SPDLOG_INFO("onTimer!"); } - -TEST_F(LibEventTest, TimerEvent) { - struct event *timer_event; - timer_event = event_new(m_ev_base_, -1, 0, onTimer, nullptr); - struct timeval tv {}; - - tv.tv_sec = 2; - - evtimer_add(timer_event, &tv); - - event_base_dispatch(m_ev_base_); - SPDLOG_INFO("Loop Exit"); - - event_free(timer_event); -} \ No newline at end of file diff --git a/test/Misc/libuv_test.cpp b/test/Misc/libuv_test.cpp deleted file mode 100644 index bf70c4f19..000000000 --- a/test/Misc/libuv_test.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include - -#include -#include - -uv_loop_t* loop; - -void timer_cb2(uv_timer_t* timer, int status) { - printf("Timer 2 Status: %d\n", status); - uv_timer_stop(timer); - delete timer; -} - -void timer_cb1(uv_timer_t* timer, int status) { - printf("Timer 1 Status: %d\n", status); - uv_timer_stop(timer); - - auto* timer2 = new uv_timer_t; - uv_timer_init(loop, timer2); - uv_timer_start(timer2, (uv_timer_cb)&timer_cb2, 1000, 1000); -} - -TEST(Libuv, simple) { - loop = static_cast(malloc(sizeof(uv_loop_t))); - uv_loop_init(loop); - - uv_timer_t timer1; - uv_timer_init(loop, &timer1); - uv_timer_start(&timer1, (uv_timer_cb)&timer_cb1, 1000, 1000); - - uv_run(loop, UV_RUN_DEFAULT); - printf("Now quitting.\n"); - - uv_loop_close(loop); - free(loop); -} \ No newline at end of file diff --git a/test/Misc/mariadb_connector_c_test.cpp b/test/Misc/mariadb_connector_c_test.cpp deleted file mode 100644 index 4f5bf2c7e..000000000 --- a/test/Misc/mariadb_connector_c_test.cpp +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include -#include - -#include "CtldPublicDefs.h" - -const char* mysql_user = ""; -const char* mysql_password = ""; - -class MariadbClient { - public: - MariadbClient() = default; - - ~MariadbClient() { - if (m_conn) { - mysql_close(m_conn); - } - } - - bool Init() { - m_conn = mysql_init(nullptr); - if (m_conn == nullptr) return false; - - // Reconnect when Mariadb closes connection after a long idle time (8 - // hours). - my_bool reconnect = 1; - mysql_options(m_conn, MYSQL_OPT_RECONNECT, &reconnect); - - return true; - } - - bool Connect(const std::string& username, const std::string& password) { - if (mysql_real_connect(m_conn, "127.0.0.1", username.c_str(), - password.c_str(), nullptr, 3306, nullptr, - 0) == nullptr) { - PrintError_("Cannot connect to database"); - return false; - } - - if (mysql_query(m_conn, - fmt::format("CREATE DATABASE IF NOT EXISTS {};", m_db_name) - .c_str())) { - PrintError_(fmt::format("Cannot check the existence of {}", m_db_name)); - return false; - } - - if (mysql_select_db(m_conn, m_db_name.c_str()) != 0) { - PrintError_(fmt::format("Cannot select {}", m_db_name)); - return false; - } - - if (mysql_query( - m_conn, - "CREATE TABLE IF NOT EXISTS job_table(" - "job_db_inx bigint unsigned not null auto_increment primary key," - "mod_time bigint unsigned default 0 not null," - "deleted tinyint default 0 not null," - "account tinytext," - "cpus_req int unsigned not null," - "mem_req bigint unsigned default 0 not null," - "job_name tinytext not null," - "env text," - "id_job int unsigned not null," - "id_user int unsigned not null," - "id_group int unsigned not null," - "nodelist text," - "nodes_alloc int unsigned not null," - "node_inx text," - "partition_name tinytext not null," - "priority int unsigned not null," - "time_eligible bigint unsigned default 0 not null," - "time_start bigint unsigned default 0 not null," - "time_end bigint unsigned default 0 not null," - "time_suspended bigint unsigned default 0 not null," - "script text not null default ''," - "state int unsigned not null," - "timelimit int unsigned default 0 not null," - "time_submit bigint unsigned default 0 not null," - "work_dir text not null default ''," - "submit_line text," - "task_to_ctld blob not null" - ");")) { - PrintError_("Cannot check the existence of job_table"); - return false; - } - - return true; - } - - bool GetMaxExistingJobId(uint64_t* job_id) { - if (mysql_query(m_conn, - "SELECT COALESCE(MAX(job_db_inx), 0) FROM job_table;")) { - PrintError_("Cannot get the max id"); - return false; - } - - MYSQL_RES* result = mysql_store_result(m_conn); - if (result == nullptr) { - PrintError_("Error in getting the max job id result"); - return false; - } - - MYSQL_ROW row = mysql_fetch_row(result); - unsigned long* lengths = mysql_fetch_lengths(result); - - if (lengths == nullptr) { - PrintError_("Error in fetching rows of max id result"); - mysql_free_result(result); - return false; - } - - *job_id = strtoul(row[0], nullptr, 10); - - mysql_free_result(result); - return true; - } - - bool GetLastInsertId(uint64_t* id) { - if (mysql_query(m_conn, "SELECT LAST_INSERT_ID();")) { - PrintError_("Cannot get last insert id"); - return false; - } - - MYSQL_RES* result = mysql_store_result(m_conn); - if (result == nullptr) { - PrintError_("Error in getting the max job id result"); - return false; - } - - MYSQL_ROW row = mysql_fetch_row(result); - unsigned long* lengths = mysql_fetch_lengths(result); - - if (lengths == nullptr) { - PrintError_("Error in fetching rows of max id result"); - mysql_free_result(result); - return false; - } - - *id = strtoul(row[0], nullptr, 10); - - mysql_free_result(result); - return true; - } - - bool InsertJob(uint64_t* job_db_inx, uint64_t mod_timestamp, - const std::string& account, uint32_t cpu, - uint64_t memory_bytes, const std::string& job_name, - const std::string& env, uint32_t id_job, uid_t id_user, - uid_t id_group, const std::string& nodelist, - uint32_t nodes_alloc, const std::string& node_inx, - const std::string& partition_name, uint32_t priority, - uint64_t submit_timestamp, const std::string& script, - uint32_t state, uint32_t timelimit, - const std::string& work_dir, - const crane::grpc::TaskToCtld& task_to_ctld) { - size_t blob_size = task_to_ctld.ByteSizeLong(); - constexpr size_t blob_max_size = 8192; - - static char blob[blob_max_size]; - static char query[blob_max_size * 2]; - task_to_ctld.SerializeToArray(blob, blob_max_size); - - std::string query_head = fmt::format( - "INSERT INTO job_table(" - "mod_time, deleted, account, cpus_req, mem_req, job_name, env, " - "id_job, id_user, id_group, nodelist, nodes_alloc, node_inx, " - "partition_name, priority, time_submit, script, state, timelimit, " - " work_dir, task_to_ctld) " - " VALUES({}, 0, '{}', {}, {}, '{}', '{}', {}, {}, {}, " - "'{}', {}, '{}', '{}', {}, {}, '{}', {}, {}, " - "'{}', '", - mod_timestamp, account, cpu, memory_bytes, job_name, env, id_job, - id_user, id_group, nodelist, nodes_alloc, node_inx, partition_name, - priority, submit_timestamp, script, state, timelimit, work_dir); - char* query_ptr = std::copy(query_head.c_str(), - query_head.c_str() + query_head.size(), query); - size_t escaped_size = - mysql_real_escape_string(m_conn, query_ptr, blob, blob_size); - query_ptr += escaped_size; - - const char query_end[] = "')"; - query_ptr = - std::copy(query_end, query_end + sizeof(query_end) - 1, query_ptr); - - if (mysql_real_query(m_conn, query, query_ptr - query)) { - PrintError_("Failed to insert job record"); - return false; - } - - uint64_t last_id; - if (!GetLastInsertId(&last_id)) { - PrintError_("Failed to GetLastInsertId"); - return false; - } - *job_db_inx = last_id; - - return true; - } - - bool UpdateJobRecordField(uint64_t job_db_inx, const std::string& field_name, - const std::string& val) { - std::string query = fmt::format( - "UPDATE job_table SET {} = '{}', mod_time = UNIX_TIMESTAMP() WHERE " - "job_db_inx = {};", - field_name, val, job_db_inx); - - if (mysql_query(m_conn, query.c_str())) { - PrintError_("Failed to update job record"); - return false; - } - - return true; - } - - bool FetchJobRecordsWithStates( - std::list* task_list, - const std::list& states) { - std::vector state_piece; - for (auto state : states) { - state_piece.emplace_back(fmt::format("state = {}", state)); - } - std::string state_str = absl::StrJoin(state_piece, " or "); - - std::string query = - fmt::format("SELECT * FROM job_table WHERE {};", state_str); - - if (mysql_query(m_conn, query.c_str())) { - PrintError_("Failed to fetch job record"); - return false; - } - - MYSQL_RES* result = mysql_store_result(m_conn); - if (result == nullptr) { - PrintError_("Error in getting `fetch job` result"); - return false; - } - - uint32_t num_fields = mysql_num_fields(result); - - MYSQL_ROW row; - // 0 job_db_inx mod_time deleted account cpus_req - // 5 mem_req job_name env id_job id_user - // 10 id_group nodelist nodes_alloc node_inx partition_name - // 15 priority time_eligible time_start time_end time_suspended - // 20 script state timelimit time_submit work_dir - // 25 submit_line task_to_ctld - - while ((row = mysql_fetch_row(result))) { - size_t* lengths = mysql_fetch_lengths(result); - - Ctld::TaskInCtld task; - task.task_db_id = std::strtoul(row[0], nullptr, 10); - task.resources.allocatable_resource.cpu_count = - std::strtoul(row[4], nullptr, 10); - task.resources.allocatable_resource.memory_bytes = - task.resources.allocatable_resource.memory_sw_bytes = - std::strtoul(row[5], nullptr, 10); - task.name = row[6]; - task.env = row[7]; - task.task_id = std::strtoul(row[8], nullptr, 10); - task.uid = std::strtoul(row[9], nullptr, 10); - task.gid = std::strtoul(row[10], nullptr, 10); - if (row[11]) task.allocated_craneds_regex = row[11]; - task.partition_id = row[14]; - task.start_time = - absl::FromUnixSeconds(std::strtol(row[17], nullptr, 10)); - task.end_time = absl::FromUnixSeconds(std::strtol(row[18], nullptr, 10)); - - task.meta = Ctld::BatchMetaInTask{}; - auto& batch_meta = std::get(task.meta); - batch_meta.sh_script = row[20]; - task.status = crane::grpc::TaskStatus(std::stoi(row[21])); - task.time_limit = absl::Seconds(std::strtol(row[22], nullptr, 10)); - task.cwd = row[24]; - - if (row[25]) task.cmd_line = row[25]; - - bool ok = task.task_to_ctld.ParseFromArray(row[26], lengths[26]); - - task_list->emplace_back(std::move(task)); - } - - mysql_free_result(result); - return true; - } - - private: - void PrintError_(const std::string& msg) { - CRANE_ERROR("{}: {}\n", msg, mysql_error(m_conn)); - } - - void PrintError_(const char* msg) { - CRANE_ERROR("{}: {}\n", msg, mysql_error(m_conn)); - } - - MYSQL* m_conn{nullptr}; - const std::string m_db_name{"crane_db"}; -}; - -TEST(MariadbConnector, Simple) { - MariadbClient client; - ASSERT_TRUE(client.Init()); - ASSERT_TRUE(client.Connect(mysql_user, mysql_password)); - - crane::grpc::TaskToCtld task_to_ctld; - task_to_ctld.set_name("riley_job_2"); - task_to_ctld.set_uid(1000); - task_to_ctld.set_type(crane::grpc::Batch); - task_to_ctld.set_ntasks_per_node(2); - task_to_ctld.set_cmd_line("cmd_line"); - task_to_ctld.set_cwd("cwd"); - task_to_ctld.mutable_time_limit()->set_seconds(360); - task_to_ctld.mutable_batch_meta()->set_sh_script("#sbatch"); - - uint64_t job_id; - uint64_t job_db_inx; - ASSERT_TRUE(client.GetMaxExistingJobId(&job_id)); - ASSERT_TRUE(client.InsertJob( - &job_db_inx, 0, std::string("Riley"), 2, 20480000, - std::string("test_job"), std::string("PATH=XXXX"), 3, 1000, 1000, - std::string("1"), 1, std::string("1"), std::string("CPU"), 0, 12312321, - std::string("script"), 0, 1000, std::string("/"), task_to_ctld)); - - ASSERT_TRUE(client.UpdateJobRecordField(job_db_inx, std::string("env"), - std::string("PATH=ABDASDAS"))); - - std::list task_list; - client.FetchJobRecordsWithStates( - &task_list, - {crane::grpc::TaskStatus::Pending, crane::grpc::TaskStatus::Running}); - GTEST_LOG_(INFO) << "End of Test\n"; -} \ No newline at end of file diff --git a/test/Misc/yaml_cpp_test.cpp b/test/Misc/yaml_cpp_test.cpp deleted file mode 100644 index 3c5ba8e9b..000000000 --- a/test/Misc/yaml_cpp_test.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include -#include -#include - -#include - -TEST(YAML, Sample) { - std::string config_yaml_path = "/etc/crane/config.yaml"; - - try { - YAML::Node node = YAML::LoadFile(config_yaml_path); - - if (node["ControlMachine"]) { - fmt::print("ControlMachine: {}\n", - node["ControlMachine"].as()); - } - } catch (YAML::BadFile &e) { - fmt::print(stderr, "Config file {} doesn't exist.", config_yaml_path); - } -} \ No newline at end of file diff --git a/test/SharedTestImpl/CMakeLists.txt b/test/SharedTestImpl/CMakeLists.txt deleted file mode 100644 index df1199346..000000000 --- a/test/SharedTestImpl/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -add_library(shared_test_impl_lib greeter_service_impl.cpp - include/SharedTestImpl/GlobalDefs.h include/SharedTestImpl/greeter_service_impl.h) - -target_link_libraries(shared_test_impl_lib PUBLIC test_proto Utility_PublicHeader) -target_include_directories(shared_test_impl_lib PUBLIC include) - diff --git a/test/SharedTestImpl/greeter_service_impl.cpp b/test/SharedTestImpl/greeter_service_impl.cpp deleted file mode 100644 index 804979295..000000000 --- a/test/SharedTestImpl/greeter_service_impl.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#include "SharedTestImpl/greeter_service_impl.h" - -#include - -#include "crane/Logger.h" - -grpc::Status GreeterServiceImpl::SayHello( - grpc::ServerContext* context, const grpc_example::HelloRequest* request, - grpc_example::HelloReply* reply) { - std::string prefix("Hello "); - reply->set_message(prefix + request->name()); - return grpc::Status::OK; -} - -grpc::Status GreeterServiceImpl::SleepSeconds( - grpc::ServerContext* context, const grpc_example::SleepRequest* request, - grpc_example::SleepReply* response) { - response->set_ok(true); - - std::this_thread::sleep_for(std::chrono::seconds(request->seconds())); - - return grpc::Status::OK; -} - -GreeterSyncServer::GreeterSyncServer(const std::string& server_address) { - grpc::ServerBuilder builder; - // Listen on the given address without any authentication mechanism. - builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); - // Register "service" as the instance through which we'll communicate with - // clients. In this case it corresponds to an *synchronous* service. - builder.RegisterService(&m_service_impl_); - // Finally assemble the server. - m_server_ = builder.BuildAndStart(); - - CRANE_INFO("Server listening on {}", server_address); -} diff --git a/test/SharedTestImpl/include/SharedTestImpl/GlobalDefs.h b/test/SharedTestImpl/include/SharedTestImpl/GlobalDefs.h deleted file mode 100644 index d0dc8267f..000000000 --- a/test/SharedTestImpl/include/SharedTestImpl/GlobalDefs.h +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include - -namespace Internal { - -class EnableSpdlogTraceLevel { - public: - EnableSpdlogTraceLevel() { spdlog::set_level(spdlog::level::trace); } -}; - -[[maybe_unused]] inline EnableSpdlogTraceLevel enableSpdlogTraceLevel; - -} // namespace Internal \ No newline at end of file diff --git a/test/SharedTestImpl/include/SharedTestImpl/greeter_service_impl.h b/test/SharedTestImpl/include/SharedTestImpl/greeter_service_impl.h deleted file mode 100644 index 2ddb63f31..000000000 --- a/test/SharedTestImpl/include/SharedTestImpl/greeter_service_impl.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -#pragma once - -#include - -#include "protos/greeter.grpc.pb.h" -#include "protos/greeter.pb.h" - -class GreeterServiceImpl final : public grpc_example::Greeter::Service { - grpc::Status SayHello(grpc::ServerContext* context, - const grpc_example::HelloRequest* request, - grpc_example::HelloReply* reply) override; - - grpc::Status SleepSeconds(grpc::ServerContext* context, - const grpc_example::SleepRequest* request, - grpc_example::SleepReply* response) override; -}; - -class GreeterSyncServer { - public: - GreeterSyncServer(const std::string& server_address); - - inline void Shutdown() { m_server_->Shutdown(); } - - inline void Wait() { m_server_->Wait(); } - - private: - std::unique_ptr m_server_; - GreeterServiceImpl m_service_impl_; -}; \ No newline at end of file diff --git a/test/protos/CMakeLists.txt b/test/protos/CMakeLists.txt deleted file mode 100644 index f0a2a8cfb..000000000 --- a/test/protos/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -set(test_proto_src math.proto greeter.proto) - -# The path used for #include -set(GENERATED_TEST_PATH ${CMAKE_SOURCE_DIR}/generated/test) - -# The path where proto cpp/h are generated -set(GENERATED_TEST_PROTO_PATH ${GENERATED_TEST_PATH}/protos) - -file(MAKE_DIRECTORY ${GENERATED_TEST_PROTO_PATH}) -set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES - "${CMAKE_SOURCE_DIR}/generated/test/*") - -include(${CMAKE_SOURCE_DIR}/CMakeModule/ProtobufGenerate.cmake) -PROTOBUF_GENERATE_GRPC_CPP(TestProtoCxxSources TestProtoCxxHeaders - ${GENERATED_TEST_PROTO_PATH} - ${protobuf_SOURCE_DIR}/src - ${test_proto_src}) - -add_library(test_proto STATIC - ${TestProtoCxxSources} ${TestProtoCxxHeaders}) -target_link_libraries(test_proto - ${_GRPC_GRPCPP} - ${_PROTOBUF_LIBPROTOBUF}) -target_link_libraries(test_proto absl::base absl::synchronization) - -target_include_directories(test_proto PUBLIC ${GENERATED_TEST_PATH}) \ No newline at end of file diff --git a/test/protos/greeter.proto b/test/protos/greeter.proto deleted file mode 100644 index 78bea8c93..000000000 --- a/test/protos/greeter.proto +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -syntax = "proto3"; - -package grpc_example; - -// The greeting service definition. -service Greeter { - // Sends a greeting - rpc SayHello (HelloRequest) returns (HelloReply) {} - - rpc SleepSeconds(SleepRequest) returns (SleepReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings -message HelloReply { - string message = 1; -} - -message SleepRequest { - uint32 seconds = 1; -} - -message SleepReply { - bool ok = 1; -} \ No newline at end of file diff --git a/test/protos/math.proto b/test/protos/math.proto deleted file mode 100644 index e007485aa..000000000 --- a/test/protos/math.proto +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2024 Peking University and Peking University - * Changsha Institute for Computing and Digital Economy - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -syntax = "proto3"; - -package grpc_example; - -service Math { - rpc Max (stream MaxRequest) returns (stream MaxResponse); -} - -message MaxRequest { - int32 a = 1; - int32 b = 2; -} - -message MaxResponse { - int32 result = 1; -} \ No newline at end of file From acec2d1b2293cb3136335f1255a84c6b97d13b37 Mon Sep 17 00:00:00 2001 From: NamelessOIer <70872016+NamelessOIer@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:46:18 +0800 Subject: [PATCH 03/14] Bugfix: Check permission when update node (#395) --- protos/Crane.proto | 7 ++++--- src/CraneCtld/CtldGrpcServer.cpp | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/protos/Crane.proto b/protos/Crane.proto index aa0d57bda..be2f822f0 100644 --- a/protos/Crane.proto +++ b/protos/Crane.proto @@ -224,9 +224,10 @@ message ModifyTaskReply { } message ModifyCranedStateRequest{ - repeated string craned_ids = 1; - CranedControlState new_state = 2; - string reason = 3; + uint32 uid = 1; + repeated string craned_ids = 2; + CranedControlState new_state = 3; + string reason = 4; } message ModifyCranedStateReply{ diff --git a/src/CraneCtld/CtldGrpcServer.cpp b/src/CraneCtld/CtldGrpcServer.cpp index a27cae29d..c14c087d8 100644 --- a/src/CraneCtld/CtldGrpcServer.cpp +++ b/src/CraneCtld/CtldGrpcServer.cpp @@ -235,6 +235,14 @@ grpc::Status CraneCtldServiceImpl::ModifyNode( grpc::ServerContext *context, const crane::grpc::ModifyCranedStateRequest *request, crane::grpc::ModifyCranedStateReply *response) { + auto res = g_account_manager->CheckUidIsAdmin(request->uid()); + if (!res) { + for (auto crane_id : request->craned_ids()) { + response->add_not_modified_nodes(crane_id); + response->add_not_modified_reasons(res.error()); + } + return grpc::Status::OK; + } *response = g_meta_container->ChangeNodeState(*request); return grpc::Status::OK; From c4540ed8421b7ae4fde2e9434eca3bda278460d9 Mon Sep 17 00:00:00 2001 From: Junlin Li <70465472+L-Xiafeng@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:01:41 +0800 Subject: [PATCH 04/14] Feat: Crun support pty (#362) * Refactor: Crun support pty Signed-off-by: Li Junlin * Refactor: Use original grpc message id order Signed-off-by: Li Junlin * Fix: Fix ci compile error Signed-off-by: Li Junlin * feat: Crun support pty for single node Signed-off-by: Li Junlin * feat: Support crun --pty with multi node Signed-off-by: Li Junlin * refactor: Refactor cfored/crun/calloc task completion code. Signed-off-by: Li Junlin * fix: Fix crun/calloc fail when cancel pending task by ctrl c Signed-off-by: Li Junlin * refactor Signed-off-by: Li Junlin * chore: Typo Signed-off-by: Li Junlin * Refactor. Signed-off-by: RileyW * Fix compilation error. Signed-off-by: RileyW --------- Signed-off-by: Li Junlin Signed-off-by: RileyW Co-authored-by: RileyW --- CMakeLists.txt | 1 + protos/PublicDefs.proto | 1 + src/CraneCtld/CranedKeeper.cpp | 1 + src/CraneCtld/CtldGrpcServer.cpp | 46 ++++++++-------- src/CraneCtld/CtldGrpcServer.h | 6 +-- src/CraneCtld/CtldPublicDefs.h | 10 ++-- src/CraneCtld/TaskScheduler.cpp | 92 +++++++++++++++++++------------- src/CraneCtld/TaskScheduler.h | 19 +++++-- src/Craned/CMakeLists.txt | 1 + src/Craned/CforedClient.cpp | 57 +++++++++++++++----- src/Craned/CforedClient.h | 8 +-- src/Craned/TaskManager.cpp | 80 ++++++++++++++------------- src/Craned/TaskManager.h | 5 +- 13 files changed, 199 insertions(+), 128 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a981c68e..b065eef2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -278,6 +278,7 @@ set(DEPENDENCIES_PRE_INSTALLED_DIR ${CMAKE_CURRENT_SOURCE_DIR}/dependencies/pre_ add_subdirectory(${DEPENDENCIES_PRE_INSTALLED_DIR}) find_package(Threads REQUIRED) +find_library(LIBUTIL_LIBRARY util) # New in version cmake3.24: # Set ZLIB_USE_STATIC_LIBS to ON to look for static libraries. Default is OFF. diff --git a/protos/PublicDefs.proto b/protos/PublicDefs.proto index 8c328d860..6e28d983a 100644 --- a/protos/PublicDefs.proto +++ b/protos/PublicDefs.proto @@ -223,6 +223,7 @@ message InteractiveTaskAdditionalMeta { string sh_script = 2; string term_env = 3; InteractiveTaskType interactive_type = 4; + bool pty = 5; } message TaskInfo { diff --git a/src/CraneCtld/CranedKeeper.cpp b/src/CraneCtld/CranedKeeper.cpp index 863bfb5dd..dd67ce3d5 100644 --- a/src/CraneCtld/CranedKeeper.cpp +++ b/src/CraneCtld/CranedKeeper.cpp @@ -319,6 +319,7 @@ crane::grpc::ExecuteTasksRequest CranedStub::NewExecuteTasksRequests( mutable_meta->set_sh_script(meta_in_ctld.sh_script); mutable_meta->set_term_env(meta_in_ctld.term_env); mutable_meta->set_interactive_type(meta_in_ctld.interactive_type); + mutable_meta->set_pty(meta_in_ctld.pty); } } diff --git a/src/CraneCtld/CtldGrpcServer.cpp b/src/CraneCtld/CtldGrpcServer.cpp index c14c087d8..bc00b3152 100644 --- a/src/CraneCtld/CtldGrpcServer.cpp +++ b/src/CraneCtld/CtldGrpcServer.cpp @@ -71,7 +71,7 @@ grpc::Status CraneCtldServiceImpl::SubmitBatchTasks( results.emplace_back(std::move(result)); } - for (auto& res : results) { + for (auto &res : results) { if (res.has_value()) response->mutable_task_id_list()->Add(res.value().get()); else @@ -740,21 +740,22 @@ grpc::Status CraneCtldServiceImpl::CforedStream( CRANE_ERROR("Expect type CFORED_REGISTRATION from peer {}.", context->peer()); return Status::CANCELLED; - } else { - cfored_name = cfored_request.payload_cfored_reg().cfored_name(); - CRANE_INFO("Cfored {} registered.", cfored_name); + } - ok = stream_writer->WriteCforedRegistrationAck({}); - if (ok) { - state = StreamState::kWaitMsg; - } else { - CRANE_ERROR( - "Failed to send msg to cfored {}. Connection is broken. " - "Exiting...", - cfored_name); - state = StreamState::kCleanData; - } + cfored_name = cfored_request.payload_cfored_reg().cfored_name(); + CRANE_INFO("Cfored {} registered.", cfored_name); + + ok = stream_writer->WriteCforedRegistrationAck({}); + if (ok) { + state = StreamState::kWaitMsg; + } else { + CRANE_ERROR( + "Failed to send msg to cfored {}. Connection is broken. " + "Exiting...", + cfored_name); + state = StreamState::kCleanData; } + } else { state = StreamState::kCleanData; } @@ -784,15 +785,16 @@ grpc::Status CraneCtldServiceImpl::CforedStream( }; meta.cb_task_cancel = [writer_weak_ptr](task_id_t task_id) { + CRANE_TRACE("Sending TaskCancelRequest in task_cancel", task_id); if (auto writer = writer_weak_ptr.lock(); writer) writer->WriteTaskCancelRequest(task_id); }; - meta.cb_task_completed = [this, i_type, cfored_name, - writer_weak_ptr](task_id_t task_id) { - CRANE_TRACE("Sending TaskCompletionAckReply in task_completed", - task_id); - if (auto writer = writer_weak_ptr.lock(); writer) + meta.cb_task_completed = [this, i_type, cfored_name, writer_weak_ptr]( + task_id_t task_id, + bool send_completion_ack) { + if (auto writer = writer_weak_ptr.lock(); + writer && send_completion_ack) writer->WriteTaskCompletionAckReply(task_id); m_ctld_server_->m_mtx_.Lock(); @@ -838,8 +840,7 @@ grpc::Status CraneCtldServiceImpl::CforedStream( case StreamCforedRequest::TASK_COMPLETION_REQUEST: { auto const &payload = cfored_request.payload_task_complete_req(); CRANE_TRACE("Recv TaskCompletionReq of Task #{}", payload.task_id()); - - if (g_task_scheduler->TerminatePendingOrRunningTask( + if (g_task_scheduler->TerminatePendingOrRunningIaTask( payload.task_id()) != CraneErr::kOk) stream_writer->WriteTaskCompletionAckReply(payload.task_id()); } break; @@ -973,8 +974,7 @@ CtldServer::SubmitTaskToScheduler(std::unique_ptr task) { task->Username(), task->partition_id, task->account)); } - auto enable_res = - g_account_manager->CheckIfUserOfAccountIsEnabled( + auto enable_res = g_account_manager->CheckIfUserOfAccountIsEnabled( task->Username(), task->account); if (!enable_res) { return std::unexpected(enable_res.error()); diff --git a/src/CraneCtld/CtldGrpcServer.h b/src/CraneCtld/CtldGrpcServer.h index fdc24d0ac..d35493fec 100644 --- a/src/CraneCtld/CtldGrpcServer.h +++ b/src/CraneCtld/CtldGrpcServer.h @@ -67,8 +67,7 @@ class CforedStreamWriter { bool WriteTaskResAllocReply( task_id_t task_id, - std::expected>, - std::string> + std::expected>, std::string> res) { LockGuard guard(&m_stream_mtx_); if (!m_valid_) return false; @@ -121,8 +120,7 @@ class CforedStreamWriter { return m_stream_->Write(reply); } - bool WriteCforedRegistrationAck( - const std::expected &res) { + bool WriteCforedRegistrationAck(const std::expected &res) { LockGuard guard(&m_stream_mtx_); if (!m_valid_) return false; diff --git a/src/CraneCtld/CtldPublicDefs.h b/src/CraneCtld/CtldPublicDefs.h index 2e3eeb252..1e7b92386 100644 --- a/src/CraneCtld/CtldPublicDefs.h +++ b/src/CraneCtld/CtldPublicDefs.h @@ -225,12 +225,14 @@ struct InteractiveMetaInTask { std::string sh_script; std::string term_env; + bool pty; std::function const&)> cb_task_res_allocated; - std::function cb_task_completed; - // only for calloc. + std::function cb_task_completed; + + // This will ask front end like crun/calloc to exit std::function cb_task_cancel; // only for crun. @@ -493,8 +495,10 @@ struct TaskInCtld { InteractiveMeta.interactive_type = val.interactive_meta().interactive_type(); if (InteractiveMeta.interactive_type == - crane::grpc::InteractiveTaskType::Crun) + crane::grpc::InteractiveTaskType::Crun) { InteractiveMeta.term_env = val.interactive_meta().term_env(); + InteractiveMeta.pty = val.interactive_meta().pty(); + } } node_num = val.node_num(); diff --git a/src/CraneCtld/TaskScheduler.cpp b/src/CraneCtld/TaskScheduler.cpp index ffa3025e7..46835eb25 100644 --- a/src/CraneCtld/TaskScheduler.cpp +++ b/src/CraneCtld/TaskScheduler.cpp @@ -686,22 +686,37 @@ void TaskScheduler::ScheduleThread_() { task->allocated_craneds_regex = util::HostNameListToStr(task->CranedIds()); + // Task execute on all node, otherwise on the first node + bool launch_on_all_nodes; if (task->type == crane::grpc::Batch) { // For cbatch tasks whose --node > 1, // only execute the command at the first allocated node. - task->executing_craned_ids.emplace_back(task->CranedIds().front()); + launch_on_all_nodes = false; } else { const auto& meta = std::get(task->meta); if (meta.interactive_type == crane::grpc::Calloc) // For calloc tasks we still need to execute a dummy empty task to // set up a timer. - task->executing_craned_ids.emplace_back(task->CranedIds().front()); - else - // For crun tasks we need to execute tasks on all allocated nodes. - for (auto const& craned_id : task->CranedIds()) - task->executing_craned_ids.emplace_back(craned_id); + launch_on_all_nodes = false; + else { + // For crun tasks we need to execute tasks on all allocated + // nodes. + + // Crun task with pty only launch on first node + if (task->TaskToCtld().interactive_meta().pty()) + launch_on_all_nodes = false; + else + launch_on_all_nodes = true; + } } + + if (launch_on_all_nodes) { + for (auto const& craned_id : task->CranedIds()) + task->executing_craned_ids.emplace_back(craned_id); + } else + task->executing_craned_ids.emplace_back(task->CranedIds().front()); } + end = std::chrono::steady_clock::now(); CRANE_TRACE( "Set task fields costed {} ms", @@ -1273,18 +1288,12 @@ crane::grpc::CancelTaskReply TaskScheduler::CancelPendingOrRunningTask( reply.add_not_cancelled_tasks(task_id); reply.add_not_cancelled_reasons("Permission Denied."); } else { - bool is_calloc = false; if (task->type == crane::grpc::Interactive) { auto& meta = std::get(task->meta); - if (meta.interactive_type == crane::grpc::Calloc) is_calloc = true; - - if (is_calloc && !meta.has_been_cancelled_on_front_end) { + if (!meta.has_been_cancelled_on_front_end) { meta.has_been_cancelled_on_front_end = true; meta.cb_task_cancel(task_id); } - } - - if (is_calloc) { reply.add_cancelled_tasks(task_id); } else { CraneErr err = TerminateRunningTaskNoLock_(task); @@ -1457,8 +1466,18 @@ void TaskScheduler::CleanCancelQueueCb_() { if (task->type == crane::grpc::Interactive) { auto& meta = std::get(task->meta); - g_thread_pool->detach_task([cb = meta.cb_task_cancel, - task_id = task->TaskId()] { cb(task_id); }); + // Cancel request may not come from crun/calloc, ask them to exit + if (!meta.has_been_cancelled_on_front_end) { + meta.has_been_cancelled_on_front_end = true; + g_thread_pool->detach_task([cb = meta.cb_task_cancel, + task_id = task->TaskId()] { cb(task_id); }); + } else { + // Cancel request from crun/calloc, reply CompletionAck + g_thread_pool->detach_task( + [cb = meta.cb_task_completed, task_id = task->TaskId()] { + cb(task_id, true); + }); + } } } @@ -1619,34 +1638,35 @@ void TaskScheduler::CleanTaskStatusChangeQueueCb_() { task->SetStatus(new_status); } else { auto& meta = std::get(task->meta); - if (meta.interactive_type == crane::grpc::Calloc) { - // TaskStatusChange may indicate the time limit has been reached and - // the task has been terminated. No more TerminateTask RPC should be - // sent to the craned node if any further CancelTask or - // TaskCompletionRequest RPC is received. - meta.has_been_terminated_on_craned = true; - - if (new_status == crane::grpc::ExceedTimeLimit || - exit_code == ExitCode::kExitCodeCranedDown) { - meta.has_been_cancelled_on_front_end = true; - meta.cb_task_cancel(task->TaskId()); - task->SetStatus(new_status); - } else { - task->SetStatus(crane::grpc::Completed); - } - meta.cb_task_completed(task->TaskId()); - } else { // Crun - if (++meta.status_change_cnt < task->node_num) { + if (meta.interactive_type == crane::grpc::Crun) { // Crun + if (++meta.status_change_cnt < task->executing_craned_ids.size()) { CRANE_TRACE( "{}/{} TaskStatusChanges of Crun task #{} were received. " "Keep waiting...", - meta.status_change_cnt, task->node_num, task->TaskId()); + meta.status_change_cnt, task->executing_craned_ids.size(), + task->TaskId()); continue; } + } - task->SetStatus(new_status); - meta.cb_task_completed(task->TaskId()); + // TaskStatusChange may indicate the time limit has been reached and + // the task has been terminated. No more TerminateTask RPC should be + // sent to the craned node if any further CancelTask or + // TaskCompletionRequest RPC is received. + + // Task end triggered by craned. + if (!meta.has_been_cancelled_on_front_end) { + meta.has_been_cancelled_on_front_end = true; + meta.cb_task_cancel(task->TaskId()); + // Completion ack will send in grpc server triggered by task complete + // req + meta.cb_task_completed(task->TaskId(), false); + } else { + // Send Completion Ack to frontend now. + meta.cb_task_completed(task->TaskId(), true); } + + task->SetStatus(new_status); } task->SetExitCode(exit_code); diff --git a/src/CraneCtld/TaskScheduler.h b/src/CraneCtld/TaskScheduler.h index 32eab6508..1896e59de 100644 --- a/src/CraneCtld/TaskScheduler.h +++ b/src/CraneCtld/TaskScheduler.h @@ -254,21 +254,34 @@ class TaskScheduler { crane::grpc::CancelTaskReply CancelPendingOrRunningTask( const crane::grpc::CancelTaskRequest& request); - CraneErr TerminatePendingOrRunningTask(uint32_t task_id) { + CraneErr TerminatePendingOrRunningIaTask(uint32_t task_id) { LockGuard pending_guard(&m_pending_task_map_mtx_); LockGuard running_guard(&m_running_task_map_mtx_); auto pd_it = m_pending_task_map_.find(task_id); if (pd_it != m_pending_task_map_.end()) { + auto& task = pd_it->second; + if (task->type == crane::grpc::TaskType::Interactive) { + auto& meta = std::get(task->meta); + meta.has_been_cancelled_on_front_end = true; + } m_cancel_task_queue_.enqueue( - CancelPendingTaskQueueElem{.task = std::move(pd_it->second)}); + CancelPendingTaskQueueElem{.task = std::move(task)}); m_cancel_task_async_handle_->send(); m_pending_task_map_.erase(pd_it); return CraneErr::kOk; } auto rn_it = m_running_task_map_.find(task_id); - if (rn_it == m_running_task_map_.end()) return CraneErr::kNonExistent; + if (rn_it == m_running_task_map_.end()) + return CraneErr::kNonExistent; + else { + auto& task = rn_it->second; + if (task->type == crane::grpc::TaskType::Interactive) { + auto& meta = std::get(task->meta); + meta.has_been_cancelled_on_front_end = true; + } + } return TerminateRunningTaskNoLock_(rn_it->second.get()); } diff --git a/src/Craned/CMakeLists.txt b/src/Craned/CMakeLists.txt index f7d3902bc..a1d5accf6 100644 --- a/src/Craned/CMakeLists.txt +++ b/src/Craned/CMakeLists.txt @@ -44,6 +44,7 @@ target_link_libraries(craned cxxopts Threads::Threads + ${LIBUTIL_LIBRARY} nlohmann_json::nlohmann_json absl::flat_hash_map diff --git a/src/Craned/CforedClient.cpp b/src/Craned/CforedClient.cpp index 2818c1e62..01c9447f7 100644 --- a/src/Craned/CforedClient.cpp +++ b/src/Craned/CforedClient.cpp @@ -18,6 +18,8 @@ #include "CforedClient.h" +#include + #include "crane/String.h" namespace Craned { @@ -343,12 +345,8 @@ void CforedManager::EvLoopThread_(const std::shared_ptr& uvw_loop) { } void CforedManager::RegisterIOForward(std::string const& cfored, - task_id_t task_id, int in_fd, - int out_fd) { - RegisterElem elem{.cfored = cfored, - .task_id = task_id, - .in_fd = in_fd, - .out_fd = out_fd}; + task_id_t task_id, int fd, bool pty) { + RegisterElem elem{.cfored = cfored, .task_id = task_id, .fd = fd, .pty = pty}; std::promise done; std::future done_fut = done.get_future(); @@ -372,7 +370,7 @@ void CforedManager::RegisterCb_() { } m_cfored_client_map_[elem.cfored]->InitTaskFwdAndSetInputCb( - elem.task_id, [fd = elem.in_fd](const std::string& msg) -> bool { + elem.task_id, [fd = elem.fd](const std::string& msg) -> bool { ssize_t sz_sent = 0, sz_written; while (sz_sent != msg.size()) { sz_written = write(fd, msg.c_str() + sz_sent, msg.size() - sz_sent); @@ -386,9 +384,9 @@ void CforedManager::RegisterCb_() { return true; }); - CRANE_TRACE("Registering fd {} for outputs of task #{}", elem.out_fd, + CRANE_TRACE("Registering fd {} for outputs of task #{}", elem.fd, elem.task_id); - auto poll_handle = m_loop_->resource(elem.out_fd); + auto poll_handle = m_loop_->resource(elem.fd); poll_handle->on([this, elem = std::move(elem)]( const uvw::poll_event&, uvw::poll_handle& h) { @@ -397,12 +395,46 @@ void CforedManager::RegisterCb_() { constexpr int MAX_BUF_SIZE = 4096; char buf[MAX_BUF_SIZE]; - auto ret = read(elem.out_fd, buf, MAX_BUF_SIZE); + auto ret = read(elem.fd, buf, MAX_BUF_SIZE); + bool read_finished{false}; + if (ret == 0) { + if (!elem.pty) { + read_finished = true; + } else { + // For pty,do nothing, process exit on return -1 and error set to EIO + CRANE_TRACE("Read EOF from pty task #{} on cfored {}", elem.task_id, + elem.cfored); + } + } + + if (ret == -1) { + if (!elem.pty) { + CRANE_ERROR("Error when reading task #{} output, error {}", + elem.task_id, std::strerror(errno)); + return; + } + + if (errno == EIO) { + // For pty output, the read() will return -1 with errno set to EIO + // when process exit. + // ref: https://unix.stackexchange.com/questions/538198 + read_finished = true; + } else if (errno == EAGAIN) { + // Read before the process begin. + return; + } else { + CRANE_ERROR("Error when reading task #{} output, error {}", + elem.task_id, std::strerror(errno)); + return; + } + } + + if (read_finished) { CRANE_TRACE("Task #{} to cfored {} finished its output.", elem.task_id, elem.cfored); h.close(); - close(elem.out_fd); + close(elem.fd); bool ok_to_free = m_cfored_client_map_[elem.cfored]->TaskOutputFinish(elem.task_id); @@ -414,9 +446,6 @@ void CforedManager::RegisterCb_() { return; } - if (ret == -1) - CRANE_ERROR("Error when reading task #{} output", elem.task_id); - std::string output(buf, ret); CRANE_TRACE("Fwd to task #{}: {}", elem.task_id, output); m_cfored_client_map_[elem.cfored]->TaskOutPutForward(elem.task_id, diff --git a/src/Craned/CforedClient.h b/src/Craned/CforedClient.h index d61a0ce98..65a36cff9 100644 --- a/src/Craned/CforedClient.h +++ b/src/Craned/CforedClient.h @@ -86,16 +86,16 @@ class CforedManager { bool Init(); - void RegisterIOForward(std::string const& cfored, task_id_t task_id, - int in_fd, int out_fd); + void RegisterIOForward(std::string const& cfored, task_id_t task_id, int fd, + bool pty); void TaskProcOnCforedStopped(std::string const& cfored, task_id_t task_id); private: struct RegisterElem { std::string cfored; task_id_t task_id; - int in_fd; - int out_fd; + int fd; + bool pty; }; struct TaskStopElem { diff --git a/src/Craned/TaskManager.cpp b/src/Craned/TaskManager.cpp index 949e41213..1ef5d37b8 100644 --- a/src/Craned/TaskManager.cpp +++ b/src/Craned/TaskManager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "CforedClient.h" @@ -102,7 +103,9 @@ TaskManager::TaskManager() { m_sigchld_handle_ = m_uvw_loop_->resource(); m_sigchld_handle_->on( - [this](const uvw::signal_event&, uvw::signal_handle&) { EvSigchldCb_(); }); + [this](const uvw::signal_event&, uvw::signal_handle&) { + EvSigchldCb_(); + }); if (m_sigchld_handle_->start(SIGCLD) != 0) { CRANE_ERROR("Failed to start the SIGCLD handle"); @@ -516,9 +519,10 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, using crane::grpc::subprocess::CanStartMessage; using crane::grpc::subprocess::ChildProcessReady; - int ctrl_sock_pair[2]; // Socket pair for passing control messages. - int io_in_sock_pair[2]; // Socket pair for forwarding IO of crun tasks. - int io_out_sock_pair[2]; // Socket pair for forwarding IO of crun tasks. + int ctrl_sock_pair[2]; // Socket pair for passing control messages. + + // Socket pair for forwarding IO of crun tasks. Craned read from index 0. + int crun_io_sock_pair[2]; // The ResourceInNode structure should be copied here for being accessed in // the child process. @@ -539,26 +543,6 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, return CraneErr::kSystemErr; } - // Create IO socket pair for crun tasks. - if (instance->IsCrun()) { - if (socketpair(AF_UNIX, SOCK_STREAM, 0, io_in_sock_pair) != 0) { - CRANE_ERROR("Failed to create socket pair for task io forward: {}", - strerror(errno)); - return CraneErr::kSystemErr; - } - - if (socketpair(AF_UNIX, SOCK_STREAM, 0, io_out_sock_pair) != 0) { - CRANE_ERROR("Failed to create socket pair for task io forward: {}", - strerror(errno)); - return CraneErr::kSystemErr; - } - - auto* crun_meta = - dynamic_cast(instance->meta.get()); - crun_meta->proc_in_fd = io_in_sock_pair[0]; - crun_meta->proc_out_fd = io_out_sock_pair[0]; - } - // save the current uid/gid SavedPrivilege saved_priv{getuid(), getgid()}; @@ -575,7 +559,31 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, return CraneErr::kSystemErr; } - pid_t child_pid = fork(); + pid_t child_pid; + bool launch_pty{false}; + + if (instance->IsCrun()) { + auto* crun_meta = + dynamic_cast(instance->meta.get()); + launch_pty = instance->task.interactive_meta().pty(); + CRANE_DEBUG("Launch crun task #{} pty:{}", instance->task.task_id(), + launch_pty); + + if (launch_pty) { + child_pid = forkpty(&crun_meta->msg_fd, nullptr, nullptr, nullptr); + } else { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, crun_io_sock_pair) != 0) { + CRANE_ERROR("Failed to create socket pair for task io forward: {}", + strerror(errno)); + return CraneErr::kSystemErr; + } + crun_meta->msg_fd = crun_io_sock_pair[0]; + child_pid = fork(); + } + } else { + child_pid = fork(); + } + if (child_pid == -1) { CRANE_ERROR("fork() failed for task #{}: {}", instance->task.task_id(), strerror(errno)); @@ -591,14 +599,13 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, auto* meta = dynamic_cast(instance->meta.get()); g_cfored_manager->RegisterIOForward( instance->task.interactive_meta().cfored_name(), - instance->task.task_id(), meta->proc_in_fd, meta->proc_out_fd); + instance->task.task_id(), meta->msg_fd, launch_pty); } int ctrl_fd = ctrl_sock_pair[0]; close(ctrl_sock_pair[1]); - if (instance->IsCrun()) { - close(io_in_sock_pair[1]); - close(io_out_sock_pair[1]); + if (instance->IsCrun() && !launch_pty) { + close(crun_io_sock_pair[1]); } setegid(saved_priv.gid); @@ -779,16 +786,13 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, } close(stdout_fd); - } else if (instance->IsCrun()) { - close(io_in_sock_pair[0]); - close(io_out_sock_pair[0]); - - dup2(io_in_sock_pair[1], 0); - close(io_in_sock_pair[1]); + } else if (instance->IsCrun() && !launch_pty) { + close(crun_io_sock_pair[0]); - dup2(io_out_sock_pair[1], 1); - dup2(io_out_sock_pair[1], 2); - close(io_out_sock_pair[1]); + dup2(crun_io_sock_pair[1], 0); + dup2(crun_io_sock_pair[1], 1); + dup2(crun_io_sock_pair[1], 2); + close(crun_io_sock_pair[1]); } child_process_ready.set_ok(true); diff --git a/src/Craned/TaskManager.h b/src/Craned/TaskManager.h index 2632db685..3ee9449b6 100644 --- a/src/Craned/TaskManager.h +++ b/src/Craned/TaskManager.h @@ -119,8 +119,7 @@ struct BatchMetaInTaskInstance : MetaInTaskInstance { }; struct CrunMetaInTaskInstance : MetaInTaskInstance { - int proc_in_fd; - int proc_out_fd; + int msg_fd; ~CrunMetaInTaskInstance() override = default; }; @@ -141,7 +140,7 @@ struct TaskInstance { } if (this->IsCrun()) { - close(dynamic_cast(meta.get())->proc_in_fd); + close(dynamic_cast(meta.get())->msg_fd); } } From 5a61084970b5b3102e4c1936b0acbb89e45ac907 Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Wed, 11 Dec 2024 20:51:55 +0800 Subject: [PATCH 05/14] chore: Sync protos (#396) --- protos/Plugin.proto | 2 +- protos/PublicDefs.proto | 70 +++++++++---------- src/Utilities/PluginClient/PluginClient.cpp | 32 +++++---- .../PluginClient/include/crane/PluginClient.h | 9 +-- 4 files changed, 58 insertions(+), 55 deletions(-) diff --git a/protos/Plugin.proto b/protos/Plugin.proto index 843a88bbe..3f049de42 100644 --- a/protos/Plugin.proto +++ b/protos/Plugin.proto @@ -54,7 +54,7 @@ message EndHookReply { message CreateCgroupHookRequest { uint32 task_id = 1; string cgroup = 2; - DedicatedResourceInNode request_res = 3; + DedicatedResourceInNode resource = 3; } message CreateCgroupHookReply { diff --git a/protos/PublicDefs.proto b/protos/PublicDefs.proto index 6e28d983a..7e96a3aad 100644 --- a/protos/PublicDefs.proto +++ b/protos/PublicDefs.proto @@ -320,45 +320,45 @@ message TrimmedPartitionInfo { enum ErrCode { SUCCESS = 0; // Success - ERR_INVALID_UID = 10001; - ERR_INVALID_OP_USER = 10002; - ERR_INVALID_USER = 10003; - ERR_PERMISSION_USER = 10004; - ERR_USER_DUPLICATE_ACCOUNT = 10005; - ERR_USER_ALLOWED_ACCOUNT = 10006; - ERR_INVALID_ADMIN_LEVEL = 10007; - ERR_USER_ACCOUNT_MISMATCH = 10008; + ERR_INVALID_UID = 10001; // Invalid UID passed + ERR_INVALID_OP_USER = 10002; // Invalid operation user + ERR_INVALID_USER = 10003; // Invalid user + ERR_PERMISSION_USER = 10004; // User permissions too low, no permission to operate + ERR_USER_DUPLICATE_ACCOUNT = 10005; // User duplicate account insertion + ERR_USER_ALLOWED_ACCOUNT = 10006; // User does not have permission for the account + ERR_INVALID_ADMIN_LEVEL = 10007; // Invalid permission level + ERR_USER_ACCOUNT_MISMATCH = 10008; // User does not belong to the account ERR_NO_ACCOUNT_SPECIFIED = 10009; - ERR_INVALID_ACCOUNT = 10010; - ERR_DUPLICATE_ACCOUNT = 10011; - ERR_INVALID_PARENTACCOUNT = 10012; - ERR_DELETE_ACCOUNT = 10013; - - ERR_INVALID_PARTITION = 10014; - ERR_ALLOWED_PARTITION = 10015; - ERR_DUPLICATE_PARTITION = 10016; - ERR_PARENT_ALLOWED_PARTITION = 10017; - ERR_USER_EMPTY_PARTITION = 10018; - ERR_CHILD_HAS_PARTITION = 10019; - - ERR_INVALID_QOS = 10020; - ERR_DB_DUPLICATE_QOS = 10021; - ERR_DELETE_QOS = 10022; - ERR_CONVERT_TO_INTERGER = 10023; - ERR_TIME_LIMIT = 10024; - ERR_ALLOWED_QOS = 10025; - ERR_DUPLICATE_QOS = 10026; - ERR_PARENT_ALLOWED_QOS = 10027; - ERR_SET_ALLOWED_QOS = 10028; - ERR_ALLOWED_DEFAULT_QOS = 10029; - ERR_DUPLICATE_DEFAULT_QOS = 10030; - ERR_CHILD_HAS_DEFAULT_QOS = 10031; - ERR_SET_ACCOUNT_QOS = 10032; - ERR_SET_DEFAULT_QOS = 10033; + ERR_INVALID_ACCOUNT = 10010; // Invalid account + ERR_DUPLICATE_ACCOUNT = 10011; // Duplicate account insertion + ERR_INVALID_PARENTACCOUNT = 10012; // Invalid parent account + ERR_DELETE_ACCOUNT = 10013; // Account has child nodes + + ERR_INVALID_PARTITION = 10014; // Invalid partition, partition does not exist + ERR_ALLOWED_PARTITION = 10015; // Account/user does not include this partition + ERR_DUPLICATE_PARTITION = 10016; // Account/user duplicate insertion + ERR_PARENT_ALLOWED_PARTITION = 10017; // Parent account does not include this partition + ERR_USER_EMPTY_PARTITION = 10018; // Cannot add QoS when user has no partition + ERR_CHILD_HAS_PARTITION = 10019; // Partition '{}' is used by some descendant node of the account '{}'. Ignoring this constraint with forced operation. + + ERR_INVALID_QOS = 10020; // Invalid QoS, QoS does not exist + ERR_DB_DUPLICATE_QOS = 10021; // Duplicate QoS insertion in the database. + ERR_DELETE_QOS = 10022; // QoS reference count is not zero. + ERR_CONVERT_TO_INTERGER = 10023; // String to integer conversion failed + ERR_TIME_LIMIT = 10024; // Invalid time value + ERR_ALLOWED_QOS = 10025; // Account/user does not include this QoS. + ERR_DUPLICATE_QOS = 10026; // Account/user duplicate insertion. + ERR_PARENT_ALLOWED_QOS = 10027; // Parent account does not include this QoS. + ERR_SET_ALLOWED_QOS = 10028; // QoS '{}' is the default QoS of partition '{}', but not found in the new QoS list. + ERR_ALLOWED_DEFAULT_QOS = 10029; // Default QoS is not in the allowed QoS list + ERR_DUPLICATE_DEFAULT_QOS = 10030; // Duplicate default QoS setting + ERR_CHILD_HAS_DEFAULT_QOS = 10031; // Someone is using QoS '{}' as default QoS. Ignoring this constraint with forced deletion, the deleted default QoS is randomly replaced with one of the remaining items in the QoS list. + ERR_SET_ACCOUNT_QOS = 10032; // QoS '{}' is used by some descendant node or itself of the account '{}'. Ignoring this constraint with forced operation. + ERR_SET_DEFAULT_QOS = 10033; // Qos '{}' not in allowed qos list or is already the default qos ERR_IS_DEFAULT_QOS = 10034; - ERR_UPDATE_DATABASE = 10035; + ERR_UPDATE_DATABASE = 10035; // Database update failed ERR_GENERIC_FAILURE = 10100; ERR_NO_RESOURCE = 10101; diff --git a/src/Utilities/PluginClient/PluginClient.cpp b/src/Utilities/PluginClient/PluginClient.cpp index e3bcc595d..7dfa961d3 100644 --- a/src/Utilities/PluginClient/PluginClient.cpp +++ b/src/Utilities/PluginClient/PluginClient.cpp @@ -132,7 +132,7 @@ grpc::Status PluginClient::SendStartHook_(grpc::ClientContext* context, auto* request = dynamic_cast(msg); CRANE_ASSERT(request != nullptr); - + StartHookReply reply; CRANE_TRACE("[Plugin] Sending StartHook."); @@ -146,35 +146,35 @@ grpc::Status PluginClient::SendEndHook_(grpc::ClientContext* context, auto* request = dynamic_cast(msg); CRANE_ASSERT(request != nullptr); - + EndHookReply reply; CRANE_TRACE("[Plugin] Sending EndHook."); return m_stub_->EndHook(context, *request, &reply); } -grpc::Status PluginClient::SendCreateCgroupHook_(grpc::ClientContext* context, - google::protobuf::Message* msg) { +grpc::Status PluginClient::SendCreateCgroupHook_( + grpc::ClientContext* context, google::protobuf::Message* msg) { using crane::grpc::plugin::CreateCgroupHookReply; using crane::grpc::plugin::CreateCgroupHookRequest; auto* request = dynamic_cast(msg); CRANE_ASSERT(request != nullptr); - + CreateCgroupHookReply reply; CRANE_TRACE("[Plugin] Sending CreateCgroupHook."); return m_stub_->CreateCgroupHook(context, *request, &reply); } -grpc::Status PluginClient::SendDestroyCgroupHook_(grpc::ClientContext* context, - google::protobuf::Message* msg) { +grpc::Status PluginClient::SendDestroyCgroupHook_( + grpc::ClientContext* context, google::protobuf::Message* msg) { using crane::grpc::plugin::DestroyCgroupHookReply; using crane::grpc::plugin::DestroyCgroupHookRequest; auto* request = dynamic_cast(msg); CRANE_ASSERT(request != nullptr); - + DestroyCgroupHookReply reply; CRANE_TRACE("[Plugin] Sending DestroyCgroupHook."); @@ -211,13 +211,14 @@ void PluginClient::EndHookAsync(std::vector tasks) { m_event_queue_.enqueue(std::move(e)); } -void PluginClient::CreateCgroupHookAsync(task_id_t task_id, - const std::string& cgroup, - const crane::grpc::DedicatedResourceInNode &request_resource) { - auto request = std::make_unique(); +void PluginClient::CreateCgroupHookAsync( + task_id_t task_id, const std::string& cgroup, + const crane::grpc::DedicatedResourceInNode& resource) { + auto request = + std::make_unique(); request->set_task_id(task_id); request->set_cgroup(cgroup); - request->mutable_request_res()->CopyFrom(request_resource); + request->mutable_resource()->CopyFrom(resource); HookEvent e{HookType::CREATE_CGROUP, std::unique_ptr(std::move(request))}; @@ -225,8 +226,9 @@ void PluginClient::CreateCgroupHookAsync(task_id_t task_id, } void PluginClient::DestroyCgroupHookAsync(task_id_t task_id, - const std::string& cgroup) { - auto request = std::make_unique(); + const std::string& cgroup) { + auto request = + std::make_unique(); request->set_task_id(task_id); request->set_cgroup(cgroup); diff --git a/src/Utilities/PluginClient/include/crane/PluginClient.h b/src/Utilities/PluginClient/include/crane/PluginClient.h index 17ec2c2d6..15897755f 100644 --- a/src/Utilities/PluginClient/include/crane/PluginClient.h +++ b/src/Utilities/PluginClient/include/crane/PluginClient.h @@ -68,8 +68,9 @@ class PluginClient { void EndHookAsync(std::vector tasks); // Launched by Craned - void CreateCgroupHookAsync(task_id_t task_id, const std::string& cgroup, - const crane::grpc::DedicatedResourceInNode &request_resource); + void CreateCgroupHookAsync( + task_id_t task_id, const std::string& cgroup, + const crane::grpc::DedicatedResourceInNode& resource); void DestroyCgroupHookAsync(task_id_t task_id, const std::string& cgroup); private: @@ -82,9 +83,9 @@ class PluginClient { grpc::Status SendEndHook_(grpc::ClientContext* context, google::protobuf::Message* msg); grpc::Status SendCreateCgroupHook_(grpc::ClientContext* context, - google::protobuf::Message* msg); + google::protobuf::Message* msg); grpc::Status SendDestroyCgroupHook_(grpc::ClientContext* context, - google::protobuf::Message* msg); + google::protobuf::Message* msg); void AsyncSendThread_(); From a7646cdaebcfcc55cf7d1d5d9f27bf3cae4908d4 Mon Sep 17 00:00:00 2001 From: Taoran Wang <123521007+wtr0504@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:53:09 +0800 Subject: [PATCH 06/14] add bpf.o to craned rpm pack (#384) * fix wrong variable ref to BasicDevice * add bpf.o to craned rpm pack * fix: eBPF object installation prefix --------- Co-authored-by: Nativu5 <44155313+Nativu5@users.noreply.github.com> --- CMakeLists.txt | 8 ++++++++ src/Craned/CgroupManager.cpp | 4 ++-- src/Craned/CgroupManager.h | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b065eef2d..99ed10da2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -409,6 +409,14 @@ install(FILES PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) # Install configuration files +if(ENABLE_BPF) + install(FILES + ${CMAKE_BINARY_DIR}/src/Misc/BPF/cgroup_dev_bpf.o + DESTINATION /usr/local/lib64/bpf/ + COMPONENT cranedc + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) +endif() + install(FILES ${CMAKE_SOURCE_DIR}/etc/config.yaml DESTINATION /etc/crane/ COMPONENT cranectldc diff --git a/src/Craned/CgroupManager.cpp b/src/Craned/CgroupManager.cpp index 6c97870cf..a5ed60d03 100644 --- a/src/Craned/CgroupManager.cpp +++ b/src/Craned/CgroupManager.cpp @@ -1322,8 +1322,8 @@ bool CgroupV2::SetDeviceAccess(const std::unordered_set &devices, auto &bpf_devices = m_cgroup_bpf_devices; for (const auto &[_, this_device] : Craned::g_this_node_device) { - if (!devices.contains(this_device->dev_id)) { - for (const auto &dev_meta : this_device->device_metas) { + if (!devices.contains(this_device->slot_id)) { + for (const auto &dev_meta : this_device->device_file_metas) { short op_type = 0; if (dev_meta.op_type == 'c') { op_type |= BPF_DEVCG_DEV_CHAR; diff --git a/src/Craned/CgroupManager.h b/src/Craned/CgroupManager.h index f88d338e4..68e700ca4 100644 --- a/src/Craned/CgroupManager.h +++ b/src/Craned/CgroupManager.h @@ -93,7 +93,7 @@ enum class ControllerFile : uint64_t { inline const char *kTaskCgPathPrefix = "Crane_Task_"; inline const char *RootCgroupFullPath = "/sys/fs/cgroup"; #ifdef CRANE_ENABLE_BPF -inline const char *BpfObjectFile = "/etc/crane/cgroup_dev_bpf.o"; +inline const char *BpfObjectFile = "/usr/local/lib64/bpf/cgroup_dev_bpf.o"; inline const char *BpfDeviceMapFile = "/sys/fs/bpf/craned_dev_map"; inline const char *BpfMapName = "craned_dev_map"; inline const char *BpfProgramName = "craned_device_access"; From 47fcc975103b885f6d42b01474c9cbd562247f03 Mon Sep 17 00:00:00 2001 From: Junlin Li <70465472+L-Xiafeng@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:58:31 +0800 Subject: [PATCH 07/14] fix: Add IO cgroup controller check on craned start. (#397) * fix: add IO cgroup controller check on craned start. Signed-off-by: Li Junlin * fix: Add error output Signed-off-by: Li Junlin --------- Signed-off-by: Li Junlin --- src/Craned/Craned.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Craned/Craned.cpp b/src/Craned/Craned.cpp index 1a27eafad..d1f07104e 100644 --- a/src/Craned/Craned.cpp +++ b/src/Craned/Craned.cpp @@ -607,15 +607,18 @@ void GlobalVariableInit() { Craned::CgroupConstant::CgroupVersion::CGROUP_V1 && (!g_cg_mgr->Mounted(Controller::CPU_CONTROLLER) || !g_cg_mgr->Mounted(Controller::MEMORY_CONTROLLER) || - !g_cg_mgr->Mounted(Controller::DEVICES_CONTROLLER))) { - CRANE_ERROR("Failed to initialize cpu,memory,devices cgroups controller."); + !g_cg_mgr->Mounted(Controller::DEVICES_CONTROLLER) || + !g_cg_mgr->Mounted(Controller::BLOCK_CONTROLLER))) { + CRANE_ERROR( + "Failed to initialize cpu,memory,devices,block cgroups controller."); std::exit(1); } if (g_cg_mgr->GetCgroupVersion() == Craned::CgroupConstant::CgroupVersion::CGROUP_V2 && (!g_cg_mgr->Mounted(Controller::CPU_CONTROLLER_V2) || - !g_cg_mgr->Mounted(Controller::MEMORY_CONTORLLER_V2))) { - CRANE_ERROR("Failed to initialize cpu,memory cgroups controller."); + !g_cg_mgr->Mounted(Controller::MEMORY_CONTORLLER_V2) || + !g_cg_mgr->Mounted(Controller::IO_CONTROLLER_V2))) { + CRANE_ERROR("Failed to initialize cpu,memory,IO cgroups controller."); std::exit(1); } From 872cbff8dee7b60b95d1e70d6db9aa4b56e552f2 Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Sat, 14 Dec 2024 22:54:37 +0800 Subject: [PATCH 08/14] refactor: Add cleanup script in Python (#398) * refactor: Add cleanup script in Python * feat: Add -D option --- scripts/CraneMininet.py | 508 -------------------------- scripts/CraneMininet.yaml | 23 -- scripts/wipe_data.py | 210 +++++++++++ scripts/{WipeData.sh => wipe_data.sh} | 0 4 files changed, 210 insertions(+), 531 deletions(-) delete mode 100644 scripts/CraneMininet.py delete mode 100644 scripts/CraneMininet.yaml create mode 100755 scripts/wipe_data.py rename scripts/{WipeData.sh => wipe_data.sh} (100%) diff --git a/scripts/CraneMininet.py b/scripts/CraneMininet.py deleted file mode 100644 index fa7900c44..000000000 --- a/scripts/CraneMininet.py +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/env python3 - -""" -Craned in Mininet -CraneCtld should be manually started on the head node. -Tested on Rocky Linux 9, Python 3.9.18, Mininet 2.3.1b4. -""" - -import os -import pty -import yaml -import select -import argparse -import resource -import ipaddress as ipa -from mininet.topo import Topo -from mininet.net import Mininet -from mininet.node import Host -from mininet.link import TCLink -from mininet.log import error, setLogLevel -from mininet.cli import CLI -from mininet.clean import cleanup - -# Constants -HostPath = "/etc/hosts" -LogPath = "/tmp/output/{}.log" -StdoutPath = "/tmp/output/{}.out" -StderrPath = "/tmp/output/{}.err" -HostName = "craned{}" - - -class NodeConfig: - """Node configuration""" - - def __init__(self, name) -> None: - self.name = name - self.num = 3 - self.offset = 1 - self.subnet = ipa.IPv4Network("10.0.0.0/8", strict=True) - self.addr = ipa.IPv4Interface("192.168.0.10/24") - - def __str__(self) -> str: - return ( - f"NodeConfig(name={self.name}, num={self.num}, " - f"offset={self.offset}, subnet={self.subnet}, addr={self.addr})" - ) - - def hosts(self, cidr=True): - for idx in range(self.offset, self.num + self.offset): - yield ( - HostName.format(idx), - f"{self.subnet[idx - self.offset + 1]}" - + (f"/{self.subnet.prefixlen}" if cidr else ""), - ) - - -class ClusterConfig: - """Cluster configuration""" - - def __init__(self, args) -> None: - self.nodes = {} - self.this = NodeConfig("") - thisname = os.popen("hostname").read().strip() - try: - with open(args.conf, "r") as file: - config = yaml.safe_load(file) # type: dict - for name, params in config["cluster"].items(): - # Parse config into NodeConfig - if name == thisname: - node = self.setThisNode(thisname, params, args) - else: - node = NodeConfig(name) - node.num = params["HostNum"] - node.offset = params["Offset"] - node.subnet = ipa.IPv4Network(params["Subnet"]) - node.addr = ipa.IPv4Interface(params["NodeAddr"]) - self.nodes[name] = node - if len(self.this.name) == 0 and not args.head: - # If config not found and is not head, set it to defaults - print(f"Cannot find config for `{thisname}`, use defaults for it") - self.nodes[thisname] = self.setThisNode(thisname, {}, args) - except FileNotFoundError or TypeError or KeyError or ValueError: - print("Invalid config file, ignore and fall back to defaults") - self.nodes = {thisname: self.setThisNode(thisname, {}, args)} - - def __str__(self) -> str: - return f"ClusterConfig(this={self.this}, nodes={self.nodes})" - - def setThisNode(self, name: str, param: dict, args) -> NodeConfig: - """ - Set `.this`. Specify default values here. - """ - self.this.name = name - self.this.num = args.num if args.num else param.get("HostNum", 3) - self.this.offset = args.offset if args.offset else param.get("Offset", 1) - self.this.subnet = ipa.IPv4Network( - args.subnet if args.subnet else param.get("Subnet", "10.0.0.0/8") - ) - self.this.addr = ipa.IPv4Interface( - args.addr if args.addr else param.get("NodeAddr", "192.168.0.10/24") - ) - return self.this - - def getHostEntry(self) -> list[tuple[str, str]]: - entry = [] - for n in sorted(self.nodes, key=lambda i: self.nodes[i].offset): - for name, addr in self.nodes[n].hosts(cidr=False): - entry.append((name, addr)) - return entry - - def getRouteEntry(self) -> list[tuple[str, str]]: - """ - Get needed routes for `this` host to reach subnets on other hosts. - """ - entry = [] - for name, node in self.nodes.items(): - if name == self.this.name: - continue - entry.append( - ( - f"{node.subnet.network_address}/{node.subnet.prefixlen}", - str(node.addr.ip), - ) - ) - return entry - - -class CranedHost(Host): - """ - Virtual host for Craned - """ - - # Craned executable - CranedExec = "CraneSched/ReleaseBuild/src/Craned/craned" - # (A, B) means contents in B will be persisted in A. - PersistList = [("/tmp/output", "/tmp/output")] - # Each host's temporary directory is invisible to others. - TempList = ["/tmp/crane", "/tmp/craned"] - - def __init__(self, name, **params): - """ - With private dirs set - """ - super().__init__( - name=name, - inNamespace=True, - privateDirs=self.PersistList + self.TempList, - **params, - ) - - # Command support via shell process in namespace - def startShell(self, mnopts=None): - """ - Copied and modified from `mininet/node.py`. - Start a shell process for running commands - """ - if self.shell: - error(f"{self.name}: shell is already running\n") - return - # mnexec: (c)lose descriptors, (d)etach from tty, - # (p)rint pid, and run in (n)amespace - opts = "-cd" if mnopts is None else mnopts - if self.inNamespace: - opts += "n" - - # Modified here, add a seperated uts ns - cmd = ["unshare", "--uts"] - - # bash -i: force interactive - # -s: pass $* to shell, and make process easy to find in ps - # prompt is set to sentinel chr( 127 ) - cmd += [ - "mnexec", - opts, - "env", - "PS1=" + chr(127), - "bash", - "--norc", - "--noediting", - "-is", - "mininet:" + self.name, - ] - - # Spawn a shell subprocess in a pseudo-tty, to disable buffering - # in the subprocess and insulate it from signals (e.g. SIGINT) - # received by the parent - self.master, self.slave = pty.openpty() - self.shell = self._popen( - cmd, stdin=self.slave, stdout=self.slave, stderr=self.slave, close_fds=False - ) - # XXX BL: This doesn't seem right, and we should also probably - # close our files when we exit... - self.stdin = os.fdopen(self.master, "r") - self.stdout = self.stdin - self.pid = self.shell.pid - self.pollOut = select.poll() - self.pollOut.register(self.stdout) - # Maintain mapping between file descriptors and nodes - # This is useful for monitoring multiple nodes - # using select.poll() - self.outToNode[self.stdout.fileno()] = self - self.inToNode[self.stdin.fileno()] = self - self.execed = False - self.lastCmd = None - self.lastPid = None - self.readbuf = "" - # Wait for prompt - while True: - data = self.read(1024) - if data[-1] == chr(127): - break - self.pollOut.poll() - self.waiting = False - # +m: disable job control notification - self.cmd("unset HISTFILE; stty -echo; set +m") - - # Prepare environment for Craned - self.setHostname() - self.setCgroup(ver=1) - - def terminate(self): - """ - Explicitly kill Craned process - """ - # Kill all processes in subtree - self.cmd(r"pkill -SIGKILL -e -f '^craned\s'") - super().terminate() - - def setHostname(self, hostname=""): - """ - Set hostname in new UTS namespace - """ - if hostname == "": - hostname = self.name - self.cmd(f"hostname {hostname}") - - def setCgroup(self, ver=2): - """ - Setup Cgroup for Crane - """ - if ver == 1: - self.cmd("mount -t tmpfs tmpfs /sys/fs/cgroup") - # pid - self.cmd("mkdir /sys/fs/cgroup/pids") - self.cmd("mount -t cgroup pids -opids /sys/fs/cgroup/pids") - # freezer - self.cmd("mkdir /sys/fs/cgroup/freezer") - self.cmd("mount -t cgroup freezer -ofreezer /sys/fs/cgroup/freezer") - # cpuset - self.cmd("mkdir /sys/fs/cgroup/cpuset") - self.cmd("mount -t cgroup cpuset -ocpuset /sys/fs/cgroup/cpuset") - # cpu, cpuacct - self.cmd("mkdir /sys/fs/cgroup/cpu,cpuacct") - self.cmd("mount -t cgroup cpu,cpuacct -ocpu,cpuacct /sys/fs/cgroup/cpu,cpuacct") - # memory - self.cmd("mkdir /sys/fs/cgroup/memory") - self.cmd("mount -t cgroup memory -omemory /sys/fs/cgroup/memory") - # devices - self.cmd("mkdir /sys/fs/cgroup/devices") - self.cmd("mount -t cgroup devices -odevices /sys/fs/cgroup/devices") - # blkio - self.cmd("mkdir /sys/fs/cgroup/blkio") - self.cmd("mount -t cgroup blkio -oblkio /sys/fs/cgroup/blkio") - elif ver == 2: - self.cmd( - "mount -t cgroup2 -o", - "rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot", - "cgroup2", - "/sys/fs/cgroup", - ) - - # Enable controllers for subtree - self.cmd( - r"echo '+cpuset +cpu +io +memory +pids' > /sys/fs/cgroup/cgroup.subtree_control" - ) - else: - raise ValueError(f"Illegal Cgroup version: {ver}") - - def launch(self, logfile: str, stdout: str, stderr: str, reset=True): - """ - Launch Craned process - """ - if reset: - self.cmd("echo >", logfile) - self.cmd("echo >", stdout) - self.cmd("echo >", stderr) - - self.cmdPrint( - self.CranedExec, - "-C", - ConfPath, - "-L", - logfile, - ">", - stdout, - "2>", - stderr, - "&", - ) - - -class SingleSwitchTopo(Topo): - """Single switch connected to n hosts.""" - - def __init__(self, config: NodeConfig, **opts): - Topo.__init__(self, **opts) - switch = self.addSwitch("switch1") - - for name, ip in config.hosts(cidr=True): - host = self.addHost(name=name, ip=ip) - self.addLink( - host, - switch, - # bw=100, - # delay="1ms", - # loss=0, - # max_queue_size=1000, - # use_htb=True, - ) - - -def writeHostfile(entry: list[tuple[str, str]] = [], clean=False): - """Generate hostfile for Crane""" - smark = "# BEGIN Mininet hosts #\n" - emark = "# END Mininet hosts #\n" - - # Read all and check - with open(HostPath, "r") as file: - lines = file.readlines() - - try: - start = lines.index(smark) - end = lines.index(emark) + 1 - del lines[start:end] - except ValueError: - if clean: - return - - # Write back and mark - with open(HostPath, "w") as file: - file.writelines(lines) - if not clean: - file.write(smark) - for hostname, addr in entry: - file.write(f"{addr}\t{hostname}\n") - file.write(emark) - - -def writeRoute(entry: list[tuple[str, str]], clean=False): - """ - Check and add required route. - Note: Routes are temporarily added. Reboot will clean them. - """ - for dest, nexthop in entry: - # Clean existing routes - ret = os.popen(f"ip route del {dest}").read() - if len(ret) and "No such process" not in ret: - print(ret) - if clean: - continue - - # Write new routes - ret = os.popen(f"ip route add {dest} via {nexthop}").read() - if len(ret): - print(ret) - - -def reset(): - cleanup() - # Kill all craned - os.system(r"pkill -SIGKILL -e -f '^craned\s'") - # Reset cgroup - os.system(r'pushd /sys/fs/cgroup/cpu; for i in $(ls | grep Crane); do cgdelete "cpu:$i" ; done; popd') - os.system(r'pushd /sys/fs/cgroup/memory; for i in $(ls | grep Crane); do cgdelete "memory:$i" ; done; popd') - # Reset hosts and routes - writeHostfile(clean=True) - try: - writeRoute(Cluster.getRouteEntry(), clean=True) - except NameError: - pass - - -def setMaxLimit(): - """ - Set the max limit of file descriptors and process number - """ - maxLimit = 4194304 - try: - soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) - resource.setrlimit(resource.RLIMIT_NOFILE, (maxLimit, maxLimit)) - print(f"File descriptor limit set to {maxLimit} successfully!") - except ValueError as e: - print(f"Error setting limit: {e}") - except resource.error as e: - print(f"Error setting limit: {e}") - - # Kernel settings - kernelParams = { - "kernel.pid_max": "4194304", - "kernel.threads-max": "8388608", - "net.core.somaxconn": "8192", - "vm.max_map_count": "1677720", - "net.ipv6.conf.default.disable_ipv6": "1", - "net.ipv6.conf.all.disable_ipv6": "1", - } - for param, value in kernelParams.items(): - ret = os.system(f"sysctl -w {param}={value}") - if ret != 0: - print(f"Error setting {param} to {value}") - else: - print(f"Set {param} to {value}") - - -def Run(config: NodeConfig): - """Create network and run the simulation""" - topo = SingleSwitchTopo(config) - net = Mininet( - ipBase=str(config.subnet), - topo=topo, - host=CranedHost, # customized host - link=TCLink, - ) - net.addController("c1") - - # We DO NOT need NAT. Only use this to create a gateway node. - nat = net.addNAT(ip=f"{config.subnet[-2]}/{config.subnet.prefixlen}") - nat.configDefault() - # Disable firewall on gateway node - nat.cmd("iptables -t nat -F") - nat.cmd("iptables -F") - - net.start() - - print("Testing connectivity") - if ( - net.pingAll() - if config.num < 5 - else net.ping(hosts=[net.hosts[0], net.hosts[-1]]) - ) > 0: - print("Network not fully connected, exiting...") - return - - print("Starting craned..." + ("Dryrun=True, won't start craned" if Dryrun else "")) - for h in net.hosts: - # Ignore NATs - if not isinstance(h, CranedHost): - continue - - cranedlog = LogPath.format(h.name) - outfile = StdoutPath.format(h.name) - errfile = StderrPath.format(h.name) - - if not Dryrun: - h.launch(cranedlog, outfile, errfile) - # sleep(0.1) - - # Open CLI for debugging - CLI(net) - net.stop() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Craned in Mininet") - parser.add_argument( - "-c", - "--conf", - type=str, - default="config.yaml", - help="cluster configuration in YAML format", - ) - parser.add_argument("-n", "--num", type=int, help="number of virtual hosts") - parser.add_argument( - "--offset", type=int, help="naming offset of virtual hosts, default=1" - ) - parser.add_argument("--subnet", type=str, help="subnet for virtual hosts") - parser.add_argument("--crane-conf", type=str, help="`config.yaml` for Craned") - parser.add_argument( - "--addr", type=str, help="primary IP (CIDR) of this node used in the cluster" - ) - parser.add_argument("--head", action="store_true", help="generate hosts and routes only") - parser.add_argument("--dryrun", action="store_true", help="prepare the env but not start Craned") - parser.add_argument("--clean", action="store_true", help="clean the environment") - - args = parser.parse_args() - - reset() - if args.clean: - exit() - setLogLevel("info") - setMaxLimit() - - # Always from CLI - ConfPath = os.path.abspath( - args.crane_conf if args.crane_conf else "/etc/crane/config.yaml" - ) - Dryrun = args.dryrun if args.dryrun else False - - # Build ClusterConfig - Cluster = ClusterConfig(args) - - # Generate hostfile and route - writeHostfile(Cluster.getHostEntry()) - if len(Cluster.nodes) > 1: - writeRoute(Cluster.getRouteEntry()) - - # Only generate files for head node, do not run Mininet - if not args.head: - Run(Cluster.this) diff --git a/scripts/CraneMininet.yaml b/scripts/CraneMininet.yaml deleted file mode 100644 index 582cb1d0b..000000000 --- a/scripts/CraneMininet.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# Config for `crane-mininet.py` -# This file is used to generate routes and hosts for mininet -cluster: - crane01: - NodeAddr: 192.168.2.10/24 - HostNum: 1000 - Offset: 1 - Subnet: 10.10.0.0/16 - crane02: - NodeAddr: 192.168.2.11/24 - HostNum: 1000 - Offset: 1001 - Subnet: 10.11.0.0/16 - crane03: - NodeAddr: 192.168.2.12/24 - HostNum: 1000 - Offset: 2001 - Subnet: 10.12.0.0/16 - # cranectl: - # NodeAddr: 192.168.2.13/24 - # HostNum: 500 - # Offset: 3001 - # Subnet: 10.13.0.0/16 diff --git a/scripts/wipe_data.py b/scripts/wipe_data.py new file mode 100755 index 000000000..800fd2fe2 --- /dev/null +++ b/scripts/wipe_data.py @@ -0,0 +1,210 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +import logging +import yaml +import pymongo + +from pathlib import Path +from enum import Enum + +logger = logging.getLogger() +# Suppress overly verbose logs from pymongo +logging.getLogger("pymongo").setLevel(logging.INFO) + +class Collection(Enum): + ACCT = "acct_table" + QOS = "qos_table" + TASK = "task_table" + USER = "user_table" + + +def load_config(crane_path: str, db_path: str = None): + """Load and validate configurations.""" + global_config = _read_config(crane_path) + + # db_path in param comes first + real_db_path = db_path if db_path else global_config.get("DbConfigPath") + base_dir = global_config.get("CraneBaseDir") + if not real_db_path or not base_dir: + raise ValueError("Missing keys in config (DbConfigPath, CraneBaseDir).") + + db_config = _read_config(real_db_path) + + username = db_config.get("DbUser") + password = db_config.get("DbPassword") + host = db_config.get("DbHost") + port = db_config.get("DbPort") + dbname = db_config.get("DbName") + embedded_db_path = os.path.join(base_dir, db_config.get("CraneCtldDbPath", "")) + + if not all([username, password, host, port, dbname, embedded_db_path]): + raise ValueError("Missing keys in DB config parameters.") + + return username, password, host, port, dbname, embedded_db_path + + +def wipe_collection(db, collection: Collection): + """Delete all documents in a MongoDB collection.""" + collection_name = collection.value + try: + logger.debug(f"Wiping collection {collection_name}...") + db[collection_name].delete_many({}) + except Exception as e: + logger.error(f"Error wiping collection {collection_name}: {e}") + raise e + + +def wipe_embedded(embedded_db_path: str): + """Remove embedded database files.""" + db_dir = Path(embedded_db_path).parent + db_filename = Path(embedded_db_path).name + + if db_dir.exists(): + logger.debug(f"Removing files matching {db_filename}* in {db_dir}...") + for file in db_dir.glob(f"{db_filename}*"): + try: + file.unlink() + except Exception as e: + logger.error(f"Error removing file {file}: {e}") + raise e + + +def wipe_mongo(db, collections: list[Collection]): + """Handle MongoDB collection wiping based on user input.""" + for c in collections: + wipe_collection(db, c) + + +def connect_to_mongo(username, password, host, port, dbname): + """Establish a connection to MongoDB.""" + try: + client = pymongo.MongoClient( + host=host, + port=int(port), + username=username, + password=password, + authSource="admin", + ) + return client[dbname] + except Exception as e: + logger.error(f"Error: Failed to connect to MongoDB: {e}") + raise e + + +def parse_arguments(): + """Parse command-line arguments.""" + parser = argparse.ArgumentParser(description="CraneSched Cleanup Script") + parser.add_argument( + "mode", + choices=["mongo", "embedded", "all"], + help="Mode of operation: 'mongo' (MongoDB only), 'embedded' (embedded DB only), 'all' (both MongoDB and embedded DB).", + ) + parser.add_argument( + "-C", + "--config", + default="/etc/crane/config.yaml", + help="Path to the crane config. Default: /etc/crane/config.yaml", + ) + parser.add_argument( + "-D", + "--db-config", + default=None, + help="Path to the DB config. Default is the value in crane config.", + ) + parser.add_argument( + "-a", + "--acct_table", + action="store_true", + help="Include acct_table in MongoDB wipe.", + ) + parser.add_argument( + "-q", + "--qos_table", + action="store_true", + help="Include qos_table in MongoDB wipe.", + ) + parser.add_argument( + "-t", + "--task_table", + action="store_true", + help="Include task_table in MongoDB wipe.", + ) + parser.add_argument( + "-u", + "--user_table", + action="store_true", + help="Include user_table in MongoDB wipe.", + ) + return parser.parse_args() + + +def _read_config(path): + """Read configuration from a YAML file.""" + try: + with open(path, "r") as file: + config = yaml.safe_load(file) + return config + except FileNotFoundError as e: + logger.error(f"Error: Configuration file {path} not found.") + raise e + except yaml.YAMLError as e: + logger.error(f"Error: Failed to parse YAML file {path}: {e}") + raise e + + +def _main(): + args = parse_arguments() + + # Set logging for standalone run + global logger + logger = logging.getLogger(__name__) + logging.basicConfig( + level=logging.DEBUG, + format="[%(levelname)s] [%(filename)s:%(lineno)d] %(message)s", + ) + + # Load configurations + username, password, host, port, dbname, embedded_db_path = load_config(args.config, args.db_config) + + logger.debug(f"MongoDB Config: {username}, {host}, {port}, {dbname}") + logger.debug(f"Embedded DB Config: {embedded_db_path}") + + # Handle MongoDB cleanup + db = None + if args.mode in ["mongo", "all"]: + db = connect_to_mongo(username, password, host, port, dbname) + to_wipe = [] + if not any([args.acct_table, args.qos_table, args.task_table, args.user_table]): + to_wipe = [ + Collection.ACCT, + Collection.QOS, + Collection.TASK, + Collection.USER, + ] # Default to all + else: + if args.acct_table: + to_wipe.append(Collection.ACCT) + if args.qos_table: + to_wipe.append(Collection.QOS) + if args.task_table: + to_wipe.append(Collection.TASK) + if args.user_table: + to_wipe.append(Collection.USER) + wipe_mongo(db, to_wipe) + + # Handle embedded database cleanup + if args.mode in ["embedded", "all"]: + wipe_embedded(embedded_db_path) + + logger.info("Done.") + + +if __name__ == "__main__": + try: + _main() + except Exception as e: + logger.error(f"Error: {e}") + sys.exit(1) diff --git a/scripts/WipeData.sh b/scripts/wipe_data.sh similarity index 100% rename from scripts/WipeData.sh rename to scripts/wipe_data.sh From 815c7978d3e25b001348b71957e5ee5bcbcf3946 Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Sun, 15 Dec 2024 15:46:54 +0800 Subject: [PATCH 09/14] fix: Skip Link-Local IPv6 Address (#400) * feat: Skip getaddrinfo if IPv6 not enabled * feat: Elevate logging level for craned connection * fix: Skip link local only ipv6 addr * fix: Remove useless headers --- src/CraneCtld/CraneCtld.cpp | 4 ++-- src/Utilities/PublicHeader/Network.cpp | 23 ++++++++++++++++------- src/Utilities/PublicHeader/OS.cpp | 2 +- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/CraneCtld/CraneCtld.cpp b/src/CraneCtld/CraneCtld.cpp index 0943138c1..480ca8b95 100644 --- a/src/CraneCtld/CraneCtld.cpp +++ b/src/CraneCtld/CraneCtld.cpp @@ -696,7 +696,7 @@ void InitializeCtldGlobalVariables() { g_craned_keeper = std::make_unique(g_config.Nodes.size()); g_craned_keeper->SetCranedIsUpCb([](const CranedId& craned_id) { - CRANE_TRACE( + CRANE_DEBUG( "A new node #{} is up now. Add its resource to the global resource " "pool.", craned_id); @@ -706,7 +706,7 @@ void InitializeCtldGlobalVariables() { }); g_craned_keeper->SetCranedIsDownCb([](const CranedId& craned_id) { - CRANE_TRACE( + CRANE_DEBUG( "CranedNode #{} is down now. " "Remove its resource from the global resource pool.", craned_id); diff --git a/src/Utilities/PublicHeader/Network.cpp b/src/Utilities/PublicHeader/Network.cpp index fc62b5aa3..45fe6431b 100644 --- a/src/Utilities/PublicHeader/Network.cpp +++ b/src/Utilities/PublicHeader/Network.cpp @@ -143,7 +143,7 @@ bool ResolveHostnameFromIpv4(ipv4_t addr, std::string* hostname) { if (internal::g_hosts_map->FindFirstHostnameOfIpv4(addr, hostname)) return true; - struct sockaddr_in sa {}; + struct sockaddr_in sa{}; char hbuf[NI_MAXHOST]; std::string ipv4_str = Ipv4ToStr(addr); @@ -169,7 +169,7 @@ bool ResolveHostnameFromIpv6(const ipv6_t& addr, std::string* hostname) { if (internal::g_hosts_map->FindFirstHostnameOfIpv6(addr, hostname)) return true; - struct sockaddr_in6 sa6 {}; + struct sockaddr_in6 sa6{}; char hbuf[NI_MAXHOST]; /* For IPv6*/ @@ -196,7 +196,7 @@ bool ResolveHostnameFromIpv6(const ipv6_t& addr, std::string* hostname) { bool ResolveIpv4FromHostname(const std::string& hostname, ipv4_t* addr) { if (internal::g_hosts_map->FindIpv4OfHostname(hostname, addr)) return true; - struct addrinfo hints {}; + struct addrinfo hints{}; struct addrinfo* res; char host[NI_MAXHOST]; @@ -227,7 +227,7 @@ bool ResolveIpv4FromHostname(const std::string& hostname, ipv4_t* addr) { bool ResolveIpv6FromHostname(const std::string& hostname, ipv6_t* addr) { if (internal::g_hosts_map->FindIpv6OfHostname(hostname, addr)) return true; - struct addrinfo hints {}; + struct addrinfo hints{}; struct addrinfo *res, *tmp; char host[NI_MAXHOST]; @@ -245,13 +245,22 @@ bool ResolveIpv6FromHostname(const std::string& hostname, ipv6_t* addr) { nullptr, 0, NI_NUMERICHOST); auto ipv6_sockaddr = (struct sockaddr_in6*)tmp->ai_addr; if (ret == 0) { + if (IN6_IS_ADDR_LINKLOCAL(&ipv6_sockaddr->sin6_addr)) { + // Check if it is a link-local address, which must have a scope ID (e.g. + // %eth0). We don't support link-local address as it's too complex to + // handle. + CRANE_TRACE("Skipping link-local address of {}", hostname); + continue; + } + + *addr = 0; for (int i = 0; i < 4; ++i) { - *addr = 0; uint32_t part = ntohl(ipv6_sockaddr->sin6_addr.s6_addr32[i]); *addr = (*addr << 32) | part; } - } else - CRANE_ERROR("error getnameinfo {}", gai_strerror(ret)); + } else { + CRANE_ERROR("Error in getnameinfo {}", gai_strerror(ret)); + } } freeaddrinfo(res); diff --git a/src/Utilities/PublicHeader/OS.cpp b/src/Utilities/PublicHeader/OS.cpp index a41fe812f..127ac0fbc 100644 --- a/src/Utilities/PublicHeader/OS.cpp +++ b/src/Utilities/PublicHeader/OS.cpp @@ -104,7 +104,7 @@ void SetCloseOnExecFromFd(int fd_begin) { } bool SetMaxFileDescriptorNumber(unsigned long num) { - struct rlimit rlim {}; + struct rlimit rlim{}; rlim.rlim_cur = num; rlim.rlim_max = num; From ce3beaf9c4e8941741d91c32558a66718f2a7524 Mon Sep 17 00:00:00 2001 From: Junlin Li <70465472+L-Xiafeng@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:30:08 +0800 Subject: [PATCH 10/14] Update build.yaml (#407) Skip ci for draft --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 177ca1dc9..31955025a 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -12,6 +12,7 @@ on: jobs: build: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest container: image: ghcr.io/nativu5/cranedev:ci From 8682b03fb1fc264c69e64e0fc3a97f6710b66953 Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Wed, 25 Dec 2024 17:03:03 +0800 Subject: [PATCH 11/14] Fix: Fix libcgroup configuring and DEB packaging size (#409) * fix: Fix libgroup configure * fix: Too large deb package for dist --- CMakeLists.txt | 21 ++++++++++++--------- dependencies/cmake/libcgroup/CMakeLists.txt | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 99ed10da2..17257d1a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -395,6 +395,14 @@ install(TARGETS craned PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_WRITE WORLD_EXECUTE ) +if(ENABLE_BPF) + install(FILES + ${CMAKE_BINARY_DIR}/src/Misc/BPF/cgroup_dev_bpf.o + DESTINATION /usr/local/lib64/bpf/ + COMPONENT cranedc + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) +endif() + # Install unit files install(FILES ${CMAKE_BINARY_DIR}/etc/cranectld.service @@ -409,14 +417,6 @@ install(FILES PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) # Install configuration files -if(ENABLE_BPF) - install(FILES - ${CMAKE_BINARY_DIR}/src/Misc/BPF/cgroup_dev_bpf.o - DESTINATION /usr/local/lib64/bpf/ - COMPONENT cranedc - PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) -endif() - install(FILES ${CMAKE_SOURCE_DIR}/etc/config.yaml DESTINATION /etc/crane/ COMPONENT cranectldc @@ -448,6 +448,7 @@ set(CPACK_PACKAGE_DESCRIPTION "An HPC and Cloud Computing Fused Job Scheduling S set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md") set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CMAKE_PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + # Do not allow the package to be relocated set(CPACK_PACKAGE_RELOCATABLE OFF) @@ -455,9 +456,11 @@ set(CPACK_PACKAGE_RELOCATABLE OFF) set(CPACK_RPM_PACKAGE_GROUP "System Environment/Base") set(CPACK_RPM_PACKAGE_LICENSE "AGPL v3") set(CPACK_RPM_COMPRESSION_TYPE "xz") -#TODO CPACK_PACKAGE_DESCRIPTION + # DEB set(CPACK_DEB_COMPONENT_INSTALL ON) set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") +# Seperate the debuginfo to avoid large DEB package +set(CPACK_DEBIAN_DEBUGINFO_PACKAGE ON) include(CPack) \ No newline at end of file diff --git a/dependencies/cmake/libcgroup/CMakeLists.txt b/dependencies/cmake/libcgroup/CMakeLists.txt index 98524db9d..85b797ffb 100644 --- a/dependencies/cmake/libcgroup/CMakeLists.txt +++ b/dependencies/cmake/libcgroup/CMakeLists.txt @@ -48,7 +48,7 @@ set(LIBCGROUP_PATH "${libcgroup_BINARY_DIR}" CACHE STRING "Path to libcgroup dep ExternalProject_Add( libcgroup SOURCE_DIR ${libcgroup_SOURCE_DIR} - CONFIGURE_COMMAND ./configure --prefix=${libcgroup_BINARY_DIR}/ + CONFIGURE_COMMAND autoreconf --force && ./configure --prefix=${libcgroup_BINARY_DIR}/ BUILD_COMMAND make -j INSTALL_COMMAND make install BUILD_IN_SOURCE 1 From 9199295db2943964d7d6d15434bf1f7913bf2fc3 Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Thu, 26 Dec 2024 00:43:15 +0800 Subject: [PATCH 12/14] fix: Find and enable ccache (#410) --- CMakeLists.txt | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 17257d1a6..0dea503d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,9 +43,18 @@ if (VERSION_CONTENT STREQUAL "") set(CMAKE_PROJECT_VERSION "0.0.0") endif () +# If ccache is found, set it as the compiler launcher +find_program(CCACHE_PROGRAM ccache) +if (CCACHE_PROGRAM) + message(STATUS "ccache found. Use ccache to launch compilers.") + set_property(GLOBAL PROPERTY CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") + set_property(GLOBAL PROPERTY CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") +endif () + project(Crane VERSION ${CMAKE_PROJECT_VERSION} LANGUAGES C CXX) -# check and set compiler -set(REQUIRED_GNU_VERSION 13.0.0) + +# Check and set compiler +set(REQUIRED_GNU_VERSION 14.0.0) set(REQUIRED_CLANG_VERSION 19.0.0) set(REQUIRED_BPF_CLANG_VERSION 17.0.0) @@ -252,13 +261,6 @@ else () message(STATUS "IPO / LTO not supported: <${error}>") endif () -find_program(CCACHE_PROGRAM ccache) -if (CCACHE_PROGRAM) - message(STATUS "ccache found. Use ccache to launch compilers.") - set_property(GLOBAL PROPERTY CMAKE_C_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") - set_property(GLOBAL PROPERTY CMAKE_CXX_COMPILER_LAUNCHER "${CCACHE_PROGRAM}") -endif () - if (${CRANE_FULL_DYNAMIC}) set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) From 7ae55d2ec1f723f10aab84592c1a498621618e23 Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Thu, 26 Dec 2024 20:32:57 +0800 Subject: [PATCH 13/14] Update build.yaml (#412) --- .github/workflows/build.yaml | 162 +++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 53 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 31955025a..69af47c3e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,4 +1,4 @@ -name: Build and Upload Packages +name: Build, Upload and Test on: push: @@ -9,96 +9,152 @@ on: - master - release/* workflow_dispatch: + inputs: + frontend_branch: + description: 'Branch of FrontEnd to use' + required: true + default: 'master' + +env: + SCRIPT_PATH: /opt/actions-runner-external/script + CACHE_PATH: /opt/actions-runner-external/cache + TEST_FAILED: false jobs: build: if: github.event.pull_request.draft == false - runs-on: ubuntu-latest - container: - image: ghcr.io/nativu5/cranedev:ci - credentials: - username: ${{ github.actor }} - password: ${{ github.token }} + runs-on: self-hosted defaults: run: shell: bash -leo pipefail {0} steps: + # Checkout repo - name: Checkout code uses: actions/checkout@v4 + with: + path: CraneSched - - name: Configure - run: | - echo "Configuring at $(pwd)" - mkdir -p out - cmake -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCRANE_FULL_DYNAMIC=OFF \ - -S . -B out - - - name: Build + # Checkout frontend + - name: Checkout frontend + uses: actions/checkout@v4 + with: + repository: PKUHPC/CraneSched-FrontEnd + path: CraneSched-FrontEnd + ref: ${{ inputs.frontend_branch }} + + # Pull CI Image and Prepare + - name: Pull CI Image run: | - echo "Building at $(pwd)" - cmake --build out --config Release + podman pull ghcr.io/nativu5/cranedev:ci + echo "Linking $SCRIPT_PATH to $(pwd)/script" + ln -sfT $SCRIPT_PATH $(pwd)/script + echo "Linking $CACHE_PATH to $(pwd)/cache" + ln -sfT $CACHE_PATH $(pwd)/cache + mkdir -p output + mkdir -p log - - name: Package with CPack + - name: Build in Container run: | - echo "Packaging with CPack" - pushd out - cpack - popd + podman run --rm \ + -v ./CraneSched:/Workspace/CraneSched \ + -v ./CraneSched-FrontEnd:/Workspace/CraneSched-FrontEnd \ + -v ./output:/Workspace/output \ + -v ./script:/Workspace/script \ + -v ./cache/ccache:/root/.ccache \ + ghcr.io/nativu5/cranedev:ci /bin/bash --login script/build.sh - - name: Collect packages + # Collect and upload artifacts + - name: Count Artifacts run: | - mkdir -p upload/packages - cp out/*.deb upload/packages/ || true - cp out/*.rpm upload/packages/ || true - for package in upload/packages/*; do - package_name=$(basename "$package") - case "$package_name" in - *craned.rpm) - echo "ARTIFACT_NAME_CRANED_RPM=$package_name" >> $GITHUB_ENV - ;; - *craned.deb) - echo "ARTIFACT_NAME_CRANED_DEB=$package_name" >> $GITHUB_ENV - ;; - *cranectld.rpm) - echo "ARTIFACT_NAME_CRANECTLD_RPM=$package_name" >> $GITHUB_ENV - ;; - *cranectld.deb) - echo "ARTIFACT_NAME_CRANECTLD_DEB=$package_name" >> $GITHUB_ENV - ;; - esac - done + ARTIFACT_NAME_CRANED_DEB=$(basename $(ls output/*craned.deb)) + ARTIFACT_NAME_CRANED_RPM=$(basename $(ls output/*craned.rpm)) + ARTIFACT_NAME_CRANECTLD_DEB=$(basename $(ls output/*cranectld.deb)) + ARTIFACT_NAME_CRANECTLD_RPM=$(basename $(ls output/*cranectld.rpm)) + ARTIFACT_NAME_FRONTEND=bin + ARTIFACT_NAME_PLUGIN=plugin + echo "ARTIFACT_NAME_CRANED_DEB=$ARTIFACT_NAME_CRANED_DEB" >> $GITHUB_ENV + echo "ARTIFACT_NAME_CRANED_RPM=$ARTIFACT_NAME_CRANED_RPM" >> $GITHUB_ENV + echo "ARTIFACT_NAME_CRANECTLD_DEB=$ARTIFACT_NAME_CRANECTLD_DEB" >> $GITHUB_ENV + echo "ARTIFACT_NAME_CRANECTLD_RPM=$ARTIFACT_NAME_CRANECTLD_RPM" >> $GITHUB_ENV + echo "ARTIFACT_NAME_FRONTEND=$ARTIFACT_NAME_FRONTEND" >> $GITHUB_ENV + echo "ARTIFACT_NAME_PLUGIN=$ARTIFACT_NAME_PLUGIN" >> $GITHUB_ENV - name: Upload craned .deb package uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME_CRANED_DEB }} - path: upload/packages/${{ env.ARTIFACT_NAME_CRANED_DEB }} - retention-days: 30 + path: output/${{ env.ARTIFACT_NAME_CRANED_DEB }} + retention-days: 14 if-no-files-found: error - name: Upload craned .rpm package uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME_CRANED_RPM }} - path: upload/packages/${{ env.ARTIFACT_NAME_CRANED_RPM }} - retention-days: 30 + path: output/${{ env.ARTIFACT_NAME_CRANED_RPM }} + retention-days: 14 if-no-files-found: error - name: Upload cranectld .deb package uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME_CRANECTLD_DEB }} - path: upload/packages/${{ env.ARTIFACT_NAME_CRANECTLD_DEB }} - retention-days: 30 + path: output/${{ env.ARTIFACT_NAME_CRANECTLD_DEB }} + retention-days: 14 if-no-files-found: error - name: Upload cranectld .rpm package uses: actions/upload-artifact@v4 with: name: ${{ env.ARTIFACT_NAME_CRANECTLD_RPM }} - path: upload/packages/${{ env.ARTIFACT_NAME_CRANECTLD_RPM }} - retention-days: 30 + path: output/${{ env.ARTIFACT_NAME_CRANECTLD_RPM }} + retention-days: 14 + if-no-files-found: error + + - name: Upload frontend + uses: actions/upload-artifact@v4 + with: + name: frontend + path: output/${{ env.ARTIFACT_NAME_FRONTEND }} + retention-days: 14 if-no-files-found: error + + - name: Upload plugin + uses: actions/upload-artifact@v4 + with: + name: plugin + path: output/${{ env.ARTIFACT_NAME_PLUGIN }} + retention-days: 14 + if-no-files-found: error + + # Run AutoTest + - name: Run AutoTest + continue-on-error: true + run: | + NETWORK_ID=$(podman container inspect mongodb | jq -r '.[0].NetworkSettings.Networks | keys[]') + podman run -d --rm --name autotest \ + --privileged \ + --systemd true \ + -v /lib/modules:/lib/modules:ro \ + -v ./script:/CraneSched-AutoTest/script \ + -v ./output:/CraneSched-AutoTest/output \ + -v ./log:/CraneSched-AutoTest/log \ + --network $NETWORK_ID \ + localhost/autotest + podman exec autotest /bin/bash --login script/run.sh || echo "TEST_FAILED=true" >> $GITHUB_ENV + podman stop autotest + + # Upload AutoTest Results + - name: Upload AutoTest Results + uses: actions/upload-artifact@v4 + with: + name: result.json + path: output/result.json + retention-days: 14 + if-no-files-found: error + + # Alarm if has failed cases + - name: Alarm on test failure + if: env.TEST_FAILED == 'true' + run: exit 1 From 224c13a34a69399a314033a8b2a07e6bbaef161f Mon Sep 17 00:00:00 2001 From: Yongkun Li <44155313+Nativu5@users.noreply.github.com> Date: Sat, 28 Dec 2024 00:53:37 +0800 Subject: [PATCH 14/14] feat: Add multiple GID support (#413) * chore: Sync protos * refactor: Move gid into TaskInCtld * feat: Give gid to craneds * fix: Fix gid switching in launching process * fix: Overflowed timestamp in end_time --- protos/PublicDefs.proto | 20 ++++++++++--------- src/CraneCtld/CranedKeeper.cpp | 1 + src/CraneCtld/CtldPublicDefs.h | 18 ++++++++--------- src/CraneCtld/DbClient.cpp | 4 ++-- src/Craned/TaskManager.cpp | 20 +++++++++++++++---- .../PublicHeader/include/crane/PublicHeader.h | 2 ++ 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/protos/PublicDefs.proto b/protos/PublicDefs.proto index 7e96a3aad..a635191eb 100644 --- a/protos/PublicDefs.proto +++ b/protos/PublicDefs.proto @@ -131,6 +131,8 @@ message TaskToCtld { bool requeue_if_failed = 12; bool get_user_env = 13; + uint32 gid = 14; // egid + oneof payload { BatchTaskAdditionalMeta batch_meta = 21; InteractiveTaskAdditionalMeta interactive_meta = 22; @@ -155,7 +157,6 @@ message RuntimeAttrOfTask { // Fields that won't change after this task is accepted. uint32 task_id = 1; int64 task_db_id = 3; - int32 gid = 4; string username = 5; // Fields that will change after this task is accepted. @@ -187,20 +188,21 @@ message TaskToD { string partition = 8; uint32 uid = 9; + uint32 gid = 10; oneof payload { - BatchTaskAdditionalMeta batch_meta = 10; - InteractiveTaskAdditionalMeta interactive_meta = 11; + BatchTaskAdditionalMeta batch_meta = 11; + InteractiveTaskAdditionalMeta interactive_meta = 12; } - map env = 12; - string cwd = 13; + map env = 13; + string cwd = 14; - repeated string allocated_nodes = 14; + repeated string allocated_nodes = 15; - string name = 15; - string account = 16; - string qos = 17; + string name = 16; + string account = 17; + string qos = 18; repeated string excludes = 19; repeated string nodelist = 20; diff --git a/src/CraneCtld/CranedKeeper.cpp b/src/CraneCtld/CranedKeeper.cpp index dd67ce3d5..b3994d343 100644 --- a/src/CraneCtld/CranedKeeper.cpp +++ b/src/CraneCtld/CranedKeeper.cpp @@ -293,6 +293,7 @@ crane::grpc::ExecuteTasksRequest CranedStub::NewExecuteTasksRequests( mutable_task->set_cpus_per_task(static_cast(task->cpus_per_task)); mutable_task->set_uid(task->uid); + mutable_task->set_gid(task->gid); mutable_task->mutable_env()->insert(task->env.begin(), task->env.end()); mutable_task->set_cwd(task->cwd); diff --git a/src/CraneCtld/CtldPublicDefs.h b/src/CraneCtld/CtldPublicDefs.h index 1e7b92386..1cd237ed9 100644 --- a/src/CraneCtld/CtldPublicDefs.h +++ b/src/CraneCtld/CtldPublicDefs.h @@ -279,6 +279,7 @@ struct TaskInCtld { crane::grpc::TaskType type; uid_t uid; + gid_t gid; std::string account; std::string name; std::string qos; @@ -308,7 +309,6 @@ struct TaskInCtld { * ------------------------------- */ task_id_t task_id{0}; task_db_id_t task_db_id{0}; - gid_t gid; std::string username; /* ----------- [3] ---------------- @@ -386,12 +386,6 @@ struct TaskInCtld { } task_id_t TaskDbId() const { return task_db_id; } - void SetGid(gid_t id) { - gid = id; - runtime_attr.set_gid(id); - } - uid_t Gid() const { return gid; } - void SetUsername(std::string const& val) { username = val; runtime_attr.set_username(val); @@ -448,10 +442,10 @@ struct TaskInCtld { int64_t StartTimeInUnixSecond() const { return ToUnixSeconds(start_time); } void SetEndTime(absl::Time const& val) { - end_time = val; - runtime_attr.mutable_end_time()->set_seconds(ToUnixSeconds(end_time)); + SetEndTimeByUnixSecond(ToUnixSeconds(val)); } void SetEndTimeByUnixSecond(uint64_t val) { + if (val > kTaskMaxTimeStampSec) val = kTaskMaxTimeStampSec; end_time = absl::FromUnixSeconds(val); runtime_attr.mutable_end_time()->set_seconds(val); } @@ -507,6 +501,11 @@ struct TaskInCtld { uid = val.uid(); password_entry = std::make_unique(uid); + + // Note: gid is egid, which may be different from the + // primary group of the user in `password_entry`. + gid = val.gid(); + account = val.account(); name = val.name(); qos = val.qos(); @@ -527,7 +526,6 @@ struct TaskInCtld { task_id = runtime_attr.task_id(); task_db_id = runtime_attr.task_db_id(); - gid = runtime_attr.gid(); username = runtime_attr.username(); nodes_alloc = craned_ids.size(); diff --git a/src/CraneCtld/DbClient.cpp b/src/CraneCtld/DbClient.cpp index 1bef5af95..03ee66eb6 100644 --- a/src/CraneCtld/DbClient.cpp +++ b/src/CraneCtld/DbClient.cpp @@ -865,7 +865,7 @@ MongodbClient::document MongodbClient::TaskInEmbeddedDbToDocument_( task_to_ctld.name(), env_str, static_cast(task_to_ctld.uid()), // 10-14 - static_cast(runtime_attr.gid()), + static_cast(task_to_ctld.gid()), util::HostNameListToStr(runtime_attr.craned_ids()), runtime_attr.craned_ids().size(), 0, task_to_ctld.partition_name(), // 15-19 @@ -939,7 +939,7 @@ MongodbClient::document MongodbClient::TaskInCtldToDocument_(TaskInCtld* task) { static_cast(task->requested_node_res_view.MemoryBytes()), task->name, env_str, static_cast(task->uid), // 10-14 - static_cast(task->Gid()), task->allocated_craneds_regex, + static_cast(task->gid), task->allocated_craneds_regex, static_cast(task->nodes_alloc), 0, task->partition_id, // 15-19 0, 0, task->StartTimeInUnixSecond(), task->EndTimeInUnixSecond(), diff --git a/src/Craned/TaskManager.cpp b/src/Craned/TaskManager.cpp index 1ef5d37b8..fe0e82cf5 100644 --- a/src/Craned/TaskManager.cpp +++ b/src/Craned/TaskManager.cpp @@ -546,13 +546,25 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, // save the current uid/gid SavedPrivilege saved_priv{getuid(), getgid()}; - int rc = setegid(instance->pwd_entry.Gid()); + // TODO: Add all other supplementary groups. + // Currently we only add the main gid and the egid when task is submitted. + std::vector gids; + if (instance->task.gid() != instance->pwd_entry.Gid()) + gids.emplace_back(instance->task.gid()); + + gids.emplace_back(instance->pwd_entry.Gid()); + int rc = setgroups(gids.size(), gids.data()); + if (rc == -1) { + CRANE_ERROR("error: setgroups. {}", strerror(errno)); + return CraneErr::kSystemErr; + } + + rc = setegid(instance->task.gid()); if (rc == -1) { CRANE_ERROR("error: setegid. {}", strerror(errno)); return CraneErr::kSystemErr; } - __gid_t gid_a[1] = {instance->pwd_entry.Gid()}; - setgroups(1, gid_a); + rc = seteuid(instance->pwd_entry.Uid()); if (rc == -1) { CRANE_ERROR("error: seteuid. {}", strerror(errno)); @@ -727,7 +739,7 @@ CraneErr TaskManager::SpawnProcessInInstance_(TaskInstance* instance, } setreuid(instance->pwd_entry.Uid(), instance->pwd_entry.Uid()); - setregid(instance->pwd_entry.Gid(), instance->pwd_entry.Gid()); + setregid(instance->task.gid(), instance->task.gid()); // Set pgid to the pid of task root process. setpgid(0, 0); diff --git a/src/Utilities/PublicHeader/include/crane/PublicHeader.h b/src/Utilities/PublicHeader/include/crane/PublicHeader.h index 4cf91633e..fbb080ada 100644 --- a/src/Utilities/PublicHeader/include/crane/PublicHeader.h +++ b/src/Utilities/PublicHeader/include/crane/PublicHeader.h @@ -93,6 +93,8 @@ inline const char* kDefaultPlugindUnixSockPath = "cplugind/cplugind.sock"; constexpr uint64_t kTaskMinTimeLimitSec = 11; constexpr int64_t kTaskMaxTimeLimitSec = google::protobuf::util::TimeUtil::kDurationMaxSeconds; +constexpr int64_t kTaskMaxTimeStampSec = + google::protobuf::util::TimeUtil::kTimestampMaxSeconds; namespace ExitCode {