Sam Miller
Sam Miller

Reputation: 24174

Why no strand::wrap() equivalent for strand::post()?

The behavior of strand::wrap() is defined such that it creates a functor that will execute strand::dispatch() when invoked. I have recently come across a bug in one of our applications that performs the following sequence:

my_great_function(..., s.wrap(a), s.wrap(b));

The application guaranteed that the functor created by s.wrap(a) was invoked before s.wrap(b). However, there is a race condition such that the first functor is invoked outside the strand, and therefore deferred for invocation whereas the second functor is invoked inside the strand and executed immediately. This violated the application's ordering assumption of a before b and resulted in undefined behavior.

Using strand::post() instead of strand::dispatch() is one way to resolve this, but there's no easy way to accomplish this like using strand.wrap(). I could create helper functions to post through the strand, I'm wondering if there's an easier way?

Upvotes: 4

Views: 2951

Answers (1)

Tanner Sansbury
Tanner Sansbury

Reputation: 51941

Pure speculation as to why a strand.wrap equivalent for strand.post does not exists:

  • I could not find where anyone has officially presented the case for needing the strand.wrap() equivalent in a feature request.
  • Based on strand.wrap() most common usage, it is likely implemented in terms of strand.dispatch() to optimize intermediate handlers for composed operations. The user's completion handler can be invoked immediately after the completion condition has been met, rather than having to post the completion handler for deferred invocation.

The easiest solution may be to pass the strand to my_great_function alongside a and b handlers. If my_great_function requires a specific order of handler invocations, then it seems acceptable to have my_great_function guarantee the ordering rather than passing the onus onto the caller, which may be oblivious to the necessary ordering. On the other hand, if my_great_function is fairly generic, with the handlers requiring the specific invocation order amongst themselves, then consider passing the handlers together in a structure that directly or indirectly implies ordering, such as std::tuple.

While neither of these solutions provide a generically reusable solution, it may be the easiest solution. The officially supported solution would be to use a custom handler type, in addition to providing asio_handler_invoke functions available through ADL to account for intermediate and completion handlers for operations. Here is a complete example based primarily off of detail/wrapped_handler.hpp:

#include <iostream>

#include <boost/asio.hpp>

/// @brief Custom handler wrapper type that will post into its dispatcher.
template <typename Dispatcher,
          typename Handler>
class post_handler
{
public:
  typedef void result_type;

  post_handler(Dispatcher dispatcher, Handler handler)
    : dispatcher_(dispatcher),
      handler_(handler)
  {}

  void operator()()
  {
    dispatcher_.post(handler_);
  }

  template <typename Arg1>
  void operator()(Arg1 arg1)
  {
    dispatcher_.post(boost::bind(handler_, arg1));
  }

  template <typename Arg1, typename Arg2>
  void operator()(Arg1 arg1, Arg2 arg2)
  {
    dispatcher_.post(boost::bind(handler_, arg1, arg2));
  }

  Dispatcher dispatcher_;
  Handler handler_;
};

// Custom invocation hooks for post_handler.  These must be declared in 
// post_handler's associated namespace for proper resolution.

template <typename Function, typename Dispatcher, typename Handler>
inline void asio_handler_invoke(Function& function,
    post_handler<Dispatcher, Handler>* this_handler)
{
  this_handler->dispatcher_.post(
      boost::asio::detail::rewrapped_handler<Function, Handler>(
        function, this_handler->handler_));
}

template <typename Function, typename Dispatcher, typename Handler>
inline void asio_handler_invoke(const Function& function,
    post_handler<Dispatcher, Handler>* this_handler)
{
  this_handler->dispatcher_.post(
      boost::asio::detail::rewrapped_handler<Function, Handler>(
        function, this_handler->handler_));
}

/// @brief Factory function used to create handlers that post through the
///        dispatcher.
template <typename Dispatcher, typename Handler>
post_handler<Dispatcher, Handler>
wrap_post(Dispatcher dispatcher, Handler handler)
{
  return post_handler<Dispatcher, Handler>(dispatcher, handler);
}

/// @brief Convenience factory function used to wrap handlers created from
///        strand.wrap.
template <typename Dispatcher, typename Handler>
post_handler<Dispatcher, 
             boost::asio::detail::wrapped_handler<Dispatcher, Handler> >
wrap_post(boost::asio::detail::wrapped_handler<Dispatcher, Handler> handler)
{
  return wrap_post(handler.dispatcher_, handler);
}

boost::asio::io_service io_service;
boost::asio::strand strand(io_service);
boost::asio::deadline_timer timer(io_service);

void a() { std::cout << "a" << std::endl; }
void b() { std::cout << "b" << std::endl; }
void c() { std::cout << "c" << std::endl; }
void d() { std::cout << "d" << std::endl; }
void noop() {}

void my_great_function()
{
  std::cout << "++my_great_function++" << std::endl;
  // Standard dispatch.
  strand.dispatch(&a);

  // Direct wrapping.
  wrap_post(strand, &b)();

  // Convenience wrapping.
  wrap_post(strand.wrap(&c))();

  // ADL hooks.
  timer.async_wait(wrap_post(strand.wrap(boost::bind(&d))));
  timer.cancel();
  std::cout << "--my_great_function--" << std::endl;
}

int main()
{
  // Execute my_great_function not within a strand.  The noop
  // is used to force handler invocation within strand.
  io_service.post(&my_great_function);
  strand.post(&noop);
  io_service.run();
  io_service.reset();

  // Execute my_great_function within a strand.
  std::cout << std::endl;
  io_service.post(strand.wrap(&my_great_function));
  strand.post(&noop);
  io_service.run();
}

Which produces the following output:

++my_great_function++
--my_great_function--
a
b
c
d

++my_great_function++
a
--my_great_function--
b
c
d

A slightly easier solution that depends on implementation details, is to adapt detail::wrapped_handler's Dispatcher type argument. This approach allows for wrapped_handlers with adapted Dispatcher types to be transparently used within the rest of Boost.Asio.

/// @brief Class used to adapter the wrapped_handler's Dispatcher type
///        requirement to post handlers instead of dispatching handlers.
template <typename Dispatcher>
struct post_adapter
{
  post_adapter(Dispatcher& dispatcher)
    : dispatcher_(dispatcher)
  {}

  template <typename Handler>
  void dispatch(const Handler& handler)
  {
    dispatcher_.post(handler);
  }

  Dispatcher dispatcher_;
};

/// @brief Factory function used to create handlers that post through an
///        adapted dispatcher.
template <typename Dispatcher, typename Handler>
boost::asio::detail::wrapped_handler<post_adapter<Dispatcher>, Handler>
wrap_post(Dispatcher& dispatcher, Handler handler)
{
  typedef post_adapter<Dispatcher> adapter_type;
  return boost::asio::detail::wrapped_handler<
    adapter_type, Handler>(adapter_type(dispatcher), handler);
}

/// @brief Convenience factory function used to wrap handlers created from
///        strand.wrap.
template <typename Dispatcher, typename Handler>
boost::asio::detail::wrapped_handler<
  post_adapter<Dispatcher>, 
  boost::asio::detail::wrapped_handler<Dispatcher, Handler> >
wrap_post(boost::asio::detail::wrapped_handler<Dispatcher, Handler> handler)
{
  return wrap_post(handler.dispatcher_, handler);
}

Both of the wrap_post solutions may introduce a level of complexity with regards to the defined order of handler invocations.

Upvotes: 2

Related Questions