mirror of
https://github.com/scylladb/scylladb.git
synced 2026-05-12 19:02:12 +00:00
utils/exceptions: add try_catch
Introduces a utility function which allows obtaining a pointer to the exception data held behind an std::exception_ptr if the data matches the requested type. It can be used to implement manual but concise try..catch chains. The `try_catch` has the best performance when used with libstdc++ as it uses the stdlib specific functions for simulating a try..catch without having to actually throw. For other stdlibs, the implementation falls back to a throw surrounded by an actual try..catch.
This commit is contained in:
@@ -508,6 +508,8 @@ scylla_tests = set([
|
||||
'test/boost/rate_limiter_test',
|
||||
'test/boost/per_partition_rate_limit_test',
|
||||
'test/boost/expr_test',
|
||||
'test/boost/exceptions_optimized_test',
|
||||
'test/boost/exceptions_fallback_test',
|
||||
'test/manual/ec2_snitch_test',
|
||||
'test/manual/enormous_table_scan_test',
|
||||
'test/manual/gce_snitch_test',
|
||||
@@ -1290,6 +1292,8 @@ deps['test/boost/linearizing_input_stream_test'] = [
|
||||
]
|
||||
deps['test/boost/expr_test'] = ['test/boost/expr_test.cc'] + scylla_core
|
||||
deps['test/boost/rate_limiter_test'] = ['test/boost/rate_limiter_test.cc', 'db/rate_limiter.cc']
|
||||
deps['test/boost/exceptions_optimized_test'] = ['test/boost/exceptions_optimized_test.cc', 'utils/exceptions.cc']
|
||||
deps['test/boost/exceptions_fallback_test'] = ['test/boost/exceptions_fallback_test.cc', 'utils/exceptions.cc']
|
||||
|
||||
deps['test/boost/duration_test'] += ['test/lib/exception_utils.cc']
|
||||
deps['test/boost/schema_loader_test'] += ['tools/schema_loader.cc']
|
||||
|
||||
11
test/boost/exceptions_fallback_test.cc
Normal file
11
test/boost/exceptions_fallback_test.cc
Normal file
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* Copyright (C) 2022-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#define NO_OPTIMIZED_EXCEPTION_HANDLING
|
||||
|
||||
#include "exceptions_test.inc.cc"
|
||||
30
test/boost/exceptions_optimized_test.cc
Normal file
30
test/boost/exceptions_optimized_test.cc
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (C) 2022-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
#if defined(NO_OPTIMIZED_EXCEPTION_HANDLING)
|
||||
#undef NO_OPTIMIZED_EXCEPTION_HANDLING
|
||||
#endif
|
||||
|
||||
#include "utils/exceptions.hh"
|
||||
|
||||
#if defined(OPTIMIZED_EXCEPTION_HANDLING_AVAILABLE)
|
||||
|
||||
#include "exceptions_test.inc.cc"
|
||||
|
||||
#else
|
||||
|
||||
#include <boost/test/unit_test_log.hpp>
|
||||
#include <seastar/testing/test_case.hh>
|
||||
|
||||
SEASTAR_TEST_CASE(test_noop) {
|
||||
BOOST_TEST_MESSAGE("Optimized implementation of handling exceptions "
|
||||
"without throwing is not available. Skipping tests in this file.");
|
||||
return seastar::make_ready_future<>();
|
||||
}
|
||||
|
||||
#endif
|
||||
137
test/boost/exceptions_test.inc.cc
Normal file
137
test/boost/exceptions_test.inc.cc
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2022-present ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Common definitions of test cases used in
|
||||
// handle_exception_optimized_test.cc
|
||||
// handle_exception_fallback_test.cc
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <cxxabi.h>
|
||||
#include <boost/test/unit_test_log.hpp>
|
||||
#include <boost/test/tools/old/interface.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "seastarx.hh"
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/testing/test_case.hh>
|
||||
#include <seastar/util/log.hh>
|
||||
|
||||
#include "utils/exceptions.hh"
|
||||
|
||||
class base_exception : public std::exception {};
|
||||
class derived_exception : public base_exception {};
|
||||
|
||||
static void dummy_fn(void*) {
|
||||
//
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
static std::exception_ptr maybe_wrap_eptr(T&& t) {
|
||||
if constexpr (std::is_same_v<T, std::exception_ptr>) {
|
||||
return std::move(t);
|
||||
} else {
|
||||
return std::make_exception_ptr(std::move(t));
|
||||
}
|
||||
}
|
||||
|
||||
static const std::type_info& eptr_typeid(std::exception_ptr eptr) {
|
||||
try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (...) {
|
||||
return *abi::__cxa_current_exception_type();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Capture, typename Throw>
|
||||
static void check_catch(Throw&& ex) {
|
||||
auto eptr = maybe_wrap_eptr(std::move(ex));
|
||||
BOOST_TEST_MESSAGE("Checking if " << seastar::pretty_type_name(eptr_typeid(eptr))
|
||||
<< " is caught as " << seastar::pretty_type_name(typeid(Capture)));
|
||||
|
||||
auto typed_eptr = try_catch<Capture>(eptr);
|
||||
BOOST_CHECK_NE(typed_eptr, nullptr);
|
||||
|
||||
// Verify that it's the same as what the usual throwing gives
|
||||
// TODO: Does this check make sense? Does the standard guarantee
|
||||
// that this will give the same pointer?
|
||||
try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (Capture& t) {
|
||||
BOOST_CHECK_EQUAL(typed_eptr, &t);
|
||||
} catch (...) {
|
||||
// Can happen if the first check fails, just skip
|
||||
assert(typed_eptr == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Capture, typename Throw>
|
||||
static void check_no_catch(Throw&& ex) {
|
||||
auto eptr = maybe_wrap_eptr(std::move(ex));
|
||||
BOOST_TEST_MESSAGE("Checking if " << seastar::pretty_type_name(eptr_typeid(eptr))
|
||||
<< " is NOT caught as " << seastar::pretty_type_name(typeid(Capture)));
|
||||
|
||||
auto typed_eptr = try_catch<Capture>(eptr);
|
||||
BOOST_CHECK_EQUAL(typed_eptr, nullptr);
|
||||
}
|
||||
|
||||
template<typename A, typename B>
|
||||
static std::exception_ptr make_nested_eptr(A&& a, B&& b) {
|
||||
try {
|
||||
throw std::move(b);
|
||||
} catch (...) {
|
||||
try {
|
||||
std::throw_with_nested(std::move(a));
|
||||
} catch (...) {
|
||||
return std::current_exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SEASTAR_TEST_CASE(test_try_catch) {
|
||||
// Some standard examples, throwing exceptions derived from std::exception
|
||||
// and catching them through their base types
|
||||
|
||||
check_catch<derived_exception>(derived_exception());
|
||||
check_catch<base_exception>(derived_exception());
|
||||
check_catch<std::exception>(derived_exception());
|
||||
check_no_catch<std::runtime_error>(derived_exception());
|
||||
|
||||
check_no_catch<derived_exception>(base_exception());
|
||||
check_catch<base_exception>(base_exception());
|
||||
check_catch<std::exception>(base_exception());
|
||||
check_no_catch<std::runtime_error>(base_exception());
|
||||
|
||||
// Catching nested exceptions
|
||||
check_catch<base_exception>(make_nested_eptr(base_exception(), derived_exception()));
|
||||
check_catch<std::nested_exception>(make_nested_eptr(base_exception(), derived_exception()));
|
||||
check_no_catch<derived_exception>(make_nested_eptr(base_exception(), derived_exception()));
|
||||
|
||||
// Check that everything works if we throw some crazy stuff
|
||||
check_catch<int>(int(1));
|
||||
check_no_catch<std::exception>(int(1));
|
||||
|
||||
check_no_catch<int>(nullptr);
|
||||
check_no_catch<std::exception>(nullptr);
|
||||
|
||||
// Catching pointers is not supported, but nothing should break if they are
|
||||
// being thrown
|
||||
derived_exception exc;
|
||||
check_no_catch<int>(&exc);
|
||||
check_no_catch<std::exception>(&exc);
|
||||
|
||||
check_no_catch<int>(&dummy_fn);
|
||||
check_no_catch<std::exception>(&dummy_fn);
|
||||
|
||||
check_no_catch<int>(&std::exception::what);
|
||||
check_no_catch<std::exception>(&std::exception::what);
|
||||
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <system_error>
|
||||
#include <atomic>
|
||||
#include "exceptions.hh"
|
||||
#include "utils/abi/eh_ia64.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
@@ -73,3 +74,25 @@ bool is_timeout_exception(std::exception_ptr e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if defined(OPTIMIZED_EXCEPTION_HANDLING_AVAILABLE)
|
||||
|
||||
#include <typeinfo>
|
||||
#include "utils/abi/eh_ia64.hh"
|
||||
|
||||
void* utils::internal::try_catch_dynamic(std::exception_ptr& eptr, const std::type_info* catch_type) noexcept {
|
||||
// In both libstdc++ and libc++, exception_ptr has just one field
|
||||
// which is a pointer to the exception data
|
||||
void* raw_ptr = reinterpret_cast<void*&>(eptr);
|
||||
const std::type_info* ex_type = utils::abi::get_cxa_exception(raw_ptr)->exceptionType;
|
||||
|
||||
// __do_catch can return true and set raw_ptr to nullptr, but only in the case
|
||||
// when catch_type is a pointer and a nullptr is thrown. try_catch_dynamic
|
||||
// doesn't work with catching pointers.
|
||||
if (catch_type->__do_catch(ex_type, &raw_ptr, 1)) {
|
||||
return raw_ptr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif // __GLIBCXX__
|
||||
|
||||
@@ -8,11 +8,26 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#if defined(__GLIBCXX__) && (defined(__x86_64__) || defined(__aarch64__))
|
||||
#define OPTIMIZED_EXCEPTION_HANDLING_AVAILABLE
|
||||
#endif
|
||||
|
||||
#if !defined(NO_OPTIMIZED_EXCEPTION_HANDLING)
|
||||
#if defined(OPTIMIZED_EXCEPTION_HANDLING_AVAILABLE)
|
||||
#define USE_OPTIMIZED_EXCEPTION_HANDLING
|
||||
#else
|
||||
#warn "Fast implementation of some of the exception handling routines is not available for this platform. Expect poor exception handling performance."
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/core/on_internal_error.hh>
|
||||
|
||||
#include <functional>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
|
||||
namespace seastar { class logger; }
|
||||
|
||||
@@ -60,3 +75,36 @@ inline void maybe_rethrow_exception(std::exception_ptr ex) {
|
||||
std::rethrow_exception(std::move(ex));
|
||||
}
|
||||
}
|
||||
|
||||
namespace utils::internal {
|
||||
|
||||
#if defined(OPTIMIZED_EXCEPTION_HANDLING_AVAILABLE)
|
||||
void* try_catch_dynamic(std::exception_ptr& eptr, const std::type_info* catch_type) noexcept;
|
||||
#endif
|
||||
|
||||
} // utils::internal
|
||||
|
||||
/// If the exception_ptr holds an exception which would match on a `catch (T&)`
|
||||
/// clause, returns a pointer to it. Otherwise, returns `nullptr`.
|
||||
///
|
||||
/// The exception behind the pointer is valid as long as the exception
|
||||
/// behind the exception_ptr is valid.
|
||||
template<typename T>
|
||||
inline T* try_catch(std::exception_ptr& eptr) noexcept {
|
||||
static_assert(!std::is_pointer_v<T>, "catching pointers is not supported");
|
||||
static_assert(!std::is_lvalue_reference_v<T> && !std::is_rvalue_reference_v<T>,
|
||||
"T must not be a reference");
|
||||
|
||||
#if defined(USE_OPTIMIZED_EXCEPTION_HANDLING)
|
||||
void* opt_ptr = utils::internal::try_catch_dynamic(eptr, &typeid(std::remove_const_t<T>));
|
||||
return reinterpret_cast<T*>(opt_ptr);
|
||||
#else
|
||||
try {
|
||||
std::rethrow_exception(eptr);
|
||||
} catch (T& t) {
|
||||
return &t;
|
||||
} catch (...) {
|
||||
}
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user