首页 > 解决方案 > 提升上下文:异常传播问题

问题描述

我正在使用boost::context::execution_context(版本 2)编写一个 C++ 11 库,我想将异常从一个传播execution_context到调用执行。

我想处理客户端提供给我的库函数的 lambda 内的异常;但是,我遇到了一个奇怪的问题,在某些情况下 boost::context 没有正确处理异常。

这可以按预期工作,并且与一些 boost 的测试示例非常相似:

TEST(execution_context, works) {
  // Client callable
  auto &&f = [](boost::context::execution_context<void> &&ctx) {
    throw std::runtime_error("help!");
    return std::move(ctx);
  };

  // Library code
  std::exception_ptr exc{};
  boost::context::execution_context<void> source(
      [&exc, &f](boost::context::execution_context<void> &&ctx) {
        try {
          ctx = f(std::move(ctx));
        } catch (boost::context::detail::forced_unwind const &) {
          throw;
        } catch (...) {
          exc = std::current_exception();
        }
        return std::move(ctx);
      });

  try {
    source = source();
    if (exc) {
      std::rethrow_exception(exc);
    }
  } catch (std::runtime_error const &) {
    std::cout << "Runtime Error Caught" << std::endl;
  }
}

输出:

[ RUN      ] execution_context.works
Runtime Error Caught
[       OK ] execution_context.works (0 ms)

但以下更改不起作用:

我们添加一个包装类execution_context

class Core {
  boost::context::execution_context<void> ctx_;

public:
  explicit Core(boost::context::execution_context<void> &&ctx)
      : ctx_{std::move(ctx)} {}

  auto &&done() { return std::move(ctx_); }
};

现在我们进行与之前相同的测试,但使用定义的类:

TEST(execution_context, fails) {
  // Client callable
  auto &&f = [](Core c) {
    throw std::runtime_error("help!");
    return c.done();
  };

  // Library code
  std::exception_ptr exc{};
  boost::context::execution_context<void> source(
      [&exc, &f](boost::context::execution_context<void> &&ctx) {
        try {
          ctx = f(Core(std::move(ctx)));
        } catch (boost::context::detail::forced_unwind const &) {
          throw;
        } catch (...) {
          exc = std::current_exception();
        }
        return std::move(ctx);
      });

  try {
    source = source();
    if (exc) {
      std::rethrow_exception(exc);
    }
  } catch (std::runtime_error const &) {
    std::cout << "Runtime Error Caught" << std::endl;
  }
}

输出:

[ RUN      ] execution_context.fails
unknown file: Failure
Unknown C++ exception thrown in the test body.
generators.t.tsk: /home/plewis/dpkg/refroot/amd64/opt/include/boost/context/detail/exception.hpp:37: boost::context::detail::forced_unwind::~forced_unwind(): Assertion `caught' failed.
zsh: abort (core dumped)  ./test.t.tsk

我注意到的唯一区别是它execution_context包含在一个类中,这会导致异常处理不当。这是没有意义的。

环境

我正在使用 GTest。

编译器

> g++ --version
g++ (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)
...

系统

> uname -a
Linux myhostname 2.6.32-642.6.2.el6.x86_64 #1 SMP Mon Oct 24 10:22:33 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux

促进

boost version 1.69.0 compiled for amd64

标签: exceptionboostexecutioncontextboost-context

解决方案


问题在于移动ctx实例。假设您有两个实例execution_context

execution_context src, dest;
// src and dest have some states
dest = std::move(src); // [1]

在 [1] 中,我们调用移动赋值运算符,它“窃取”资源src并将它们放入dest. 如果execution_context有一个指针,这个指针在src实例中移动后会为0,所以这个对象是没用的,不应该使用。对其进行的任何操作都可能引发一些不良行为。

简而言之,您的代码如下所示:

void foo ()
{
  auto &&f = [](Core c) {};

  boost::context::execution_context<void> source(
      [&exc, &f](boost::context::execution_context<void> &&ctx) {
        // many lines        
        return std::move(ctx);
      });

    source = source();
}

我们有两个上下文,foo并将 lambda 的主体传递给源构造函数。当source()被调用时,上下文被切换并被foo恢复并被lambda执行。在这个 lambda 上下文中foo被销毁,因为它只是被移动到Core实例中。那么你想如何恢复foo执行呢?你不能。

核心问题:

class Core {
  boost::context::execution_context<void> ctx_;

public:
  explicit Core(boost::context::execution_context<void> &&ctx)
      : ctx_{std::move(ctx)} {} // [2]

  auto &&done() { return std::move(ctx_); }
};

ctx_是非引用数据成员,因此在 [2] 中调用了移动构造函数,它窃取了 ctx 的资源。

done如果它没有抛出异常,下一个问题将是方法:看这个:

  auto &&f = [](Core c)
  {
    // throw std::runtime_error("help!"); COMMENTED
    return c.done();
  };

c在 lambda 内部是本地的。返回对在 lambda 结束时被销毁done的数据成员的引用。Core所以你有悬空的参考。

修复:您可以只将对上下文的引用存储在Core. 那么原始上下文foo将是安全的。


推荐阅读