1

I posted a very similar question before, but it hasn't been fully answered yet.

(I'm trying to read a serial port repeatedly but I have to test what happens when the wire is disconnected. Apparently the first time it will time out as expected, but the second time the port seems to cancel it's own operation immediately and will just hang the thread. That is a problem.

I posted the code below. Some lines are LOG lines that come from our logging system, please ignore those, but the comments under those lines will say if the the log fires or not.

I have no clue why it behaves like this. First it just fires the timer and cancels the port and then I try again and it immediately cancels the port with an boost::error_code = 125

boost::asio::io_context io;
boost::asio::serial_port port;

std::size_t RS485CommunicationLayer::readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) {
    using boost::system::error_code;
    boost::optional<error_code> read_result;
    boost::optional<error_code> timer_result;
    size_t msglen = 0;
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: start"); 
// runs 1st and 2nd
    io.reset();

    boost::asio::system_timer timer(io, timeout);
    boost::asio::async_read_until(port, boost::asio::dynamic_buffer(buffer), delim, [&](error_code ec, size_t n) {
            if(!timer_result) {
                read_result = ec;
                timer.cancel();
                msglen = n;
                if(ec)
                    LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil() :: Error %s", ec.message().c_str());
// runs 2nd time only
            } // else canceled, do nothing
        }
    );
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: read action set");
    timer.async_wait([&](error_code ec) { 
        if (!read_result) {
            timer_result = ec;
            port.cancel();
            if(ec) {
                LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil() :: Timed out with error %s", ec.message().c_str());    
            } else {
                LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil() :: Timed out");
// runs 1st time only " RS485CommunicationLayer::readUntil() :: Error Operation canceled "
            }
            throw exception::read_exception(exception::read_exception::Code::TIME_OUT);
        } // else canceled, do nothing
    });
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: timer set : %d", static_cast<int>(timeout.count()));
    io.run();
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: ran");
// runs 1st time only
    if (read_result.get()) {
        std::stringstream sstream;
        sstream << read_result.get();
        LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: Port threw an error <message: %s>", sstream.str().c_str());
        throw exception::read_exception(exception::read_exception::Code::PORT, sstream.str());
    } 
    return msglen;
}

int main() {
//set up port options (baudrate etc)
for(size_t i = 0; i<10; i++) {
std::vector<char> buffer;
readUntil(buffer, '\', std::chrono::microseconds(1000));
}

}
Typhaon
  • 828
  • 8
  • 27
  • That can be related to how that "rs485" port is made. What hardware, what software. – Öö Tiib Jan 30 '23 at 12:09
  • @ÖöTiib Apparently the problem was that I threw an error from the wrong place. I should've handled it after io.run() – Typhaon Jan 30 '23 at 13:24
  • The exception causes io_service::run to stop. I think by now you should consider just using async IO. It will save you all the artificial trouble with correctly running the `io_context`. – sehe Jan 30 '23 at 13:52

1 Answers1

0

I shouldn't have thrown the error from the callback. This will work:

std::size_t RS485CommunicationLayer::readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) {
    using boost::system::error_code;
    boost::optional<error_code> read_result {};
    boost::optional<error_code> timer_result {};
    size_t msglen = 0;
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: start");
    io.reset();

    boost::asio::system_timer timer(io, timeout);
    boost::asio::async_read_until(port, boost::asio::dynamic_buffer(buffer), delim, [&](error_code ec, size_t n) {
            if(!timer_result) {
                read_result = ec;
                timer.cancel();
                msglen = n;
            } // else canceled, do nothing
        }
    );
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: read action set");
    timer.async_wait([&](error_code ec) { 
        if (!read_result) {
            timer_result = ec;
            port.cancel();
        } // else canceled, do nothing
    });
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: timer set : %d", static_cast<int>(timeout.count()));

    io.run();
    LOG_INFO_FORMAT_IF(rS485CommunicationLayerLogger, "RS485CommunicationLayer::readUntil :: end");

    if(timer_result) {
        if(timer_result.get()) {
            LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, 
                "RS485CommunicationLayer::readUntil() :: Timed out with error %s", 
                timer_result.get().message().c_str()
            );
        } else {
            LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, 
                "RS485CommunicationLayer::readUntil() :: Timed out"
            );
        }
        throw exception::read_exception(exception::read_exception::Code::TIME_OUT);
    } else if(read_result && read_result.get()) {
        std::stringstream sstream;
        sstream << read_result.get();
        LOG_ERROR_FORMAT_IF(rS485CommunicationLayerLogger, 
            "RS485CommunicationLayer::readUntil :: Port threw an error <message: %s>", 
            sstream.str().c_str());
        throw exception::read_exception(exception::read_exception::Code::PORT, sstream.str());
    }
    return msglen;
   // TODO Maybe throw error in the completely unexpected case if no optional is populated
}
Typhaon
  • 828
  • 8
  • 27