2

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.

Community
  • 1
  • 1
Christopher Pisz
  • 3,757
  • 4
  • 29
  • 65
  • 2
    If you just want the message, just call `errorCode.message()`. This will give you the appropriate message. If you want to understand what kind of error it is, use the `value()`. I don't see why the comparison would fail, the `operator==` is defined to check pointers (the error code system uses static instances of categories..) Perhaps you can check the address of `errorCode.category()` and `boost::asio::error::misc_category` to see if they are indeed the same? – Nim Mar 23 '17 at 20:48
  • I don't want to built in message. I want to produce my own message that my QA team will understand when reading my log. Joe Bob tester is not going to know what "End of File" means. – Christopher Pisz Mar 23 '17 at 21:06
  • debugMsg = "Error Category: asio.misc. Error Message: End of file." &(errorCode.category())= 0x016e3768. &(boost::asio::error::misc_category) = &0x536c1250 – Christopher Pisz Mar 23 '17 at 21:12
  • What is that last comment? Surely that is not what you want the message to look like, I hope? – sehe Mar 24 '17 at 09:08
  • Seems to me it already works http://coliru.stacked-crooked.com/a/da721c8c31c7377b. Though why not make it simpler. http://coliru.stacked-crooked.com/a/928568b28141c399 – sehe Mar 24 '17 at 09:37
  • Well clearly then the `operator==` will fail. I'm not quite sure why they have done it this way. TBF, I've never used the category, simply tested for the error code (as @sehe has shown), and it works fine.. – Nim Mar 24 '17 at 10:30
  • @Nim My first example does use the `operator==` wholesale. And that also works. – sehe Mar 24 '17 at 10:59
  • @sehe you code is only comparing the error code value and not the category, in your map. Most people are only using the value, but as has been mentioned, the value is not unique, it can occur in more than one category. It states this both in the boost documentation and has been verified on the boost mailing list. That is the origin of the original problem. – Christopher Pisz Mar 24 '17 at 14:19
  • I'm not exactly sure what you claim has been checked and verified, but I can read the source code, and so can you. I think there has been some misunderstanding about what has been said/asked. (Perhaps if you linked to the relevant discussion in your question, we can properly appreciate the inputs) – sehe Mar 24 '17 at 17:15
  • @sehe How do you propose I link to the relevant discussion from a mailing list?...as in email. I'm not about to copy paste the whole thing here. Don't need to anyway. As you said, look at the source code. Can you look at the values of boost::system::errc::no_such_file_or_directory; boost::asio::error::host_not_found; boost::asio::error::eof;and then solidify your understanding of what category is for? After that, look at line 198 of \boost\system\error_code.hpp and tell me what happens when we statically link boost to 3 projects and compare an error from one in the executable of another? – Christopher Pisz Mar 24 '17 at 19:21
  • 1
    You can link to the archives. Mailing lists are public: http://boost.2283326.n4.nabble.com/Compare-boost-system-error-category-td4692861.html – sehe Mar 24 '17 at 20:15
  • I cannot predict how your library situation behaves because I don't have it. I have just tested using statically linked components and `BOOST_ERROR_CODE_HEADER_ONLY` in one TU. Works like a charm. Possibly not for you when you do the same in multiple libraries, but I don't know how you do that without getting duplicate symbols. So, until you help us reproduce the situation I cannot "tell you what happens". You tell us! – sehe Mar 24 '17 at 20:18
  • I've already explained how to reproduce the situation. Make a library that produces a boost::system::error_code from a boost library. Make an executable that compares it. Statically link all boost libraries to both. Link your library to your executable. Start your debugger. Look at boost::system::errc::no_such_file_or_directory;boost::asio::error::host_not_found; boost::asio::error::eof; Then take an hour break and think about it for awhile. – Christopher Pisz Mar 24 '17 at 20:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/138989/discussion-between-sehe-and-christopher-pisz). – sehe Mar 24 '17 at 23:26

1 Answers1

1

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.

Niall Douglas
  • 9,212
  • 2
  • 44
  • 54