Files
scylladb/test/boost/exception_container_test.cc
Piotr Dulikowski 80f6224959 utils: add exception_container
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.
2022-02-04 20:18:00 +01:00

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<>();
}