0

I have a c++17 project using the non-boost version of ASIO because i need to connect, read and write to a TCP socket. The application has a read and write thread that run periodically and share a mutex therefore my reading thread has a time slot of 20 milliseconds in which it needs to read as much as it can and exit.

My problem is that i cant figure out how to get ASIO to read and then stop reading gracefully until another read is requested. There is no read with timeout functions and neither could i find any examples of such behaviour.

The closest thing ive found seems to kinda work but not exactly and i have no idea why. My current code is something like this:

ErrorCode Read(uint8_t* buf, unsigned int maxAmountOfBytesToRead, unsigned int& nRead)
{
    std::lock_guard<std::mutex> tcpSocketLock(m_TCPSocketMutex);

    asio::error_code asioError;
    unsigned int amountOfBytesInBuffer = 0;
    m_TCPConnectionSocket.async_read_some(asio::buffer(buf, maxAmountOfBytesToRead), 
        [&](const asio::error_code& errorCode, unsigned int result_n)
        {
            asioError = errorCode;
            amountOfBytesInBuffer = result_n;
        });

    RunIOContextWithTimeOut(std::chrono::milliseconds(20));
    nRead = amountOfBytesInBuffer;
    // finish up and exit.
}

void RunIOContextWithTimeOut(std::chrono::steady_clock::duration timeout)
{
    // Restart the io_context, as it may have been left in the "stopped" state
    // by a previous operation.
    m_TCPioContext.restart();

    // Block until the asynchronous operation has completed, or timed out. If
    // the pending asynchronous operation is a composed operation, the deadline
    // applies to the entire operation, rather than individual operations on
    // the socket.
    m_TCPioContext.run_for(timeout);

    // If the asynchronous operation completed successfully then the io_context
    // would have been stopped due to running out of work. If it was not
    // stopped, then the io_context::run_for call must have timed out.
    if (!m_TCPioContext.stopped())
    {
        m_TCPioContext.stop();

        // Run the io_context again until the operation completes.
        m_TCPioContext.run();
    }
}

But when running this code, i do notice that the data coming in is not exactly correct and that there are chunks of it missing. Adding logs and debugging i see that when the run_for pops out because of a time out, it never finishes the async read callback handler which makes me suspect that when the run_for doesnt finish on its own and is asked to stop, it abandons what ever data is has read and exits.

But i thought that was what the subsequent run() function was used for, to make the thread go back in and finish running the read before exiting. But apparently not? I dont understand how to make it just read and when its time to stop, copy over all that has read and stop gracefully. All other examples have you closing sockets and cancelling everything but i want to keep the socket open, the connection established, just to stop reading.

I cant let it read for as long as it wants because there is a write thread waiting for the read to finish so that it can be executed. I also would prefer not to make a solution that uses an additional thread of continues reading because this solution will be scaled up which will cause the usage of an additional 40 threads on a system with limited resources, we want to be as efficient as possible with our CPU resources.

pSquared
  • 65
  • 7
  • Your code is very broken. Why are you calling `RunIOContextWithTimeOut` while holding a mutex? And what's the point of starting an async operation and then waiting for it to finish? You are defeating the entire purpose of having an asynchronous I/O framework. – David Schwartz Oct 25 '22 at 08:12
  • Did I get it right? You have a resource limited system running 40 threads. Each thread has a hard realtime of 20ms. You additionally synchronize an asynchronous read each thread and merge their data. Each of this 40 realtime threads include a realtime write thread. – akira hinoshiro Oct 25 '22 at 08:18
  • https://stackoverflow.com/questions/13126776/asioread-with-timeout – Alan Birtles Oct 25 '22 at 08:26
  • @David the mutex is used for thread safety as there is a reading and writing thread both using the resource and the socket. I start an async operation because the sync operations blocks indefinitely and i would like a timeout on the read, from the examples ive seen they use async and then stop after some time. Would you then kindly suggest a good and reputable synchronise IO framework? – pSquared Oct 25 '22 at 08:27
  • @akira No, i meant that its possible to solve it by introducing another thread that will keep reading continuously and writing to another shared buffer but since i already have a read thread, i want to use that and not need to create another thread. This code is a small snippet of a much larger project which will use this sequence another 40 times. Therefore introducing another thread will actually use another 40 when running in production, we would prefer to avoid this. – pSquared Oct 25 '22 at 08:29
  • @Alan the provided link uses boost deadline timers or something, i do not have boost and am using a non-boost version of ASIO. It also doesnt control for how long the read is run because the run one could have nothing to read and will block until something is read. – pSquared Oct 25 '22 at 08:31
  • @pSquared The mutex should not be held while a thread is *waiting* though! You are waiting for the read to complete. That should not be done while holding the mutex. Similarly, you should not hold the mutex while the write thread is waiting for the write to complete. The point of an async I/O framework is that I/O can take place asynchronously, but you create needless dependencies with the way you hold the mutex when the thread is not doing anything but waiting. – David Schwartz Oct 25 '22 at 08:34
  • https://think-async.com/Asio/asio-1.24.0/doc/asio/reference/deadline_timer.html – Alan Birtles Oct 25 '22 at 08:37
  • @David im not sure if i understand you correctly. My situation is that i have 2 threads, 1 thread wakes up and attempts to read from the socket. If there is data read out, it will then parse it (the data is a byte stream that needs to be interpreted) send it to another part of the application and go back to sleep. The write is the same but with getting data to write from another part of the application. They use a mutex to share the socket and other resources for reading/writing.I think what your saying is that i dont need the mutex because the reading and writing can be done at the same time? – pSquared Oct 25 '22 at 09:00
  • @David But from what i understand, with ASIO, you register a handler for when the operation is finished and call one of the run functions which will consume your thread and use that for running the registered functions. I need that thread back so it can parse what it has read and push the data to another part of the application. Therefore i need another thread ot run indefinitely and push what it reads to my reading thread. But im hoping there is a way to just use the existing thread thread for that job. – pSquared Oct 25 '22 at 09:01
  • @Alan thats interesting, i didnt know non-boost ASIO had this but i guess i should have figured. However when using it and including the header, it doesnt compile or recognize the deadline timer. Looking at the code i see that the definitions of the deadline timer is wrapped in a def `#if defined(ASIO_HAS_BOOST_DATE_TIME) \ || defined(GENERATING_DOCUMENTATION)` So this still seems to only work if you have boost available, Im not sure what the `GENERATING_DOCUMENTATION` macro is, i do not have this defined. – pSquared Oct 25 '22 at 09:16
  • Use https://think-async.com/Asio/asio-1.24.0/doc/asio/reference/steady_timer.html instead – Alan Birtles Oct 25 '22 at 10:12
  • @pSquared You do need the mutex to share resources for reading/writing, but you definitely should not hold the mutex while you are *waiting* for I/O. Sure, when you figure out what I/O you want to do and manipulate shared data, you should hold the mutex that protects that data. But if you care at all about performance, it is suicide to hold the mutex while you wait for data to arrive or wait for space in the send buffer or something like that. Waiting is not a manipulation of shared state. – David Schwartz Oct 25 '22 at 16:06
  • @pSquared You should hold the mutex when you call async_read/async_write and you should acquire the mutex in the code that runs when the operation completes so it can manipulate shared variables like `nRead`. But why are you holding the mutex when you aren't doing anything at all but waiting for I/O to complete or be possible?! Why prevent the write thread from processing at all when the read thread is just waiting for data to arrive on the socket?! Is performance completely unimportant to your application? Do you care at all about efficiency? – David Schwartz Oct 25 '22 at 16:07

0 Answers0