Сброс всех асинхронных обработчиков в boost::asio

Я запускаю некоторые тесты, которым требуется асинхронная связь, а базовой структурой является Asio. Иногда обработчик остается в цикле обработки, даже если тест был удален по уважительным причинам. Но затем он вызывается после удаления целей.

Класс Test:

virtual void SetUp()
{
  _client = new Client;
  _server = new Server;

  Service::run();
}

virtual void TearDown()
{
  Service::stop();

  delete _client;
  delete _server;
}

Класс Service:

static void run()
{
  _thread = new asio::thread(boost::bind(&asio::io_service::run, _service));
}

static void stop()
{
  _service->stop();

  _thread->join();
  delete _thread;

  _service->reset();
}

io_service::stop() не блокирует, поэтому в моем случае он становится совершенно бесполезным. Если я удалю объект io_service в конце функции, обработчик не будет вызываться, но мне нужно лучшее решение для принудительного завершения до удаления объектов.

Примечание: фактический цикл обработки выполняется во втором потоке, но он объединен в оболочке io_service::stop(), и вся проблема, похоже, не связана с потоком.

Я использую Asio (не Boost) 1.4.5, но могу подумать об обновлении (чтобы получить операцию io_service::stopped()?).

РЕДАКТИРОВАТЬ: добавьте код оболочки io_service, поскольку он кажется уместным в соответствии с комментариями.


person Warren Seine    schedule 15.04.2011    source источник


Ответы (2)


Мне кажется, вам нужно немного переосмыслить свой дизайн. io_service::stop действительно асинхронный, как вы заметили. Это приводит к тому, что любые вызовы io_service::run() возвращаются как можно скорее. Любые незавершенные обработчики не вызываются с помощью boost::asio::error::operation_aborted до тех пор, пока не запустится деструктор ~io_service(). Для управления временем жизни объекта следует использовать shared_ptr в качестве документация предполагает:

Описанная выше последовательность уничтожения позволяет программам упростить управление своими ресурсами с помощью shared_ptr‹>. Там, где время жизни объекта привязано к времени жизни соединения (или какой-либо другой последовательности асинхронных операций), shared_ptr для объекта будет привязан к обработчикам для всех связанных с ним асинхронных операций. Это работает следующим образом:

  • Когда одно соединение завершается, все связанные с ним асинхронные операции завершаются. Соответствующие объекты обработчика уничтожаются, и все ссылки shared_ptr на объекты уничтожаются.
  • Чтобы закрыть всю программу, вызывается функция io_service stop(), которая завершает все вызовы run() как можно скорее. Определенный выше деструктор io_service уничтожает все обработчики, вызывая уничтожение всех ссылок shared_ptr на все объекты подключения.

В частности, у вас есть состояние гонки

virtual void TearDown()
{
  service->stop();

  // ok, io_service is no longer running
  // what if outstanding handlers use _client or _server ??

  delete _client;
  delete _server;

  // now you have undefined behavior due to a dangling pointer
}
person Sam Miller    schedule 15.04.2011
comment
Я не могу принять запуск следующего теста до завершения всех обработчиков. И я могу что-то упустить, если уничтожу io_service вручную. Я обновил код: _thread->join() должен убедиться, что все обработчики вызываются, поэтому я действительно не понимаю, как я могу получить здесь состояние гонки. - person Warren Seine; 16.04.2011
comment
@Warren, как присоединение к треду предотвращает состояние гонки? Ваш поток вызывает io_service::run, который вернется после io_service::stop. В этот момент обработчики все еще могут быть незавершенными, поэтому я опубликовал документацию по деструктору ~io_service и его предложение управлять временем жизни объекта с помощью shared_ptr. - person Sam Miller; 16.04.2011

Рассматривали ли вы возможность использования boost::shared_ptr для управления временем жизни объекта? В некоторых случаях может быть полезен boost::enable_shared_from_this. Обсуждение SO можно найти здесь.

Другим вариантом может быть присоединение к потоку после вызова остановки, чтобы убедиться, что обработчики были вызваны перед удалением объектов.

person Ralf    schedule 15.04.2011
comment
Это становится довольно специфичным для моей среды, но идея shared_ptr здесь не поможет: объекты все еще будут существовать после завершения теста, вызывая ужасные побочные эффекты для следующих тестов. Единственный объект, которому разрешено выдержать тесты, — это service, потому что он также используется внутри для управления тайм-аутами и прочим. Может быть, это не очень хорошая практика. - person Warren Seine; 15.04.2011
comment
Ваша вторая идея уже реализована, но, похоже, тоже не помогает (может быть, здесь тоже есть что исследовать). - person Warren Seine; 15.04.2011
comment
Хорошо, странно, я бы подумал, что если вы присоединитесь к потоку перед удалением объектов, это сработает, поскольку блоки соединения. Обработчики должны вызываться из потока, выполняющего io_service::run, и поэтому никакие обработчики не могут/не должны вызываться после. - person Ralf; 15.04.2011
comment
Это ожидаемое поведение, в другом месте может быть что-то не так. - person Warren Seine; 15.04.2011