注意
本文档适用于 Ceph 的开发版本。
错误处理
在 Seastar 中,future 表示一个尚未可用但稍后可能可用的值。future 可以具有以下状态之一:
不可用:值尚不可用,
值,
失败:计算值时抛出了异常。该异常已被捕获并通过
std::exception_ptr存储在future实例中。
在最后一种情况下,可以使用 future::handle_exception() 或 future::handle_exception_type() 来处理异常。Seastar 甚至提供了 future::or_terminate(),以便在 future 失败时终止程序。
但在 Crimson 中,许多错误并没有严重到足以使程序完全失败。例如,如果我们尝试通过对象 ID 查找一个对象,并且该操作可能因为对象不存在或已损坏而失败,我们需要恢复该对象以满足请求,而不是终止进程。
换句话说,这些错误是预期中的。此外,非正常路径的性能也应与正常路径的性能相当。我们还希望有一种方法来确保所有预期错误都得到处理。它应该类似于编译器执行的静态分析,如果在 switch-case 语句中未处理任何枚举值时发出警告。
不幸的是,seastar::future 无法满足这两个要求。
Seastar 要求重新抛出异常以在不同类型的异常之间进行分派。这性能不高,甚至没有可扩展性,因为可能会发生语言运行时中的锁定。
Seastar 不会在返回的
seastar::future类型中编码预期的异常类型。只编码了值的类型。这给程序员带来了巨大的心智负担,因为确保所有预期错误都得到处理需要手动代码审计。
因此,创建了“errorator”。它是对普通 seastar::future 的封装。它解决了性能和可扩展性问题,同时将所有预期错误类型的信息嵌入到 future 的类型中。
using ertr = crimson::errorator<crimson::ct_error::enoent,
crimson::ct_error::einval>;
在上面的示例中,我们定义了一个允许两种错误类型的 errorator
crimson::ct_error::enoent和crimson::ct_error::einval.
这些(以及 crimson::ct_error 命名空间中的其他错误)基本上是 std::error_code 的不可抛出封装,以排除意外抛出并确保以启用编译时检查的方式发出错误信号。
errorator 中最基本的是 seastar::future 的后代,可以用作例如函数的返回类型
static ertr::future<int> foo(int bar) {
if (bar == 42) {
return crimson::ct_error::einval::make();
} else {
return ertr::make_ready_future(bar);
}
}
值得注意的是,返回不属于 errorator 错误集的错误会导致编译时错误
static ertr::future<int> foo(int bar) {
// Oops, input_output_error is not allowed in `ertr`. static_assert() will
// terminate the compilation. This behaviour is absolutely fundamental for
// callers -- to figure out about all possible errors they need to worry
// about is enough to just take a look on the function's signature; reading
// through its implementation is not necessary anymore!
return crimson::ct_error::input_output_error::make();
}
errorator 概念更进一步。它不仅向调用者提供嵌入在函数类型中的所有潜在错误信息;它还在调用者站点确保所有这些错误都得到处理。正如读者可能知道的,seastar::future 中的主要方法是 then()。在 errorated future 上,它是可用的,但仅当 errorator 的错误集为空时(字面意思是:errorator<>::future);否则,调用者必须改用 safe_then()
seastar::future<> baz() {
return foo(42).safe_then(
[] (const int bar) {
std::cout << "the optimistic path! got bar=" << bar << std::endl
return ertr::now();
},
ertr::all_same_way(const std::error_code& err) {
// handling errors removes them from errorator's error set
std::cout << "the error path! got err=" << err << std::endl;
return ertr::now();
}).then([] {
// as all errors have been handled, errorator's error set became
// empty and the future instance returned from `safe_then()` has
// `then()` available!
return seastar::now();
});
}
在上面的示例中,ertr::all_same_way 已用于以相同方式处理所有错误。这不是强制性的——调用者可以分别处理每个错误。此外,它只能为错误的子集提供处理程序。代价是 then() 的可用性
using einval_ertr = crimson::errorator<crimson::ct_error::einval>;
// we can't return seastar::future<> (aka errorator<>::future<>) as handling
// as this level deals only with enoent leaving einval without a handler.
// handling it becomes a responsibility of a caller of `baz()`.
einval_ertr::future<> baz() {
return foo(42).safe_then(
[] (const int bar) {
std::cout << "the optimistic path! got bar=" << bar << std::endl
return ertr::now();
},
// provide a handler only for crimson::ct_error::enoent.
// crimson::ct_error::einval stays unhandled!
crimson::ct_error::enoent::handle([] {
std::cout << "the enoent error path!" << std::endl;
return ertr::now();
}));
// .safe_then() above returned `errorator<crimson::ct_error::einval>::future<>`
// which lacks `then()`.
}
也就是说,处理错误会从 errorated future 的错误集中移除它们。这也适用于相反的方向——在 safe_then() 中返回新错误会将它们附加到错误集中。当然,这个集合必须与 baz() 签名中的错误集兼容
using broader_ertr = crimson::errorator<crimson::ct_error::enoent,
crimson::ct_error::einval,
crimson::ct_error::input_output_error>;
broader_ertr::future<> baz() {
return foo(42).safe_then(
[] (const int bar) {
std::cout << "oops, the optimistic path generates a new error!";
return crimson::ct_error::input_output_error::make();
},
// we have a special handler to delegate the handling up. For convenience,
// the same behaviour is available as single argument-taking variant of
// `safe_then()`.
ertr::pass_further{});
}
可以看出,在 safe_then() 中处理和发出错误信号基本上是对在编译时检查的错误集的操作。
更多详细信息可以在 Seastar Summit 2019 上展示的ceph::errorator<> throw/catch-free, compile time-checked exceptions for seastar::future<> 的幻灯片中找到。