Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to use boost::asio::defer() in composing function?

On Boost 1.66, Asio have deprecated the asio_handler_is_continuation hook function, promoting usage of defer function. It seems that defer function is behaving exactly the same as post when asio_handler_is_continuation==true. However, the way of using defer is different from the way of using asio_handler_is_continuation, and I am not sure how to properly use defer.

EDIT: I think the sample below is too verbose to clearly express what i mean. Here's shorter example:

async_read_until(stream, read_buffer, "\r\n", 
    [](boost::system::error_code ec, std::size_t bytes_transferred)
    {
        if(!ec)
            async_write(stream, write_buffer, some_handler);
    })

Now when async_read_until is completed, the lambda handler passed will be invoked using some means equivalent to boost::asio::post. But async_write inside the lambda handler is continuation from last async task, So I want to invoke the lambda handler using defer to take adventage of optimization.

Is there any way to use defer (instead of post) to invoke the lambda handler in above example?

ORIGINAL POST: I am trying to write a simple initating function async_echo similar to the one in beast document, except that the part that calls boost::asio::async_write will be called as a continuation. To achieve this, prior intermediate operation boost::asio::async_read_until must call the handler *this as a continuation.

This is the part that I am referring in the async_echo example of the beast document:

template<class AsyncStream, class Handler>
void echo_op<AsyncStream, Handler>::
operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
{
    // Store a reference to our state. The address of the state won't
    // change, and this solves the problem where dereferencing the
    // data member is undefined after a move.
    auto& p = *p_;

    // Now perform the next step in the state machine
    switch(ec ? 2 : p.step)
    {
        // initial entry
        case 0:
            // read up to the first newline
            p.step = 1;
            return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this));

        case 1:
            // write everything back
            p.step = 2;
            // async_read_until could have read past the newline,
            // use buffers_prefix to make sure we only send one line
            return boost::asio::async_write(p.stream,
                boost::beast::buffers_prefix(bytes_transferred, p.buffer.data()), std::move(*this));

        case 2:
            p.buffer.consume(bytes_transferred);
            break;
    }

    // Invoke the final handler. The implementation of `handler_ptr`
    // will deallocate the storage for the state before the handler
    // is invoked. This is necessary to provide the
    // destroy-before-invocation guarantee on handler memory
    // customizations.
    //
    // If we wanted to pass any arguments to the handler which come
    // from the `state`, they would have to be moved to the stack
    // first or else undefined behavior results.
    //
    p_.invoke(ec);
    return;
}

On pre-1.66 days, I could simply hook the function as follows:

template <Function, Handler>
friend bool asio_handler_is_continuation(echo_op<Function, Handler>* handler)
{
    using boost::asio::asio_handler_is_continuation;
    return handler.p_->step == 1 || 
        asio_handler_is_continuation(std::addressof(handler.p_->handler()));
}

inside the declaration of echo_op.

Starting from Boost 1.66, the code above is not likely to have any effect (without BOOST_ASIO_NO_DEPRECATION macro). So I should be using defer.

But since boost::asio::async_read_until has a guarantee that "Invocation of the handler will be performed in a manner equivalent to using boost::asio::io_context::post().", *this will not be invoked using defer, that is, as a continuation.

Is there any workaround that makes boost::asio::async_read_until invoke the handler using defer? And is there any good examples that utilize defer function?

like image 715
he rambled Avatar asked Jan 29 '23 04:01

he rambled


2 Answers

After playing around a bit, it turns out that asio_handler_is_continuation is not deprecated; and there is no way to replace it with defer currently.

To redirect any post calls to defer, I provided following custom executor:

template<typename UnderlyingExecutor, typename std::enable_if<boost::asio::is_executor<UnderlyingExecutor>::value, int>::type = 0>
class continuation_executor
{
    private:
        UnderlyingExecutor _ex;

    public:

        continuation_executor(UnderlyingExecutor ex)
            :_ex(ex){}

        template<class Function, class Allocator>
        void post(Function f, Allocator a)
        {
            std::cout<<"Redirected to defer()"<<std::endl;
            _ex.defer(BOOST_ASIO_MOVE_CAST(Function)(f),a);
        }

        template<class Function, class Allocator>
        void defer(Function f, Allocator a)
        {
            std::cout<<"defer() called"<<std::endl;
            _ex.defer(BOOST_ASIO_MOVE_CAST(Function)(f),a);
        }

