4

I'm running some tests that need asynchronous communication, and the underlying framework is Asio. Sometimes, a handler is kept in the processing loop even if the test has been tore down, for good reasons. But then, it is called after the targets are deleted.

The Test class:

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

  Service::run();
}

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

  delete _client;
  delete _server;
}

The Service class:

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() is non-blocking so it gets quite useless in my case. If I delete the io_service object at the end of the function, the handler won't be called, but I'd like a better solution to force completion before the objects are deleted.

Note: the actual processing loop is done in a second thread, but it is joined in a io_service::stop() wrapper, and the whole problem doesn't seem to be thread-related.

I'm using Asio (non-Boost) 1.4.5, but could consider upgrading (to get the io_service::stopped() operation?).

EDIT: Add the io_service wrapper code as it seems to be relevant according to the comments.

NmdMystery
  • 2,778
  • 3
  • 32
  • 60
Warren Seine
  • 2,311
  • 2
  • 25
  • 38

2 Answers2

5

It appears to me you need to re-think your design slightly. io_service::stop is indeed asynchronous, as you've noticed. It causes any invocations of io_service::run() to return as soon as possible. Any outstanding handlers are not invoked with boost::asio::error::operation_aborted until the ~io_service() destructor runs. To manage object lifetimes, you should use a shared_ptr as the documentation suggests:

The destruction sequence described above permits programs to simplify their resource management by using shared_ptr<>. Where an object's lifetime is tied to the lifetime of a connection (or some other sequence of asynchronous operations), a shared_ptr to the object would be bound into the handlers for all asynchronous operations associated with it. This works as follows:

  • When a single connection ends, all associated asynchronous operations complete. The corresponding handler objects are destroyed, and all shared_ptr references to the objects are destroyed.
  • To shut down the whole program, the io_service function stop() is called to terminate any run() calls as soon as possible. The io_service destructor defined above destroys all handlers, causing all shared_ptr references to all connection objects to be destroyed.

More specifically, you have a race condition

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
}
Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • I cannot accept the next test to start before all the handlers have completed. And I could miss something if I destroy the `io_service` manually. I've updated the code: `_thread->join()` should make sure all the handlers are called, so I really don't see how I could get a race condition here. – Warren Seine Apr 15 '11 at 20:28
  • 1
    @Warren how does joining the thread prevent a race condition? Your thread invokes `io_service::run`, which will return after `io_service::stop`. The handlers can still be outstanding at that point, which is why I posted the `~io_service` destructor documentation and its suggestion to manage object lifetime with `shared_ptr`. – Sam Miller Apr 15 '11 at 20:36
2

Have you considered using boost::shared_ptr to manage the object lifetimes? In some cases boost::enable_shared_from_this can be useful. An SO discussion can be found here.

Another option could be to join the thread after you call stop to make sure that the handlers have been called before deleting the objects.

Community
  • 1
  • 1
Ralf
  • 9,405
  • 2
  • 28
  • 46
  • This is getting quite specific to my environment, but the `shared_ptr` idea won't help here: the objects will still exists after the test is over, causing terrible side effects to the next tests. The only object which is allowed to survive the tests is the `service`, because it is also used internally to manage timeouts and stuff. Maybe that's not a good practice. – Warren Seine Apr 15 '11 at 12:49
  • Your second idea is already implemented, but doesn't seem to help either (maybe there's something to investigate here too). – Warren Seine Apr 15 '11 at 12:53
  • 1
    Ok, weird, I would have thought that if you join the thread before deleting the objects would work since join blocks. Handlers should be called from the thread running io_service::run, and therefore no handlers can/should be called after. – Ralf Apr 15 '11 at 13:04
  • This is the expected behavior, there might be something wrong elsewhere. – Warren Seine Apr 15 '11 at 13:39