简体   繁体   中英

When is the earliest an async_write completion handler will complete?

Consider a Connection class in a boost::asio TCP server program that looks something like this.

#ifndef CONNECTION_HPP
#define CONNECTION_HPP

#include <iostream>
#include <boost/asio.hpp>

namespace Transmission
{
    class Connection
    {
    public:
        using SocketType = boost::asio::ip::tcp::socket;
        explicit Connection(boost::asio::io_service& io_service)
        : m_socket{io_service},
            m_outputBuffer{},
            m_writeBuffer{},
            m_outputStream{&m_outputBuffer},
            m_writeStream{&m_writeBuffer},
            m_outputStreamPointer{&m_outputStream},
            m_writeStreamPointer{&m_writeStream},
            m_outputBufferPointer{&m_outputBuffer},
            m_writeBufferPointer{&m_writeBuffer},
            m_awaitingWrite{false},
            m_pendingWrites{false}
        {

        }

        template<typename T>
        void write(const T& output)
        {
            *m_outputStreamPointer << output;
            writeToSocket();
        }

        template<typename T>
        std::ostream& operator<<(const T& output)
        {
            write(output);
            m_pendingWrites = true;
            return *m_outputStreamPointer;
        }

        std::ostream& getOutputStream()
        {
            writeToSocket();
            m_pendingWrites = true;
            return *m_outputStreamPointer;
        }

        void start()
        {
            write("Connection started");
        }

        SocketType& socket() { return m_socket; }

    private:
        void writeToSocket();

        SocketType m_socket;

        boost::asio::streambuf m_outputBuffer;
        boost::asio::streambuf m_writeBuffer;

        std::ostream m_outputStream;
        std::ostream m_writeStream;

        std::ostream* m_outputStreamPointer;
        std::ostream* m_writeStreamPointer;

        boost::asio::streambuf* m_outputBufferPointer;
        boost::asio::streambuf* m_writeBufferPointer;

        bool m_awaitingWrite;
        bool m_pendingWrites;
    };
}

#endif

Where writeToSocket is defined as follows:

#include "Connection.hpp"

using namespace Transmission;

void Connection::writeToSocket()
{
    // If a write is currently happening...
    if(m_awaitingWrite)
    {
        // Alert the async_write's completion handler
        // that writeToSocket was called while async_write was busy
        // and that there is more to be written to the socket.
        m_pendingWrites = true;
        return;
    }

    // Otherwise, notify subsequent calls to this function that we're writing
    m_awaitingWrite = true;

    // Swap the buffers and stream pointers, so that subsequent writeToSockets
    // go into the clear/old/unused buffer
    std::swap(m_outputBufferPointer, m_writeBufferPointer);
    std::swap(m_outputStreamPointer, m_writeStreamPointer);


    // Kick off your async write, sending the front buffer.
    async_write(m_socket, *m_writeBufferPointer, [this](boost::system::error_code error, std::size_t){

        // The write has completed
        m_awaitingWrite = false;

        // If there was an error, report it.
        if(error)
        {
            std::cout << "Async write returned an error." << std::endl;
        }
        else if(m_pendingWrites) // If there are more pending writes
        {
            // Write them
            writeToSocket();
            m_pendingWrites = false;
        }
    });
}

Incase it's not immediately obvious, the connection uses a double-buffering system to ensure that no buffer is both being async_writen and mutated at the same time.

The piece of code I have a question regarding is:

   std::ostream& getOutputStream()
   {
       writeToSocket(); // Kicks off an async_write, returns immediately.
       m_pendingWrites = true; // Tell async_write complete handler there's more to write
       return *m_outputStreamPointer; // Return the output stream for the user to insert to.
   }

such that a connection can be used like: myConnection.getOutputStream() << "Hello";

Specifically, this code relies on an assumption that async_write s completion handler will not be executed until after we return *m_outputStreamPointer . But can we safely make that assumption?

If, for instance, the async_write completion handler completes like the following, nothing would be sent to the user:

   std::ostream& getOutputStream()
   {
       writeToSocket(); // Kicks off an async_write, returns immediately.
       // Async_write's completion handler executes.
       m_pendingWrites = true; // Tell async_write complete handler there's more to write
       // Completion handler already executed so m_pendingWrites = true does nothing.
       return *m_outputStreamPointer; // Return the output stream for the user to insert to.
   }

After looking at the documentation , I found this:

Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a manner equivalent to using boost::asio::io_service::post().

Which likely accounts for the correct behavior, but I'm not sure exactly why. Did a quick search on boost::asio::io_service::post() but that didn't add much clarity.

Thank you, ~

The documentation bit you quote merely says that the handler will not be invoked before return on the current thread , so in a single-threaded world you have your guarantee.

However if you have multiple threads running io tasks ( io_context::run and friends, or implicitly using thread_pool ), there is still the same race.

You can counter-act this posting all async tasks related to a connection on a strand ( Strands: Use Threads Without Explicit Locking , which is an executor that serializes all tasks posted on it (see ordering guarantees in the docs).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM