mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-01 13:45:53 +00:00
Adds `exception_container` - a helper type used to hold exceptions as a value, without involving the std::exception_ptr. The motivation behind this type is that it allows inspecting exception's type and value without having to rethrow that exception and catch it, unlike std::exception_ptr. In our current codebase, some exception handling paths need to rethrow the exception multiple times in order to account it into metrics or encode it as an error response to the CQL client. Some types of exceptions can be thrown very frequently in case of overload (e.g. timeouts) and inspecting those exceptions with rethrows can make the overload even worse. For those kinds of exceptions it is important to handle them as cheaply as possible, and exception_container used with conjunction with boost::outcome::result can help achieve that.
78 lines
2.4 KiB
C++
78 lines
2.4 KiB
C++
/*
|
|
* Copyright (C) 2022-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include <stdexcept>
|
|
#include "utils/exception_container.hh"
|
|
|
|
#include <seastar/testing/test_case.hh>
|
|
#include <seastar/core/sstring.hh>
|
|
|
|
using namespace seastar;
|
|
|
|
class foo_exception : public std::exception {
|
|
public:
|
|
const char* what() const noexcept override {
|
|
return "foo";
|
|
}
|
|
};
|
|
|
|
class bar_exception : public std::exception {
|
|
public:
|
|
const char* what() const noexcept override {
|
|
return "bar";
|
|
}
|
|
};
|
|
|
|
using foo_bar_container = utils::exception_container<foo_exception, bar_exception>;
|
|
|
|
static sstring foo_bar_what(const foo_bar_container& fbc) {
|
|
return fbc.accept([] (const auto& ex) { return ex.what(); });
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_exception_container) {
|
|
auto empty = foo_bar_container();
|
|
auto foo = foo_bar_container(foo_exception());
|
|
auto bar = foo_bar_container(bar_exception());
|
|
|
|
BOOST_REQUIRE(empty.empty());
|
|
BOOST_REQUIRE(!foo.empty());
|
|
BOOST_REQUIRE(!bar.empty());
|
|
|
|
BOOST_REQUIRE(!empty);
|
|
BOOST_REQUIRE(foo);
|
|
BOOST_REQUIRE(bar);
|
|
|
|
BOOST_REQUIRE_THROW(foo_bar_what(empty), utils::bad_exception_container_access);
|
|
BOOST_REQUIRE_EQUAL(foo_bar_what(foo), sstring("foo"));
|
|
BOOST_REQUIRE_EQUAL(foo_bar_what(bar), sstring("bar"));
|
|
|
|
BOOST_REQUIRE_THROW(empty.throw_me(), utils::bad_exception_container_access);
|
|
BOOST_REQUIRE_THROW(foo.throw_me(), foo_exception);
|
|
BOOST_REQUIRE_THROW(bar.throw_me(), bar_exception);
|
|
|
|
// Construct the futures outside BOOST_REQUIRE_THROW
|
|
// otherwise the checks would pass if as_exception_future throwed
|
|
// and we don't want that
|
|
auto f_empty = empty.as_exception_future();
|
|
auto f_foo = foo.as_exception_future();
|
|
auto f_bar = bar.as_exception_future();
|
|
BOOST_REQUIRE_THROW(f_empty.get(), utils::bad_exception_container_access);
|
|
BOOST_REQUIRE_THROW(f_foo.get(), foo_exception);
|
|
BOOST_REQUIRE_THROW(f_bar.get(), bar_exception);
|
|
|
|
// Same reasoning as with as_exception_future
|
|
f_empty = std::move(empty).into_exception_future();
|
|
f_foo = std::move(foo).into_exception_future();
|
|
f_bar = std::move(bar).into_exception_future();
|
|
BOOST_REQUIRE_THROW(f_empty.get(), utils::bad_exception_container_access);
|
|
BOOST_REQUIRE_THROW(f_foo.get(), foo_exception);
|
|
BOOST_REQUIRE_THROW(f_bar.get(), bar_exception);
|
|
|
|
return make_ready_future<>();
|
|
}
|