forked from scylladb/scylladb
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathexception_container.hh
157 lines (133 loc) · 5.29 KB
/
exception_container.hh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
* Copyright 2022-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#pragma once
#include <exception>
#include <type_traits>
#include <memory>
#include <variant>
#include <seastar/core/future.hh>
#include "utils/variant_element.hh"
namespace utils {
class bad_exception_container_access : public std::exception {
public:
const char* what() const noexcept override {
return "bad exception container access";
}
};
// A variant-like type capable of holding one of the allowed exception types.
// This allows inspecting the exception in the error handling code without
// having to resort to costly rethrowing of std::exception_ptr, as is
// in the case of the usual exception handling.
//
// It's not as ergonomic as using exceptions with seastar, but allows for
// fast inspection and manipulation.
//
// The exception is held behind a std::shared_ptr. In order to minimize use
// of atomic operations, the copy constructor is deleted and copying is only
// possible by using the `clone()` method.
//
// This means that the moved-out exception container becomes "empty" and
// does not contain a valid exception.
template<typename... Exs>
struct exception_container {
private:
using exception_variant = std::variant<Exs...>;
// TODO: Idea for a possible improvement: get rid of the variant
// and just store a pointer to an error allocated on the heap.
// Keep an integer which identifies the variant.
// Bonus points: if each error type has a unique, globally-assigned
// identified integer, then conversion of the exception_container
// to a container supporting a superset of errors becomes very cheap.
std::shared_ptr<exception_variant> _eptr;
// Users should use `clone()` in order to copy the exception container.
// The copy constructor is made private in order to make copying explicit.
exception_container(const exception_container&) = default;
void check_nonempty() const {
if (empty()) {
throw bad_exception_container_access();
}
}
public:
// Constructs an exception_container which does not contain any exception.
exception_container() = default;
exception_container(exception_container&&) = default;
exception_container& operator=(exception_container&&) = default;
exception_container& operator=(const exception_container&) = delete; // Must be explicitly copied with `clone()`
template<typename Ex>
requires VariantElement<Ex, exception_variant>
exception_container(Ex&& ex)
: _eptr(std::make_shared<exception_variant>(std::forward<Ex>(ex)))
{ }
inline bool empty() const {
return __builtin_expect(!_eptr, false);
}
inline operator bool() const {
return !empty();
}
// Accepts a visitor.
// If the container is empty, the visitor is called with
// a bad_exception_container_access.
auto accept(auto f) const {
if (empty()) {
return f(bad_exception_container_access());
}
return std::visit(std::move(f), *_eptr);
}
// Explicitly clones the exception container.
exception_container clone() const noexcept {
return exception_container(*this);
}
// Throws currently held exception as a C++ exception.
// If the container is empty, it throws bad_exception_container_access.
[[noreturn]] void throw_me() const {
check_nonempty();
std::visit([] (const auto& ex) { throw ex; }, *_eptr);
std::terminate(); // Should be unreachable
}
// Creates an exceptional future from this error.
// The exception is copied into the new exceptional future.
// If the container is empty, returns an exceptional future
// with the bad_exception_container_access exception.
template<typename T = void>
seastar::future<T> as_exception_future() const & {
if (!_eptr) {
return seastar::make_exception_future<T>(bad_exception_container_access());
}
return std::visit([] (const auto& ex) {
return seastar::make_exception_future<T>(ex);
}, *_eptr);
}
// Transforms this exception future into an exceptional future.
// The exception is moved out and the container becomes empty.
// If the container was empty, returns an exceptional future
// with the bad_exception_container_access exception.
template<typename T = void>
seastar::future<T> into_exception_future() && {
if (!_eptr) {
return seastar::make_exception_future<T>(bad_exception_container_access());
}
auto f = std::visit([] (auto&& ex) {
return seastar::make_exception_future<T>(std::move(ex));
}, *_eptr);
_eptr.reset();
return f;
}
};
template<typename T>
struct is_exception_container : std::false_type {};
template<typename... Exs>
struct is_exception_container<exception_container<Exs...>> : std::true_type {};
template<typename T>
concept ExceptionContainer = is_exception_container<T>::value;
}
template <typename... Exs> struct fmt::formatter<utils::exception_container<Exs...>> : fmt::formatter<string_view> {
auto format(const auto& ec, fmt::format_context& ctx) const {
auto out = ctx.out();
ec.accept([&out] (const auto& ex) { out = fmt::format_to(out, "{}", ex); });
return out;
}
};