c++ - When to use `asio_handler_invoke`? -


question

when necessary use asio_handler_invoke achieve cannot done wrapping handler?

a canonical example demonstrates case asio_handler_invoke required ideal.

background

the boost asio docs contain example of how use asio_handler_invoke here, don't think compelling example of why use invocation handler. in example appears make changes following (and remove asio_handler_invoke) , achieve identical result:

template <typename arg1> void operator()(arg1 arg1) {   queue_.add(priority_, std::bind(handler_, arg1)); } 

similarly, in answer relating handler tracking appears unnecessary use asio_handler_invoke, despite tanner sansbury's answer suggesting using invocation hook solution.

this thread on boost user group provides more information - don't understand significance.

from have seen, appears asio_handler_invoke called asio_handler_invoke(h, &h), doesn't appear make sense. in cases arguments not (essentially) copies of same object?

a final point - ever call io_service::run() single thread, may i'm missing obvious comes experience in multi-threaded loop.

in short, wrapping handler , asio_handler_invoke accomplish 2 different tasks:

  • wrap handler customize invocation of handler.
  • define asio_handler_invoke hook customize invocation of other handlers in context of handler.
template <typename handler> struct custom_handler {   void operator()(...); // customize invocation of handler_.   handler handler_; };  // customize invocation of function within context of custom_handler. template <typename function> void asio_handler_invoke(function function, custom_handler* context);  // invoke custom invocation of 'perform' within context of custom_handler. void perform() {...} custom_handler handler; using boost::asio::asio_handler_invoke; asio_handler_invoke(std::bind(&perform), &handler); 

the primary reason asio_handler_invoke hook allow 1 customize invocation strategy of handlers application may not have direct access. instance, consider composed operations composed of 0 or more calls intermediate operations. each intermediate operation, intermediate handler created on behalf of application, application not have direct access these handlers. when using custom handlers, asio_handler_invoke hook provides way customize invocation strategy of these intermediate handlers within given context. documentation states:

when asynchronous operations composed other asynchronous operations, intermediate handlers should invoked using same method final handler. required ensure user-defined objects not accessed in way may violate guarantees. [asio_handler_invoke] hooking function ensures invoked method used final handler accessible @ each intermediate step.


asio_handler_invoke

consider case wish count number of asynchronous operations executed, including each intermediate operation in composed operations. this, need create custom handler type, counting_handler, , count number of times functions invoked within context:

template <typename handler> class counting_handler {   void operator()(...)   {     // invoke handler   }    handler handler_; };  template <typename function> void asio_handler_invoke(function function, counting_handler* context) {   // increment counter   // invoke function }  counting_handler handler(&handle_read); boost::asio::async_read(socket, buffer, handler); 

in above snippet, function handle_read wrapped counting_handler. counting_handler not interested in counting number of times wrapped handler invoked, operator() not increment count , invoke handle_read. however, counting_handler interested in amount of handlers invoked within context in async_read operation, custom invocation strategy in asio_handler_invoke increment count.


example

here concrete example based on above counting_handler type. operation_counter class provides way wrap application handlers counting_handler:

namespace detail {  /// @brief counting_handler handler counts number of ///        times handler invoked within context. template <class handler> class counting_handler { public:   counting_handler(handler handler, std::size_t& count)     : handler_(handler),       count_(count)   {}    template <class... args>   void operator()(args&&... args)   {     handler_(std::forward<args>(args)...);   }    template <typename function>   friend void asio_handler_invoke(     function intermediate_handler,     counting_handler* my_handler)   {     ++my_handler->count_;     // support chaining custom strategies incase wrapped handler     // has custom strategy of own.     using boost::asio::asio_handler_invoke;     asio_handler_invoke(intermediate_handler, &my_handler->handler_);   }  private:   handler handler_;   std::size_t& count_; };  } // namespace detail  /// @brief auxiliary class used wrap handlers count ///        number of functions invoked in context. class operation_counter { public:    template <class handler>   detail::counting_handler<handler> wrap(handler handler)   {     return detail::counting_handler<handler>(handler, count_);   }    std::size_t count() { return count_; }  private:   std::size_t count_ = 0; };  ...  operation_counter counter; boost::asio::async_read(socket, buffer, counter.wrap(&handle_read)); io_service.run(); std::cout << "count of async_read_some operations: " <<              counter.count() << std::endl; 

the async_read() composed operation implemented in 0 or more intermediate stream.async_read_some() operations. each of these intermediate operations, handler unspecified type created , invoked. if above async_read() operation implemented in terms of 2 intermediate async_read_some() operations, counter.count() 2, , handler returned counter.wrap() got invoked once.

on other hand, if 1 not provide asio_handler_invoke hook , instead incremented count within wrapped handler's invocation, count 1, reflecting count of times wrapped handler invoked:

template <class handler> class counting_handler { public:   ...    template <class... args>   void operator()(args&&... args)   {     ++count_;     handler_(std::forward<args>(args)...);   }    // no asio_handler_invoke implemented. }; 

here complete example demonstrating counting number of asynchronous operations executed, including intermediate operations composed operation. example initiates 3 async operations (async_accept, async_connect, , async_read), async_read operation composed of 2 intermediate async_read_some operations:

#include <functional> // std::bind #include <iostream>   // std::cout, std::endl #include <utility>    // std::forward #include <boost/asio.hpp>  // example not interested in handlers, provide noop function // passed bind meet handler concept requirements. void noop() {}  namespace detail {  /// @brief counting_handler handler counts number of ///        times handler invoked within context. template <class handler> class counting_handler { public:   counting_handler(handler handler, std::size_t& count)     : handler_(handler),       count_(count)   {}    template <class... args>   void operator()(args&&... args)   {     handler_(std::forward<args>(args)...);   }    template <typename function>   friend void asio_handler_invoke(     function function,     counting_handler* context)   {     ++context->count_;     // support chaining custom strategies incase wrapped handler     // has custom strategy of own.     using boost::asio::asio_handler_invoke;     asio_handler_invoke(function, &context->handler_);   }  private:   handler handler_;   std::size_t& count_; };  } // namespace detail  /// @brief auxiliary class used wrap handlers count ///        number of functions invoked in context. class operation_counter { public:    template <class handler>   detail::counting_handler<handler> wrap(handler handler)   {     return detail::counting_handler<handler>(handler, count_);   }    std::size_t count() { return count_; }  private:   std::size_t count_ = 0; };  int main() {   using boost::asio::ip::tcp;   operation_counter all_operations;    // create i/o objects.   boost::asio::io_service io_service;   tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));   tcp::socket socket1(io_service);   tcp::socket socket2(io_service);    // connect sockets.   // operation 1: acceptor.async_accept   acceptor.async_accept(socket1, all_operations.wrap(std::bind(&noop)));   // operation 2: socket2.async_connect   socket2.async_connect(acceptor.local_endpoint(),       all_operations.wrap(std::bind(&noop)));   io_service.run();   io_service.reset();    // socket1 , socket2 connected.  scenario below will:   // - write bytes socket1.   // - initiate composed async_read operaiton read more bytes   //   available on socket2.  cause   //   operation  complete multple async_read_some    //   operations on socket2.   // - write more bytes socket1.    // write socket1.   std::string write_buffer = "demo";   boost::asio::write(socket1, boost::asio::buffer(write_buffer));    // guarantee socket2 has received data.   assert(socket2.available() == write_buffer.size());    // initiate composed operation more data   // available.  data available, intermediate async_read_some   // operation (operation 3) executed, , async_read_some    // operation (operation 4) initiated.   std::vector<char> read_buffer(socket2.available() + 1);   operation_counter read_only;   boost::asio::async_read(socket2, boost::asio::buffer(read_buffer),     all_operations.wrap(read_only.wrap(std::bind(&noop))));    // write more socket1.  cause async_read operation   // complete.   boost::asio::write(socket1, boost::asio::buffer(write_buffer));    io_service.run();   std::cout << "total operations: " << all_operations.count() << "\n"                "read operations: " << read_only.count() << std::endl; } 

output:

total operations: 4 read operations: 2 

composed handlers

in above example, async_read() handler composed of handler wrapped twice. first operation_counter counting read operations, , resulting functor wrapped operation_counter counting operations:

boost::asio::async_read(..., all_operations.wrap(read_only.wrap(...))); 

the counting_handler's asio_handler_invoke implementation written support composition invoking function in context of wrapped handler's context. results in appropriate counting occurred each operation_counter:

template <typename function> void asio_handler_invoke(   function function,   counting_handler* context) {   ++context->count_;   // support chaining custom strategies incase wrapped handler   // has custom strategy of own.   using boost::asio::asio_handler_invoke;   asio_handler_invoke(function, &context->handler_); } 

on other hand, if asio_handler_invoke explicitly called function(), outer wrapper's invocation strategy invoked. in case, result in all_operations.count() being 4 , read_only.count() being 0:

template <typename function> void asio_handler_invoke(   function function,   counting_handler* context) {   ++context->count_;   function(); // no chaining. } 

when composing handlers, aware asio_handler_invoke hook gets invoked located through argument-dependent lookup, based on exact handler type. composing handlers types not asio_handler_invoke aware prevent chaining of invocation strategies. instance, using std::bind() or std::function result in default asio_handler_invoke being called, causing custom invocation strategies being invoked:

// operations not counted. boost::asio::async_read(..., std::bind(all_operations.wrap(...)));     

proper chaining invocation strategies composed handlers can important. example, unspecified handler type returned strand.wrap() provides guarantee initial handler wrapped strand , functions invoked in context of returned handler not run concurrently. allows 1 meet thread-safety requirements of many of i/o objects when using composed operations, strand can used synchronize these intermediate operations application not have access.

when running io_service multiple threads, below snippet may invoke undefined behavior, intermediate operations both composed operations may run concurrently, std::bind() not invoke appropriate asio_handler_hook:

boost::asio::async_read(socket, ..., std::bind(strand.wrap(&handle_read))); boost::asio::async_write(socket, ..., std::bind(strand.wrap(&handle_write))); 

Comments

Popular posts from this blog

html - Outlook 2010 Anchor (url/address/link) -

javascript - Why does running this loop 9 times take 100x longer than running it 8 times? -

Getting gateway time-out Rails app with Nginx + Puma running on Digital Ocean -