2

I wrote a simple blocking server which waits for a single client and floods it.

I use boost::asio::ip::tcp::iostream class for interacting with the client as I want to use formatted I/O in the future. I also would like to use exceptions for error handling instead of manual flags checks after each operation, so I called .exceptions() in a fashion similar to basic_ios::exceptions (I assume Boost derives from the latter, doesn't it?).

Here is the resulting code:

#include <boost/asio.hpp>
#include <exception>
#include <iostream>
#include <sstream>
#include <utility>

using boost::asio::ip::tcp;

int main() {
    try {
        try {
            boost::asio::io_context io_context;
            tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 10000));
            std::cout << "Listening at " << acceptor.local_endpoint() << "\n";

            tcp::iostream client(acceptor.accept());
            client.exceptions(std::ios_base::failbit | std::ios_base::badbit |
                              std::ios_base::eofbit);  // (1)
            while (client << "x\n") {
            }
            std::cout << "Completed\n";
        } catch (std::exception &e) {
            std::cout << "Exception: " << e.what() << "\n";
        }
    } catch (...) {
        std::cout << "Unknown exception\n";
    }
    return 0;
}

Unfortunately, it looks like some exceptions are still falling through the cracks. If I start the server, connect via nc localhost 10000, look at xs for a while, and then Ctrl+C the client, terminating it, the server std::terminates with the following error message:

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
terminate called after throwing an instance of 'std::__ios_failure'
  what():  basic_ios::clear: iostream error

If I comment out the (1) line, everything works flawlessly and the server terminates with a Completed message when the client disconnects.

I've tried it both on Windows (g++ (Rev6, Built by MSYS2 project) 10.2.0 and Boost 1.75.0-3) and Ubuntu 20.04 (g++-10 (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0 and Boost 1.71.0.0ubuntu2).

What am I doing wrong?

GDB session on Ubuntu shows that the exception is thrown from inside a destructor, which is presumably noexcept, which is kind of weird:

#0  0x00007ffff7e86762 in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00007ffff7e7dc63 in std::__throw_ios_failure(char const*) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x00007ffff7ef0dd2 in std::basic_ios<char, std::char_traits<char> >::clear(std::_Ios_Iostate) ()
   from /lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7f0f28e in std::ostream::sentry::~sentry() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7f0f975 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_trait
s<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) ()
   from /lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7f0fd5c in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(s
td::basic_ostream<char, std::char_traits<char> >&, char const*) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x000055555555d3a8 in main () at test.cpp:19
yeputons
  • 8,478
  • 34
  • 67
  • It looks like libstdc++ [calls setstate in a sentry destructor](https://github.com/gcc-mirror/gcc/blob/88202c883c07da1c03dbb1ad440f1b70189c4399/libstdc%2B%2B-v3/include/std/ostream#L467-L470) for whatever reason, which throws, which causes `std::terminate`. If I compile the example with clang and `-stdlib=libc++`, the problem kinda goes away: no more `std::terminate`, but no exception is thrown at all. – yeputons Apr 21 '21 at 13:25
  • [This](https://en.cppreference.com/w/cpp/io/basic_ostream/sentry) doesn't indicate that `~sentry` is `noexcept` – Zoso Apr 21 '21 at 13:38
  • 1
    Found a relevant [LWG issue 397](https://wg21.link/lwg397) which is resolved by a "Ready to Review" [LWG 835](https://wg21.link/lwg835) (not yet in the standard). – yeputons Apr 21 '21 at 13:41
  • @Zoso as far as I understand, destructors are implicitly `noexcept` since C++11, unless a base destructor is potentially throwing. Not sure what the intention in the standard was for `sentry`. – yeputons Apr 21 '21 at 13:50
  • 1
    What you mentioned for the `noexcept` by default is true unless the destructor is _potentially throwing_. That call to `setstate` makes `~sentry` _potentially throwing_. [LLVM](https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed57b50122a161b91f59f19c9bd0d19/include/ostream#L276) captures this more explicitly. – Zoso Apr 21 '21 at 14:23

0 Answers0