Search code examples
c++boost-asioboost-system

Compare boost::system::error_category


The following comparison fails for an error whom outputs "asio.misc" for errorCode.category().name() and "end of file" for errorCode.message()

If it claims to be in category asio.misc, then why does the if condition of (errorCode.category() == boost::asio::error::misc_category ) evaluate to false?

Googling (including answers here) says that a boost::system::error_code can have the same value in more than one category, so I assume that in order to get the proper message and meaning we must compare boost::system::error_category as well as boost::system::error_code::value.

How do we properly compare the category if this fails to work?

Code in question:

//--------------------------------------------------------------------------------------------------
std::string ClientSocketASIO::ErrorCodeToString(const boost::system::error_code & errorCode)
{
    std::ostringstream debugMsg;
    debugMsg << " Error Category: " << errorCode.category().name() << ". "
             << " Error Message: "  << errorCode.message() << ". ";

    if( errorCode.category() == boost::asio::error::misc_category )
    {
        switch (errorCode.value())
        {
        case boost::asio::error::eof:
            debugMsg << ". Server has disconnected.";
            break;
        case boost::asio::error::connection_refused:
            debugMsg << ". Connection Refused";
            break;
        default:
            debugMsg << ". Unknown Error.";
            break;
        }
    }
    else
    {
        debugMsg << ". Unknown Error category.";
    }

    return debugMsg.str();
}

EDIT:

According to https://theboostcpplibraries.com/boost.system and Boost::file_system: Checking error codes

People commonly and mistakenly write code only comparing the error value and not the category. The error value alone is not unique and may occur in more than one category. Users are also free to create their own error codes and categories. Therefore, both must be compared. This, I assume, doesn't effect a large number of applications, because they are only using one feature or library of boost their project anyway and/or the majority of error codes are mapped to windows error codes which made a best effort to not collide.

I had to get on another computer to hit up the boost mailing list, since my work blocks almost everything.

http://boost.2283326.n4.nabble.com/Compare-boost-system-error-category-td4692861.html#a4692869

According to a fella over there, the reason the comparison is failing is because the boost library is statically linked, and boost::system::error_category::operator == compares addresses, so the LHS is the address in one lib and the RHS is the address in another. They are not going to be equal when they are expected to be.

Using the address for operator == seems like a very silly move to me. I will continue to rant about it on the boost mailing list and edit here for others if any new knowledge is discovered.

For now, I am dynamically link and use form

//--------------------------------------------------------------------------------------------------
std::string ClientSocketASIO::ErrorCodeToString(const boost::system::error_code & errorCode)
{
    std::ostringstream debugMsg;
    debugMsg << " Error Category: " << errorCode.category().name() << ". "
             << " Error Message: "  << errorCode.message() << ". ";

    // IMPORTANT - These comparisons only work if you dynamically link boost libraries
    //             Because boost chose to implement boost::system::error_category::operator == by comparing addresses
    //             The addresses are different in one library and the other when statically linking.
    //
    // We use make_error_code macro to make the correct category as well as error code value.
    // Error code value is not unique and can be duplicated in more than one category.
    if (errorCode == make_error_code(boost::asio::error::connection_refused))
    {
        debugMsg << ". Connection Refused";
    }
    else if (errorCode == make_error_code(boost::asio::error::eof))
    {
        debugMsg << ". Server has disconnected.";
    }
    else
    {
        debugMsg << ". boost::system::error_code has not been mapped to a meaningful message.";
    }

    return debugMsg.str();
}

however, this does not work when I link statically to boost either. If anyone has any more suggestions on how we are to properly compare boost::system::error_code and get expected results, please let's get to the bottom of this.


Solution

  • The C++ 11 standard implies that each error category instance shall have a globally unique address and comparisons of equality shall use that address to compare. Unfortunately, this is not reliable in portable code, only the Dinkumware STL implements true address uniqueness anywhere in the process and it adds a full memory barrier to achieve that i.e. it's expensive.

    Boost's quality of implementation is the same as libstdc++ or libc++, you can get multiple instantiations in certain circumstances and those can have differing addresses.

    In my own code, I do the comparison operator first, and if that fails I do a strcmp() of the category's name(). To date, this has not bitten me. I would personally consider this aspect of error categories to be a defect in the standard, as specified it forces non-header-only implementation if you want it to be conforming, and even that doesn't cover RTLD_LOCAL which means you need to fall back onto named shared memory or some hack.