Handling errno in multi-threaded C++ code

Here is a short, thread-safe, and portable solution to reporting errno conditions in C++:

#include <stdexcept>

class errno_exception : public std::runtime_error {
    errno_exception() : runtime_error{
        strerror_l(errno, (locale_t)0)
    } { };

To use this class, just throw one immediately after detecting any error condition that sets errno, such as when an invocation of write() returns -1, like this

if (write(fd, buf, count) < 0) {
    throw errno_exception();

We derive from runtime_exception so that we work with any code that handles standard exceptions, and use strerror_l to produce the appropriate readable string associated with the error identified with the thread-specific error variable errno.

The solution here is more succinct than the problem: safely reporting error conditions in C++ that arise in C code, in a way that is portable and standards-complilant. Many of the functions in the C library indicate an error condition by returning the value -1 and setting the variable errno to indicate what error occurred. That variable can be compared to the macros defined in errno.h (like ENOENT) and strings associated with those values (like "No such file or directory") can be printed or copied into a buffer. But there are thread-safety and portability problems lurking in the functions that create those message strings. While the errno variable itself is thread-safe (ever since POSIX.1c, which was standardized way back in 1995), the function strerror(), which is commonly used to generate error strings from that variable, is not. In a multi-threaded program, if you use strerror(), your call to that function might report an error condition that occurs in a different thread. The web abounds with tutorials explaining how to use that function that do not mention that it lacks thread-safety. The C library has two other functions for reporting errno strings that are thread-safe: strerror_r() and strerror_l(). The former has portability issues; the GNU C library provides both a compliant and a non-compliant version of strerror_r(). You could address that problem by adding feature test macros to your code, in which case you will want to check out the strerror() man page. Or you could do the simple thing, as above: use strerror_l(), which does not have any portability issues. That function requires a locale_t be passed as input, and it accepts (locale_t)0, which indicates the default locale, which is probably what you want.

The standard (POSIX.1-2008) isn’t actually clear on that last part, but it appears to be correct, or at least consistent with the implementations. If you are paranoid about that lack of confirmation, you could rewrite the runtime_error initializer as strerror_l(errno, uselocale((locale_t)0)), a usage that appears in some of the locale documentation. But since the uselocale function itself can set errno, be sure to save the actual errno value that you want to report in a local variable before you invoke that function. Or just go with the simpler form above.

Writing this little snippet of code has been fun, even though researching all of the relevant documents gave me that “you are lost in a maze of twisty standards” feeling. Why does it have to be this hard? Maybe you know of a better approach; if so, please drop me a line.

Art credit: the lovely abstract landscape painting is by Jenny Wilson.

Copyright 2022 David McGrew