From: Adam C. Emerson Date: Fri, 18 Apr 2025 07:27:36 +0000 (-0400) Subject: rgw: Add run_coro utility X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=9f17b631ded92da354d0b65f4749e199481a0f5a;p=ceph.git rgw: Add run_coro utility A convenience function for turning coroutines that return values and use exceptions, `error_code`, or similar into `int`-returning functions that take references to out parameters. Signed-off-by: Adam C. Emerson --- diff --git a/src/common/async/concepts.h b/src/common/async/concepts.h index ad960b6ab8d31..3be2a9e13a3cb 100644 --- a/src/common/async/concepts.h +++ b/src/common/async/concepts.h @@ -17,6 +17,8 @@ #include +#include + #include #include @@ -32,7 +34,6 @@ template concept execution_context = std::is_convertible_v; - /// A concept for Asio 'disposition's, a generalization of error /// codes/exception pointers, etc. template diff --git a/src/common/async/librados_completion.h b/src/common/async/librados_completion.h index 7bb380895e80b..450759f8cd401 100644 --- a/src/common/async/librados_completion.h +++ b/src/common/async/librados_completion.h @@ -97,7 +97,8 @@ struct librados_handler { } void operator ()(std::exception_ptr e) { - (*this)(ceph::from_exception(e)); + std::string what; + (*this)(ceph::from_exception(e, &what)); } }; } // namespace detail diff --git a/src/common/error_code.h b/src/common/error_code.h index f37d7ce617df4..8ad3eef8d203a 100644 --- a/src/common/error_code.h +++ b/src/common/error_code.h @@ -94,33 +94,57 @@ inline boost::system::error_condition make_error_condition(errc e) noexcept { #pragma GCC diagnostic pop #pragma clang diagnostic pop -[[nodiscard]] inline int from_exception(std::exception_ptr eptr) { +[[nodiscard]] +inline int from_exception(std::exception_ptr eptr, std::string* what = nullptr) +{ if (!eptr) [[likely]] { return 0; } try { std::rethrow_exception(eptr); } catch (const boost::system::system_error& e) { + if (what) + *what = e.what(); return from_error_code(e.code()); } catch (const std::system_error& e) { + if (what) + *what = e.what(); return from_error_code(e.code()); - } catch (const std::invalid_argument&) { + } catch (const std::invalid_argument& e) { + if (what) + *what = e.what(); return -EINVAL; - } catch (const std::domain_error&) { + } catch (const std::domain_error& e) { + if (what) + *what = e.what(); return -EDOM; - } catch (const std::length_error&) { + } catch (const std::length_error& e) { + if (what) + *what = e.what(); return -ERANGE; - } catch (const std::out_of_range&) { + } catch (const std::out_of_range& e) { + if (what) + *what = e.what(); return -ERANGE; - } catch (const std::range_error&) { + } catch (const std::range_error& e) { + if (what) + *what = e.what(); return -ERANGE; - } catch (const std::overflow_error&) { + } catch (const std::overflow_error& e) { + if (what) + *what = e.what(); return -EOVERFLOW; - } catch (const std::underflow_error&) { + } catch (const std::underflow_error& e) { + if (what) + *what = e.what(); return -EOVERFLOW; - } catch (const std::bad_alloc&) { + } catch (const std::bad_alloc& e) { + if (what) + *what = e.what(); return -ENOMEM; } catch (const std::regex_error& e) { + if (what) + *what = e.what(); using namespace std::regex_constants; switch (e.code()) { case error_space: @@ -134,28 +158,53 @@ inline boost::system::error_condition make_error_condition(errc e) noexcept { #ifdef __has_include # if __has_include() } catch (const std::format_error& e) { + if (what) + *what = e.what(); return -EINVAL; # endif #endif } catch (const fmt::format_error& e) { + if (what) + *what = e.what(); return -EINVAL; - } catch (const std::bad_typeid&) { + } catch (const std::bad_typeid& e) { + if (what) + *what = e.what(); return -EFAULT; - } catch (const std::bad_cast&) { + } catch (const std::bad_cast& e) { + if (what) + *what = e.what(); return -EFAULT; - } catch (const std::bad_optional_access&) { + } catch (const std::bad_optional_access& e) { + if (what) + *what = e.what(); return -EFAULT; - } catch (const std::bad_weak_ptr&) { + } catch (const std::bad_weak_ptr& e) { + if (what) + *what = e.what(); return -EFAULT; - } catch (const std::bad_function_call&) { + } catch (const std::bad_function_call& e) { + if (what) + *what = e.what(); return -EFAULT; - } catch (const std::bad_exception&) { + } catch (const std::bad_exception& e) { + if (what) + *what = e.what(); return -EFAULT; - } catch (const std::bad_variant_access&) { + } catch (const std::bad_variant_access& e) { + if (what) + *what = e.what(); return -EFAULT; + } catch (const std::exception& e) { + if (what) + *what = e.what(); + return -EIO; } catch (...) { + if (what) + *what = "Unknown exception"; return -EIO; } + return -EIO; } } diff --git a/src/rgw/async_utils.h b/src/rgw/async_utils.h new file mode 100644 index 0000000000000..5bc7de2173972 --- /dev/null +++ b/src/rgw/async_utils.h @@ -0,0 +1,359 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright contributors to the Ceph project + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/async/blocked_completion.h" +#include "common/async/concepts.h" +#include "common/async/yield_context.h" + +#include "common/dout.h" +#include "common/dout_fmt.h" +#include "common/error_code.h" + +#include "rgw/rgw_asio_thread.h" + +/// \file rgw/async_utils +/// +/// \brief Utilities for asynchrony + +namespace rgw { + +namespace asio = boost::asio; +namespace async = ceph::async; + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin +template +requires std::is_convertible_v +inline int run_coro( + const DoutPrefixProvider* dpp, //< In case we're blocking + Executor executor, ///< Executor on which to run the coroutine + asio::awaitable coro, ///< The coroutine itself + std::string* what ///< Where to store the result of `what()` on exception + ) noexcept +{ + try { + maybe_warn_about_blocking(dpp); + asio::co_spawn(executor, std::move(coro), async::use_blocked); + } catch (const std::exception&) { + return ceph::from_exception(std::current_exception(), what); + } + return 0; +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin +template +requires std::is_convertible_v +inline int run_coro( + const DoutPrefixProvider* dpp, //< In case we're blocking + ExecutionContext& execution_context, ///< Execution context on which to run + /// the coroutine + asio::awaitable coro, ///< The coroutine itself + std::string* what ///< Where to store the result of `what()` on exception + ) noexcept +{ + return run_coro(dpp, execution_context.get_executor(), std::move(coro), what); +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. This overload supports coroutines that return something +/// other than void. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, //< In case we're blocking + Executor executor, ///< Executor on which to run the coroutine + asio::awaitable coro, ///< The coroutine itself + T& val, ///< Where to store the returned value + std::string* what ///< Where to store the result of `what()`. + ) noexcept +{ + try { + val = asio::co_spawn(executor, std::move(coro), async::use_blocked); + maybe_warn_about_blocking(dpp); + } catch (const std::exception& e) { + return ceph::from_exception(std::current_exception(), what); + } + return 0; +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. This overload supports coroutines that return something +/// other than void. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, //< In case we're blocking + ExecutionContext& execution_context, ///< Execution context on which to run the coroutine + asio::awaitable coro, ///< The coroutine itself + T& val, ///< Where to store the returned value + std::string* what ///< Where to store the result of `what()`. + ) noexcept +{ + return run_coro(dpp, execution_context.get_executor(), std::move(coro), val, what); +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. This overload supports coroutines that return multiple +/// values as a tuple. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, //< In case we're blocking + Executor executor, ///< Executor on which to run the coroutine + asio::awaitable, AwaitableExecutor> coro, ///< The coroutine itself + std::tuple&& vals, ///< Supply with std::tie + std::string* what ///< Where to store the result of `what()`. + ) noexcept +{ + try { + maybe_warn_about_blocking(dpp); + vals = asio::co_spawn(executor, std::move(coro), async::use_blocked); + } catch (const std::exception& e) { + return ceph::from_exception(std::current_exception(), what); + } + return 0; +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. This overload supports coroutines that return something +/// other than void. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, //< In case we're blocking + ExecutionContext& execution_context, ///< Execution context on which to run the coroutine + asio::awaitable, AwaitableExecutor> coro, ///< The coroutine itself + std::tuple&& vals, ///< Supply with std::tie + std::string* what ///< Where to store the result of `what()`. + ) noexcept +{ + return run_coro(dpp, execution_context.get_executor(), std::move(coro), + std::move(vals), what); +} + +/// Call a C++ coroutine from a stacful coroutine if we can, but block +/// if we get `null_yield.` handling exceptions by returning negative +/// error codes and passing out the `what()` string. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, /// For logging + const Executor& executor, ///< Executor on which to run the coroutine + asio::awaitable coro, ///< The coroutine itself + std::string_view name, ///< Name, for logging errors + optional_yield y, /// Stackful coroutine context…hopefully + int log_level = 5 /// What level to log at + ) noexcept +{ + try { + if (y) { + auto& yield = y.get_yield_context(); + asio::co_spawn(yield.get_executor(), std::move(coro), yield); + } else { + maybe_warn_about_blocking(dpp); + asio::co_spawn(executor, std::move(coro), async::use_blocked); + } + } catch (const std::exception& e) { + ldpp_dout_fmt(dpp, log_level, "{}: failed: {}", name, e.what()); + return ceph::from_exception(std::current_exception()); + } + return 0; +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin +template +requires std::is_convertible_v +inline int run_coro( + const DoutPrefixProvider* dpp, /// For logging + ExecutionContext& execution_context, ///< Execution context on which to run + /// the coroutine + asio::awaitable coro, ///< The coroutine itself + std::string_view name, ///< Name, for logging errors + optional_yield y, /// Stackful coroutine context…hopefully + int log_level = 5 /// What level to log at + ) noexcept +{ + return run_coro(dpp, execution_context.get_executor(), std::move(coro), name, y, + log_level); +} + + +/// Call a C++ coroutine from a stackful coroutine if we can, but block +/// if we get `null_yield.` handling exceptions by returning negative +/// error codes and passing out the `what()` string. This overload +/// supports coroutines that return something other than void. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, /// For logging + Executor executor, ///< Executor on which to run the coroutine + asio::awaitable coro, ///< The coroutine itself + T& val, ///< Where to store the returned value + std::string_view name, ///< Name, for logging errors + optional_yield y, /// Stackful coroutine context…hopefully + int log_level = 5 /// What level to log at + ) noexcept +{ + try { + if (y) { + auto& yield = y.get_yield_context(); + val = asio::co_spawn(yield.get_executor(), std::move(coro), yield); + } else { + maybe_warn_about_blocking(dpp); + val = asio::co_spawn(executor, std::move(coro), async::use_blocked); + } + } catch (const std::exception& e) { + ldpp_dout_fmt(dpp, log_level, "{}: failed: {}", name, e.what()); + return ceph::from_exception(std::current_exception()); + } + + return 0; +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. This overload supports coroutines that return something +/// other than void. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, /// For logging + ExecutionContext& execution_context, ///< Execution context on which to run the coroutine + asio::awaitable coro, ///< The coroutine itself + T& val, ///< Where to store the returned value + std::string_view name, ///< Name, for logging errors + optional_yield y, /// Stackful coroutine context…hopefully + int log_level = 5 /// What level to log at + ) noexcept +{ + return run_coro(dpp, execution_context.get_executor(), std::move(coro), val, name, + y, log_level); +} + +/// Call a C++ coroutine from a stackful coroutine if we can, but block +/// if we get `null_yield.` handling exceptions by returning negative +/// error codes and passing out the `what()` string. This overload +/// supports coroutines that return multiple values with a tuple. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, /// For logging + Executor executor, ///< Executor on which to run the coroutine + asio::awaitable, AwaitableExecutor> coro, ///< The coroutine itself + std::tuple&& vals, ///< Supply with std::tie + std::string_view name, ///< Name, for logging errors + optional_yield y, /// Stackful coroutine context…hopefully + int log_level = 5 /// What level to log at + ) noexcept +{ + try { + if (y) { + auto& yield = y.get_yield_context(); + vals = asio::co_spawn(yield.get_executor(), std::move(coro), yield); + } else { + maybe_warn_about_blocking(dpp); + vals = asio::co_spawn(executor, std::move(coro), async::use_blocked); + } + } catch (const std::exception& e) { + ldpp_dout_fmt(dpp, log_level, "{}: failed: {}", name, e.what()); + return ceph::from_exception(std::current_exception()); + } + return 0; +} + +/// Call a coroutine and block until it completes, handling exceptions +/// by returning negative error codes and passing out the `what()` +/// string. This overload supports coroutines that return something +/// other than void. +/// +/// Intended for use in interactive front-ends, e.g. radosgw-admin. +template +requires std::is_convertible_v +int run_coro( + const DoutPrefixProvider* dpp, /// For logging + ExecutionContext& execution_context, ///< Execution context on which to run the coroutine + asio::awaitable, AwaitableExecutor> coro, ///< The coroutine itself + std::tuple&& vals, ///< Supply with std::tie + std::string_view name, ///< Name, for logging errors + optional_yield y, /// Stackful coroutine context…hopefully + int log_level = 5 /// What level to log at + ) noexcept +{ + return run_coro(dpp, execution_context.get_executor(), std::move(coro), + std::move(vals), name, y, log_level); +} +}