SlideShare a Scribd company logo
1
ceph::errorator<>
throw/catch-free, compile time-checked
exceptions for seastar::future<>
Radosław Zarzyński
Seastar Summit 2019
2019.11.04
2
Error handling with seastar::future
● future_state_base is discriminated union between
promised value and std::exception_ptr.
● Signaling:
○ throw Exception() or
○ seastar::make_exception_future<T…>(Exception&& ex) or
○ Its second variant taking std::exception_ptr.
● Inspecting:
○ handle_exception_type<ExceptionTakingFunc>(...) or
○ handle_exception(...) and raw std::exception_ptr.
3
● Throwing on current implementation imposes costs both in the terms of
○ performance (computational resources) and
○ scalability (number of threads throwing the same time).
● The scalability limitations have been only partially tackled down in GCC.
Throwing still imposes locking.
● Seastar workarounds the issue with the exception scalability hack at the price
of assuming no dlopen() after the initialization phase.
● The hack should be disabled in crimson as loading plugins is necessary to
fully mimic the interfaces of current OSD’s interfaces.
What’s wrong with throwing?
4
● Zero-cost exceptions tend to be
slow when it comes to throwing...
● … but without the exception hack
they are also not scalable.
● The make_exception_future
path on GCC is throw-free after
Gleb Natapov's optimization for
std::make_exception_ptr
implementation in libstdc++.
● Programmer is not enforced to
use the optimized path – someone
still can throw.
● Is there better way than review?
Signaling
try
{
- throw __ex;
+#if __cpp_rtti && !_GLIBCXX_HAVE_CDTOR_CALLABI
+ void *__e =
__cxxabiv1::__cxa_allocate_exception(sizeof(_Ex));
+ (void)__cxxabiv1::__cxa_init_primary_exception(__e,
+ const_cast<std::type_info*>(&typeid(__ex)),
+ __exception_ptr::__dest_thunk<_Ex>);
+ new (__e) _Ex(__ex);
+ return exception_ptr(__e);
+#else
+ throw __ex;
+#endif
}
catch(...)
5
● handle_exception_type()
is not suitable for a hot path
due to rethrowing
● handle_exception() doesn’t
solve the problem of
differentiating behavior basing
on exception type; it just
delegates it outside.
● Is there a throw-free approach
to match ExceptionA but not
ExceptionB?
std::tuple<T...> get() const& {
// …
if (_u.st >= state::exception_min)
std::rethrow_exception(_u.ex);
// ...
}
template <typename Func>
future<T...> handle_exception_type(Func&& func) noexcept {
// ...
return then_wrapped([func = std::forward<Func>(func)]
(auto&& fut) mutable -> future<T...>
{
try {
return make_ready_future<T...>(fut.get());
} catch(ex_type& ex) {
return futurize<func_ret>::apply(func, ex);
}
Inspecting
6
● Allows for fast, throw-free type inspection of std::exception_ptr:
*ep.__cxa_exception_type() == typeid(ExceptionA);
● Compiler extension present in both GCC and Clang.
● For other compilers can be mimicked on top of try/catch.
__cxa_exception_type()
class exception_ptr {
const std::type_info* __cxa_exception_type() const;
}
7
● The expression does exact matching while it’s perfectly
fine to:
fut.handle_exception_type([] (ExceptionBase&) {});
● So maybe handle_exception_exact_type()?
Drop-in replacement for handle_exception_type?
8
seastar::future<ceph::bufferptr> CyanStore::get_attr(
CollectionRef ch,
const ghobject_t& oid,
std::string_view name) const;
What are the errors here?
● What will happen if object does not exist?
● How no-object is distinguished from no-key?
9
seastar::future<ceph::bufferptr> CyanStore::get_attr(
// …
{
auto o = c->get_object(oid);
if (!o) {
return seastar::make_exception_future<ceph::bufferptr>(
EnoentException(fmt::format("object does not exist: {}", oid)));
}
if (auto found = o->xattr.find(name); found != o->xattr.end()) {
return seastar::make_ready_future<ceph::bufferptr>(found->second);
} else {
return seastar::make_exception_future<ceph::bufferptr>(
EnodataException(fmt::format("attr does not exist: {}/{}", oid,
name)));
}
}
What are the errors here?
10
Is get_object() anyhow relevant from the perspective of error handling?
What are the errors here?
11
Is get_object() anyhow relevant from the perspective of error handling?
Actually no:
What are the errors here?
Collection::ObjectRef Collection::get_object(ghobject_t oid)
{
auto o = object_hash.find(oid);
if (o == object_hash.end())
return ObjectRef();
return o->second;
}
12
Summary
Not all errors happen exceptionally.
Straight exceptions are not the best tool in such
situation.
13
errorator<> errorates
future<> at compile-time
14
Goals
● Performance – no throw/catch neither on signaling nor
inspecting.
● Type safety – ensure proper error handling. Ignoring
errors is fine till you do it explicitly.
● Improve code readability – make errors part of signature.
● No revolution. Keep changes minimal and separated.
15
● Header-only library separated from Seastar
● Java's checked exceptions for Seastar built on top of:
○ the Gleb's improvement for std::make_exception_ptr(),
○ __cxa_exception_type().
● 700 lines of hard-to-understand C++ metaprogramming
● Class template nesting derivative of seastar::future.
What errorator is?
16
template <class... AllowedErrors>
struct errorator {
// …
template <class... ValuesT>
class future<ValuesT...>
: private seastar::future<ValuesT...> {
// NO ADDITIONAL DATA.
// sizeof(errorated future) == sizeof(seastar::future)
};
};
ceph::errorator<> literally
17
Empty errorator aliases seastar::future.
template <>
class errorator<> {
public:
template <class... ValuesT>
using future = ::seastar::future<ValuesT...>;
};
ceph::errorator<> literally
18
Composing an errorator resembles defining enum.
using get_attr_errorator = ceph::errorator<
ceph::ct_error::enoent,
ceph::ct_error::enodata>;
These errors are aliases to instances of std::error_code
wrapped with unthrowable wrapper to prevent accidental
throwing.
Usage
19
CyanStore::get_attr_errorator::future<ceph::bufferptr>
CyanStore::get_attr(
/* ... */) const
{
// ...
throw ceph::ct_error::enoent; // won’t compile
throw ceph::ct_error::enoent::make(); // won’t compile
return ceph::ct_error::enoent::make();
}
Usage
20
● Is not implicitly convertible into seastar::future.
● Its interface offers safe_then() and basically nothing else.
● safe_then() is like then() apart:
○ can take error handlers,
○ returns potentially differently errorated future depending on the result of error handling.
● Handling an error squeezes it from return errorator's error set but
● signaling new error anywhere inside safe_then() appends it to the set,
The errorated future
21
seastar::future<PGBackend::cached_os_t > // == ceph::errorator<>::future<...>
PGBackend::_load_os(const hobject_t& oid)
return store->get_attr(coll,
ghobject_t{oid, ghobject_t::NO_GEN, shard},
OI_ATTR)
.safe_then(
[] (ceph::bufferptr&& bp) {
// optimistic path
}, ceph::errorator< ceph::ct_error::enoent,
ceph::ct_error::enodata>::all_same_way([oid, this] {
return seastar::make_ready_future<cached_os_t>(
os_cache.insert(oid,
std::make_unique<ObjectState>(object_info_t{oid}, false)));
}));
Usage
22
seastar::future<PGBackend::cached_os_t > // won’t compile
PGBackend::_load_os(const hobject_t& oid)
return store->get_attr(coll,
ghobject_t{oid, ghobject_t::NO_GEN, shard},
OI_ATTR)
.safe_then(
[] (ceph::bufferptr&& bp) {
// optimistic path
}, ceph::ct_error::enoent::handle([oid, this] {
return seastar::make_ready_future <cached_os_t>(
os_cache.insert(oid,
std::make_unique<ObjectState>(object_info_t{oid}, false)));
}));
Usage – chaining
23
ceph::errorator<ceph::ct_error::enodata>::future<PGBackend::cached_os_t >
PGBackend::_load_os(const hobject_t& oid)
return store->get_attr(coll,
ghobject_t{oid, ghobject_t::NO_GEN, shard},
OI_ATTR)
.safe_then(
[] (ceph::bufferptr&& bp) {
// optimistic path
}, ceph::ct_error::enoent::handle([oid, this] {
return seastar::make_ready_future <cached_os_t>(
os_cache.insert(oid,
std::make_unique<ObjectState>(object_info_t{oid}, false)));
}));
Usage – chaining
24
Limitations,
problems,
plans
25
__cxa_exception_data()?
● Only type can be inspected in the throw-free way.
This is fine for stateless objects.
● Currently there is no __cxa_exception_data() to cover
stateful errors.
● It might be feasible to get this feature as compiler’s
extension.
26
● then_wrapped()-imposed temporary future creation,
● get() instead get_available_state() brings some
garbage.
● Converting differently errorated futures imposes move.
Performance
27
● safe_then() is implemented with then_wrapped().
It provides the passed callable with a copy of the future
instead of *this.
● Getting rid of that is an opportunity for generic
optimization.
● Another possibility is to stop using then_wrapped() but
access to private members would be necessary.
More copying because of then_wrapped()
28
No access to get_available_state()
● safe_then() retrieves the value with the less efficient get() which checks
and does blocking for seastar::thread. DCE doesn’t optimize this out.
// if (__builtin_expect(future.failed(), false)) {
// ea25: 48 83 bd c8 fe ff ff cmpq $0x2,-0x138(%rbp)
// ea2c: 02
// ea2d: 0f 87 f0 05 00 00 ja f023 <ceph::osd::
// ...
// /// If get() is called in a ref seastar::thread context,
// /// then it need not be available; instead, the thread will
// /// be paused until the future becomes available.
// [[gnu::always_inline]]
// std::tuple<T...> get() {
// if (!_state.available()) {
// ea3a: 0f 85 1b 05 00 00 jne ef5b <ceph::osd::
// }
29
--- a/include/seastar/core/future.hh
+++ b/include/seastar/core/future.hh
@@ -729,7 +729,7 @@ class future {
promise<T...>* _promise;
future_state<T...> _state;
static constexpr bool copy_noexcept = future_state<T...>::copy_noexcept;
-private:
+protected:
future(promise<T...>* pr) noexcept : _promise(pr),
_state(std::move(pr->_local_state)) {
_promise->_future = this;
_promise->_state = &_state;
protected instead of private?
30
Converting differently errorated futures requires moving.
PGBackend::stat_errorator::future<> PGBackend::stat(
const ObjectState& os,
OSDOp& osd_op)
{
// …
- return seastar::now();
+ return stat_errorator::now();
}
Extra moves
31
● Errorator future is not a future from the perspective of e.g.
seastar::do_for_each().
● Rewriting future-util.hh would be painful and rudimentary
● There is already the concept of seastar::is_future<> trait but not all of
the utilities make use of it.
seastar::is_future<>
template<typename Iterator, typename AsyncAction>
GCC6_CONCEPT( requires requires (Iterator i, AsyncAction aa) {
{ futurize_apply(aa, *i) } -> future<>;
} )
inline
future<> do_for_each(Iterator begin, Iterator end, AsyncAction action) {
32
● Optimizing errorator unveiled unnecessary temporary spawning on hot paths:
○ future::get_available_state(),
○ future::get(),
○ future_state::get().
● There is release candidate of patchset optimizing them out:
https://github.com/ceph/seastar/pull/9
The unnecessary temporaries
33
?
Q&A
The pull request with errorator:
https://github.com/ceph/ceph/pull/30387
34
● https://ceph.io/
● Twitter: @ceph
● Docs: http://docs.ceph.com/
● Mailing lists: http://lists.ceph.io/
○ ceph-announce@ceph.io → announcements
○ ceph-users@ceph.io → user discussion
○ dev@ceph.io → developer discussion
● IRC: irc.oftc.net
○ #ceph, #ceph-devel
● GitHub: https://github.com/ceph/
● YouTube ‘Ceph’ channel
FOR MORE INFORMATION

More Related Content

PDF
Capturing NIC and Kernel TX and RX Timestamps for Packets in Go
PPTX
Seastar metrics
PDF
Seastore: Next Generation Backing Store for Ceph
PDF
Amazon Elastic Fabric Adapter: Anatomy, Capabilities, and the Road Ahead
PDF
IBM Power10.pdf
PDF
Linux Kernel - Virtual File System
PPTX
LLVM Instruction Selection
PDF
Tracing MariaDB server with bpftrace - MariaDB Server Fest 2021
Capturing NIC and Kernel TX and RX Timestamps for Packets in Go
Seastar metrics
Seastore: Next Generation Backing Store for Ceph
Amazon Elastic Fabric Adapter: Anatomy, Capabilities, and the Road Ahead
IBM Power10.pdf
Linux Kernel - Virtual File System
LLVM Instruction Selection
Tracing MariaDB server with bpftrace - MariaDB Server Fest 2021

What's hot (20)

PPTX
Linux Network Stack
PDF
Shared Memory Centric Computing with CXL & OMI
PDF
LISA2019 Linux Systems Performance
PDF
PCI Drivers
PPTX
Static partitioning virtualization on RISC-V
PDF
The Linux Block Layer - Built for Fast Storage
PDF
2021 04-20 apache arrow and its impact on the database industry.pptx
PDF
PromQL Deep Dive - The Prometheus Query Language
PDF
Apache Calcite (a tutorial given at BOSS '21)
PDF
Fun with Network Interfaces
PDF
Photon Technical Deep Dive: How to Think Vectorized
PDF
Opaque Pointers Are Coming
PDF
System Device Tree and Lopper: Concrete Examples - ELC NA 2022
PDF
[234]멀티테넌트 하둡 클러스터 운영 경험기
PPT
PDF
Seastore: Next Generation Backing Store for Ceph
PDF
LLVM Register Allocation (2nd Version)
PDF
Session 8 assertion_based_verification_and_interfaces
PDF
Outrageous Performance: RageDB's Experience with the Seastar Framework
Linux Network Stack
Shared Memory Centric Computing with CXL & OMI
LISA2019 Linux Systems Performance
PCI Drivers
Static partitioning virtualization on RISC-V
The Linux Block Layer - Built for Fast Storage
2021 04-20 apache arrow and its impact on the database industry.pptx
PromQL Deep Dive - The Prometheus Query Language
Apache Calcite (a tutorial given at BOSS '21)
Fun with Network Interfaces
Photon Technical Deep Dive: How to Think Vectorized
Opaque Pointers Are Coming
System Device Tree and Lopper: Concrete Examples - ELC NA 2022
[234]멀티테넌트 하둡 클러스터 운영 경험기
Seastore: Next Generation Backing Store for Ceph
LLVM Register Allocation (2nd Version)
Session 8 assertion_based_verification_and_interfaces
Outrageous Performance: RageDB's Experience with the Seastar Framework
Ad

Similar to ceph::errorator<> throw/catch-free, compile time-checked exceptions for seastar::future<> (20)

PDF
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
PDF
Checking the Cross-Platform Framework Cocos2d-x
PDF
C++20 the small things - Timur Doumler
PPTX
How to Adopt Modern C++17 into Your C++ Code
PPTX
How to Adopt Modern C++17 into Your C++ Code
PDF
Analysis of Haiku Operating System (BeOS Family) by PVS-Studio. Part 2
PDF
ChakraCore: analysis of JavaScript-engine for Microsoft Edge
PDF
C++ exception handling
PDF
Other Approaches (Concurrency)
PDF
Scala to assembly
PDF
An Execution-Semantic and Content-and-Context-Based Code-Clone Detection and ...
PPTX
JavaScript: The Good Parts Or: How A C# Developer Learned To Stop Worrying An...
PDF
Performance measurement and tuning
PDF
C++ amp on linux
PPTX
Adventures in Thread-per-Core Async with Redpanda and Seastar
PDF
CUDA Deep Dive
PPTX
6-Exception Handling and Templates.pptx
PDF
Analysis of Microsoft Code Contracts
PDF
How Does Kubernetes Build OpenAPI Specifications?
C++ CoreHard Autumn 2018. Concurrency and Parallelism in C++17 and C++20/23 -...
Checking the Cross-Platform Framework Cocos2d-x
C++20 the small things - Timur Doumler
How to Adopt Modern C++17 into Your C++ Code
How to Adopt Modern C++17 into Your C++ Code
Analysis of Haiku Operating System (BeOS Family) by PVS-Studio. Part 2
ChakraCore: analysis of JavaScript-engine for Microsoft Edge
C++ exception handling
Other Approaches (Concurrency)
Scala to assembly
An Execution-Semantic and Content-and-Context-Based Code-Clone Detection and ...
JavaScript: The Good Parts Or: How A C# Developer Learned To Stop Worrying An...
Performance measurement and tuning
C++ amp on linux
Adventures in Thread-per-Core Async with Redpanda and Seastar
CUDA Deep Dive
6-Exception Handling and Templates.pptx
Analysis of Microsoft Code Contracts
How Does Kubernetes Build OpenAPI Specifications?
Ad

More from ScyllaDB (20)

PDF
Understanding The True Cost of DynamoDB Webinar
PDF
Database Benchmarking for Performance Masterclass: Session 2 - Data Modeling ...
PDF
Database Benchmarking for Performance Masterclass: Session 1 - Benchmarking F...
PDF
New Ways to Reduce Database Costs with ScyllaDB
PDF
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
PDF
Powering a Billion Dreams: Scaling Meesho’s E-commerce Revolution with Scylla...
PDF
Leading a High-Stakes Database Migration
PDF
Achieving Extreme Scale with ScyllaDB: Tips & Tradeoffs
PDF
Securely Serving Millions of Boot Artifacts a Day by João Pedro Lima & Matt ...
PDF
How Agoda Scaled 50x Throughput with ScyllaDB by Worakarn Isaratham
PDF
How Yieldmo Cut Database Costs and Cloud Dependencies Fast by Todd Coleman
PDF
ScyllaDB: 10 Years and Beyond by Dor Laor
PDF
Reduce Your Cloud Spend with ScyllaDB by Tzach Livyatan
PDF
Migrating 50TB Data From a Home-Grown Database to ScyllaDB, Fast by Terence Liu
PDF
Vector Search with ScyllaDB by Szymon Wasik
PDF
Workload Prioritization: How to Balance Multiple Workloads in a Cluster by Fe...
PDF
Two Leading Approaches to Data Virtualization, and Which Scales Better? by Da...
PDF
Scaling a Beast: Lessons from 400x Growth in a High-Stakes Financial System b...
PDF
Object Storage in ScyllaDB by Ran Regev, ScyllaDB
PDF
Lessons Learned from Building a Serverless Notifications System by Srushith R...
Understanding The True Cost of DynamoDB Webinar
Database Benchmarking for Performance Masterclass: Session 2 - Data Modeling ...
Database Benchmarking for Performance Masterclass: Session 1 - Benchmarking F...
New Ways to Reduce Database Costs with ScyllaDB
Designing Low-Latency Systems with Rust and ScyllaDB: An Architectural Deep Dive
Powering a Billion Dreams: Scaling Meesho’s E-commerce Revolution with Scylla...
Leading a High-Stakes Database Migration
Achieving Extreme Scale with ScyllaDB: Tips & Tradeoffs
Securely Serving Millions of Boot Artifacts a Day by João Pedro Lima & Matt ...
How Agoda Scaled 50x Throughput with ScyllaDB by Worakarn Isaratham
How Yieldmo Cut Database Costs and Cloud Dependencies Fast by Todd Coleman
ScyllaDB: 10 Years and Beyond by Dor Laor
Reduce Your Cloud Spend with ScyllaDB by Tzach Livyatan
Migrating 50TB Data From a Home-Grown Database to ScyllaDB, Fast by Terence Liu
Vector Search with ScyllaDB by Szymon Wasik
Workload Prioritization: How to Balance Multiple Workloads in a Cluster by Fe...
Two Leading Approaches to Data Virtualization, and Which Scales Better? by Da...
Scaling a Beast: Lessons from 400x Growth in a High-Stakes Financial System b...
Object Storage in ScyllaDB by Ran Regev, ScyllaDB
Lessons Learned from Building a Serverless Notifications System by Srushith R...

Recently uploaded (20)

PDF
Approach and Philosophy of On baking technology
PPTX
sap open course for s4hana steps from ECC to s4
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PPTX
Cloud computing and distributed systems.
PPTX
Big Data Technologies - Introduction.pptx
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Empathic Computing: Creating Shared Understanding
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PPTX
Spectroscopy.pptx food analysis technology
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PDF
Electronic commerce courselecture one. Pdf
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
Approach and Philosophy of On baking technology
sap open course for s4hana steps from ECC to s4
Reach Out and Touch Someone: Haptics and Empathic Computing
The Rise and Fall of 3GPP – Time for a Sabbatical?
Cloud computing and distributed systems.
Big Data Technologies - Introduction.pptx
Diabetes mellitus diagnosis method based random forest with bat algorithm
Empathic Computing: Creating Shared Understanding
Mobile App Security Testing_ A Comprehensive Guide.pdf
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Spectroscopy.pptx food analysis technology
Per capita expenditure prediction using model stacking based on satellite ima...
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Electronic commerce courselecture one. Pdf
Chapter 3 Spatial Domain Image Processing.pdf
Understanding_Digital_Forensics_Presentation.pptx
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
MYSQL Presentation for SQL database connectivity
Building Integrated photovoltaic BIPV_UPV.pdf

ceph::errorator<> throw/catch-free, compile time-checked exceptions for seastar::future<>

  • 1. 1 ceph::errorator<> throw/catch-free, compile time-checked exceptions for seastar::future<> Radosław Zarzyński Seastar Summit 2019 2019.11.04
  • 2. 2 Error handling with seastar::future ● future_state_base is discriminated union between promised value and std::exception_ptr. ● Signaling: ○ throw Exception() or ○ seastar::make_exception_future<T…>(Exception&& ex) or ○ Its second variant taking std::exception_ptr. ● Inspecting: ○ handle_exception_type<ExceptionTakingFunc>(...) or ○ handle_exception(...) and raw std::exception_ptr.
  • 3. 3 ● Throwing on current implementation imposes costs both in the terms of ○ performance (computational resources) and ○ scalability (number of threads throwing the same time). ● The scalability limitations have been only partially tackled down in GCC. Throwing still imposes locking. ● Seastar workarounds the issue with the exception scalability hack at the price of assuming no dlopen() after the initialization phase. ● The hack should be disabled in crimson as loading plugins is necessary to fully mimic the interfaces of current OSD’s interfaces. What’s wrong with throwing?
  • 4. 4 ● Zero-cost exceptions tend to be slow when it comes to throwing... ● … but without the exception hack they are also not scalable. ● The make_exception_future path on GCC is throw-free after Gleb Natapov's optimization for std::make_exception_ptr implementation in libstdc++. ● Programmer is not enforced to use the optimized path – someone still can throw. ● Is there better way than review? Signaling try { - throw __ex; +#if __cpp_rtti && !_GLIBCXX_HAVE_CDTOR_CALLABI + void *__e = __cxxabiv1::__cxa_allocate_exception(sizeof(_Ex)); + (void)__cxxabiv1::__cxa_init_primary_exception(__e, + const_cast<std::type_info*>(&typeid(__ex)), + __exception_ptr::__dest_thunk<_Ex>); + new (__e) _Ex(__ex); + return exception_ptr(__e); +#else + throw __ex; +#endif } catch(...)
  • 5. 5 ● handle_exception_type() is not suitable for a hot path due to rethrowing ● handle_exception() doesn’t solve the problem of differentiating behavior basing on exception type; it just delegates it outside. ● Is there a throw-free approach to match ExceptionA but not ExceptionB? std::tuple<T...> get() const& { // … if (_u.st >= state::exception_min) std::rethrow_exception(_u.ex); // ... } template <typename Func> future<T...> handle_exception_type(Func&& func) noexcept { // ... return then_wrapped([func = std::forward<Func>(func)] (auto&& fut) mutable -> future<T...> { try { return make_ready_future<T...>(fut.get()); } catch(ex_type& ex) { return futurize<func_ret>::apply(func, ex); } Inspecting
  • 6. 6 ● Allows for fast, throw-free type inspection of std::exception_ptr: *ep.__cxa_exception_type() == typeid(ExceptionA); ● Compiler extension present in both GCC and Clang. ● For other compilers can be mimicked on top of try/catch. __cxa_exception_type() class exception_ptr { const std::type_info* __cxa_exception_type() const; }
  • 7. 7 ● The expression does exact matching while it’s perfectly fine to: fut.handle_exception_type([] (ExceptionBase&) {}); ● So maybe handle_exception_exact_type()? Drop-in replacement for handle_exception_type?
  • 8. 8 seastar::future<ceph::bufferptr> CyanStore::get_attr( CollectionRef ch, const ghobject_t& oid, std::string_view name) const; What are the errors here? ● What will happen if object does not exist? ● How no-object is distinguished from no-key?
  • 9. 9 seastar::future<ceph::bufferptr> CyanStore::get_attr( // … { auto o = c->get_object(oid); if (!o) { return seastar::make_exception_future<ceph::bufferptr>( EnoentException(fmt::format("object does not exist: {}", oid))); } if (auto found = o->xattr.find(name); found != o->xattr.end()) { return seastar::make_ready_future<ceph::bufferptr>(found->second); } else { return seastar::make_exception_future<ceph::bufferptr>( EnodataException(fmt::format("attr does not exist: {}/{}", oid, name))); } } What are the errors here?
  • 10. 10 Is get_object() anyhow relevant from the perspective of error handling? What are the errors here?
  • 11. 11 Is get_object() anyhow relevant from the perspective of error handling? Actually no: What are the errors here? Collection::ObjectRef Collection::get_object(ghobject_t oid) { auto o = object_hash.find(oid); if (o == object_hash.end()) return ObjectRef(); return o->second; }
  • 12. 12 Summary Not all errors happen exceptionally. Straight exceptions are not the best tool in such situation.
  • 14. 14 Goals ● Performance – no throw/catch neither on signaling nor inspecting. ● Type safety – ensure proper error handling. Ignoring errors is fine till you do it explicitly. ● Improve code readability – make errors part of signature. ● No revolution. Keep changes minimal and separated.
  • 15. 15 ● Header-only library separated from Seastar ● Java's checked exceptions for Seastar built on top of: ○ the Gleb's improvement for std::make_exception_ptr(), ○ __cxa_exception_type(). ● 700 lines of hard-to-understand C++ metaprogramming ● Class template nesting derivative of seastar::future. What errorator is?
  • 16. 16 template <class... AllowedErrors> struct errorator { // … template <class... ValuesT> class future<ValuesT...> : private seastar::future<ValuesT...> { // NO ADDITIONAL DATA. // sizeof(errorated future) == sizeof(seastar::future) }; }; ceph::errorator<> literally
  • 17. 17 Empty errorator aliases seastar::future. template <> class errorator<> { public: template <class... ValuesT> using future = ::seastar::future<ValuesT...>; }; ceph::errorator<> literally
  • 18. 18 Composing an errorator resembles defining enum. using get_attr_errorator = ceph::errorator< ceph::ct_error::enoent, ceph::ct_error::enodata>; These errors are aliases to instances of std::error_code wrapped with unthrowable wrapper to prevent accidental throwing. Usage
  • 19. 19 CyanStore::get_attr_errorator::future<ceph::bufferptr> CyanStore::get_attr( /* ... */) const { // ... throw ceph::ct_error::enoent; // won’t compile throw ceph::ct_error::enoent::make(); // won’t compile return ceph::ct_error::enoent::make(); } Usage
  • 20. 20 ● Is not implicitly convertible into seastar::future. ● Its interface offers safe_then() and basically nothing else. ● safe_then() is like then() apart: ○ can take error handlers, ○ returns potentially differently errorated future depending on the result of error handling. ● Handling an error squeezes it from return errorator's error set but ● signaling new error anywhere inside safe_then() appends it to the set, The errorated future
  • 21. 21 seastar::future<PGBackend::cached_os_t > // == ceph::errorator<>::future<...> PGBackend::_load_os(const hobject_t& oid) return store->get_attr(coll, ghobject_t{oid, ghobject_t::NO_GEN, shard}, OI_ATTR) .safe_then( [] (ceph::bufferptr&& bp) { // optimistic path }, ceph::errorator< ceph::ct_error::enoent, ceph::ct_error::enodata>::all_same_way([oid, this] { return seastar::make_ready_future<cached_os_t>( os_cache.insert(oid, std::make_unique<ObjectState>(object_info_t{oid}, false))); })); Usage
  • 22. 22 seastar::future<PGBackend::cached_os_t > // won’t compile PGBackend::_load_os(const hobject_t& oid) return store->get_attr(coll, ghobject_t{oid, ghobject_t::NO_GEN, shard}, OI_ATTR) .safe_then( [] (ceph::bufferptr&& bp) { // optimistic path }, ceph::ct_error::enoent::handle([oid, this] { return seastar::make_ready_future <cached_os_t>( os_cache.insert(oid, std::make_unique<ObjectState>(object_info_t{oid}, false))); })); Usage – chaining
  • 23. 23 ceph::errorator<ceph::ct_error::enodata>::future<PGBackend::cached_os_t > PGBackend::_load_os(const hobject_t& oid) return store->get_attr(coll, ghobject_t{oid, ghobject_t::NO_GEN, shard}, OI_ATTR) .safe_then( [] (ceph::bufferptr&& bp) { // optimistic path }, ceph::ct_error::enoent::handle([oid, this] { return seastar::make_ready_future <cached_os_t>( os_cache.insert(oid, std::make_unique<ObjectState>(object_info_t{oid}, false))); })); Usage – chaining
  • 25. 25 __cxa_exception_data()? ● Only type can be inspected in the throw-free way. This is fine for stateless objects. ● Currently there is no __cxa_exception_data() to cover stateful errors. ● It might be feasible to get this feature as compiler’s extension.
  • 26. 26 ● then_wrapped()-imposed temporary future creation, ● get() instead get_available_state() brings some garbage. ● Converting differently errorated futures imposes move. Performance
  • 27. 27 ● safe_then() is implemented with then_wrapped(). It provides the passed callable with a copy of the future instead of *this. ● Getting rid of that is an opportunity for generic optimization. ● Another possibility is to stop using then_wrapped() but access to private members would be necessary. More copying because of then_wrapped()
  • 28. 28 No access to get_available_state() ● safe_then() retrieves the value with the less efficient get() which checks and does blocking for seastar::thread. DCE doesn’t optimize this out. // if (__builtin_expect(future.failed(), false)) { // ea25: 48 83 bd c8 fe ff ff cmpq $0x2,-0x138(%rbp) // ea2c: 02 // ea2d: 0f 87 f0 05 00 00 ja f023 <ceph::osd:: // ... // /// If get() is called in a ref seastar::thread context, // /// then it need not be available; instead, the thread will // /// be paused until the future becomes available. // [[gnu::always_inline]] // std::tuple<T...> get() { // if (!_state.available()) { // ea3a: 0f 85 1b 05 00 00 jne ef5b <ceph::osd:: // }
  • 29. 29 --- a/include/seastar/core/future.hh +++ b/include/seastar/core/future.hh @@ -729,7 +729,7 @@ class future { promise<T...>* _promise; future_state<T...> _state; static constexpr bool copy_noexcept = future_state<T...>::copy_noexcept; -private: +protected: future(promise<T...>* pr) noexcept : _promise(pr), _state(std::move(pr->_local_state)) { _promise->_future = this; _promise->_state = &_state; protected instead of private?
  • 30. 30 Converting differently errorated futures requires moving. PGBackend::stat_errorator::future<> PGBackend::stat( const ObjectState& os, OSDOp& osd_op) { // … - return seastar::now(); + return stat_errorator::now(); } Extra moves
  • 31. 31 ● Errorator future is not a future from the perspective of e.g. seastar::do_for_each(). ● Rewriting future-util.hh would be painful and rudimentary ● There is already the concept of seastar::is_future<> trait but not all of the utilities make use of it. seastar::is_future<> template<typename Iterator, typename AsyncAction> GCC6_CONCEPT( requires requires (Iterator i, AsyncAction aa) { { futurize_apply(aa, *i) } -> future<>; } ) inline future<> do_for_each(Iterator begin, Iterator end, AsyncAction action) {
  • 32. 32 ● Optimizing errorator unveiled unnecessary temporary spawning on hot paths: ○ future::get_available_state(), ○ future::get(), ○ future_state::get(). ● There is release candidate of patchset optimizing them out: https://github.com/ceph/seastar/pull/9 The unnecessary temporaries
  • 33. 33 ? Q&A The pull request with errorator: https://github.com/ceph/ceph/pull/30387
  • 34. 34 ● https://ceph.io/ ● Twitter: @ceph ● Docs: http://docs.ceph.com/ ● Mailing lists: http://lists.ceph.io/ ○ ceph-announce@ceph.io → announcements ○ ceph-users@ceph.io → user discussion ○ dev@ceph.io → developer discussion ● IRC: irc.oftc.net ○ #ceph, #ceph-devel ● GitHub: https://github.com/ceph/ ● YouTube ‘Ceph’ channel FOR MORE INFORMATION