0

For an embedded system interface I am implementing a class that has up to 8 reading modes (only two are illustrated by the code below - FREERUN and BLOCKRUN) for reading the serial port. The simplified ReaderTest() function for this reader looks like:

void CSerialBoost::ReaderTest()
{
while (RUNflag)
    switch (RUNstate) {

    case FREERUN:
            port.async_read_some(asio::buffer(TextIn, SZTXT),
                boost::bind(&CSerialBoost::OnReadFree, this, asio::placeholders::error, asio::placeholders::bytes_transferred));

            server.reset();
            server.run(error);      // it calls OnReadBlock() here
            break;

    case BLOCKRUN:
            port.async_read_some(asio::buffer(TextIn, SZTXT),
                boost::bind(&CSerialBoost::OnReadBlock, this, asio::placeholders::error, asio::placeholders::bytes_transferred));

            server.reset();
            server.run(error);       // it calls OnReadFree() here
            break;

    default:    break;
    }

    port.cancel(error); // cancel all IO operations
}

The class CSerialBoost has the following members:

asio::io_service    server;
asio::serial_port   port;
asio::error_code    error;

volatile int    RUNstate;   // reader mode
volatile int    RUNflag;    // start/stop flag

There is an unexpected behavior when I switch from one mode to the other. Assuming there is no incoming data and the code runs in FREERUN, in order to switch to BLOCKRUN, from another thread I do:

RUNstate = BLOCKRUN;
server.stop();      // unblock the event loop

The operation switches to BLOCKRUN, as it should, and when it reaches the server.run(error) line under the BLOCKRUN case it calls the CSerialBoost::OnReadFree() function with the error operation_aborted. The same happens when it switches back to FREERUN - it calls CSerialBoost::OnReadBlock() when it reaches server.run(error) under the FREERUN case.

This is very misleading, since it it always calls the function for the other mode. When I stop/cancel the IO service I expect each case to call its own function (or nothing). Am I expecting too much, or this is normal operation? Am I doing something wrong? Please give a hint on how can I handle this problem. (I'm using boost:asio 1-5-3 under Win XP and Win 7, Visual Studio 2010, and I'm new to boost)

Thank you, MA.

1 Answers1

0

This is the expected behavior.

io_service::stop() and io_service::reset() only control the state of the io_service's event loop; neither affect the lifespan of handlers scheduled for deferred invocation (ready-to-run) or user-defined handler objects.

By the time server.run() has been invoked, an async_read_some operation has been queued into the io_service. When the event loop is explicitly stopped via server.stop(), if the operation's completion handler has not yet been invoked, then either the operation or a completion handler remains queued in io_service. Execution then continues in the while loop, where port.cancel() forces outstanding operations on port to be cancelled, setting their completion handlers to be ready-to-run with an error code of boost::asio::error::operation_aborted. Hence, the next time server.run() is invoked, the ready-to-run handler for the cancelled operation is executed.

Consider either:

  • Running the io_service to completion. This often requires setting state, cancelling outstanding operations, and preventing completion handlers from posting additional work into the io_service. Boost.Asio provides an official timeout example, and a timeout approach with running to the io_service to completion is also shown here.
  • Having the asynchronous call chain implement the state machine. This would prevent the need to stop(), reset(), and re-run() the io_service.
  • Controlling the lifespan of the io_service object, as the destructor for io_service will cause all outstanding handlers to be destroyed One approach can be found in this answer.
Community
  • 1
  • 1
Tanner Sansbury
  • 51,153
  • 9
  • 112
  • 169
  • Thank you Tanner. Your answer clarifies "the problem". I am interested to implement the first solution you mention (there is also a 10 seconds timeout timer in my code, but sometimes server.stop() gets called before the time runs out). Is there an internet resource where I can read more about the io_service inner behavior, and about running it to completion and preventing its operations to post additional work? The asio documentation is very short on this topic. – Manuela Arcuri Nov 02 '13 at 16:01
  • @ManuelaArcuri: I have found the [Boost.Asio](http://www.boost.org/doc/libs/1_54_0/doc/html/boost_asio.html) overview, reference, and examples to be fairly complete. Navigating around it can be tricky, but the information is often there. Do you have any specific questions that are not answered in the links? – Tanner Sansbury Nov 04 '13 at 14:13