feat(tcp): socket errors as std::system_error, with the phase that failed
SocketErrorException is now a std::system_error: code() carries the errno
under std::generic_category(), or getaddrinfo's result under the new
ResolverCategory(), whose message() is gai_strerror's text; `phase` says
which step failed (resolve, open, connect, send, receive). It is still a
std::runtime_error with the message ClientTCP always threw, the code's
text after it, so existing handlers keep working.
Before, the exception carried a `kind` and an int, and a caller that had
to print the cause its own way still had to match the message prefix to
learn where the failure happened. The standard type ends both: the
category knows how to print the number, and the phase is a field. A
caller writes "[Errno {}] {}" from code().value() and code().message(),
or compares code() with std::errc::connection_refused.
Breaking: SocketErrorException's `kind` and `code` members are gone;
read code().category() and code().value().
This commit is contained in:
parent
16da0a1979
commit
e86c3b70a0
9 changed files with 107 additions and 63 deletions
|
|
@ -44,9 +44,21 @@ namespace {
|
|||
[[noreturn]] void ThrowRecieveError() {
|
||||
const int failure = errno;
|
||||
if (failure == EAGAIN || failure == EWOULDBLOCK) throw SocketTimeoutException();
|
||||
throw SocketErrorException(SocketErrorKind::System, failure, std::strerror(failure));
|
||||
throw SocketErrorException(SocketPhase::Receive, std::error_code(failure, std::generic_category()));
|
||||
}
|
||||
|
||||
// getaddrinfo's results as an error category: the number is one of
|
||||
// netdb.h's EAI_* codes and the text is what gai_strerror makes of it.
|
||||
class ResolverErrorCategory : public std::error_category {
|
||||
public:
|
||||
const char* name() const noexcept override {
|
||||
return "resolver";
|
||||
}
|
||||
std::string message(int code) const override {
|
||||
return gai_strerror(code);
|
||||
}
|
||||
};
|
||||
|
||||
// One connect attempt to one address: a connected, blocking socket, or a
|
||||
// throw that leaves nothing open.
|
||||
int ConnectTo(const sockaddr* address, socklen_t length, std::chrono::milliseconds timeout) {
|
||||
|
|
@ -55,7 +67,7 @@ namespace {
|
|||
// An address family the host cannot open — IPv6 switched off,
|
||||
// say — fails this address alone; the caller moves on.
|
||||
const int failure = errno;
|
||||
throw SocketErrorException(SocketErrorKind::System, failure, std::string("Could not open socket: ") + std::strerror(failure));
|
||||
throw SocketErrorException(SocketPhase::Open, std::error_code(failure, std::generic_category()), "Could not open socket");
|
||||
}
|
||||
|
||||
// With a timeout the connect runs non-blocking, so poll can bound the
|
||||
|
|
@ -93,7 +105,7 @@ namespace {
|
|||
// connected to anything — every later send/recv on it would fail
|
||||
// with a far less obvious error.
|
||||
close(fd);
|
||||
throw SocketErrorException(SocketErrorKind::System, failure, std::string("Could not connect to server: ") + std::strerror(failure));
|
||||
throw SocketErrorException(SocketPhase::Connect, std::error_code(failure, std::generic_category()), "Could not connect to server");
|
||||
}
|
||||
if (bounded) {
|
||||
fcntl(fd, F_SETFL, flags);
|
||||
|
|
@ -102,6 +114,11 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
const std::error_category& Crafter::ResolverCategory() noexcept {
|
||||
static const ResolverErrorCategory category;
|
||||
return category;
|
||||
}
|
||||
|
||||
ClientTCP::ClientTCP(int socketid) : socketid(socketid)
|
||||
{
|
||||
|
||||
|
|
@ -125,8 +142,8 @@ ClientTCP::ClientTCP(const char* hostName, std::uint16_t port, std::chrono::mill
|
|||
const std::string service = std::to_string(port);
|
||||
const int status = getaddrinfo(hostName, service.c_str(), &hints, &resolved);
|
||||
if (status != 0 || resolved == nullptr) {
|
||||
throw SocketErrorException(SocketErrorKind::Resolver, status,
|
||||
std::string("Could not resolve host '") + hostName + "': " + gai_strerror(status));
|
||||
throw SocketErrorException(SocketPhase::Resolve, std::error_code(status, ResolverCategory()),
|
||||
std::string("Could not resolve host '") + hostName + "'");
|
||||
}
|
||||
for (const addrinfo* entry = resolved; entry != nullptr; entry = entry->ai_next) {
|
||||
Address address {};
|
||||
|
|
@ -215,7 +232,7 @@ void ClientTCP::Send(const void* buffer, std::uint32_t size) const {
|
|||
} else if (status < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
const int failure = errno;
|
||||
throw SocketErrorException(SocketErrorKind::System, failure, std::strerror(failure));
|
||||
throw SocketErrorException(SocketPhase::Send, std::error_code(failure, std::generic_category()));
|
||||
}
|
||||
sent += static_cast<std::uint32_t>(status);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue