This commit changes the behavior of `exception_container::accept`. Now, instead of throwing an `utils::bad_exception_container_access` exception when the container is empty, the provided visitor is invoked with that exception instead. There are two reasons for this change: - The exception_container is supposed to allow handling exceptions without using the costly C++'s exception runtime. Although an empty container is an edge case, I think it the new behavior is more aligned with the class' purpose. The old behavior can be simulated by providing a visitor which throws when called with bad access exception. - The new behavior fixes a bug in `result_try`/`result_futurize_try`. Before the change, if the `try` block returned a failed result with an empty exception container, a bad access exception would either be thrown or returned as an exceptional future without being handled by the `catch` clauses. Although nobody is supposed to return such result<>s on purpose, a moved out result can be returned by accident and it's important for the exception handling logic to be correct in such a situation. Tests: unit(dev) Closes #10086
98 lines
2.9 KiB
C++
98 lines
2.9 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_EQUAL(foo_bar_what(empty), sstring("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<>();
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_exception_container_empty_accept) {
|
|
auto empty = foo_bar_container();
|
|
|
|
struct visitor {
|
|
sstring operator()(const foo_exception&) {
|
|
return "had foo exception";
|
|
}
|
|
sstring operator()(const bar_exception&) {
|
|
return "had bar exception";
|
|
}
|
|
sstring operator()(const utils::bad_exception_container_access&) {
|
|
return "was empty";
|
|
}
|
|
};
|
|
|
|
BOOST_REQUIRE_EQUAL(empty.accept(visitor{}), "was empty");
|
|
|
|
return make_ready_future<>();
|
|
}
|