Sign in to follow this  

[SOLVED] Boost::Asio async_read hangs

This topic is 2813 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I'm using boost::asio to asynchronously read data from clients. There is a problem though - when I suddenly close one of the clients, the async_read in the server code hangs. Closing the socket and stopping the service from another thread didn't help - it still couldn't join the threads. So, I googled to find a way to set a timeout for the async_read function. Well, I found this: http://bicosyes.com/boostasio-synchronous-read-with-timeout The only problem is that I can't understand it. Well, io_service::run() blocks for sure, but I guess that when async_read is waiting for another packet, even io_service::run_one() would block? I'm simply using a variable m_mode to track the results of reading / timeout. In amc::net::SUBCLIENT::data_read, m_mode is set to 1. In amc::net::SUBCLIENT::close_check, m_mode is set to 2. Now the problem is that at first I get a bunch of "% 1"-s in the stdout, but then suddenly async_read hangs and the timer is never fired.
    // Mark us in reading mode
    m_mode = 0;

    boost::asio::deadline_timer timer( m_socket.io_service() );

    // Add a timeout
    timer.expires_from_now( boost::posix_time::seconds( 5 ) );
    timer.async_wait( boost::bind( &amc::net::SUBCLIENT::close_check, this ) );

    // Read a byte
    boost::asio::async_read( m_socket,                                          // Connection socket
        boost::asio::buffer( m_recv_buffer, 1 ),                                // Data buffer to be read full
        boost::bind( &amc::net::SUBCLIENT::data_read,                           // Handler that is thrown when it's full
            this,
            boost::asio::placeholders::error
        )
    );

    m_socket.io_service().reset();
    while (m_socket.io_service().run_one()) {
        std::cout << "% " << m_mode << std::endl;
        if (m_mode == 1)
            timer.cancel();
        else if (m_mode == 2)
            m_socket.cancel();
    }
Obviously I'm either doing something wrong or there's another way to do it. All suggestions are welcome, Thank You [Edited by - IndrekSnt on April 4, 2010 3:36:15 AM]

Share this post


Link to post
Share on other sites
Without seeing close_check and data_read, it's kind-of hard to understand what this is supposed to do. Also, where does m_mode get changed?

What is a "connection socket"? Is it a TCP socket that has already been accepted from some listening socket?
If you break the process in the debugger when it's hung, where is it hung?
Is this the only thread in your program?
Also, you're saying you get "a bunch of % 1" but there's a cancel on that timer as soon as you get the first bit of data -- I don't understand what the intention of that is.

Share this post


Link to post
Share on other sites
Thank You for the response.

These are the data_read and close_check functions where m_mode is changed:


void amc::net::SUBCLIENT::data_read( const boost::system::error_code& r_error ) {
std::map< std::string, amc::DATA_SIGNAL * >::iterator it;
static DATA data;

std::cout << "| Data read signalled.." << std::endl;

if (!r_error) {
// Mark us in post-reading mode
m_mode = 1;

// Copy data from the buffer
data = DATA( m_recv_buffer, 1 );

// Post the chat event to all signals
it = m_sig_chat_map.begin();
while (it != m_sig_chat_map.end()) {
// Post
if (it->second)
it->second->dispatch( data );
// Take the next one
it++;
}

// Read another char
do_read();
} else {
post_log("Error: \"%s\"\n", r_error.message().c_str());

// Kick ourselves
if (m_sig_kick)
m_sig_kick->dispatch( m_client_id );
}
}

void amc::net::SUBCLIENT::close_check() {
m_mode = 2;
}


Yes, under the connection socket, I meant m_socket, which has been accepted from a listening socket beforehand.

Debugging is a bit difficult, as it's not the only thread and I guess it hangs in io_service::run anyway, so I can't tell what kind of task it's running at any moment.

The timer is cancelled when data has been received (m_mode == 1). When no further data is sent, the timer is not cancelled (m_mode = 0) and a timeout should be fired, which in turn should set m_mode = 2 and cancel the socket.

Now what I don't understand very well is the following line before the while loop with service run_one calls:

m_socket.io_service().reset();


At first I left it out, as I thought that io_service().reset() would reset the whole queue. However, adding it didn't seem to change anything. I guess I need to look into it again some time today. Edit: And it's there in the code snippet I found when I googled for the problem.

I think that the problem is that I'm running the timer on the same service thread I'm running the read operation. For example, if the queue of tasks the service is executing, lookes like this:

* start the timer
* read a byte <-- blocking until another byte is available
* check the timer <-- the timer never gets checked and timeout is never fired

But that's the reason why I even wanted to use the timeout timer in the first place - to be able to cancel the "read a byte" task in the service queue.

All suggestions are welcome,
Thank You

[Edited by - IndrekSnt on April 3, 2010 5:44:05 AM]

Share this post


Link to post
Share on other sites
"read a byte" is not blocking when you use async_read(). async_read() transfers the data into the buffer you pass in to start the read.

If you're blocking in run(), it quite likely means that there is no current operation, and it's just sitting there, waiting for something to happen.

Generally, you'll want to pass 0 for size to async_read(), which will read as much as is available and fits into the buffer in each attempt, which is a lot more efficient than transferring a single byte at a time.

Have you tried starting with the async chat server sample that's on the BOOST site? I find that it works great, and has a clearer structure than your posted code. You might want to start with that, and then add the feature you want.

Also, it looks to me as if you're canceling the timeout after the first byte received. Won't this mean that you get a byte, cancel the timer, and then get whatever other data is there, after which you hang, waiting for more?

Share this post


Link to post
Share on other sites
Hmm, I also have boost::asio::io_service::work classes set up for these io_services I'm having problems with - at first they quit their threads already when the program started.

By passing 0 to async_read, do you mean something similar to boost::asio::transfer_at_least( .. )?

Edit:
I ended up using this:

// Read data
boost::asio::async_read( m_socket, // Connection socket
boost::asio::buffer( m_recv_buffer ), // Data buffer to be read full
boost::asio::transfer_at_least( 1 ), // Read at least 1 byte
boost::bind( &amc::net::CLIENT::data_read, // Handler that is thrown when it's full
this,
boost::asio::placeholders::error
)
);



Thanks for the tips..
I'll mess around with the code some more..

Edit:
I think I wouldn't actually need any timeout timers at all - there must be something I'm just doing wrong when async_read() is not supposed to be blocking.

I could reproduce the same behaviour without the work classes.

Perhaps something truly blocking is dispatched from some other service, which causes it to be pushed into the back of the queue.

I guess it's not a very good idea to run a task that tries to stop the service and join the thread on the service that it is meant to stop.

Edit: Edit:
I passed the service of the server down to all client connections and used that for stopping the other service and joining the thread. Code became a lot simpler and it seems to be working well now.

Moral: Thoroughly think through which tasks should be running on which services..

Thank You for help.

[Edited by - IndrekSnt on April 4, 2010 6:22:00 AM]

Share this post


Link to post
Share on other sites

This topic is 2813 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this