Reputation: 24174
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
Reputation: 51941
Pure speculation as to why a strand.wrap
equivalent for strand.post
does not exists:
strand.wrap()
equivalent in a feature request.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_handler
s 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