повышение контекста: проблемы с распространением исключений - PullRequest
0 голосов
/ 17 мая 2019

Я использую boost::context::execution_context (версия 2), чтобы написать библиотеку C ++ 11, и я хочу распространить исключения из execution_context на вызывающее выполнение.

Я бы хотел обработать исключения внутри лямбды, которые клиент передает моей библиотечной функции; Тем не менее, я столкнулся со странной проблемой, когда исключение неправильно обрабатывается 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)
...

System

> 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

1 Ответ

1 голос
/ 17 мая 2019

Проблема с перемещением экземпляра ctx.Предположим, у вас есть два экземпляра execution_context:

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

. В [1] мы вызываем оператор присваивания перемещения, который «крадет» ресурсы src и помещает их в dest.Если у execution_context есть указатель, этот указатель после перемещения будет 0 в src экземпляре, поэтому этот объект бесполезен и не должен использоваться.Любая операция над ним может вызвать нежелательное поведение.

В короткой версии ваш код выглядит следующим образом:

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 и тело лямбда-выражения, переданное в конструктор исходного кода.Когда вызывается source(), контекст переключается, и foo возобновляется, и 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 локально внутри лямбды.done возвращает ссылку на элемент данных Core, который уничтожается при завершении лямбды.Итак, у вас есть свисающая ссылка.

Исправлено: вы можете просто сохранить ссылку на контекст внутри Core.Тогда оригинальный контекст foo будет безопасным.

...