        template<class Function, class Allocator>
        void dispatch(Function f, Allocator a)
        {
            std::cout<<"dispatch() called"<<std::endl;
            _ex.dispatch(BOOST_ASIO_MOVE_CAST(Function)(f),a);
        }

        auto context() -> decltype(_ex.context())
        {
            return _ex.context(); 
        }

        void on_work_started()
        {
            _ex.on_work_started();
        }
        void on_work_finished()
        {
            _ex.on_work_finished();
        }
};

It is really a trivial executor, relying entirely to the underlying executor, with continuation_executor::post that redirects into underlying executor's defer.

But when I pass a handler to async_read_some with something like bind_executor(conti_exec, handler), I get following output:

dispatch() called

So the passed handler does not get scheduled via post(); it is scheduled by some other means. Concretely, built-in async function like asio::async_read_some schedule the internal operation object via scheduler::post_immediate_completion, then io_context::run executes the operation.

Upon completion of the async operation, complete method of the operation object is called to execute the user-provided handler. That complete method, at least on current implementation, uses associated executor's dispatch method to run the handler. There is no place for above hook. So it is completely obsolate; attempt to use defer instead of asio_handler_is_continuation is out of luck.

What I stated on my question, "Starting from Boost 1.66, the code above is not likely to have any effect (without BOOST_ASIO_NO_DEPRECATION macro).", is plain wrong. asio_handler_is_continuation is still in effect, and it is not deprecated as of 1.67.

This is the evidence that asio_handler_is_continuation is still in effect:

  // Start an asynchronous send. The data being sent must be valid for the
  // lifetime of the asynchronous operation.
  template <typename ConstBufferSequence, typename Handler>
  void async_send(base_implementation_type& impl,
      const ConstBufferSequence& buffers,
      socket_base::message_flags flags, Handler& handler)
  {
    bool is_continuation =
      boost_asio_handler_cont_helpers::is_continuation(handler);

    // Allocate and construct an operation to wrap the handler.
    typedef reactive_socket_send_op<ConstBufferSequence, Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      op::ptr::allocate(handler), 0 };
    p.p = new (p.v) op(impl.socket_, impl.state_, buffers, flags, handler);

    BOOST_ASIO_HANDLER_CREATION((reactor_.context(), *p.p, "socket",
          &impl, impl.socket_, "async_send"));

    start_op(impl, reactor::write_op, p.p, is_continuation, true,
        ((impl.state_ & socket_ops::stream_oriented)
          && buffer_sequence_adapter<boost::asio::const_buffer,
            ConstBufferSequence>::all_empty(buffers)));
    p.v = p.p = 0;
  }

Note that it uses boost_asio_handler_cont_helpers to find out if the handler is continuation. boost_asio_handler_cont_helpers internally invokes asio_handler_is_continuation.

async_send is used by async_write_some internally. I did not check every built-in async tasks that asio library provides, But I am pretty sure that other async tasks executes it's handler the same way.

So, if you want built-in async tasks to execute your handler as continuation, you will have to rely on asio_handler_is_continuation. defer is not going to entirely replace it! defer can only be used when you schedule your handler directly from your code.

like image 42
he rambled Avatar answered Jan 30 '23 17:01

he rambled


This has puzzled me in the past also.

Executor::defer and Executor::post both perform the same operation, except for this note:

Note: Although the requirements placed on defer are identical to post, the use of post conveys a preference that the caller does not block the first step of f1's progress, whereas defer conveys a preference that the caller does block the first step of f1. One use of defer is to convey the intention of the caller that f1 is a continuation of the current call context. The executor may use this information to optimize or otherwise adjust the way in which f1 is invoked. —end note

https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/Executor1.html

So it seems that responsibility to chaining continuations has been made an implementation detail of the Executor model.

Which as far as I can tell, means that all you need to do is call the free function defer(executor, handler) and the executor will 'do the right thing'

update:

Found some documentation which shows how to chain handlers through the final executor:

source of documentation: https://github.com/chriskohlhoff/asio-tr2/blob/master/doc/executors.qbk

example: https://github.com/chriskohlhoff/executors/blob/v0.2-branch/src/examples/executor/async_op_2.cpp

see lines 38+ in async_op_2.cpp

like image 57
Richard Hodges Avatar answered Jan 30 '23 19:01

Richard Hodges