From 7f93c8f7a6af75c954c52ef8ab711a5213e02910 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 24 Sep 2019 15:43:22 -0400 Subject: [PATCH 01/25] .gitignore: Ignore Python build artifacts Signed-off-by: Adam C. Emerson --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 985b348442b56..dd7da0fed52bc 100644 --- a/.gitignore +++ b/.gitignore @@ -79,3 +79,5 @@ GTAGS # mypy cache .mypy_cache/ +# Python building things where it shouldn't +/src/python-common/build/ \ No newline at end of file From f6f5e6c224d2ae07e7b1a25f5e34d09b7882bf01 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 6 Aug 2019 15:24:19 -0400 Subject: [PATCH 02/25] librados: Test completion creation/handling speed Signed-off-by: Adam C. Emerson --- src/test/librados/CMakeLists.txt | 4 +++ src/test/librados/completion_speed.cc | 38 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/test/librados/completion_speed.cc diff --git a/src/test/librados/CMakeLists.txt b/src/test/librados/CMakeLists.txt index d4d1c48d5519d..d5a99bd2a32d7 100644 --- a/src/test/librados/CMakeLists.txt +++ b/src/test/librados/CMakeLists.txt @@ -190,3 +190,7 @@ target_link_libraries(unittest_librados_config librados ${BLKID_LIBRARIES} ${GSSAPI_LIBRARIES} ${OPENLDAP_LIBRARIES}) +add_executable(ceph_test_rados_completion_speed + completion_speed.cc) +target_link_libraries(ceph_test_rados_completion_speed + librados ${UNITTEST_LIBS} radostest-cxx) diff --git a/src/test/librados/completion_speed.cc b/src/test/librados/completion_speed.cc new file mode 100644 index 0000000000000..708a58425e16e --- /dev/null +++ b/src/test/librados/completion_speed.cc @@ -0,0 +1,38 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" +#include "common/ceph_context.h" +#include "common/Finisher.h" +#include "librados/AioCompletionImpl.h" + + +constexpr int max_completions = 10'000'000; +int completed = 0; +auto cct = (new CephContext(CEPH_ENTITY_TYPE_CLIENT))->get(); +Finisher f(cct); + +void completion_cb(librados::completion_t cb, void* arg) { + auto c = static_cast(arg); + delete c; + if (++completed < max_completions) { + auto aio = librados::Rados::aio_create_completion(); + aio->set_complete_callback(static_cast(aio), &completion_cb); + f.queue(new librados::C_AioComplete(aio->pc)); + } +} + +int main(void) { + auto aio = librados::Rados::aio_create_completion(); + aio->set_complete_callback(static_cast(aio), &completion_cb); + f.queue(new librados::C_AioComplete(aio->pc)); + f.start(); + + while (completed < max_completions) + f.wait_for_empty(); + + f.stop(); + + assert(completed == max_completions); + cct->put(); +} From 38be6d3fb8785c23f9cf76754aa956dd0e999996 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 6 Aug 2019 16:19:12 -0400 Subject: [PATCH 03/25] librados: Op creation benchmark Signed-off-by: Adam C. Emerson --- src/test/librados/CMakeLists.txt | 5 +++++ src/test/librados/op_speed.cc | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/test/librados/op_speed.cc diff --git a/src/test/librados/CMakeLists.txt b/src/test/librados/CMakeLists.txt index d5a99bd2a32d7..14962e84d27a3 100644 --- a/src/test/librados/CMakeLists.txt +++ b/src/test/librados/CMakeLists.txt @@ -194,3 +194,8 @@ add_executable(ceph_test_rados_completion_speed completion_speed.cc) target_link_libraries(ceph_test_rados_completion_speed librados ${UNITTEST_LIBS} radostest-cxx) + +add_executable(ceph_test_rados_op_speed + op_speed.cc) +target_link_libraries(ceph_test_rados_op_speed + librados ${UNITTEST_LIBS} radostest-cxx) diff --git a/src/test/librados/op_speed.cc b/src/test/librados/op_speed.cc new file mode 100644 index 0000000000000..90c7bdac571b8 --- /dev/null +++ b/src/test/librados/op_speed.cc @@ -0,0 +1,22 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -* +// vim: ts=8 sw=2 smarttab + +#include "include/rados/librados.hpp" + +constexpr int to_create = 10'000'000; + +int main() { + for (int i = 0; i < to_create; ++i) { + librados::ObjectReadOperation op; + bufferlist bl; + std::uint64_t sz; + struct timespec tm; + std::map xattrs; + std::map omap; + bool more; + op.read(0, 0, &bl, nullptr); + op.stat2(&sz, &tm, nullptr); + op.getxattrs(&xattrs, nullptr); + op.omap_get_vals2({}, 1000, &omap, &more, nullptr); + } +} From c120aa95815b56a0b9fb59ef000307c710ddf2d7 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 13 Aug 2019 12:53:02 -0400 Subject: [PATCH 04/25] common: Add a threadpool for Asio Just to package up starting, stopping, and running everything. Signed-off-by: Adam C. Emerson --- src/common/async/context_pool.h | 99 +++++++++++++++++++++++++ src/common/detail/construct_suspended.h | 24 ++++++ 2 files changed, 123 insertions(+) create mode 100644 src/common/async/context_pool.h create mode 100644 src/common/detail/construct_suspended.h diff --git a/src/common/async/context_pool.h b/src/common/async/context_pool.h new file mode 100644 index 0000000000000..992b3eccb0a35 --- /dev/null +++ b/src/common/async/context_pool.h @@ -0,0 +1,99 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat + * Author: Adam C. Emerson + * + * 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. + * + */ + +#ifndef CEPH_COMMON_ASYNC_CONTEXT_POOL_H +#define CEPH_COMMON_ASYNC_CONTEXT_POOL_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/ceph_mutex.h" +#include "common/Thread.h" + +namespace ceph::async { +class io_context_pool { + std::vector threadvec; + boost::asio::io_context ioctx; + std::optional> guard; + ceph::mutex m = make_mutex("ceph::io_context_pool::m"); + + void cleanup() noexcept { + guard = std::nullopt; + for (auto& th : threadvec) { + th.join(); + } + threadvec.clear(); + } +public: + io_context_pool() noexcept {} + io_context_pool(std::int16_t threadcnt) noexcept { + start(threadcnt); + } + ~io_context_pool() { + stop(); + } + void start(std::int16_t threadcnt) noexcept { + auto l = std::scoped_lock(m); + if (threadvec.empty()) { + guard.emplace(boost::asio::make_work_guard(ioctx)); + ioctx.restart(); + for (std::int16_t i = 0; i < threadcnt; ++i) { + // Mark this function as noexcept so any uncaught exceptions + // call terminate at point of throw. Otherwise, under + // libstdc++, they get caught by the thread cancellation + // infrastructure, unwinding the stack and making debugging + // much more difficult. + threadvec.emplace_back(make_named_thread("io_context_pool", + [this]() noexcept { + ioctx.run(); + })); + } + } + } + void finish() noexcept { + auto l = std::scoped_lock(m); + if (!threadvec.empty()) { + cleanup(); + } + } + void stop() noexcept { + auto l = std::scoped_lock(m); + if (!threadvec.empty()) { + ioctx.stop(); + cleanup(); + } + } + + boost::asio::io_context& get_io_context() { + return ioctx; + } + operator boost::asio::io_context&() { + return ioctx; + } + boost::asio::io_context::executor_type get_executor() { + return ioctx.get_executor(); + } +}; +} + +#endif // CEPH_COMMON_ASYNC_CONTEXT_POOL_H diff --git a/src/common/detail/construct_suspended.h b/src/common/detail/construct_suspended.h new file mode 100644 index 0000000000000..521bda0f833d0 --- /dev/null +++ b/src/common/detail/construct_suspended.h @@ -0,0 +1,24 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat + * Author: Adam C. Emerson + * + * 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. + * + */ + +#ifndef CEPH_COMMON_DETAIL_CONSTRUCT_SUSPENDED_H +#define CEPH_COMMON_DETAIL_CONSTRUCT_SUSPENDED_H + +namespace ceph { + struct construct_suspended_t { }; + inline constexpr construct_suspended_t construct_suspended { }; +} + +#endif // CEPH_COMMON_DETAIL_CONSTRUCT_SUSPENDED_H From 88bfb4f483596645220dee13f3cf8067389d1ef9 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Thu, 12 Dec 2019 11:19:51 -0500 Subject: [PATCH 05/25] common: Name thread for ceph_timer Signed-off-by: Adam C. Emerson --- src/common/ceph_timer.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/common/ceph_timer.h b/src/common/ceph_timer.h index d12cc19933ae9..ae895db745bb5 100644 --- a/src/common/ceph_timer.h +++ b/src/common/ceph_timer.h @@ -26,15 +26,11 @@ #include "include/function2.hpp" +#include "common/detail/construct_suspended.h" + namespace bi = boost::intrusive; namespace ceph { -/// Newly constructed timer should be suspended at point of -/// construction. - -struct construct_suspended_t { }; -constexpr construct_suspended_t construct_suspended { }; - // Compared to the SafeTimer this does fewer allocations (you // don't have to allocate a new Context every time you // want to cue the next tick.) From 47e61c2b42b1391283f68f0ec4f77e9edae2b155 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Wed, 16 Aug 2017 20:42:00 -0400 Subject: [PATCH 06/25] common: Add maybe_timespan For handling the common idiom of a zero timeout. Signed-off-by: Adam C. Emerson --- src/common/ceph_time.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/common/ceph_time.h b/src/common/ceph_time.h index f6185c8820063..58b1550495d86 100644 --- a/src/common/ceph_time.h +++ b/src/common/ceph_time.h @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "include/ceph_assert.h" @@ -434,6 +435,9 @@ namespace ceph { return std::chrono::duration_cast( std::chrono::duration(d)); } + inline std::optional maybe_timespan(const double d) { + return d ? std::make_optional(make_timespan(d)) : std::nullopt; + } } std::ostream& operator<<(std::ostream& m, const timespan& t); From caf6ce2ac2dd0808ffec7195cdfef410bb6da854 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 28 Apr 2020 14:43:23 -0400 Subject: [PATCH 07/25] common/async: Blocking completion for asio Signed-off-by: Adam C. Emerson --- src/common/async/blocked_completion.h | 273 +++++++++++++++++++++ src/test/common/CMakeLists.txt | 4 + src/test/common/test_blocked_completion.cc | 237 ++++++++++++++++++ 3 files changed, 514 insertions(+) create mode 100644 src/common/async/blocked_completion.h create mode 100644 src/test/common/test_blocked_completion.cc diff --git a/src/common/async/blocked_completion.h b/src/common/async/blocked_completion.h new file mode 100644 index 0000000000000..633e01ecf5eb9 --- /dev/null +++ b/src/common/async/blocked_completion.h @@ -0,0 +1,273 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2020 Red Hat + * Author: Adam C. Emerson + * + * 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. + * + */ + +#ifndef CEPH_COMMON_ASYNC_BLOCKED_COMPLETION_H +#define CEPH_COMMON_ASYNC_BLOCKED_COMPLETION_H + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace ceph::async { + +namespace bs = boost::system; + +class use_blocked_t { + use_blocked_t(bs::error_code* ec) : ec(ec) {} +public: + use_blocked_t() = default; + + use_blocked_t operator [](bs::error_code& _ec) const { + return use_blocked_t(&_ec); + } + + bs::error_code* ec = nullptr; +}; + +inline constexpr use_blocked_t use_blocked; + +namespace detail { + +template +struct blocked_handler +{ + blocked_handler(use_blocked_t b) noexcept : ec(b.ec) {} + + void operator ()(Ts... values) noexcept { + std::scoped_lock l(*m); + *ec = bs::error_code{}; + *value = std::forward_as_tuple(std::move(values)...); + *done = true; + cv->notify_one(); + } + + void operator ()(bs::error_code ec, Ts... values) noexcept { + std::scoped_lock l(*m); + *this->ec = ec; + *value = std::forward_as_tuple(std::move(values)...); + *done = true; + cv->notify_one(); + } + + bs::error_code* ec; + std::optional>* value = nullptr; + std::mutex* m = nullptr; + std::condition_variable* cv = nullptr; + bool* done = nullptr; +}; + +template +struct blocked_handler +{ + blocked_handler(use_blocked_t b) noexcept : ec(b.ec) {} + + void operator ()(T value) noexcept { + std::scoped_lock l(*m); + *ec = bs::error_code(); + *this->value = std::move(value); + *done = true; + cv->notify_one(); + } + + void operator ()(bs::error_code ec, T value) noexcept { + std::scoped_lock l(*m); + *this->ec = ec; + *this->value = std::move(value); + *done = true; + cv->notify_one(); + } + + //private: + bs::error_code* ec; + std::optional* value; + std::mutex* m = nullptr; + std::condition_variable* cv = nullptr; + bool* done = nullptr; +}; + +template<> +struct blocked_handler +{ + blocked_handler(use_blocked_t b) noexcept : ec(b.ec) {} + + void operator ()() noexcept { + std::scoped_lock l(*m); + *ec = bs::error_code{}; + *done = true; + cv->notify_one(); + } + + void operator ()(bs::error_code ec) noexcept { + std::scoped_lock l(*m); + *this->ec = ec; + *done = true; + cv->notify_one(); + } + + bs::error_code* ec; + std::mutex* m = nullptr; + std::condition_variable* cv = nullptr; + bool* done = nullptr; +}; + + +template +class blocked_result +{ +public: + using completion_handler_type = blocked_handler; + using return_type = std::tuple; + + explicit blocked_result(completion_handler_type& h) noexcept { + out_ec = h.ec; + if (!out_ec) h.ec = &ec; + h.value = &value; + h.m = &m; + h.cv = &cv; + h.done = &done; + } + + return_type get() { + std::unique_lock l(m); + cv.wait(l, [this]() { return done; }); + if (!out_ec && ec) throw bs::system_error(ec); + return std::move(*value); + } + +private: + bs::error_code* out_ec; + bs::error_code ec; + std::optional value; + std::mutex m; + std::condition_variable cv; + bool done = false; +}; + +template +class blocked_result +{ +public: + using completion_handler_type = blocked_handler; + using return_type = T; + + explicit blocked_result(completion_handler_type& h) noexcept { + out_ec = h.ec; + if (!out_ec) h.ec = &ec; + h.value = &value; + h.m = &m; + h.cv = &cv; + h.done = &done; + } + + return_type get() { + std::unique_lock l(m); + cv.wait(l, [this]() { return done; }); + if (!out_ec && ec) throw bs::system_error(ec); + return std::move(*value); + } + +private: + bs::error_code* out_ec; + bs::error_code ec; + std::optional value; + std::mutex m; + std::condition_variable cv; + bool done = false; +}; + +template<> +class blocked_result +{ +public: + using completion_handler_type = blocked_handler; + using return_type = void; + + explicit blocked_result(completion_handler_type& h) noexcept { + out_ec = h.ec; + if (!out_ec) h.ec = &ec; + h.m = &m; + h.cv = &cv; + h.done = &done; + } + + void get() { + std::unique_lock l(m); + cv.wait(l, [this]() { return done; }); + if (!out_ec && ec) throw bs::system_error(ec); + } + +private: + bs::error_code* out_ec; + bs::error_code ec; + std::mutex m; + std::condition_variable cv; + bool done = false; +}; +} // namespace detail +} // namespace ceph::async + + +namespace boost::asio { +template +class async_result + : public ceph::async::detail::blocked_result +{ +public: + explicit async_result(typename ceph::async::detail::blocked_result + ::completion_handler_type& h) + : ceph::async::detail::blocked_result(h) {} +}; + +template +class async_result + : public ceph::async::detail::blocked_result...> +{ +public: + explicit async_result( + typename ceph::async::detail::blocked_result...>::completion_handler_type& h) + : ceph::async::detail::blocked_result...>(h) {} +}; + +template +class async_result + : public ceph::async::detail::blocked_result +{ +public: + explicit async_result( + typename ceph::async::detail::blocked_result::completion_handler_type& h) + : ceph::async::detail::blocked_result(h) {} +}; + +template +class async_result + : public ceph::async::detail::blocked_result...> +{ +public: + explicit async_result( + typename ceph::async::detail::blocked_result...>::completion_handler_type& h) + : ceph::async::detail::blocked_result...>(h) {} +}; +} + +#endif // !CEPH_COMMON_ASYNC_BLOCKED_COMPLETION_H diff --git a/src/test/common/CMakeLists.txt b/src/test/common/CMakeLists.txt index dbb2f63b0a9ed..93a01347838a7 100644 --- a/src/test/common/CMakeLists.txt +++ b/src/test/common/CMakeLists.txt @@ -337,3 +337,7 @@ add_ceph_unittest(unittest_rabin_chunk) add_executable(unittest_ceph_timer test_ceph_timer.cc) target_link_libraries(unittest_rabin_chunk GTest::GTest) add_ceph_unittest(unittest_ceph_timer) + +add_executable(unittest_blocked_completion test_blocked_completion.cc) +add_ceph_unittest(unittest_blocked_completion) +target_link_libraries(unittest_blocked_completion Boost::system GTest::GTest) diff --git a/src/test/common/test_blocked_completion.cc b/src/test/common/test_blocked_completion.cc new file mode 100644 index 0000000000000..71e5784af7e4a --- /dev/null +++ b/src/test/common/test_blocked_completion.cc @@ -0,0 +1,237 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2018 Red Hat + * + * 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. + * + */ + + +#include +#include + +#include + +#include "common/async/bind_handler.h" +#include "common/async/blocked_completion.h" +#include "common/async/forward_handler.h" + +using namespace std::literals; + +namespace ba = boost::asio; +namespace bs = boost::system; +namespace ca = ceph::async; + +class context_thread { + ba::io_context c; + ba::executor_work_guard guard; + std::thread th; + +public: + context_thread() noexcept + : guard(ba::make_work_guard(c)), + th([this]() noexcept { c.run();}) {} + + ~context_thread() { + guard.reset(); + th.join(); + } + + ba::io_context& io_context() noexcept { + return c; + } + + ba::io_context::executor_type get_executor() noexcept { + return c.get_executor(); + } +}; + +struct move_only { + move_only() = default; + move_only(move_only&&) = default; + move_only& operator=(move_only&&) = default; + move_only(const move_only&) = delete; + move_only& operator=(const move_only&) = delete; +}; + +struct defaultless { + int a; + defaultless(int a) : a(a) {} +}; + +template +auto id(const Executor& executor, CompletionToken&& token, + Args&& ...args) +{ + ba::async_completion init(token); + auto a = ba::get_associated_allocator(init.completion_handler); + executor.post(ca::forward_handler( + ca::bind_handler(std::move(init.completion_handler), + std::forward(args)...)), + a); + return init.result.get(); +} + +TEST(BlockedCompletion, Void) +{ + context_thread t; + + ba::post(t.get_executor(), ca::use_blocked); +} + +TEST(BlockedCompletion, Timer) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 50ms); + timer.async_wait(ca::use_blocked); +} + +TEST(BlockedCompletion, NoError) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked, bs::error_code{})); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], bs::error_code{})); + EXPECT_FALSE(ec); + + int i; + EXPECT_NO_THROW(i = id(t.get_executor(), ca::use_blocked, + bs::error_code{}, 5)); + ASSERT_EQ(5, i); + EXPECT_NO_THROW(i = id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{}, 7)); + EXPECT_FALSE(ec); + ASSERT_EQ(7, i); + + float j; + + EXPECT_NO_THROW(std::tie(i, j) = id(t.get_executor(), ca::use_blocked, 9, + 3.5)); + ASSERT_EQ(9, i); + ASSERT_EQ(3.5, j); + EXPECT_NO_THROW(std::tie(i, j) = id(t.get_executor(), ca::use_blocked[ec], + 11, 2.25)); + EXPECT_FALSE(ec); + ASSERT_EQ(11, i); + ASSERT_EQ(2.25, j); +} + +TEST(BlockedCompletion, AnError) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()})); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, 5), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, 5)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, 5, 3), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, 5, 3)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); +} + +TEST(BlockedCompletion, MoveOnly) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{}, move_only{})); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{}, move_only{})); + EXPECT_FALSE(ec); + + { + auto [i, j] = id(t.get_executor(), ca::use_blocked, move_only{}, 5); + EXPECT_EQ(j, 5); + } + { + auto [i, j] = id(t.get_executor(), ca::use_blocked[ec], move_only{}, 5); + EXPECT_EQ(j, 5); + } + EXPECT_FALSE(ec); + + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, move_only{})); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}, 3), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, + move_only{}, 3)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); +} + +TEST(BlockedCompletion, DefaultLess) +{ + context_thread t; + ba::steady_timer timer(t.io_context(), 1s); + bs::error_code ec; + + + { + auto l = id(t.get_executor(), ca::use_blocked, bs::error_code{}, defaultless{5}); + EXPECT_EQ(5, l.a); + } + { + auto l = id(t.get_executor(), ca::use_blocked[ec], bs::error_code{}, defaultless{7}); + EXPECT_EQ(7, l.a); + } + + { + auto [i, j] = id(t.get_executor(), ca::use_blocked, defaultless{3}, 5); + EXPECT_EQ(i.a, 3); + EXPECT_EQ(j, 5); + } + { + auto [i, j] = id(t.get_executor(), ca::use_blocked[ec], defaultless{3}, 5); + EXPECT_EQ(i.a, 3); + EXPECT_EQ(j, 5); + } + EXPECT_FALSE(ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, move_only{})); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); + + EXPECT_THROW(id(t.get_executor(), ca::use_blocked, + bs::error_code{EDOM, bs::system_category()}, move_only{}, 3), + bs::system_error); + EXPECT_NO_THROW(id(t.get_executor(), ca::use_blocked[ec], + bs::error_code{EDOM, bs::system_category()}, + move_only{}, 3)); + EXPECT_EQ(bs::error_code(EDOM, bs::system_category()), ec); +} From b5ab991103b5437faa52640e082bc4002f50d9e5 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Mon, 8 Feb 2016 23:49:06 -0500 Subject: [PATCH 08/25] common: add waiter template It's no fair taking away people's C_SafeCond without giving them something new to replace it. Signed-off-by: Adam C. Emerson --- src/common/async/waiter.h | 223 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 src/common/async/waiter.h diff --git a/src/common/async/waiter.h b/src/common/async/waiter.h new file mode 100644 index 0000000000000..219a27cf7bf59 --- /dev/null +++ b/src/common/async/waiter.h @@ -0,0 +1,223 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil + * + * 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. + * + */ + +#ifndef CEPH_COMMON_WAITER_H +#define CEPH_COMMON_WAITER_H + +#include +#include + +#include + +#include "include/ceph_assert.h" +#include "include/function2.hpp" + +#include "common/ceph_mutex.h" + +namespace ceph::async { +namespace detail { +// For safety reasons (avoiding undefined behavior around sequence +// points) std::reference_wrapper disallows move construction. This +// harms us in cases where we want to pass a reference in to something +// that unavoidably moves. +// +// It should not be used generally. +template +class rvalue_reference_wrapper { +public: + // types + using type = T; + + rvalue_reference_wrapper(T& r) noexcept + : p(std::addressof(r)) {} + + // We write our semantics to match those of reference collapsing. If + // we're treated as an lvalue, collapse to one. + + rvalue_reference_wrapper(const rvalue_reference_wrapper&) noexcept = default; + rvalue_reference_wrapper(rvalue_reference_wrapper&&) noexcept = default; + + // assignment + rvalue_reference_wrapper& operator=( + const rvalue_reference_wrapper& x) noexcept = default; + rvalue_reference_wrapper& operator=( + rvalue_reference_wrapper&& x) noexcept = default; + + operator T& () const noexcept { + return *p; + } + T& get() const noexcept { + return *p; + } + + operator T&& () noexcept { + return std::move(*p); + } + T&& get() noexcept { + return std::move(*p); + } + + template + std::result_of_t operator ()(Args&&... args ) const { + return (*p)(std::forward(args)...); + } + + template + std::result_of_t operator ()(Args&&... args ) { + return std::move(*p)(std::forward(args)...); + } + +private: + T* p; +}; + +class base { +protected: + ceph::mutex lock = ceph::make_mutex("ceph::async::detail::base::lock"); + ceph::condition_variable cond; + bool has_value = false; + + ~base() = default; + + auto wait_base() { + std::unique_lock l(lock); + cond.wait(l, [this](){ return has_value; }); + return l; + } + + auto exec_base() { + std::unique_lock l(lock); + // There's no really good way to handle being called twice + // without being reset. + ceph_assert(!has_value); + has_value = true; + cond.notify_one(); + return l; + } +}; +} + +// waiter is a replacement for C_SafeCond and friends. It is the +// moral equivalent of a future but plays well with a world of +// callbacks. +template +class waiter; + +template<> +class waiter<> final : public detail::base { +public: + void wait() { + wait_base(); + has_value = false; + } + + void operator()() { + exec_base(); + } + + auto ref() { + return detail::rvalue_reference_wrapper(*this); + } + + + operator fu2::unique_function() { + return fu2::unique_function(ref()); + } +}; + +template +class waiter final : public detail::base { + std::aligned_storage_t ret; + +public: + Ret wait() { + auto l = wait_base(); + auto r = reinterpret_cast(&ret); + auto t = std::move(*r); + r->~Ret(); + has_value = false; + return t; + } + + void operator()(Ret&& _ret) { + auto l = exec_base(); + auto r = reinterpret_cast(&ret); + *r = std::move(_ret); + } + + void operator()(const Ret& _ret) { + auto l = exec_base(); + auto r = reinterpret_cast(&ret); + *r = std::move(_ret); + } + + auto ref() { + return detail::rvalue_reference_wrapper(*this); + } + + operator fu2::unique_function() { + return fu2::unique_function(ref()); + } + + ~waiter() { + if (has_value) + reinterpret_cast(&ret)->~Ret(); + } +}; + +template +class waiter final : public detail::base { + std::tuple ret; + +public: + std::tuple wait() { + using std::tuple; + auto l = wait_base(); + return std::move(ret); + auto r = reinterpret_cast*>(&ret); + auto t = std::move(*r); + r->~tuple(); + has_value = false; + return t; + } + + void operator()(Ret&&... _ret) { + auto l = exec_base(); + auto r = reinterpret_cast*>(&ret); + *r = std::forward_as_tuple(_ret...); + } + + void operator()(const Ret&... _ret) { + auto l = exec_base(); + auto r = reinterpret_cast*>(&ret); + *r = std::forward_as_tuple(_ret...); + } + + auto ref() { + return detail::rvalue_reference_wrapper(*this); + } + + operator fu2::unique_function() { + return fu2::unique_function(ref()); + } + + ~waiter() { + using std::tuple; + if (has_value) + reinterpret_cast*>(&ret)->~tuple(); + } +}; +} + +#endif // CEPH_COMMON_WAITER_H From 6d652f7beb0ffd32a70091aff949034e343e0571 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Wed, 16 Aug 2017 15:53:51 -0400 Subject: [PATCH 09/25] common: Add error_code glue/infrastructure Signed-off-by: Adam C. Emerson --- src/CMakeLists.txt | 1 + src/common/error_code.cc | 196 +++++++++++++++++++++++++++++++++++++++ src/common/error_code.h | 80 ++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 src/common/error_code.cc create mode 100644 src/common/error_code.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b77f77c9c6acf..d7cb95b171fb9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -322,6 +322,7 @@ set(libcommon_files ceph_ver.c global/global_context.cc xxHash/xxhash.c + common/error_code.cc log/Log.cc mon/MonCap.cc mon/MonClient.cc diff --git a/src/common/error_code.cc b/src/common/error_code.cc new file mode 100644 index 0000000000000..c10f98a38f063 --- /dev/null +++ b/src/common/error_code.cc @@ -0,0 +1,196 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Adam C. Emerson + * + * 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. + */ + +#include + +#include "common/error_code.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" + +using boost::system::error_category; +using boost::system::error_condition; +using boost::system::generic_category; +using boost::system::system_category; + +namespace ceph { + +// A category for error conditions particular to Ceph + +class ceph_error_category : public converting_category { +public: + ceph_error_category(){} + const char* name() const noexcept override; + using converting_category::message; + std::string message(int ev) const override; + const char* message(int ev, char*, std::size_t) const noexcept override; + using converting_category::equivalent; + bool equivalent(const boost::system::error_code& c, + int ev) const noexcept override; + int from_code(int ev) const noexcept override; +}; + +const char* ceph_error_category::name() const noexcept { + return "ceph"; +} + +const char* ceph_error_category::message(int ev, char*, + std::size_t) const noexcept { + if (ev == 0) + return "No error"; + + switch (static_cast(ev)) { + + case errc::not_in_map: + return "Map does not contain requested entry."; + case errc::does_not_exist: + return "Item does not exist"; + case errc::failure: + return "An internal fault or inconsistency occurred"; + case errc::exists: + return "Already exists"; + case errc::limit_exceeded: + return "Attempt to use too much"; + case errc::auth: + return "Authentication error"; + case errc::conflict: + return "Conflict detected or precondition failed"; + } + + return "Unknown error."; +} + +std::string ceph_error_category::message(int ev) const { + return message(ev, nullptr, 0); +} + +bool ceph_error_category::equivalent(const boost::system::error_code& c, + int ev) const noexcept { + if (c.category() == system_category()) { + if (c.value() == boost::system::errc::no_such_file_or_directory) { + if (ev == static_cast(errc::not_in_map) || + ev == static_cast(errc::does_not_exist)) { + // Blargh. A bunch of stuff returns ENOENT now, so just to be safe. + return true; + } + } + if (c.value() == boost::system::errc::io_error) { + if (ev == static_cast(errc::failure)) { + return true; + } + } + if (c.value() == boost::system::errc::file_exists) { + if (ev == static_cast(errc::exists)) { + return true; + } + } + if (c.value() == boost::system::errc::no_space_on_device || + c.value() == boost::system::errc::invalid_argument) { + if (ev == static_cast(errc::limit_exceeded)) { + return true; + } + } + if (c.value() == boost::system::errc::operation_not_permitted) { + if (ev == static_cast(ceph::errc::conflict)) { + return true; + } + } + } + return false; +} + +int ceph_error_category::from_code(int ev) const noexcept { + if (ev == 0) + return 0; + + switch (static_cast(ev)) { + case errc::not_in_map: + case errc::does_not_exist: + // What we use now. + return -ENOENT; + case errc::failure: + return -EIO; + case errc::exists: + return -EEXIST; + case errc::limit_exceeded: + return -EIO; + case errc::auth: + return -EACCES; + case errc::conflict: + return -EINVAL; + } + return -EDOM; +} + +const error_category& ceph_category() noexcept { + static const ceph_error_category c; + return c; +} + + +// This is part of the glue for hooking new code to old. Since +// Context* and other things give us integer codes from errno, wrap +// them in an error_code. +boost::system::error_code to_error_code(int ret) noexcept +{ + if (ret == 0) + return {}; + return { std::abs(ret), boost::system::system_category() }; +} + +// This is more complicated. For the case of categories defined +// elsewhere, we have to convert everything here. +int from_error_code(boost::system::error_code e) noexcept +{ + if (!e) + return 0; + + auto c = dynamic_cast(&e.category()); + // For categories we define + if (c) + return c->from_code(e.value()); + + // For categories matching values of errno + if (e.category() == boost::system::system_category() || + e.category() == boost::system::generic_category() || + // ASIO uses the system category for these and matches system + // error values. + e.category() == boost::asio::error::get_netdb_category() || + e.category() == boost::asio::error::get_addrinfo_category()) + return -e.value(); + + if (e.category() == boost::asio::error::get_misc_category()) { + // These values are specific to asio + switch (e.value()) { + case boost::asio::error::already_open: + return -EIO; + case boost::asio::error::eof: + return -EIO; + case boost::asio::error::not_found: + return -ENOENT; + case boost::asio::error::fd_set_failure: + return -EINVAL; + } + } + // Add any other categories we use here. + + // Marcus likes this as a sentinel for 'Error code? What error code?' + return -EDOM; +} +} +#pragma GCC diagnostic pop +#pragma clang diagnostic pop diff --git a/src/common/error_code.h b/src/common/error_code.h new file mode 100644 index 0000000000000..8a9b9b09467fb --- /dev/null +++ b/src/common/error_code.h @@ -0,0 +1,80 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 Red Hat, Inc. + * + * Author: Adam C. Emerson + * + * 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. + */ + +#ifndef COMMON_CEPH_ERROR_CODE +#define COMMON_CEPH_ERROR_CODE + +#include + +#include +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" + +namespace ceph { + +// This is for error categories we define, so we can specify the +// equivalent integral value at the point of definition. +class converting_category : public boost::system::error_category { +public: + virtual int from_code(int code) const noexcept = 0; +}; + +const boost::system::error_category& ceph_category() noexcept; + +enum class errc { + not_in_map = 1, // The requested item was not found in the map + does_not_exist, // Item does not exist + failure, // An internal fault or inconsistency + exists, // Already exists + limit_exceeded, // Attempting to use too much of something + auth, // May not be an auth failure. It could be that the + // preconditions to attempt auth failed. + conflict, // Conflict or precondition failure +}; +} + +namespace boost::system { +template<> +struct is_error_condition_enum<::ceph::errc> { + static const bool value = true; +}; +template<> +struct is_error_code_enum<::ceph::errc> { + static const bool value = false; +}; +} + +namespace ceph { +// explicit conversion: +inline boost::system::error_code make_error_code(errc e) noexcept { + return { static_cast(e), ceph_category() }; +} + +// implicit conversion: +inline boost::system::error_condition make_error_condition(errc e) noexcept { + return { static_cast(e), ceph_category() }; +} + +boost::system::error_code to_error_code(int ret) noexcept; +int from_error_code(boost::system::error_code e) noexcept; +} +#pragma GCC diagnostic pop +#pragma clang diagnostic pop + +#endif // COMMON_CEPH_ERROR_CODE From 406f223024408f2a5806c025d070e6c3817f0739 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 30 Apr 2019 12:28:07 -0400 Subject: [PATCH 10/25] osd: Add error_category for OSD result codes Actually to be used by /clients/. Signed-off-by: Adam C. Emerson --- src/CMakeLists.txt | 1 + src/osd/error_code.cc | 105 ++++++++++++++++++++++++++++++++++++++++++ src/osd/error_code.h | 53 +++++++++++++++++++++ 3 files changed, 159 insertions(+) create mode 100644 src/osd/error_code.cc create mode 100644 src/osd/error_code.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d7cb95b171fb9..5dbe424b504a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -336,6 +336,7 @@ set(libcommon_files osd/OSDMap.cc osd/OSDMapMapping.cc osd/osd_types.cc + osd/error_code.cc osd/PGPeeringEvent.cc osd/OpRequest.cc osd/ClassHandler.cc diff --git a/src/osd/error_code.cc b/src/osd/error_code.cc new file mode 100644 index 0000000000000..96a171ec766f9 --- /dev/null +++ b/src/osd/error_code.cc @@ -0,0 +1,105 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat + * Author: Adam C. Emerson + * + * 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. + * + */ + +#include + +#include "common/error_code.h" +#include "common/errno.h" +#include "error_code.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +class osd_error_category : public ceph::converting_category { +public: + osd_error_category(){} + const char* name() const noexcept override; + const char* message(int ev, char*, std::size_t) const noexcept override; + std::string message(int ev) const override; + boost::system::error_condition default_error_condition(int ev) const noexcept + override; + bool equivalent(int ev, const boost::system::error_condition& c) const + noexcept override; + using ceph::converting_category::equivalent; + int from_code(int ev) const noexcept override; +}; +#pragma GCC diagnostic pop +#pragma clang diagnostic pop + +const char* osd_error_category::name() const noexcept { + return "osd"; +} + +const char* osd_error_category::message(int ev, char* buf, + std::size_t len) const noexcept { + if (ev == 0) + return "No error"; + + switch (static_cast(ev)) { + case osd_errc::old_snapc: + return "ORDERSNAP flag set; writer has old snapc"; + case osd_errc::blacklisted: + return "Blacklisted"; + } + + if (len) { + auto s = cpp_strerror(ev); + auto n = s.copy(buf, len - 1); + *(buf + n) = '\0'; + } + return buf; +} + +std::string osd_error_category::message(int ev) const { + if (ev == 0) + return "No error"; + + switch (static_cast(ev)) { + case osd_errc::old_snapc: + return "ORDERSNAP flag set; writer has old snapc"; + case osd_errc::blacklisted: + return "Blacklisted"; + } + + return cpp_strerror(ev); +} + +boost::system::error_condition osd_error_category::default_error_condition(int ev) const noexcept { + if (ev == static_cast(osd_errc::old_snapc) || + ev == static_cast(osd_errc::blacklisted)) + return { ev, *this }; + else + return { ev, boost::system::generic_category() }; +} + +bool osd_error_category::equivalent(int ev, const boost::system::error_condition& c) const noexcept { + switch (static_cast(ev)) { + case osd_errc::old_snapc: + return c == boost::system::errc::invalid_argument; + case osd_errc::blacklisted: + return c == boost::system::errc::operation_not_permitted; + } + return default_error_condition(ev) == c; +} + +int osd_error_category::from_code(int ev) const noexcept { + return -ev; +} + +const boost::system::error_category& osd_category() noexcept { + static const osd_error_category c; + return c; +} diff --git a/src/osd/error_code.h b/src/osd/error_code.h new file mode 100644 index 0000000000000..ae112de2f0cfc --- /dev/null +++ b/src/osd/error_code.h @@ -0,0 +1,53 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat + * Author: Adam C. Emerson + * + * 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/rados.h" + +const boost::system::error_category& osd_category() noexcept; + +// Since the OSD mostly uses POSIX error codes plus a couple +// additions, this will be a degenerate error category for now that +// mostly forwards to POSIX. + +enum class osd_errc { + old_snapc = 85, /* ORDERSNAP flag set; writer has old snapc*/ + blacklisted = 108 /* blacklisted */ +}; + +namespace boost::system { +template<> +struct is_error_code_enum<::osd_errc> { + static const bool value = true; +}; + +template<> +struct is_error_condition_enum<::osd_errc> { + static const bool value = false; +}; +} + +// implicit conversion: +inline boost::system::error_code make_error_code(osd_errc e) noexcept { + return { static_cast(e), osd_category() }; +} + +// explicit conversion: +inline boost::system::error_condition make_error_condition(osd_errc e) noexcept { + return { static_cast(e), osd_category() }; +} From 8ca563e33b850b2f8adeae81b21ac58c6243edb8 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Fri, 10 May 2019 16:59:36 -0400 Subject: [PATCH 11/25] osdc: Add error codes for Objecter error returns For errors generated /within/ the Objecter, not those returned from the OSD. Signed-off-by: Adam C. Emerson --- src/CMakeLists.txt | 1 + src/osdc/CMakeLists.txt | 1 + src/osdc/error_code.cc | 159 ++++++++++++++++++++++++++++++++++++++++ src/osdc/error_code.h | 55 ++++++++++++++ 4 files changed, 216 insertions(+) create mode 100644 src/osdc/error_code.cc create mode 100644 src/osdc/error_code.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5dbe424b504a0..47ff4a5dcd708 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -343,6 +343,7 @@ set(libcommon_files osd/osd_op_util.cc osdc/Striper.cc osdc/Objecter.cc + osdc/error_code.cc librbd/Features.cc ${mds_files}) set_source_files_properties(ceph_ver.c diff --git a/src/osdc/CMakeLists.txt b/src/osdc/CMakeLists.txt index 6dd97b3936237..551b105f893fb 100644 --- a/src/osdc/CMakeLists.txt +++ b/src/osdc/CMakeLists.txt @@ -2,6 +2,7 @@ set(osdc_files Filer.cc ObjectCacher.cc Objecter.cc + error_code.cc Striper.cc) add_library(osdc STATIC ${osdc_files}) if(WITH_EVENTTRACE) diff --git a/src/osdc/error_code.cc b/src/osdc/error_code.cc new file mode 100644 index 0000000000000..5dc548385e8d0 --- /dev/null +++ b/src/osdc/error_code.cc @@ -0,0 +1,159 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat + * Author: Adam C. Emerson + * + * 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. + * + */ + +#include + +#include "common/error_code.h" +#include "error_code.h" + +namespace bs = boost::system; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +class osdc_error_category : public ceph::converting_category { +public: + osdc_error_category(){} + const char* name() const noexcept override; + const char* message(int ev, char*, std::size_t) const noexcept override; + std::string message(int ev) const override; + bs::error_condition default_error_condition(int ev) const noexcept + override; + bool equivalent(int ev, const bs::error_condition& c) const + noexcept override; + using ceph::converting_category::equivalent; + int from_code(int ev) const noexcept override; +}; +#pragma GCC diagnostic pop +#pragma clang diagnostic pop + +const char* osdc_error_category::name() const noexcept { + return "osdc"; +} + +const char* osdc_error_category::message(int ev, char*, + std::size_t) const noexcept { + if (ev == 0) + return "No error"; + + switch (static_cast(ev)) { + case osdc_errc::pool_dne: + return "Pool does not exist"; + + case osdc_errc::pool_exists: + return "Pool already exists"; + + case osdc_errc::precondition_violated: + return "Precondition for operation not satisfied"; + + case osdc_errc::not_supported: + return "Operation not supported"; + + case osdc_errc::snapshot_exists: + return "Snapshot already exists"; + + case osdc_errc::snapshot_dne: + return "Snapshot does not exist"; + + case osdc_errc::timed_out: + return "Operation timed out"; + } + + return "Unknown error"; +} + +std::string osdc_error_category::message(int ev) const { + return message(ev, nullptr, 0); +} + +bs::error_condition +osdc_error_category::default_error_condition(int ev) const noexcept { + switch (static_cast(ev)) { + case osdc_errc::pool_dne: + return ceph::errc::does_not_exist; + case osdc_errc::pool_exists: + return ceph::errc::exists; + case osdc_errc::precondition_violated: + return bs::errc::invalid_argument; + case osdc_errc::not_supported: + return bs::errc::operation_not_supported; + case osdc_errc::snapshot_exists: + return ceph::errc::exists; + case osdc_errc::snapshot_dne: + return ceph::errc::does_not_exist; + case osdc_errc::timed_out: + return bs::errc::timed_out; + } + + return { ev, *this }; +} + +bool osdc_error_category::equivalent(int ev, + const bs::error_condition& c) const noexcept { + if (static_cast(ev) == osdc_errc::pool_dne) { + if (c == bs::errc::no_such_file_or_directory) { + return true; + } + if (c == ceph::errc::not_in_map) { + return true; + } + } + if (static_cast(ev) == osdc_errc::pool_exists) { + if (c == bs::errc::file_exists) { + return true; + } + } + if (static_cast(ev) == osdc_errc::snapshot_exists) { + if (c == bs::errc::file_exists) { + return true; + } + } + if (static_cast(ev) == osdc_errc::snapshot_dne) { + if (c == bs::errc::no_such_file_or_directory) { + return true; + } + if (c == ceph::errc::not_in_map) { + return true; + } + } + + return default_error_condition(ev) == c; +} + +int osdc_error_category::from_code(int ev) const noexcept { + switch (static_cast(ev)) { + case osdc_errc::pool_dne: + return -ENOENT; + case osdc_errc::pool_exists: + return -EEXIST; + case osdc_errc::precondition_violated: + return -EINVAL; + case osdc_errc::not_supported: + return -EOPNOTSUPP; + case osdc_errc::snapshot_exists: + return -EEXIST; + case osdc_errc::snapshot_dne: + return -ENOENT; + case osdc_errc::timed_out: + return -ETIMEDOUT; + } + return -EDOM; +} + +const bs::error_category& osdc_category() noexcept { + static const osdc_error_category c; + return c; +} diff --git a/src/osdc/error_code.h b/src/osdc/error_code.h new file mode 100644 index 0000000000000..53c9e3c3a2a82 --- /dev/null +++ b/src/osdc/error_code.h @@ -0,0 +1,55 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat + * Author: Adam C. Emerson + * + * 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/rados.h" + +const boost::system::error_category& osdc_category() noexcept; + +enum class osdc_errc { + pool_dne = 1, + pool_exists, + // Come the revolution, we'll just kill your program. Maybe. + precondition_violated, + not_supported, + snapshot_exists, + snapshot_dne, + timed_out +}; + +namespace boost::system { +template<> +struct is_error_code_enum<::osdc_errc> { + static const bool value = true; +}; + +template<> +struct is_error_condition_enum<::osdc_errc> { + static const bool value = false; +}; +} + +// implicit conversion: +inline boost::system::error_code make_error_code(osdc_errc e) noexcept { + return { static_cast(e), osdc_category() }; +} + +// explicit conversion: +inline boost::system::error_condition make_error_condition(osdc_errc e) noexcept { + return { static_cast(e), osdc_category() }; +} From 513f37bd2c54b85fb2efcde57d1289c82013484e Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Fri, 10 May 2019 18:01:53 -0400 Subject: [PATCH 12/25] mon: Add error_category for error_codes from Monitor These are, like error returns from most Ceph daemons, punned with POSIX error codes, so treat them as such. This category lets the MonClient wrap Monitor errors and distinguish them from errors generated within MonClient. Signed-off-by: Adam C. Emerson --- src/CMakeLists.txt | 1 + src/mon/error_code.cc | 85 +++++++++++++++++++++++++++++++++++++++++++ src/mon/error_code.h | 49 +++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 src/mon/error_code.cc create mode 100644 src/mon/error_code.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 47ff4a5dcd708..2537e5110c621 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -328,6 +328,7 @@ set(libcommon_files mon/MonClient.cc mon/MonMap.cc mon/MonSub.cc + mon/error_code.cc mgr/MgrClient.cc mon/PGMap.cc mgr/ServiceMap.cc diff --git a/src/mon/error_code.cc b/src/mon/error_code.cc new file mode 100644 index 0000000000000..a2cd39299993f --- /dev/null +++ b/src/mon/error_code.cc @@ -0,0 +1,85 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat + * Author: Adam C. Emerson + * + * 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. + * + */ + +#include + +#include "common/error_code.h" +#include "common/errno.h" +#include "error_code.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" + +namespace bs = boost::system; + +class mon_error_category : public ceph::converting_category { +public: + mon_error_category(){} + const char* name() const noexcept override; + const char* message(int ev, char*, std::size_t) const noexcept override; + std::string message(int ev) const override; + bs::error_condition default_error_condition(int ev) const noexcept + override; + bool equivalent(int ev, const bs::error_condition& c) const + noexcept override; + using ceph::converting_category::equivalent; + int from_code(int ev) const noexcept override; +}; + +const char* mon_error_category::name() const noexcept { + return "mon"; +} + +const char* mon_error_category::message(int ev, char* buf, + std::size_t len) const noexcept { + if (ev == 0) + return "No error"; + + if (len) { + auto s = cpp_strerror(ev); + auto n = s.copy(buf, len - 1); + *(buf + n) = '\0'; + } + return buf; +} + +std::string mon_error_category::message(int ev) const { + if (ev == 0) + return "No error"; + + return cpp_strerror(ev); +} + +bs::error_condition +mon_error_category::default_error_condition(int ev) const noexcept { + return { ev, bs::generic_category() }; +} + +bool mon_error_category::equivalent(int ev,const bs::error_condition& c) const noexcept { + return default_error_condition(ev) == c; +} + +int mon_error_category::from_code(int ev) const noexcept { + return -ev; +} + +const bs::error_category& mon_category() noexcept { + static const mon_error_category c; + return c; +} +#pragma GCC diagnostic pop +#pragma clang diagnostic pop diff --git a/src/mon/error_code.h b/src/mon/error_code.h new file mode 100644 index 0000000000000..2a6e88061fe2d --- /dev/null +++ b/src/mon/error_code.h @@ -0,0 +1,49 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 Red Hat + * Author: Adam C. Emerson + * + * 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/rados.h" + +const boost::system::error_category& mon_category() noexcept; + +// The Monitor, like the OSD, mostly replies with POSIX error codes. + +enum class mon_errc { +}; + +namespace boost::system { +template<> +struct is_error_code_enum<::mon_errc> { + static const bool value = true; +}; + +template<> +struct is_error_condition_enum<::mon_errc> { + static const bool value = false; +}; +} + +// explicit conversion: +inline boost::system::error_code make_error_code(mon_errc e) noexcept { + return { static_cast(e), mon_category() }; +} + +// implicit conversion: +inline boost::system::error_condition make_error_condition(mon_errc e) noexcept { + return { static_cast(e), mon_category() }; +} From ab729f01949fac22c4380fec54df250abd557469 Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Fri, 1 Feb 2019 15:51:16 -0500 Subject: [PATCH 13/25] include: Add expected.hpp Not worth making a submodule since it's a single-file include. Originally from https://github.com/TartanLlama/expected and released under CC0 Signed-off-by: Adam C. Emerson --- COPYING | 6 +- src/include/expected.hpp | 2282 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 2287 insertions(+), 1 deletion(-) create mode 100644 src/include/expected.hpp diff --git a/COPYING b/COPYING index 33645acdb975f..7dba4af7faa8a 100644 --- a/COPYING +++ b/COPYING @@ -191,4 +191,8 @@ License: BSD 3-clause Files: src/include/function2.hpp Copyright: 2015-2018, Denis Blank -License: Boost Software License, Version 1.0 \ No newline at end of file +License: Boost Software License, Version 1.0 + +Files: src/include/expected.hpp +Copyright: 2017, Simon Brand +License: CC0 diff --git a/src/include/expected.hpp b/src/include/expected.hpp new file mode 100644 index 0000000000000..740c6ad249506 --- /dev/null +++ b/src/include/expected.hpp @@ -0,0 +1,2282 @@ +/// +// expected - An implementation of std::expected with extensions +// Written in 2017 by Simon Brand (@TartanLlama) +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_EXPECTED_HPP +#define TL_EXPECTED_HPP + +#define TL_EXPECTED_VERSION_MAJOR 0 +#define TL_EXPECTED_VERSION_MINOR 2 + +#include +#include +#include +#include + +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define TL_EXPECTED_EXCEPTIONS_ENABLED +#endif + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +/// \exclude +#define TL_EXPECTED_MSVC2015 +#define TL_EXPECTED_MSVC2015_CONSTEXPR +#else +#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +/// \exclude +#define TL_EXPECTED_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +/// \exclude +#define TL_EXPECTED_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +/// \exclude +#define TL_EXPECTED_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +/// \exclude +#define TL_EXPECTED_NO_CONSTRR + +// GCC < 5 doesn't support some standard C++11 type traits +/// \exclude +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor +/// \exclude +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign + +// This one will be different for GCC 5.7 if it's ever supported +/// \exclude +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && \ + !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { + namespace detail { + template + struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; +#ifdef _GLIBCXX_VECTOR + template + struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible{}; +#endif + } +} +#endif + +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible +#else +/// \exclude +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible +/// \exclude +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +/// \exclude +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#endif + +#if __cplusplus > 201103L +/// \exclude +#define TL_EXPECTED_CXX14 +#endif + +#ifdef TL_EXPECTED_GCC49 +#define TL_EXPECTED_GCC49_CONSTEXPR +#else +#define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#endif + +#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ + defined(TL_EXPECTED_GCC49)) +/// \exclude +#define TL_EXPECTED_11_CONSTEXPR +#else +/// \exclude +#define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +namespace tl { +template class expected; + +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +/// \brief Used to represent an expected with no data +class monostate {}; + +/// \brief A tag type to tell expected to construct its value in-place +struct in_place_t { + explicit in_place_t() = default; +}; +/// \brief A tag to tell expected to construct its value in-place +static constexpr in_place_t in_place{}; +#endif + +/// Used as a wrapper to store the unexpected value +template class unexpected { +public: + static_assert(!std::is_same::value, "E must not be void"); + + unexpected() = delete; + constexpr explicit unexpected(const E &e) : m_val(e) {} + + constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} + + /// \returns the contained value + /// \group unexpected_value + constexpr const E &value() const & { return m_val; } + /// \group unexpected_value + TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } + /// \group unexpected_value + TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } + /// \exclude + constexpr const E &&value() const && { return std::move(m_val); } + +private: + E m_val; +}; + +/// \brief Compares two unexpected objects +/// \details Simply compares lhs.value() to rhs.value() +/// \group unexpected_relop +template +constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() == rhs.value(); +} +/// \group unexpected_relop +template +constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() != rhs.value(); +} +/// \group unexpected_relop +template +constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() < rhs.value(); +} +/// \group unexpected_relop +template +constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() <= rhs.value(); +} +/// \group unexpected_relop +template +constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() > rhs.value(); +} +/// \group unexpected_relop +template +constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() >= rhs.value(); +} + +/// Create an `unexpected` from `e`, deducing the return type +/// +/// *Example:* +/// auto e1 = tl::make_unexpected(42); +/// unexpected e2 (42); //same semantics +template +unexpected::type> make_unexpected(E &&e) { + return unexpected::type>(std::forward(e)); +} + +/// \brief A tag type to tell expected to construct the unexpected value +struct unexpect_t { + unexpect_t() = default; +}; +/// \brief A tag to tell expected to construct the unexpected value +static constexpr unexpect_t unexpect{}; + +/// \exclude +namespace detail { +template +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward(e); +#else + #ifdef _MSC_VER + __assume(0); + #else + __builtin_unreachable(); + #endif +#endif +} + +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template >{}>, + int = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >{}>> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; +#endif + +// Trait for checking if a type is a tl::expected +template struct is_expected_impl : std::false_type {}; +template +struct is_expected_impl> : std::true_type {}; +template using is_expected = is_expected_impl>; + +template +using expected_enable_forward_value = detail::enable_if_t< + std::is_constructible::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using expected_enable_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using is_void_or = conditional_t::value, std::true_type, U>; + +template +using is_copy_constructible_or_void = + is_void_or>; + +template +using is_move_constructible_or_void = + is_void_or>; + +template +using is_copy_assignable_or_void = + is_void_or>; + + +template +using is_move_assignable_or_void = + is_void_or>; + + +} // namespace detail + +/// \exclude +namespace detail { +struct no_init_t {}; +static constexpr no_init_t no_init{}; + +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` +template ::value, + bool = std::is_trivially_destructible::value> +struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } else { + m_unexpect.~unexpected(); + } + } + union { + char m_no_init; + T m_val; + unexpected m_unexpect; + }; + bool m_has_val; +}; + +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + union { + char m_no_init; + T m_val; + unexpected m_unexpect; + }; + bool m_has_val; +}; + +// T is trivial, E is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + char m_no_init; + T m_val; + unexpected m_unexpect; + }; + bool m_has_val; +}; + +// E is trivial, T is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&... args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&... args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } + } + union { + char m_no_init; + T m_val; + unexpected m_unexpect; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is trivially-destructible +template struct expected_storage_base { + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base() : m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + struct dummy {}; + union { + dummy m_val; + unexpected m_unexpect; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is not trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&... args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&... args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + char m_dummy; + unexpected m_unexpect; + }; + bool m_has_val; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct(Args &&... args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template void construct_with(Rhs &&rhs) noexcept { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } + + template void construct_error(Args &&... args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template ::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template ::value && + std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } else { + assign_common(rhs); + } + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template ::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + + try { + construct(rhs.get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } + } else { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + try { + construct(std::move(rhs).get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } + } else { + assign_common(std::move(rhs)); + } + } + + #else + + // If exceptions are disabled then we can just copy-construct + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(rhs); + } + } + + #endif + + // The common part of move/copy assigning + template void assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + get() = std::forward(rhs).get(); + } else { + destroy_val(); + construct_error(std::forward(rhs).geterr()); + } + } else { + if (!rhs.m_has_val) { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + constexpr void destroy_val() { + get().~T(); + } +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct() noexcept { this->m_has_val = true; } + + // This function doesn't use its argument, but needs it so that code in + // levels above this can work independently of whether T is void + template void construct_with(Rhs &&) noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&... args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template void assign(Rhs &&rhs) noexcept { + if (!this->m_has_val) { + if (rhs.m_has_val) { + geterr().~unexpected(); + construct(); + } else { + geterr() = std::forward(rhs).geterr(); + } + } else { + if (!rhs.m_has_val) { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + constexpr void destroy_val() { + //no-op + } +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T and E are trivially copy constructible +template :: + value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are not trivially copy constructible +template +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base &rhs) + : expected_operations_base(no_init) { + if (rhs.has_value()) { + this->construct_with(rhs); + } else { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_EXPECTED_GCC49 +template >::value + &&std::is_trivially_move_constructible::value> +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; +}; +#else +template struct expected_move_base; +#endif +template +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base &rhs) = default; + + expected_move_base(expected_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) { + if (rhs.has_value()) { + this->construct_with(std::move(rhs)); + } else { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template >::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_EXPECTED_GCC49 +template , + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>:: + value &&std::is_trivially_destructible::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else +template struct expected_move_assign_base; +#endif + +template +struct expected_move_assign_base + : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base(expected_move_assign_base &&rhs) = default; + + expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// expected_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value && + std::is_copy_constructible::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value)> +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T and E are copy/move constructible + +// assignable +template ::value && + std::is_copy_constructible::value && + is_copy_assignable_or_void::value && + std::is_copy_assignable::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value && + is_move_assignable_or_void::value && + std::is_move_assignable::value)> +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag { + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template ::value || std::is_void::value> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; +} // namespace detail + +template class bad_expected_access : public std::exception { +public: + explicit bad_expected_access(E e) : m_val(std::move(e)) {} + + virtual const char *what() const noexcept override { + return "Bad expected access"; + } + + const E &error() const & { return m_val; } + E &error() & { return m_val; } + const E &&error() const && { return std::move(m_val); } + E &&error() && { return std::move(m_val); } + +private: + E m_val; +}; + +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same>::value, + "T must not be in_place_t"); + static_assert(!std::is_same>::value, + "T must not be unexpect_t"); + static_assert(!std::is_same>>::value, + "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); + + T *valptr() { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { return std::addressof(this->m_unexpect); } + + template ::value> * = nullptr> + U &val() { + return this->m_val; + } + unexpected &err() { return this->m_unexpect; } + + template ::value> * = nullptr> + const U &val() const { + return this->m_val; + } + const unexpected &err() const { return this->m_unexpect; } + + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + /// \group and_then + /// Carries out some operation which returns an expected on the stored object + /// if there is one. \requires `std::invoke(std::forward(f), value())` + /// returns an `expected` for some `U`. \returns Let `U` be the result + /// of `std::invoke(std::forward(f), value())`. Returns an + /// `expected`. The return value is empty if `*this` is empty, + /// otherwise the return value of `std::invoke(std::forward(f), value())` + /// is returned. + /// \synopsis template \nconstexpr auto and_then(F &&f) &; + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + return and_then_impl(*this, std::forward(f)); + } + + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) &&; + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + return and_then_impl(std::move(*this), std::forward(f)); + } + + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) const &; + template constexpr auto and_then(F &&f) const & { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) const &&; + template constexpr auto and_then(F &&f) const && { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif + +#else + /// \group and_then + /// Carries out some operation which returns an expected on the stored object + /// if there is one. \requires `std::invoke(std::forward(f), value())` + /// returns an `expected` for some `U`. \returns Let `U` be the result + /// of `std::invoke(std::forward(f), value())`. Returns an + /// `expected`. The return value is empty if `*this` is empty, + /// otherwise the return value of `std::invoke(std::forward(f), value())` + /// is returned. + /// \synopsis template \nconstexpr auto and_then(F &&f) &; + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) & -> decltype(and_then_impl(*this, std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) &&; + template + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype( + and_then_impl(std::move(*this), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } + + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) const &; + template + constexpr auto and_then(F &&f) const & -> decltype( + and_then_impl(*this, std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + /// \group and_then + /// \synopsis template \nconstexpr auto and_then(F &&f) const &&; + template + constexpr auto and_then(F &&f) const && -> decltype( + and_then_impl(std::move(*this), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + /// \brief Carries out some operation on the stored object if there is one. + /// \returns Let `U` be the result of `std::invoke(std::forward(f), + /// value())`. If `U` is `void`, returns an `expected, otherwise + // returns an `expected`. If `*this` is unexpected, the + /// result is `*this`, otherwise an `expected` is constructed from the + /// return value of `std::invoke(std::forward(f), value())` and is + /// returned. + /// + /// \group map + /// \synopsis template constexpr auto map(F &&f) &; + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template constexpr auto map(F &&f) &&; + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + + /// \group map + /// \synopsis template constexpr auto map(F &&f) const &; + template constexpr auto map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template constexpr auto map(F &&f) const &&; + template constexpr auto map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + /// \brief Carries out some operation on the stored object if there is one. + /// \returns Let `U` be the result of `std::invoke(std::forward(f), + /// value())`. If `U` is `void`, returns an `expected, otherwise + // returns an `expected`. If `*this` is unexpected, the + /// result is `*this`, otherwise an `expected` is constructed from the + /// return value of `std::invoke(std::forward(f), value())` and is + /// returned. + /// + /// \group map + /// \synopsis template constexpr auto map(F &&f) &; + template + TL_EXPECTED_11_CONSTEXPR decltype( + expected_map_impl(std::declval(), std::declval())) + map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template constexpr auto map(F &&f) &&; + template + TL_EXPECTED_11_CONSTEXPR decltype( + expected_map_impl(std::declval(), std::declval())) + map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + + /// \group map + /// \synopsis template constexpr auto map(F &&f) const &; + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + /// \group map + /// \synopsis template constexpr auto map(F &&f) const &&; + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + /// \brief Carries out some operation on the stored unexpected object if there + /// is one. + /// \returns Let `U` be the result of `std::invoke(std::forward(f), + /// value())`. If `U` is `void`, returns an `expected`, otherwise + /// returns an `expected`. If `*this` has an expected + /// value, the result is `*this`, otherwise an `expected` is constructed + /// from `make_unexpected(std::invoke(std::forward(f), value()))` and is + /// returned. + /// + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) &; + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) &&; + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) const &; + template constexpr auto map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) const &&; + template constexpr auto map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + /// \brief Carries out some operation on the stored unexpected object if there + /// is one. + /// \returns Let `U` be the result of `std::invoke(std::forward(f), + /// value())`. Returns an `expected`. If `*this` has an expected + /// value, the result is `*this`, otherwise an `expected` is constructed + /// from `make_unexpected(std::invoke(std::forward(f), value()))` and is + /// returned. + /// + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) &; + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) &&; + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) const &; + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + /// \group map_error + /// \synopsis template constexpr auto map_error(F &&f) const &&; + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// \brief Calls `f` if the expectd is in the unexpected state + /// \requires `F` is invokable with `E`, and `std::invoke_result_t` + /// must be void or convertible to `expcted`. + /// \effects If `*this` has a value, returns `*this`. + /// Otherwise, if `f` returns `void`, calls `std::forward(f)(E)` and returns + /// `std::nullopt`. Otherwise, returns `std::forward(f)(E)`. + /// + /// \group or_else + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + return or_else_impl(*this, std::forward(f)); + } + + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + return or_else_impl(std::move(*this), std::forward(f)); + } + + template expected constexpr or_else(F &&f) const & { + return or_else_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template expected constexpr or_else(F &&f) const && { + return or_else_impl(std::move(*this), std::forward(f)); + } +#endif + constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; + + template ::value> * = + nullptr> + constexpr expected(in_place_t, Args &&... args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&... args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + /// \group unexpected_ctor + /// \synopsis EXPLICIT constexpr expected(const unexpected &e); + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + /// \exclude + template < + class G = E, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + /// \group unexpected_ctor + /// \synopsis EXPLICIT constexpr expected(unexpected &&e); + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + /// \exclude + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr> + constexpr explicit expected(unexpect_t, Args &&... args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + /// \exclude + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, + Args &&... args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + /// \exclude + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template < + class U, class G, + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + /// \exclude + template < + class U, class G, + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + /// \exclude + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + /// \exclude + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::move(v)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(std::move(v)); + this->m_has_val = true; + #endif + } + + return *this; + } + + template ::value && + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } else { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } + + return *this; + } + + template ::value && + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } else { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template ::value> * = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } + } + + /// \exclude + template ::value> * = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + #endif + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + } + + /// \exclude + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + #endif + } + } + + // TODO SFINAE + void swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value &&noexcept( + swap(std::declval(), std::declval())) && + std::is_nothrow_move_constructible::value && + noexcept(swap(std::declval(), std::declval()))) { + if (has_value() && rhs.has_value()) { + using std::swap; + swap(val(), rhs.val()); + } else if (!has_value() && rhs.has_value()) { + using std::swap; + swap(err(), rhs.err()); + } else if (has_value()) { + auto temp = std::move(rhs.err()); + ::new (rhs.valptr()) T(val()); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } else { + auto temp = std::move(this->err()); + ::new (valptr()) T(rhs.val()); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + } + + /// \returns a pointer to the stored value + /// \requires a value is stored + /// \group pointer + constexpr const T *operator->() const { return valptr(); } + /// \group pointer + TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } + + /// \returns the stored value + /// \requires a value is stored + /// \group deref + template ::value> * = nullptr> + constexpr const U &operator*() const & { + return val(); + } + /// \group deref + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + return val(); + } + /// \group deref + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + return std::move(val()); + } + /// \group deref + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + return std::move(val()); + } + + /// \returns whether or not the optional has a value + /// \group has_value + constexpr bool has_value() const noexcept { return this->m_has_val; } + /// \group has_value + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + /// \returns the contained value if there is one, otherwise throws + /// [bad_expected_access] + /// + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return std::move(val()); + } + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return std::move(val()); + } + + /// \returns the unexpected value + /// \requires there is an unexpected value + /// \group error + constexpr const E &error() const & { return err().value(); } + /// \group error + TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } + /// \group error + constexpr const E &&error() const && { return std::move(err().value()); } + /// \group error + TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } + + /// \returns the stored value if there is one, otherwise returns `u` + /// \group value_or + template constexpr T value_or(U &&v) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + /// \group value_or + template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } +}; + +/// \exclude +namespace detail { +template using exp_t = typename detail::decay_t::value_type; +template using err_t = typename detail::decay_t::error_type; +template using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, exp.error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, exp.error()); +} +#else +template struct TC; +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, exp.error()); +} + +template ())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, exp.error()); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto or_else_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() + ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), + std::forward(exp)); +} +#else +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() + ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), + std::forward(exp)); +} +#endif +} // namespace detail + +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); +} + +template +constexpr bool operator==(const expected &x, const U &v) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator==(const U &v, const expected &x) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator!=(const expected &x, const U &v) { + return x.has_value() ? *x != v : true; +} +template +constexpr bool operator!=(const U &v, const expected &x) { + return x.has_value() ? *x != v : true; +} + +template +constexpr bool operator==(const expected &x, const unexpected &e) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected &e, const expected &x) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected &x, const unexpected &e) { + return x.has_value() ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected &e, const expected &x) { + return x.has_value() ? true : x.error() != e.value(); +} + +// TODO is_swappable +template ::value && + std::is_move_constructible::value> * = nullptr> +void swap(expected &lhs, + expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { + lhs.swap(rhs); +} +} // namespace tl + +#define TL_OPTIONAL_EXPECTED_MUTEX +#endif From 7d73fa6a309dca4c5381596c5e92813e2e11ed3b Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 4 Sep 2018 19:51:08 -0400 Subject: [PATCH 14/25] buffer: base exceptions on system_error Thanks to Kefu Chai for assistance. Signed-off-by: Adam C. Emerson --- src/cls/rgw/cls_rgw_types.h | 2 +- src/common/buffer.cc | 125 ++++++++++++++++++++++++----- src/common/buffer_seastar.h | 1 + src/common/error_code.h | 71 ++++++++++++++++ src/include/Context.h | 53 +++++++++++- src/include/buffer.h | 43 +++------- src/include/denc.h | 1 + src/librbd/Journal.cc | 2 +- src/mds/CDir.cc | 6 +- src/mds/CInode.cc | 2 +- src/mds/MDCache.cc | 2 +- src/mon/MonMap.cc | 6 +- src/msg/async/ProtocolV2.cc | 2 +- src/osd/PrimaryLogPG.cc | 4 +- src/rgw/rgw_bucket.cc | 3 +- src/rgw/rgw_common.h | 2 +- src/test/encoding.cc | 4 +- src/test/perf_helper.cc | 1 + src/test/rgw/test_rgw_common.h | 2 +- src/tools/ceph_monstore_tool.cc | 2 +- src/tools/ceph_objectstore_tool.cc | 2 +- src/tools/cephfs/DataScan.cc | 7 +- src/tools/cephfs/MetaTool.cc | 10 +-- 23 files changed, 268 insertions(+), 85 deletions(-) diff --git a/src/cls/rgw/cls_rgw_types.h b/src/cls/rgw/cls_rgw_types.h index 2187603f26cdc..fd92803c30055 100644 --- a/src/cls/rgw/cls_rgw_types.h +++ b/src/cls/rgw/cls_rgw_types.h @@ -304,7 +304,7 @@ void decode_packed_val(T& val, ceph::buffer::list::const_iterator& bl) } break; default: - throw ceph::buffer::error(); + throw ceph::buffer::malformed_input(); } } diff --git a/src/common/buffer.cc b/src/common/buffer.cc index 65ef5964cfc66..15ecd9626e0b4 100644 --- a/src/common/buffer.cc +++ b/src/common/buffer.cc @@ -27,12 +27,14 @@ #include "armor.h" #include "common/environment.h" #include "common/errno.h" +#include "common/error_code.h" #include "common/safe_io.h" #include "common/strtol.h" #include "common/likely.h" #include "common/valgrind.h" #include "common/deleter.h" #include "common/RWLock.h" +#include "common/error_code.h" #include "include/spinlock.h" #include "include/scope_guard.h" @@ -75,21 +77,6 @@ static ceph::spinlock debug_lock; return buffer_missed_crc; } - const char * buffer::error::what() const throw () { - return "buffer::exception"; - } - const char * buffer::bad_alloc::what() const throw () { - return "buffer::bad_alloc"; - } - const char * buffer::end_of_buffer::what() const throw () { - return "buffer::end_of_buffer"; - } - const char * buffer::malformed_input::what() const throw () { - return buf; - } - buffer::error_code::error_code(int error) : - buffer::malformed_input(cpp_strerror(error).c_str()), code(error) {} - /* * raw_combined is always placed within a single allocation along * with the data buffer. the data goes at the beginning, and @@ -640,6 +627,19 @@ static ceph::spinlock debug_lock; memset(c_str()+o, 0, l); } + template + buffer::ptr::iterator_impl& buffer::ptr::iterator_impl::operator +=(size_t len) { + pos += len; + if (pos > end_ptr) + throw end_of_buffer(); + return *this; + } + + template buffer::ptr::iterator_impl& + buffer::ptr::iterator_impl::operator +=(size_t len); + template buffer::ptr::iterator_impl& + buffer::ptr::iterator_impl::operator +=(size_t len); + // -- buffer::list::iterator -- /* buffer::list::iterator operator=(const buffer::list::iterator& other) @@ -2177,11 +2177,6 @@ std::ostream& buffer::operator<<(std::ostream& out, const buffer::list& bl) { return out; } -std::ostream& buffer::operator<<(std::ostream& out, const buffer::error& e) -{ - return out << e.what(); -} - MEMPOOL_DEFINE_OBJECT_FACTORY(buffer::raw_malloc, buffer_raw_malloc, buffer_meta); MEMPOOL_DEFINE_OBJECT_FACTORY(buffer::raw_posix_aligned, @@ -2192,3 +2187,93 @@ MEMPOOL_DEFINE_OBJECT_FACTORY(buffer::raw_claimed_char, buffer_raw_claimed_char, MEMPOOL_DEFINE_OBJECT_FACTORY(buffer::raw_static, buffer_raw_static, buffer_meta); + +namespace ceph::buffer { +inline namespace v15_2_0 { + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnon-virtual-dtor" +class buffer_error_category : public ceph::converting_category { +public: + buffer_error_category(){} + const char* name() const noexcept override; + const char* message(int ev, char*, std::size_t) const noexcept override; + std::string message(int ev) const override; + boost::system::error_condition default_error_condition(int ev) const noexcept + override; + using ceph::converting_category::equivalent; + bool equivalent(int ev, const boost::system::error_condition& c) const + noexcept override; + int from_code(int ev) const noexcept override; +}; +#pragma GCC diagnostic pop +#pragma clang diagnostic pop + +const char* buffer_error_category::name() const noexcept { + return "buffer"; +} + +const char* +buffer_error_category::message(int ev, char*, std::size_t) const noexcept { + using ceph::buffer::errc; + if (ev == 0) + return "No error"; + + switch (static_cast(ev)) { + case errc::bad_alloc: + return "Bad allocation"; + + case errc::end_of_buffer: + return "End of buffer"; + + case errc::malformed_input: + return "Malformed input"; + } + + return "Unknown error"; +} + +std::string buffer_error_category::message(int ev) const { + return message(ev, nullptr, 0); +} + +boost::system::error_condition +buffer_error_category::default_error_condition(int ev)const noexcept { + using ceph::buffer::errc; + switch (static_cast(ev)) { + case errc::bad_alloc: + return boost::system::errc::not_enough_memory; + case errc::end_of_buffer: + case errc::malformed_input: + return boost::system::errc::io_error; + } + return { ev, *this }; +} + +bool buffer_error_category::equivalent(int ev, const boost::system::error_condition& c) const noexcept { + return default_error_condition(ev) == c; +} + +int buffer_error_category::from_code(int ev) const noexcept { + using ceph::buffer::errc; + switch (static_cast(ev)) { + case errc::bad_alloc: + return -ENOMEM; + + case errc::end_of_buffer: + return -EIO; + + case errc::malformed_input: + return -EIO; + } + return -EDOM; +} + +const boost::system::error_category& buffer_category() noexcept { + static const buffer_error_category c; + return c; +} +} +} diff --git a/src/common/buffer_seastar.h b/src/common/buffer_seastar.h index 6a0e0cf6ca2f9..70a7b93251b89 100644 --- a/src/common/buffer_seastar.h +++ b/src/common/buffer_seastar.h @@ -3,6 +3,7 @@ #include #include "include/buffer.h" +#include "common/error_code.h" namespace details { diff --git a/src/common/error_code.h b/src/common/error_code.h index 8a9b9b09467fb..67c6cb448d462 100644 --- a/src/common/error_code.h +++ b/src/common/error_code.h @@ -77,4 +77,75 @@ int from_error_code(boost::system::error_code e) noexcept; #pragma GCC diagnostic pop #pragma clang diagnostic pop +// Moved here from buffer.h so librados doesn't gain a dependency on +// Boost.System + +namespace ceph::buffer { +inline namespace v15_2_0 { +const boost::system::error_category& buffer_category() noexcept; +enum class errc { bad_alloc = 1, + end_of_buffer, + malformed_input }; +} +} + +namespace boost::system { +template<> +struct is_error_code_enum<::ceph::buffer::errc> { + static const bool value = true; +}; + +template<> +struct is_error_condition_enum<::ceph::buffer::errc> { + static const bool value = false; +}; +} + +namespace ceph::buffer { +inline namespace v15_2_0 { + +// implicit conversion: +inline boost::system::error_code make_error_code(errc e) noexcept { + return { static_cast(e), buffer_category() }; +} + +// explicit conversion: +inline boost::system::error_condition +make_error_condition(errc e) noexcept { + return { static_cast(e), buffer_category() }; +} + +struct error : boost::system::system_error { + using system_error::system_error; +}; + +struct bad_alloc : public error { + bad_alloc() : error(errc::bad_alloc) {} + bad_alloc(const char* what_arg) : error(errc::bad_alloc, what_arg) {} + bad_alloc(const std::string& what_arg) : error(errc::bad_alloc, what_arg) {} +}; +struct end_of_buffer : public error { + end_of_buffer() : error(errc::end_of_buffer) {} + end_of_buffer(const char* what_arg) : error(errc::end_of_buffer, what_arg) {} + end_of_buffer(const std::string& what_arg) + : error(errc::end_of_buffer, what_arg) {} +}; + +struct malformed_input : public error { + malformed_input() : error(errc::malformed_input) {} + malformed_input(const char* what_arg) + : error(errc::malformed_input, what_arg) {} + malformed_input(const std::string& what_arg) + : error(errc::malformed_input, what_arg) {} +}; +struct error_code : public error { + error_code(int r) : error(-r, boost::system::system_category()) {} + error_code(int r, const char* what_arg) + : error(-r, boost::system::system_category(), what_arg) {} + error_code(int r, const std::string& what_arg) + : error(-r, boost::system::system_category(), what_arg) {} +}; +} +} + #endif // COMMON_CEPH_ERROR_CODE diff --git a/src/include/Context.h b/src/include/Context.h index 6d39be55ba1a2..bef85ca5b52fd 100644 --- a/src/include/Context.h +++ b/src/include/Context.h @@ -18,10 +18,15 @@ #include "common/dout.h" -#include +#include #include -#include #include +#include + +#include +#include + +#include "common/error_code.h" #include "include/ceph_assert.h" #include "common/ceph_mutex.h" @@ -48,6 +53,23 @@ class GenContext { finish(std::forward(t)); delete this; } + + template + void operator()(C &&t) noexcept { + complete(std::forward(t)); + } + + template + auto operator()() noexcept + -> typename std::enable_if::value, + void>::type { + complete(T{}); + } + + + std::reference_wrapper func() { + return std::ref(*this); + } }; template @@ -84,6 +106,20 @@ class Context { } return false; } + void complete(boost::system::error_code ec) { + complete(ceph::from_error_code(ec)); + } + void operator()(boost::system::error_code ec) noexcept { + complete(ec); + } + + void operator()() noexcept { + complete({}); + } + + std::reference_wrapper func() { + return std::ref(*this); + } }; /** @@ -126,7 +162,10 @@ class LambdaContext : public Context { public: LambdaContext(T &&t) : t(std::forward(t)) {} void finish(int r) override { - t(r); + if constexpr (std::is_invocable_v) + t(r); + else + t(); } private: T t; @@ -483,6 +522,14 @@ class ContextFactory { virtual ContextType *build() = 0; }; +inline auto lambdafy(Context *c) { + return [fin = std::unique_ptr(c)] + (boost::system::error_code ec) mutable { + fin.release()->complete(ceph::from_error_code(ec)); + }; +} + + #undef mydout #endif diff --git a/src/include/buffer.h b/src/include/buffer.h index 80e990f30d62e..3f00dbcc2a507 100644 --- a/src/include/buffer.h +++ b/src/include/buffer.h @@ -54,6 +54,7 @@ #include "crc32c.h" #include "buffer_fwd.h" + #ifdef __CEPH__ # include "include/ceph_assert.h" #else @@ -101,32 +102,12 @@ struct unique_leakable_ptr : public std::unique_ptr> { namespace buffer CEPH_BUFFER_API { inline namespace v15_2_0 { - /* - * exceptions - */ - - struct error : public std::exception{ - const char *what() const throw () override; - }; - struct bad_alloc : public error { - const char *what() const throw () override; - }; - struct end_of_buffer : public error { - const char *what() const throw () override; - }; - struct malformed_input : public error { - explicit malformed_input(const std::string& w) { - snprintf(buf, sizeof(buf), "buffer::malformed_input: %s", w.c_str()); - } - const char *what() const throw () override; - private: - char buf[256]; - }; - struct error_code : public malformed_input { - explicit error_code(int error); - int code; - }; - +/// Actual definitions in common/error_code.h +struct error; +struct bad_alloc; +struct end_of_buffer; +struct malformed_input; +struct error_code; /// count of cached crc hits (matching input) int get_cached_crc(); @@ -227,12 +208,7 @@ inline namespace v15_2_0 { } } - iterator_impl& operator+=(size_t len) { - pos += len; - if (pos > end_ptr) - throw end_of_buffer(); - return *this; - } + iterator_impl& operator+=(size_t len); const char *get_pos() { return pos; @@ -1271,8 +1247,6 @@ std::ostream& operator<<(std::ostream& out, const buffer::raw &r); std::ostream& operator<<(std::ostream& out, const buffer::list& bl); -std::ostream& operator<<(std::ostream& out, const buffer::error& e); - inline bufferhash& operator<<(bufferhash& l, const bufferlist &r) { l.update(r); return l; @@ -1282,4 +1256,5 @@ inline bufferhash& operator<<(bufferhash& l, const bufferlist &r) { } // namespace ceph + #endif diff --git a/src/include/denc.h b/src/include/denc.h index 6cc53277b9450..24fe8b08d215c 100644 --- a/src/include/denc.h +++ b/src/include/denc.h @@ -46,6 +46,7 @@ #include "byteorder.h" #include "common/convenience.h" +#include "common/error_code.h" template struct denc_traits { diff --git a/src/librbd/Journal.cc b/src/librbd/Journal.cc index 66849c461fad2..4a32d93c94b8d 100644 --- a/src/librbd/Journal.cc +++ b/src/librbd/Journal.cc @@ -1645,7 +1645,7 @@ int Journal::check_resync_requested(bool *do_resync) { decode(client_data, bl_it); } catch (const buffer::error &err) { lderr(cct) << this << " " << __func__ << ": " - << "failed to decode client data: " << err << dendl; + << "failed to decode client data: " << err.what() << dendl; return -EINVAL; } diff --git a/src/mds/CDir.cc b/src/mds/CDir.cc index 7d6d69337fcf2..47b6d5ec6484f 100644 --- a/src/mds/CDir.cc +++ b/src/mds/CDir.cc @@ -1915,9 +1915,9 @@ void CDir::_omap_fetched(bufferlist& hdrbl, map& omap, decode(got_fnode, p); } catch (const buffer::error &err) { derr << "Corrupt fnode in dirfrag " << dirfrag() - << ": " << err << dendl; + << ": " << err.what() << dendl; clog->warn() << "Corrupt fnode header in " << dirfrag() << ": " - << err << " (" << get_path() << ")"; + << err.what() << " (" << get_path() << ")"; go_bad(complete); return; } @@ -1983,7 +1983,7 @@ void CDir::_omap_fetched(bufferlist& hdrbl, map& omap, } catch (const buffer::error &err) { cache->mds->clog->warn() << "Corrupt dentry '" << dname << "' in " "dir frag " << dirfrag() << ": " - << err << "(" << get_path() << ")"; + << err.what() << "(" << get_path() << ")"; // Remember that this dentry is damaged. Subsequent operations // that try to act directly on it will get their EIOs, but this diff --git a/src/mds/CInode.cc b/src/mds/CInode.cc index 0de21439be188..f690908b60a91 100644 --- a/src/mds/CInode.cc +++ b/src/mds/CInode.cc @@ -1287,7 +1287,7 @@ void CInode::_fetched(bufferlist& bl, bufferlist& bl2, Context *fin) fin->complete(0); } } catch (buffer::error &err) { - derr << "Corrupt inode " << ino() << ": " << err << dendl; + derr << "Corrupt inode " << ino() << ": " << err.what() << dendl; fin->complete(-EINVAL); return; } diff --git a/src/mds/MDCache.cc b/src/mds/MDCache.cc index e0a8313c49727..2909db1101849 100644 --- a/src/mds/MDCache.cc +++ b/src/mds/MDCache.cc @@ -8797,7 +8797,7 @@ void MDCache::_open_ino_backtrace_fetched(inodeno_t ino, bufferlist& bl, int err decode(backtrace, bl); } catch (const buffer::error &decode_exc) { derr << "corrupt backtrace on ino x0" << std::hex << ino - << std::dec << ": " << decode_exc << dendl; + << std::dec << ": " << decode_exc.what() << dendl; open_ino_finish(ino, info, -EIO); return; } diff --git a/src/mon/MonMap.cc b/src/mon/MonMap.cc index f02a65dab7a09..dc9c4a8b1a5d8 100644 --- a/src/mon/MonMap.cc +++ b/src/mon/MonMap.cc @@ -660,7 +660,7 @@ int MonMap::init_with_config_file(const ConfigProxy& conf, using namespace seastar; -future<> MonMap::read_monmap(const std::string& monmap) +seastar::future<> MonMap::read_monmap(const std::string& monmap) { return open_file_dma(monmap, open_flags::ro).then([this] (file f) { return f.size().then([this, f = std::move(f)](size_t s) { @@ -676,7 +676,7 @@ future<> MonMap::read_monmap(const std::string& monmap) }); } -future<> MonMap::init_with_dns_srv(bool for_mkfs, const std::string& name) +seastar::future<> MonMap::init_with_dns_srv(bool for_mkfs, const std::string& name) { string domain; string service = name; @@ -753,7 +753,7 @@ seastar::future<> MonMap::build_monmap(const crimson::common::ConfigProxy& conf, }); } -future<> MonMap::build_initial(const crimson::common::ConfigProxy& conf, bool for_mkfs) +seastar::future<> MonMap::build_initial(const crimson::common::ConfigProxy& conf, bool for_mkfs) { // file? if (const auto monmap = conf.get_val("monmap"); diff --git a/src/msg/async/ProtocolV2.cc b/src/msg/async/ProtocolV2.cc index 36aa16cc5f3e3..36469e0b35ff9 100644 --- a/src/msg/async/ProtocolV2.cc +++ b/src/msg/async/ProtocolV2.cc @@ -44,7 +44,7 @@ void ProtocolV2::run_continuation(CtRef continuation) { try { CONTINUATION_RUN(continuation) } catch (const ceph::buffer::error &e) { - lderr(cct) << __func__ << " failed decoding of frame header: " << e + lderr(cct) << __func__ << " failed decoding of frame header: " << e.what() << dendl; _fault(); } catch (const ceph::crypto::onwire::MsgAuthError &e) { diff --git a/src/osd/PrimaryLogPG.cc b/src/osd/PrimaryLogPG.cc index 08d3a39bcc307..23197dd8eed88 100644 --- a/src/osd/PrimaryLogPG.cc +++ b/src/osd/PrimaryLogPG.cc @@ -11442,8 +11442,8 @@ SnapSetContext *PrimaryLogPG::get_snapset_context( bufferlist::const_iterator bvp = bv.begin(); try { ssc->snapset.decode(bvp); - } catch (ceph::buffer::error& e) { - dout(0) << __func__ << " Can't decode snapset: " << e << dendl; + } catch (const ceph::buffer::error& e) { + dout(0) << __func__ << " Can't decode snapset: " << e.what() << dendl; return NULL; } ssc->exists = true; diff --git a/src/rgw/rgw_bucket.cc b/src/rgw/rgw_bucket.cc index 4d3abd187a82f..d94d22a3ef0df 100644 --- a/src/rgw/rgw_bucket.cc +++ b/src/rgw/rgw_bucket.cc @@ -3663,7 +3663,8 @@ int RGWBucketCtl::chown(rgw::sal::RGWRadosStore *store, RGWBucketInfo& bucket_in decode(policy, bl); owner = policy.get_owner(); } catch (buffer::error& err) { - ldout(store->ctx(), 0) << "ERROR: decode policy failed" << err << dendl; + ldout(store->ctx(), 0) << "ERROR: decode policy failed" << err.what() + << dendl; return -EIO; } diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index f21944011066e..b58e062a426cd 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -1905,7 +1905,7 @@ struct rgw_obj { } else { ssize_t pos = key.name.find('_', 1); if (pos < 0) { - throw buffer::error(); + throw buffer::malformed_input(); } key.name = key.name.substr(pos + 1); } diff --git a/src/test/encoding.cc b/src/test/encoding.cc index 4f91ecc4debd8..6d252fae18b71 100644 --- a/src/test/encoding.cc +++ b/src/test/encoding.cc @@ -320,8 +320,8 @@ TEST(EncodingRoundTrip, Integers) { } const char* expected_what[] = { - "buffer::malformed_input: void lame_decoder(int) no longer understand old encoding version 100 < 200", - "buffer::malformed_input: void lame_decoder(int) decode past end of struct encoding", + "void lame_decoder(int) no longer understand old encoding version 100 < 200: Malformed input", + "void lame_decoder(int) decode past end of struct encoding: Malformed input" }; void lame_decoder(int which) { diff --git a/src/test/perf_helper.cc b/src/test/perf_helper.cc index 7661cb58e2505..00527ce9f7975 100644 --- a/src/test/perf_helper.cc +++ b/src/test/perf_helper.cc @@ -16,6 +16,7 @@ */ #include "include/buffer.h" +#include "common/error_code.h" using namespace ceph; diff --git a/src/test/rgw/test_rgw_common.h b/src/test/rgw/test_rgw_common.h index c360e3b0ad503..29ab3e4ac38a6 100644 --- a/src/test/rgw/test_rgw_common.h +++ b/src/test/rgw/test_rgw_common.h @@ -414,7 +414,7 @@ class old_rgw_obj { } else { ssize_t pos = object.find('_', 1); if (pos < 0) { - throw buffer::error(); + throw buffer::malformed_input(); } orig_obj = object.substr(pos); } diff --git a/src/tools/ceph_monstore_tool.cc b/src/tools/ceph_monstore_tool.cc index d7169582b3338..044fe30d6a479 100644 --- a/src/tools/ceph_monstore_tool.cc +++ b/src/tools/ceph_monstore_tool.cc @@ -952,7 +952,7 @@ int main(int argc, char **argv) { } } catch (const buffer::error &err) { std::cerr << "Could not decode for human readable output (you may still" - " use non-readable mode). Detail: " << err << std::endl; + " use non-readable mode). Detail: " << err.what() << std::endl; } out.append(ss); diff --git a/src/tools/ceph_objectstore_tool.cc b/src/tools/ceph_objectstore_tool.cc index 3699c3821c952..2262924e8e7de 100644 --- a/src/tools/ceph_objectstore_tool.cc +++ b/src/tools/ceph_objectstore_tool.cc @@ -681,7 +681,7 @@ int do_trim_pg_log(ObjectStore *store, const coll_t &coll, try { e.decode_with_checksum(bp); } catch (const buffer::error &e) { - cerr << "Error reading pg log entry: " << e << std::endl; + cerr << "Error reading pg log entry: " << e.what() << std::endl; } if (debug) { cerr << "read entry " << e << std::endl; diff --git a/src/tools/cephfs/DataScan.cc b/src/tools/cephfs/DataScan.cc index 8fb670ad08e62..1698fdd7e4f4b 100644 --- a/src/tools/cephfs/DataScan.cc +++ b/src/tools/cephfs/DataScan.cc @@ -1317,7 +1317,7 @@ int DataScan::scan_frags() auto q = parent_bl.cbegin(); backtrace.decode(q); } catch (buffer::error &e) { - dout(4) << "Corrupt backtrace on '" << oid << "': " << e << dendl; + dout(4) << "Corrupt backtrace on '" << oid << "': " << e.what() << dendl; if (!force_corrupt) { return -EINVAL; } else { @@ -1332,7 +1332,7 @@ int DataScan::scan_frags() auto q = layout_bl.cbegin(); decode(loaded_layout, q); } catch (buffer::error &e) { - dout(4) << "Corrupt layout on '" << oid << "': " << e << dendl; + dout(4) << "Corrupt layout on '" << oid << "': " << e.what() << dendl; if (!force_corrupt) { return -EINVAL; } @@ -1615,7 +1615,8 @@ int MetadataDriver::get_frag_of( backtrace.decode(q); have_backtrace = true; } catch (buffer::error &e) { - dout(4) << "Corrupt backtrace on '" << root_frag_oid << "': " << e << dendl; + dout(4) << "Corrupt backtrace on '" << root_frag_oid << "': " + << e.what() << dendl; } } diff --git a/src/tools/cephfs/MetaTool.cc b/src/tools/cephfs/MetaTool.cc index 06357e527f1aa..7a07d86646582 100644 --- a/src/tools/cephfs/MetaTool.cc +++ b/src/tools/cephfs/MetaTool.cc @@ -383,7 +383,7 @@ int MetaTool::_show_meta(inode_meta_t& inode_meta, const string& fn){ } }catch (const buffer::error &err){ cerr << "corrupt decode in snap_blob" - << ": " << err << std::endl; + << ": " << err.what() << std::endl; return -1; } @@ -503,7 +503,7 @@ int MetaTool::list_meta(meta_op &op){ ::decode(got_fnode, p); }catch (const buffer::error &err){ cerr << "corrupt fnode header in " << oid - << ": " << err << std::endl; + << ": " << err.what() << std::endl; return -1; } @@ -551,7 +551,7 @@ int MetaTool::list_meta(meta_op &op){ } } catch (const buffer::error &err) { derr << "Corrupt dentry '" << dname << "' : " - << err << "(" << "" << ")" << dendl; + << err.what() << "(" << "" << ")" << dendl; return -1; } } @@ -580,7 +580,7 @@ int MetaTool::list_meta(meta_op &op){ } } catch (const buffer::error &err) { derr << "Corrupt dentry '" << dname << "' : " - << err << "(" << "" << ")" << dendl; + << err.what() << "(" << "" << ")" << dendl; return -1; } } @@ -788,7 +788,7 @@ int MetaTool::show_child(std::string_view key, } }catch (const buffer::error &err){ cerr << "corrupt decode in snap_blob" - << ": " << err << std::endl; + << ": " << err.what() << std::endl; } f->close_section(); f->close_section(); From 306eebe05bec1220b2477a66b6501da222bacdfc Mon Sep 17 00:00:00 2001 From: "Adam C. Emerson" Date: Tue, 28 Apr 2020 17:32:56 -0400 Subject: [PATCH 15/25] monc: Asifoact MonClient Of course now everyone has to feed an io_context into the MonClient. Signed-off-by: Adam C. Emerson --- src/ceph_fuse.cc | 8 +- src/ceph_mds.cc | 8 +- src/ceph_osd.cc | 9 +- src/ceph_syn.cc | 9 +- src/client/Client.cc | 21 +- src/common/ceph_context.h | 17 +- src/common/options.cc | 24 ++ src/global/global_init.cc | 16 +- src/global/global_init.h | 6 +- src/libcephfs.cc | 8 +- src/librados/RadosClient.cc | 89 +++-- src/librados/RadosClient.h | 22 +- src/libradosstriper/MultiAioCompletionImpl.cc | 10 - src/libradosstriper/MultiAioCompletionImpl.h | 17 +- src/libradosstriper/RadosStriperImpl.h | 3 + src/mds/MDSDaemon.cc | 14 +- src/mds/MDSDaemon.h | 5 +- src/mds/MDSRank.cc | 11 +- src/mds/MDSRank.h | 9 +- src/mgr/MgrContext.h | 1 + src/mgr/MgrStandby.cc | 13 +- src/mgr/MgrStandby.h | 2 + src/mon/AuthMonitor.cc | 2 +- src/mon/MonClient.cc | 312 +++++++++--------- src/mon/MonClient.h | 261 ++++++++++++--- src/mount/conf.cc | 4 +- src/osd/OSD.cc | 25 +- src/osd/OSD.h | 9 +- src/osdc/Objecter.cc | 57 ++-- src/osdc/Objecter.h | 24 +- src/test/mon/test-mon-msg.cc | 5 +- src/test/mon/test_mon_workloadgen.cc | 6 +- src/test/osd/TestOSDScrub.cc | 11 +- src/tools/cephfs/MDSUtility.cc | 4 +- src/tools/cephfs/MDSUtility.h | 2 + 35 files changed, 674 insertions(+), 370 deletions(-) diff --git a/src/ceph_fuse.cc b/src/ceph_fuse.cc index 9d6141c1eaa4c..ab4db60920c02 100644 --- a/src/ceph_fuse.cc +++ b/src/ceph_fuse.cc @@ -16,7 +16,9 @@ #include #include #include +#include +#include "common/async/context_pool.h" #include "common/config.h" #include "common/errno.h" @@ -45,6 +47,8 @@ #define dout_context g_ceph_context +ceph::async::io_context_pool icp; + static void fuse_usage() { const char* argv[] = { @@ -234,7 +238,8 @@ int main(int argc, const char **argv, const char *envp[]) { int tester_r = 0; void *tester_rp = nullptr; - MonClient *mc = new MonClient(g_ceph_context); + icp.start(cct->_conf.get_val("client_asio_thread_count")); + MonClient *mc = new MonClient(g_ceph_context, icp); int r = mc->build_initial_monmap(); if (r == -EINVAL) { cerr << "failed to generate initial mon list" << std::endl; @@ -316,6 +321,7 @@ int main(int argc, const char **argv, const char *envp[]) { client->unmount(); cfuse->finalize(); out_shutdown: + icp.stop(); client->shutdown(); out_init_failed: unregister_async_signal_handler(SIGHUP, sighup_handler); diff --git a/src/ceph_mds.cc b/src/ceph_mds.cc index 4d960dc2e7f27..82c89ae9d5280 100644 --- a/src/ceph_mds.cc +++ b/src/ceph_mds.cc @@ -20,6 +20,7 @@ #include #include +#include "common/async/context_pool.h" #include "include/ceph_features.h" #include "include/compat.h" #include "include/random.h" @@ -195,7 +196,8 @@ int main(int argc, const char **argv) register_async_signal_handler(SIGHUP, sighup_handler); // get monmap - MonClient mc(g_ceph_context); + ceph::async::io_context_pool ctxpool(2); + MonClient mc(g_ceph_context, ctxpool); if (mc.build_initial_monmap() < 0) forker.exit(1); global_init_chdir(g_ceph_context); @@ -203,7 +205,7 @@ int main(int argc, const char **argv) msgr->start(); // start mds - mds = new MDSDaemon(g_conf()->name.get_id().c_str(), msgr, &mc); + mds = new MDSDaemon(g_conf()->name.get_id().c_str(), msgr, &mc, ctxpool); // in case we have to respawn... mds->orig_argc = argc; @@ -234,6 +236,7 @@ int main(int argc, const char **argv) shutdown_async_signal_handler(); shutdown: + ctxpool.stop(); // yuck: grab the mds lock, so we can be sure that whoever in *mds // called shutdown finishes what they were doing. mds->mds_lock.lock(); @@ -257,4 +260,3 @@ int main(int argc, const char **argv) return 0; } - diff --git a/src/ceph_osd.cc b/src/ceph_osd.cc index 9fde44000288f..63ae56643fa6e 100644 --- a/src/ceph_osd.cc +++ b/src/ceph_osd.cc @@ -670,7 +670,10 @@ int main(int argc, const char **argv) srand(time(NULL) + getpid()); - MonClient mc(g_ceph_context); + ceph::async::io_context_pool poolctx( + cct->_conf.get_val("osd_asio_thread_count")); + + MonClient mc(g_ceph_context, poolctx); if (mc.build_initial_monmap() < 0) return -1; global_init_chdir(g_ceph_context); @@ -691,7 +694,8 @@ int main(int argc, const char **argv) ms_objecter, &mc, data_path, - journal_path); + journal_path, + poolctx); int err = osdptr->pre_init(); if (err < 0) { @@ -746,6 +750,7 @@ int main(int argc, const char **argv) shutdown_async_signal_handler(); // done + poolctx.stop(); delete osdptr; delete ms_public; delete ms_hb_front_client; diff --git a/src/ceph_syn.cc b/src/ceph_syn.cc index 50e26f2815a00..e3ca132870206 100644 --- a/src/ceph_syn.cc +++ b/src/ceph_syn.cc @@ -18,6 +18,7 @@ #include "common/config.h" +#include "common/async/context_pool.h" #include "client/SyntheticClient.h" #include "client/Client.h" @@ -50,7 +51,8 @@ int main(int argc, const char **argv, char *envp[]) pick_addresses(g_ceph_context, CEPH_PICK_ADDRESS_PUBLIC); // get monmap - MonClient mc(g_ceph_context); + ceph::async::io_context_pool poolctx(1); + MonClient mc(g_ceph_context, poolctx); if (mc.build_initial_monmap() < 0) return -1; @@ -64,7 +66,7 @@ int main(int argc, const char **argv, char *envp[]) messengers[i] = Messenger::create_client_messenger(g_ceph_context, "synclient"); messengers[i]->bind(g_conf()->public_addr); - mclients[i] = new MonClient(g_ceph_context); + mclients[i] = new MonClient(g_ceph_context, poolctx); mclients[i]->build_initial_monmap(); auto client = new StandaloneClient(messengers[i], mclients[i]); client->set_filer_flags(syn_filer_flags); @@ -79,6 +81,8 @@ int main(int argc, const char **argv, char *envp[]) ++p) (*p)->start_thread(); + poolctx.stop(); + //cout << "waiting for client(s) to finish" << std::endl; while (!clients.empty()) { Client *client = clients.front(); @@ -99,4 +103,3 @@ int main(int argc, const char **argv, char *envp[]) } return 0; } - diff --git a/src/client/Client.cc b/src/client/Client.cc index 0a2a4da425557..8bd78b4046bed 100644 --- a/src/client/Client.cc +++ b/src/client/Client.cc @@ -44,6 +44,7 @@ #include "common/config.h" #include "common/version.h" +#include "common/async/blocked_completion.h" #include "mon/MonClient.h" @@ -124,6 +125,9 @@ #define DEBUG_GETATTR_CAPS (CEPH_CAP_XATTR_SHARED) +namespace bs = boost::system; +namespace ca = ceph::async; + void client_flush_set_callback(void *p, ObjectCacher::ObjectSet *oset) { Client *client = static_cast(p); @@ -5703,22 +5707,21 @@ int Client::authenticate() int Client::fetch_fsmap(bool user) { - int r; // Retrieve FSMap to enable looking up daemon addresses. We need FSMap // rather than MDSMap because no one MDSMap contains all the daemons, and // a `tell` can address any daemon. version_t fsmap_latest; + bs::error_code ec; do { - C_SaferCond cond; - monclient->get_version("fsmap", &fsmap_latest, NULL, &cond); client_lock.unlock(); - r = cond.wait(); + std::tie(fsmap_latest, std::ignore) = + monclient->get_version("fsmap", ca::use_blocked[ec]); client_lock.lock(); - } while (r == -EAGAIN); + } while (ec == bs::errc::resource_unavailable_try_again); - if (r < 0) { - lderr(cct) << "Failed to learn FSMap version: " << cpp_strerror(r) << dendl; - return r; + if (ec) { + lderr(cct) << "Failed to learn FSMap version: " << ec << dendl; + return ceph::from_error_code(ec); } ldout(cct, 10) << __func__ << " learned FSMap version " << fsmap_latest << dendl; @@ -14669,7 +14672,7 @@ mds_rank_t Client::_get_random_up_mds() const StandaloneClient::StandaloneClient(Messenger *m, MonClient *mc) - : Client(m, mc, new Objecter(m->cct, m, mc, NULL, 0, 0)) + : Client(m, mc, new Objecter(m->cct, m, mc, nullptr, 0, 0)) { monclient->set_messenger(m); objecter->set_client_incarnation(0); diff --git a/src/common/ceph_context.h b/src/common/ceph_context.h index adacecebfc399..187cd3d165a7a 100644 --- a/src/common/ceph_context.h +++ b/src/common/ceph_context.h @@ -25,8 +25,10 @@ #include #include -#include "include/common_fwd.h" +#include + #include "include/any.h" +#include "include/common_fwd.h" #include "common/cmdparse.h" #include "common/code_environment.h" @@ -375,4 +377,17 @@ class CephContext { #endif #endif // WITH_SEASTAR +#if !(defined(WITH_SEASTAR) && !defined(WITH_ALIEN)) && defined(__cplusplus) +namespace ceph::common { +inline void intrusive_ptr_add_ref(CephContext* cct) +{ + cct->get(); +} + +inline void intrusive_ptr_release(CephContext* cct) +{ + cct->put(); +} +} +#endif // !(defined(WITH_SEASTAR) && !defined(WITH_ALIEN)) && defined(__cplusplus) #endif diff --git a/src/common/options.cc b/src/common/options.cc index 9d7c97af532e6..53cc9df101c04 100644 --- a/src/common/options.cc +++ b/src/common/options.cc @@ -5357,6 +5357,18 @@ std::vector