注意

本文档适用于 Ceph 开发版本。

错误处理

在 Seastar 中,一个future表示一个尚未可用但稍后可以变得可用。future可以具有以下状态:

  • 不可用:值尚未可用,

  • 值,

  • 失败:在计算值时抛出了异常。该异常已被捕获并存储在future实例中通过std::exception_ptr.

在最后一种情况下,可以使用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::futurethen()中的主方法。在 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()`.
}

的可用性。safe_then()appends thembaz()’s

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<>演示的幻灯片中找到。

由 Ceph 基金会带给您

Ceph 文档是一个社区资源,由非盈利的 Ceph 基金会资助和托管Ceph Foundation. 如果您想支持这一点和我们的其他工作,请考虑加入现在加入.