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
|
|
@ -1,12 +1,12 @@
|
|||
//SPDX-License-Identifier: LGPL-3.0-only
|
||||
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
||||
|
||||
// A send or a receive that fails throws SocketErrorException with its errno,
|
||||
// as a failed connect does: a caller that reports the cause in its own words
|
||||
// — "the connection broke ([Errno 104] Connection reset by peer)" — needs the
|
||||
// code, and the message alone is the strerror text a send and a receive
|
||||
// always threw. A receive timeout is still SocketTimeoutException and a
|
||||
// clean close SocketClosedException.
|
||||
// A send or a receive that fails throws SocketErrorException with its errno
|
||||
// in code() and the phase that failed, as a failed connect does: a caller
|
||||
// that reports the cause in its own words — "the connection broke ([Errno
|
||||
// 104] Connection reset by peer)" — needs the code, and the message alone is
|
||||
// the strerror text a send and a receive always threw. A receive timeout is
|
||||
// still SocketTimeoutException and a clean close SocketClosedException.
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
|
@ -73,8 +73,8 @@ int main() {
|
|||
client.RecieveSync(16);
|
||||
} catch (const SocketErrorException& e) {
|
||||
thrown = true;
|
||||
Check(e.kind == SocketErrorKind::System, "a reset connection fails the receive as a system error");
|
||||
Check(e.code == ECONNRESET, std::format("it carries ECONNRESET (got {})", e.code));
|
||||
Check(e.phase == SocketPhase::Receive, "a reset connection fails in the receive phase");
|
||||
Check(e.code() == std::errc::connection_reset && e.code().value() == ECONNRESET, std::format("it carries ECONNRESET (got {})", e.code().value()));
|
||||
Check(std::string_view(e.what()) == std::strerror(ECONNRESET), std::format("its message is the strerror text (got '{}')", e.what()));
|
||||
} catch (const std::exception& e) {
|
||||
Check(false, std::format("the receive threw something else: {}", e.what()));
|
||||
|
|
@ -90,12 +90,14 @@ int main() {
|
|||
// kernel answers the next one with EPIPE or ECONNRESET.
|
||||
std::int32_t code = 0;
|
||||
bool runtime = false;
|
||||
bool sending = false;
|
||||
const std::string data(4096, 'x');
|
||||
for (int i = 0; i < 50 && code == 0; ++i) {
|
||||
try {
|
||||
client.Send(data.data(), static_cast<std::uint32_t>(data.size()));
|
||||
} catch (const SocketErrorException& e) {
|
||||
code = e.code;
|
||||
code = e.code().value();
|
||||
sending = e.phase == SocketPhase::Send && e.code().category() == std::generic_category();
|
||||
runtime = dynamic_cast<const std::runtime_error*>(&e) != nullptr;
|
||||
} catch (const std::exception&) {
|
||||
break;
|
||||
|
|
@ -103,7 +105,7 @@ int main() {
|
|||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
Check(code == EPIPE || code == ECONNRESET, std::format("a send into a reset connection throws SocketErrorException with EPIPE or ECONNRESET (got {})", code));
|
||||
Check(runtime, "and it is still a std::runtime_error");
|
||||
Check(sending && runtime, "in the send phase, an errno, and still a std::runtime_error");
|
||||
}
|
||||
|
||||
std::println("{}", failures == 0 ? "all passed" : std::format("{} failed", failures));
|
||||
|
|
|
|||
Loading…
Reference in a new issue