From c9d55790e8df2b0b7d78e2ff0b24cfc6226380e2 Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Thu, 20 Aug 2026 22:45:46 +0200 Subject: [PATCH 1/5] refactor: report normal errors through the return value across the sync surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Applies the library-wide error model to every synchronous operation: expected runtime conditions travel through [[nodiscard]] returns — std::error_code, or io_result when a payload rides along — while exceptions are reserved for precondition violations. One channel per operation. - File ops: open, resize, sync_data, sync_all, and assign return std::error_code; seek returns io_result carrying the new position. Missing files, disk full, device I/O errors, data-driven seek offsets, and foreign handles are runtime conditions; size() and release(), whose only failure is misuse of a closed file, keep throwing. The implementation virtuals change shape and become noexcept across posix, io_uring, and IOCP. - open() on the four socket classes and both acceptors returns the code: descriptor exhaustion and address-family rejection are on par with bind's EADDRINUSE, so the whole setup sequence speaks one channel. Opening an already-open object stays a no-op reporting success; connect()'s auto-open forwards an open failure into the returned awaitable, which completes immediately instead of throwing from the initiator. - assign() returns the code: adopting a foreign descriptor is untrusted input, and the service layer already produced the code the wrapper was converting into an exception. On failure the caller retains the descriptor. - shutdown() returns the code: tcp and udp were silently discarding the underlying error, and the local sockets carried the asio-style dual overloads — throwing plus an error_code& out-param — which are removed. A peer that already disconnected is a normal runtime condition the caller may want to observe. udp_socket gains shutdown() across all backends — connected datagram sockets support it everywhere. - Every remaining sync error-code return is [[nodiscard]]: the tls_context setters, signal_set add/remove/clear, tcp_server and local_datagram_socket bind, and both connect_pair overloads. A silently dropped certificate-load failure is a latent bug; the attribute makes it a warning. Tests and doc snippets consume the returns they previously ignored. --- .../ROOT/pages/4.guide/4d.sockets.adoc | 6 +- .../ROOT/pages/4.guide/4o.file-io.adoc | 9 +- example/client/http_client.cpp | 2 +- example/https-client/https_client.cpp | 2 +- include/boost/corosio/connect.hpp | 14 +- include/boost/corosio/detail/op_base.hpp | 4 +- include/boost/corosio/detail/scheduler.hpp | 11 +- include/boost/corosio/local_connect_pair.hpp | 6 +- .../boost/corosio/local_datagram_socket.hpp | 44 ++-- .../boost/corosio/local_stream_acceptor.hpp | 14 +- include/boost/corosio/local_stream_socket.hpp | 43 ++-- .../native/detail/epoll/epoll_scheduler.hpp | 6 +- .../native/detail/epoll/epoll_types.hpp | 5 + .../io_uring/io_uring_random_access_file.hpp | 24 +- .../detail/io_uring/io_uring_scheduler.hpp | 10 +- .../detail/io_uring/io_uring_stream_file.hpp | 30 +-- .../native/detail/io_uring/io_uring_types.hpp | 8 + .../native/detail/iocp/win_file_service.hpp | 75 ++++--- .../detail/iocp/win_random_access_file.hpp | 16 +- .../iocp/win_random_access_file_service.hpp | 61 +++--- .../native/detail/iocp/win_stream_file.hpp | 22 +- .../native/detail/iocp/win_udp_service.hpp | 23 ++ .../native/detail/iocp/win_udp_socket.hpp | 2 + .../native/detail/kqueue/kqueue_scheduler.hpp | 6 +- .../native/detail/kqueue/kqueue_types.hpp | 5 + .../detail/posix/posix_random_access_file.hpp | 39 ++-- .../detail/posix/posix_signal_service.hpp | 18 +- .../native/detail/posix/posix_stream_file.hpp | 58 ++--- .../native/detail/select/select_scheduler.hpp | 6 +- .../native/detail/select/select_types.hpp | 5 + .../native/native_local_datagram_socket.hpp | 9 +- .../native/native_local_stream_socket.hpp | 13 +- .../corosio/native/native_udp_socket.hpp | 13 +- include/boost/corosio/random_access_file.hpp | 71 ++++-- include/boost/corosio/signal_set.hpp | 8 +- include/boost/corosio/stream_file.hpp | 80 ++++--- include/boost/corosio/tcp_acceptor.hpp | 11 +- include/boost/corosio/tcp_server.hpp | 2 +- include/boost/corosio/tcp_socket.hpp | 37 ++-- include/boost/corosio/test/mocket.hpp | 6 +- include/boost/corosio/test/socket_pair.hpp | 6 +- include/boost/corosio/tls_context.hpp | 44 ++-- include/boost/corosio/udp_socket.hpp | 47 +++- perf/bench/corosio/accept_churn_bench.cpp | 21 +- perf/bench/corosio/http_server_bench.cpp | 3 +- .../corosio/local_socket_latency_bench.cpp | 3 +- .../corosio/local_socket_throughput_bench.cpp | 13 +- perf/bench/corosio/socket_latency_bench.cpp | 3 +- .../bench/corosio/socket_throughput_bench.cpp | 15 +- src/corosio/src/local_connect_pair.cpp | 32 +-- src/corosio/src/local_datagram_socket.cpp | 42 ++-- src/corosio/src/local_stream_acceptor.cpp | 16 +- src/corosio/src/local_stream_socket.cpp | 42 ++-- src/corosio/src/random_access_file.cpp | 44 ++-- src/corosio/src/stream_file.cpp | 54 ++--- src/corosio/src/tcp_acceptor.cpp | 19 +- src/corosio/src/tcp_socket.cpp | 34 ++- src/corosio/src/udp_socket.cpp | 30 ++- test/doc/programs/index_page_connect.cpp | 2 +- test/doc/snippets/3b_http_client.cpp | 4 +- test/doc/snippets/3c_dns_lookup.cpp | 3 +- test/doc/snippets/3d_tls_context.cpp | 116 ++++++---- test/doc/snippets/4d_sockets.cpp | 13 +- test/doc/snippets/4e_tcp_acceptor.cpp | 14 +- test/doc/snippets/4f_endpoints.cpp | 2 +- test/doc/snippets/4g_composed_operations.cpp | 2 +- test/doc/snippets/4j_resolver.cpp | 7 +- test/doc/snippets/4k_tcp_server.cpp | 12 +- test/doc/snippets/4l_tls.cpp | 6 +- test/doc/snippets/4m_error_handling.cpp | 20 +- test/doc/snippets/4o_file_io.cpp | 65 +++--- test/doc/snippets/4p_unix_sockets.cpp | 24 +- test/doc/snippets/4q_udp.cpp | 49 +++-- test/doc/snippets/4r_wait.cpp | 7 +- test/unit/connect.cpp | 4 +- test/unit/cross_ssl_stream.cpp | 4 +- test/unit/datagram_paths.cpp | 26 +-- test/unit/error_conditions.cpp | 2 +- test/unit/local_connect_pair.cpp | 4 +- test/unit/local_datagram_socket.cpp | 69 ++---- test/unit/local_stream_socket.cpp | 137 ++++-------- test/unit/native/native_io.cpp | 4 +- test/unit/native/native_io_uring_specific.cpp | 2 +- .../native/native_local_datagram_socket.cpp | 20 +- .../unit/native/native_random_access_file.cpp | 10 +- test/unit/native/native_stream_file.cpp | 10 +- test/unit/native/native_tcp_socket.cpp | 6 +- test/unit/native/native_udp_socket.cpp | 30 +-- test/unit/openssl_engine.cpp | 17 +- test/unit/openssl_stream.cpp | 6 +- test/unit/precancel.cpp | 16 +- test/unit/random_access_file.cpp | 135 +++++------- test/unit/reactor_paths.cpp | 82 +++---- test/unit/socket_option.cpp | 12 +- test/unit/socket_stress.cpp | 8 +- test/unit/stream_file.cpp | 206 ++++++++---------- test/unit/tcp_acceptor.cpp | 103 ++++----- test/unit/tcp_server.cpp | 23 +- test/unit/tcp_socket.cpp | 149 ++++++------- test/unit/test_utils.hpp | 203 ++++++++--------- test/unit/tls_stream_tests.hpp | 168 ++++++-------- test/unit/udp_socket.cpp | 172 +++++++-------- test/unit/wait.cpp | 24 +- test/unit/wolfssl_engine.cpp | 8 +- test/unit/wolfssl_stream.cpp | 6 +- 105 files changed, 1663 insertions(+), 1641 deletions(-) diff --git a/doc/modules/ROOT/pages/4.guide/4d.sockets.adoc b/doc/modules/ROOT/pages/4.guide/4d.sockets.adoc index cb4df7b0a..0b4e1c34e 100644 --- a/doc/modules/ROOT/pages/4.guide/4d.sockets.adoc +++ b/doc/modules/ROOT/pages/4.guide/4d.sockets.adoc @@ -53,8 +53,10 @@ Creates the underlying TCP socket: include::example$snippets/4d_sockets.cpp[tag=open,indent=0] ---- -This allocates a socket handle and registers it with the I/O backend. -Throws `std::system_error` on failure. +This allocates a socket handle and registers it with the I/O backend, +returning a `std::error_code`; failures such as descriptor exhaustion +are reported through the code. Explicit `open()` is only needed to set +socket options before connecting — `connect()` opens automatically. === close() diff --git a/doc/modules/ROOT/pages/4.guide/4o.file-io.adoc b/doc/modules/ROOT/pages/4.guide/4o.file-io.adoc index 093423faa..42677a3bf 100644 --- a/doc/modules/ROOT/pages/4.guide/4o.file-io.adoc +++ b/doc/modules/ROOT/pages/4.guide/4o.file-io.adoc @@ -146,8 +146,13 @@ end-of-file return `capy::cond::eof`: include::example$snippets/4o_file_io.cpp[tag=error_handling,indent=0] ---- -Opening a nonexistent file with `read_only` throws `std::system_error`. -Use `create` to create files that may not exist. +Synchronous operations that can fail in normal use — `open`, +`resize`, `sync_data`, `sync_all`, and `assign` — return a +`std::error_code`; `seek` returns the code together with the new +position. Opening a nonexistent file with `read_only` reports +`no_such_file_or_directory`; use `create` to create files that may +not exist. Only misuse, such as calling `size()` or `release()` on +a closed file, throws `std::system_error`. == Thread Safety diff --git a/example/client/http_client.cpp b/example/client/http_client.cpp index ed4134515..4f0fbd8a1 100644 --- a/example/client/http_client.cpp +++ b/example/client/http_client.cpp @@ -82,8 +82,8 @@ run_client( corosio::ipv4_address addr, std::uint16_t port) { + // connect() opens the socket automatically corosio::tcp_socket s(ioc); - s.open(); // Connect to the server if (auto [ec] = co_await s.connect(corosio::endpoint(addr, port)); ec) diff --git a/example/https-client/https_client.cpp b/example/https-client/https_client.cpp index 46c86e888..f0d3c905b 100644 --- a/example/https-client/https_client.cpp +++ b/example/https-client/https_client.cpp @@ -76,8 +76,8 @@ run_client( std::uint16_t port, std::string_view hostname) { + // connect() opens the socket automatically corosio::tcp_socket s(ioc); - s.open(); // Connect to the server if (auto [ec] = co_await s.connect(corosio::endpoint(addr, port)); ec) diff --git a/include/boost/corosio/connect.hpp b/include/boost/corosio/connect.hpp index 36025ae56..d223eb64a 100644 --- a/include/boost/corosio/connect.hpp +++ b/include/boost/corosio/connect.hpp @@ -124,8 +124,9 @@ connect(Socket& s, Iter begin, Iter end, ConnectCondition cond); `reuse_address`) are lost. Apply options after this operation completes. - @throws std::system_error if auto-opening the socket fails during - an attempt (inherits the contract of `Socket::connect`). + If auto-opening the socket fails during an attempt, that attempt + completes with the open error (inherits the contract of + `Socket::connect`). @par Example @code @@ -168,7 +169,8 @@ connect(Socket& s, Range endpoints) @return Same as the non-condition overload. If every candidate is rejected, completes with `std::errc::no_such_device_or_address`. - @throws std::system_error if auto-opening the socket fails. + If auto-opening the socket fails, the attempt completes with the + open error. */ template requires std::convertible_to< @@ -233,7 +235,8 @@ connect(Socket& s, Range endpoints, ConnectCondition cond) - on empty range: `std::errc::no_such_device_or_address` and `end`. - @throws std::system_error if auto-opening the socket fails. + If auto-opening the socket fails, the attempt completes with the + open error. */ template requires std::convertible_to< @@ -261,7 +264,8 @@ connect(Socket& s, Iter begin, Iter end) @return Same as the plain iterator overload. If every candidate is rejected, completes with `std::errc::no_such_device_or_address`. - @throws std::system_error if auto-opening the socket fails. + If auto-opening the socket fails, the attempt completes with the + open error. */ template requires std::convertible_to< diff --git a/include/boost/corosio/detail/op_base.hpp b/include/boost/corosio/detail/op_base.hpp index 55afe9e63..066c64b4a 100644 --- a/include/boost/corosio/detail/op_base.hpp +++ b/include/boost/corosio/detail/op_base.hpp @@ -87,7 +87,9 @@ class void_op_base bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before dispatch + // (e.g. auto-open); complete immediately with that error. + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept diff --git a/include/boost/corosio/detail/scheduler.hpp b/include/boost/corosio/detail/scheduler.hpp index bd8f0e1f2..59425467f 100644 --- a/include/boost/corosio/detail/scheduler.hpp +++ b/include/boost/corosio/detail/scheduler.hpp @@ -13,6 +13,8 @@ #define BOOST_COROSIO_DETAIL_SCHEDULER_HPP #include + +#include #include #include @@ -94,8 +96,15 @@ struct BOOST_COROSIO_DECL scheduler uses synchronous C-runtime signal handling instead). @param read_fd The read end of the global signal self-pipe. + + @return The error code, empty on success. */ - virtual void register_signal_reader(int read_fd) { (void)read_fd; } + [[nodiscard]] virtual std::error_code + register_signal_reader(int read_fd) + { + (void)read_fd; + return {}; + } /// Decomposed threading configuration applied via @ref configure_threading. struct threading_config diff --git a/include/boost/corosio/local_connect_pair.hpp b/include/boost/corosio/local_connect_pair.hpp index 90ce3acee..ffafc3259 100644 --- a/include/boost/corosio/local_connect_pair.hpp +++ b/include/boost/corosio/local_connect_pair.hpp @@ -47,8 +47,7 @@ namespace boost::corosio { @return Empty on success; otherwise the underlying system error. */ -BOOST_COROSIO_DECL -std::error_code +[[nodiscard]] BOOST_COROSIO_DECL std::error_code connect_pair(local_stream_socket& a, local_stream_socket& b) noexcept; #if BOOST_COROSIO_POSIX @@ -69,8 +68,7 @@ connect_pair(local_stream_socket& a, local_stream_socket& b) noexcept; @return Empty on success; otherwise the underlying system error. */ -BOOST_COROSIO_DECL -std::error_code +[[nodiscard]] BOOST_COROSIO_DECL std::error_code connect_pair(local_datagram_socket& a, local_datagram_socket& b) noexcept; #endif // BOOST_COROSIO_POSIX diff --git a/include/boost/corosio/local_datagram_socket.hpp b/include/boost/corosio/local_datagram_socket.hpp index 2fbe51af1..f99c7f420 100644 --- a/include/boost/corosio/local_datagram_socket.hpp +++ b/include/boost/corosio/local_datagram_socket.hpp @@ -506,11 +506,16 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object Creates a Unix datagram socket and associates it with the platform reactor. + Failures such as descriptor exhaustion are normal runtime + conditions and are reported through the returned error code. + Opening an already-open socket is a no-op that reports + success. + @param proto The protocol. Defaults to local_datagram{}. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open(local_datagram proto = {}); + [[nodiscard]] std::error_code open(local_datagram proto = {}) noexcept; /** Close the socket. @@ -547,7 +552,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @throws std::logic_error if the socket is not open. */ - std::error_code bind(corosio::local_endpoint ep); + [[nodiscard]] std::error_code bind(corosio::local_endpoint ep); /** Initiate an asynchronous connect to set the default peer. @@ -564,14 +569,15 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return An awaitable that completes with io_result<>. - @throws std::system_error if the socket needs to be opened - and the open fails. + If the socket needs to be opened and the open fails, the + awaitable completes immediately with that error. */ auto connect(corosio::local_endpoint ep) { + connect_awaitable aw(*this, ep); if (!is_open()) - open(); - return connect_awaitable(*this, ep); + aw.ec_ = open(); + return aw; } /** Wait for the socket to become ready in a given direction. @@ -768,18 +774,15 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object /** Shut down part or all of the socket. - @param what Which direction to shut down. - - @throws std::system_error on failure. - */ - void shutdown(shutdown_type what); - - /** Shut down part or all of the socket (non-throwing). + Failures such as an unconnected socket are normal runtime + conditions and are reported through the returned error + code. A closed socket reports `errc::bad_file_descriptor`. @param what Which direction to shut down. - @param ec Set to the error code on failure. + + @return The error code, empty on success. */ - void shutdown(shutdown_type what, std::error_code& ec) noexcept; + [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept; /** Set a socket option. @@ -854,10 +857,11 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @param fd The native socket to adopt. On success the object owns it and will close it. - @throws std::system_error On validation or registration - failure. + @return The error code, empty on success. Validation and + registration failures are normal runtime conditions when + adopting foreign descriptors. */ - void assign(native_handle_type fd); + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; /** Get the local endpoint of the socket. @@ -885,7 +889,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object } private: - void open_for_family(int family, int type, int protocol); + std::error_code open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/local_stream_acceptor.hpp b/include/boost/corosio/local_stream_acceptor.hpp index 6a6352c0c..ef7f46511 100644 --- a/include/boost/corosio/local_stream_acceptor.hpp +++ b/include/boost/corosio/local_stream_acceptor.hpp @@ -248,11 +248,14 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object /** Create the acceptor socket. + Failures such as descriptor exhaustion are normal runtime + conditions and are reported through the returned error code. + @param proto The protocol. Defaults to local_stream{}. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open(local_stream proto = {}); + [[nodiscard]] std::error_code open(local_stream proto = {}) noexcept; /** Bind to a local endpoint. @@ -428,10 +431,11 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @param fd The native socket to adopt. On success the object owns it and will close it. - @throws std::system_error On validation or registration - failure. + @return The error code, empty on success. Validation and + registration failures are normal runtime conditions when + adopting foreign descriptors. */ - void assign(native_handle_type fd); + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; /** Return the local endpoint the acceptor is bound to. diff --git a/include/boost/corosio/local_stream_socket.hpp b/include/boost/corosio/local_stream_socket.hpp index 87b6d4f70..aa98d628d 100644 --- a/include/boost/corosio/local_stream_socket.hpp +++ b/include/boost/corosio/local_stream_socket.hpp @@ -297,11 +297,16 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream Creates a Unix stream socket and associates it with the platform reactor. + Failures such as descriptor exhaustion are normal runtime + conditions and are reported through the returned error code. + Opening an already-open socket is a no-op that reports + success. + @param proto The protocol. Defaults to local_stream{}. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open(local_stream proto = {}); + [[nodiscard]] std::error_code open(local_stream proto = {}) noexcept; /** Close the socket. @@ -331,14 +336,15 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @return An awaitable that completes with io_result<>. - @throws std::system_error if the socket needs to be opened - and the open fails. + If the socket needs to be opened and the open fails, the + awaitable completes immediately with that error. */ auto connect(corosio::local_endpoint ep) { + connect_awaitable aw(*this, ep); if (!is_open()) - open(); - return connect_awaitable(*this, ep); + aw.ec_ = open(); + return aw; } /** Wait for the socket to become ready in a given direction. @@ -407,19 +413,17 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream allows you to close one or both directions without destroying the socket. + Failures such as a peer that already disconnected are + normal runtime conditions and are reported through the + returned error code. A closed socket reports + `errc::bad_file_descriptor`. + @param what Determines what operations will no longer be allowed. - @throws std::system_error on failure. - */ - void shutdown(shutdown_type what); - - /** Shut down part or all of the socket (non-throwing). - - @param what Which direction to shut down. - @param ec Set to the error code on failure. + @return The error code, empty on success. */ - void shutdown(shutdown_type what, std::error_code& ec) noexcept; + [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept; /** Set a socket option. @@ -490,10 +494,11 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @param fd The native socket to adopt. On success the object owns it and will close it. - @throws std::system_error On validation or registration - failure. + @return The error code, empty on success. Validation and + registration failures are normal runtime conditions when + adopting foreign descriptors. */ - void assign(native_handle_type fd); + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; /** Get the local endpoint of the socket. @@ -523,7 +528,7 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream private: friend class local_stream_acceptor; - void open_for_family(int family, int type, int protocol); + std::error_code open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp b/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp index dfbbc135f..e87c69e55 100644 --- a/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp +++ b/include/boost/corosio/native/detail/epoll/epoll_scheduler.hpp @@ -127,10 +127,10 @@ class BOOST_COROSIO_DECL epoll_scheduler final : public reactor_scheduler void deregister_descriptor(int fd) const; /// Watch the read end of the POSIX signal self-pipe (see scheduler.hpp). - void register_signal_reader(int read_fd) override + [[nodiscard]] std::error_code + register_signal_reader(int read_fd) override { - if (auto ec = register_descriptor(read_fd, signal_pipe_reader_.arm())) - detail::throw_system_error(ec, "epoll_ctl (register)"); + return register_descriptor(read_fd, signal_pipe_reader_.arm()); } private: diff --git a/include/boost/corosio/native/detail/epoll/epoll_types.hpp b/include/boost/corosio/native/detail/epoll/epoll_types.hpp index 39768dd51..264d9e356 100644 --- a/include/boost/corosio/native/detail/epoll/epoll_types.hpp +++ b/include/boost/corosio/native/detail/epoll/epoll_types.hpp @@ -101,6 +101,11 @@ class epoll_udp_socket final explicit epoll_udp_socket(epoll_udp_service& svc) noexcept : base_type(svc) {} + std::error_code shutdown(corosio::shutdown_type what) noexcept override + { + return this->do_shutdown(static_cast(what)); + } + native_handle_type release_socket() noexcept override { return this->do_release_socket(); diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp index 7d940beee..66b6e3d34 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp @@ -117,33 +117,32 @@ class BOOST_COROSIO_DECL io_uring_random_access_file final return static_cast(st.st_size); } - void resize(std::uint64_t new_size) override + std::error_code resize(std::uint64_t new_size) noexcept override { if (new_size > static_cast( (std::numeric_limits::max)())) - throw_system_error( - make_err(EOVERFLOW), "random_access_file::resize"); + return make_err(EOVERFLOW); if (::ftruncate(fd_, static_cast(new_size)) < 0) - throw_system_error( - make_err(errno), "random_access_file::resize"); + return make_err(errno); + return {}; } - void sync_data() override + std::error_code sync_data() noexcept override { #if BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO if (::fdatasync(fd_) < 0) #else if (::fsync(fd_) < 0) #endif - throw_system_error( - make_err(errno), "random_access_file::sync_data"); + return make_err(errno); + return {}; } - void sync_all() override + std::error_code sync_all() noexcept override { if (::fsync(fd_) < 0) - throw_system_error( - make_err(errno), "random_access_file::sync_all"); + return make_err(errno); + return {}; } native_handle_type release() override @@ -153,10 +152,11 @@ class BOOST_COROSIO_DECL io_uring_random_access_file final return fd; } - void assign(native_handle_type handle) override + std::error_code assign(native_handle_type handle) noexcept override { close_file(); fd_ = handle; + return {}; } // -- Internal -- diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_scheduler.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_scheduler.hpp index ba2eaf2b5..0bba10032 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_scheduler.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_scheduler.hpp @@ -100,7 +100,8 @@ class BOOST_COROSIO_DECL io_uring_scheduler final /// Watch the read end of the POSIX signal self-pipe (see scheduler.hpp). /// Submits a multishot POLL on @p read_fd; on readiness the drain+deliver /// runs in dispatch context via signal_drain_op_. - void register_signal_reader(int read_fd) override; + [[nodiscard]] std::error_code + register_signal_reader(int read_fd) override; /** Return the underlying liburing ring. @@ -802,7 +803,7 @@ io_uring_scheduler::prep_multishot_poll(int fd, void* data) noexcept ::io_uring_sqe_set_data(sqe, data); } -inline void +inline std::error_code io_uring_scheduler::register_signal_reader(int read_fd) { // Called once per service from add_signal(), holding neither the @@ -817,7 +818,10 @@ io_uring_scheduler::register_signal_reader(int read_fd) lock_type lock(ring_mutex_); prep_multishot_poll(read_fd, &signal_pipe_sentinel_); - ::io_uring_submit(&ring_); + int rc = ::io_uring_submit(&ring_); + if (rc < 0) + return make_err(-rc); + return {}; } inline void diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp index d470638ee..1051e7deb 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp @@ -124,31 +124,32 @@ class BOOST_COROSIO_DECL io_uring_stream_file final return static_cast(st.st_size); } - void resize(std::uint64_t new_size) override + std::error_code resize(std::uint64_t new_size) noexcept override { if (new_size > static_cast( (std::numeric_limits::max)())) - throw_system_error( - make_err(EOVERFLOW), "stream_file::resize"); + return make_err(EOVERFLOW); if (::ftruncate(fd_, static_cast(new_size)) < 0) - throw_system_error(make_err(errno), "stream_file::resize"); + return make_err(errno); + return {}; } - void sync_data() override + std::error_code sync_data() noexcept override { #if BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO if (::fdatasync(fd_) < 0) #else if (::fsync(fd_) < 0) #endif - throw_system_error( - make_err(errno), "stream_file::sync_data"); + return make_err(errno); + return {}; } - void sync_all() override + std::error_code sync_all() noexcept override { if (::fsync(fd_) < 0) - throw_system_error(make_err(errno), "stream_file::sync_all"); + return make_err(errno); + return {}; } native_handle_type release() override @@ -158,14 +159,15 @@ class BOOST_COROSIO_DECL io_uring_stream_file final return fd; } - void assign(native_handle_type handle) override + std::error_code assign(native_handle_type handle) noexcept override { close_file(); fd_ = handle; + return {}; } - std::uint64_t seek( - std::int64_t offset, file_base::seek_basis origin) override + capy::io_result seek( + std::int64_t offset, file_base::seek_basis origin) noexcept override { int whence = SEEK_SET; if (origin == file_base::seek_cur) whence = SEEK_CUR; @@ -173,8 +175,8 @@ class BOOST_COROSIO_DECL io_uring_stream_file final off_t r = ::lseek(fd_, static_cast(offset), whence); if (r == static_cast(-1)) - throw_system_error(make_err(errno), "stream_file::seek"); - return static_cast(r); + return {make_err(errno), 0}; + return {std::error_code{}, static_cast(r)}; } // -- Internal -- diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp index f6c7fe58a..8ee7609ef 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp @@ -1936,6 +1936,14 @@ class BOOST_COROSIO_DECL io_uring_udp_socket final // native_handle / is_open / set_option / get_option / local_endpoint // are inherited from native_socket_base. + std::error_code shutdown( + udp_socket::shutdown_type what) noexcept override + { + if (::shutdown(fd_, static_cast(what)) != 0) + return make_err(errno); + return {}; + } + native_handle_type release_socket() noexcept override { // Flush while the fd is still open so the kernel resolves diff --git a/include/boost/corosio/native/detail/iocp/win_file_service.hpp b/include/boost/corosio/native/detail/iocp/win_file_service.hpp index 73b197447..4352fada6 100644 --- a/include/boost/corosio/native/detail/iocp/win_file_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_file_service.hpp @@ -265,34 +265,35 @@ win_stream_file_internal::size() const return static_cast(li.QuadPart); } -inline void -win_stream_file_internal::resize(std::uint64_t new_size) +inline std::error_code +win_stream_file_internal::resize(std::uint64_t new_size) noexcept { LARGE_INTEGER li; li.QuadPart = static_cast(new_size); if (!::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN)) - throw_system_error(make_err(::GetLastError()), "stream_file::resize"); + return make_err(::GetLastError()); if (!::SetEndOfFile(handle_)) - throw_system_error(make_err(::GetLastError()), "stream_file::resize"); + return make_err(::GetLastError()); + return {}; } -inline void -win_stream_file_internal::sync_data() +inline std::error_code +win_stream_file_internal::sync_data() noexcept { // Attempt data-only flush; fall back to full flush if (svc_.try_flush_data(handle_)) - return; + return {}; if (!::FlushFileBuffers(handle_)) - throw_system_error( - make_err(::GetLastError()), "stream_file::sync_data"); + return make_err(::GetLastError()); + return {}; } -inline void -win_stream_file_internal::sync_all() +inline std::error_code +win_stream_file_internal::sync_all() noexcept { if (!::FlushFileBuffers(handle_)) - throw_system_error( - make_err(::GetLastError()), "stream_file::sync_all"); + return make_err(::GetLastError()); + return {}; } inline native_handle_type @@ -304,8 +305,8 @@ win_stream_file_internal::release() return reinterpret_cast(h); } -inline void -win_stream_file_internal::assign(native_handle_type handle) +inline std::error_code +win_stream_file_internal::assign(native_handle_type handle) noexcept { close_handle(); HANDLE h = reinterpret_cast(handle); @@ -313,16 +314,16 @@ win_stream_file_internal::assign(native_handle_type handle) if (!::CreateIoCompletionPort( h, static_cast(svc_.iocp_handle()), key_io, 0)) { - throw_system_error( - make_err(::GetLastError()), "stream_file::assign"); + return make_err(::GetLastError()); } handle_ = h; offset_ = 0; + return {}; } -inline std::uint64_t +inline capy::io_result win_stream_file_internal::seek( - std::int64_t offset, file_base::seek_basis origin) + std::int64_t offset, file_base::seek_basis origin) noexcept { // We manage offset_ ourselves (same as POSIX impl). std::int64_t new_pos; @@ -339,17 +340,15 @@ win_stream_file_internal::seek( { LARGE_INTEGER li; if (!::GetFileSizeEx(handle_, &li)) - throw_system_error( - make_err(::GetLastError()), "stream_file::seek"); + return {make_err(::GetLastError()), 0}; new_pos = li.QuadPart + offset; } if (new_pos < 0) - throw_system_error( - make_err(ERROR_NEGATIVE_SEEK), "stream_file::seek"); + return {make_err(ERROR_NEGATIVE_SEEK), 0}; offset_ = static_cast(new_pos); - return offset_; + return {std::error_code{}, offset_}; } inline std::coroutine_handle<> @@ -539,22 +538,22 @@ win_stream_file::size() const return internal_->size(); } -inline void -win_stream_file::resize(std::uint64_t new_size) +inline std::error_code +win_stream_file::resize(std::uint64_t new_size) noexcept { - internal_->resize(new_size); + return internal_->resize(new_size); } -inline void -win_stream_file::sync_data() +inline std::error_code +win_stream_file::sync_data() noexcept { - internal_->sync_data(); + return internal_->sync_data(); } -inline void -win_stream_file::sync_all() +inline std::error_code +win_stream_file::sync_all() noexcept { - internal_->sync_all(); + return internal_->sync_all(); } inline native_handle_type @@ -563,14 +562,14 @@ win_stream_file::release() return internal_->release(); } -inline void -win_stream_file::assign(native_handle_type handle) +inline std::error_code +win_stream_file::assign(native_handle_type handle) noexcept { - internal_->assign(handle); + return internal_->assign(handle); } -inline std::uint64_t -win_stream_file::seek(std::int64_t offset, file_base::seek_basis origin) +inline capy::io_result +win_stream_file::seek(std::int64_t offset, file_base::seek_basis origin) noexcept { return internal_->seek(offset, origin); } diff --git a/include/boost/corosio/native/detail/iocp/win_random_access_file.hpp b/include/boost/corosio/native/detail/iocp/win_random_access_file.hpp index 79fefab72..5b17942a2 100644 --- a/include/boost/corosio/native/detail/iocp/win_random_access_file.hpp +++ b/include/boost/corosio/native/detail/iocp/win_random_access_file.hpp @@ -105,11 +105,11 @@ class win_random_access_file_internal void close_handle() noexcept; std::uint64_t size() const; - void resize(std::uint64_t new_size); - void sync_data(); - void sync_all(); + std::error_code resize(std::uint64_t new_size) noexcept; + std::error_code sync_data() noexcept; + std::error_code sync_all() noexcept; native_handle_type release(); - void assign(native_handle_type handle); + std::error_code assign(native_handle_type handle) noexcept; }; /** Random-access file implementation wrapper for IOCP-based I/O. */ @@ -146,11 +146,11 @@ class win_random_access_file final native_handle_type native_handle() const noexcept override; void cancel() noexcept override; std::uint64_t size() const override; - void resize(std::uint64_t new_size) override; - void sync_data() override; - void sync_all() override; + std::error_code resize(std::uint64_t new_size) noexcept override; + std::error_code sync_data() noexcept override; + std::error_code sync_all() noexcept override; native_handle_type release() override; - void assign(native_handle_type handle) override; + std::error_code assign(native_handle_type handle) noexcept override; win_random_access_file_internal* get_internal() const noexcept; }; diff --git a/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp b/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp index 7f3d90595..ad706443e 100644 --- a/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp @@ -249,36 +249,35 @@ win_random_access_file_internal::size() const return static_cast(li.QuadPart); } -inline void -win_random_access_file_internal::resize(std::uint64_t new_size) +inline std::error_code +win_random_access_file_internal::resize(std::uint64_t new_size) noexcept { LARGE_INTEGER li; li.QuadPart = static_cast(new_size); if (!::SetFilePointerEx(handle_, li, nullptr, FILE_BEGIN)) - throw_system_error( - make_err(::GetLastError()), "random_access_file::resize"); + return make_err(::GetLastError()); if (!::SetEndOfFile(handle_)) - throw_system_error( - make_err(::GetLastError()), "random_access_file::resize"); + return make_err(::GetLastError()); + return {}; } -inline void -win_random_access_file_internal::sync_data() +inline std::error_code +win_random_access_file_internal::sync_data() noexcept { // Attempt data-only flush; fall back to full flush if (svc_.try_flush_data(handle_)) - return; + return {}; if (!::FlushFileBuffers(handle_)) - throw_system_error( - make_err(::GetLastError()), "random_access_file::sync_data"); + return make_err(::GetLastError()); + return {}; } -inline void -win_random_access_file_internal::sync_all() +inline std::error_code +win_random_access_file_internal::sync_all() noexcept { if (!::FlushFileBuffers(handle_)) - throw_system_error( - make_err(::GetLastError()), "random_access_file::sync_all"); + return make_err(::GetLastError()); + return {}; } inline native_handle_type @@ -289,8 +288,8 @@ win_random_access_file_internal::release() return reinterpret_cast(h); } -inline void -win_random_access_file_internal::assign(native_handle_type handle) +inline std::error_code +win_random_access_file_internal::assign(native_handle_type handle) noexcept { close_handle(); HANDLE h = reinterpret_cast(handle); @@ -298,10 +297,10 @@ win_random_access_file_internal::assign(native_handle_type handle) if (!::CreateIoCompletionPort( h, static_cast(svc_.iocp_handle()), key_io, 0)) { - throw_system_error( - make_err(::GetLastError()), "random_access_file::assign"); + return make_err(::GetLastError()); } handle_ = h; + return {}; } inline std::coroutine_handle<> @@ -506,22 +505,22 @@ win_random_access_file::size() const return internal_->size(); } -inline void -win_random_access_file::resize(std::uint64_t new_size) +inline std::error_code +win_random_access_file::resize(std::uint64_t new_size) noexcept { - internal_->resize(new_size); + return internal_->resize(new_size); } -inline void -win_random_access_file::sync_data() +inline std::error_code +win_random_access_file::sync_data() noexcept { - internal_->sync_data(); + return internal_->sync_data(); } -inline void -win_random_access_file::sync_all() +inline std::error_code +win_random_access_file::sync_all() noexcept { - internal_->sync_all(); + return internal_->sync_all(); } inline native_handle_type @@ -530,10 +529,10 @@ win_random_access_file::release() return internal_->release(); } -inline void -win_random_access_file::assign(native_handle_type handle) +inline std::error_code +win_random_access_file::assign(native_handle_type handle) noexcept { - internal_->assign(handle); + return internal_->assign(handle); } inline win_random_access_file_internal* diff --git a/include/boost/corosio/native/detail/iocp/win_stream_file.hpp b/include/boost/corosio/native/detail/iocp/win_stream_file.hpp index d9ad1fff6..9f338a503 100644 --- a/include/boost/corosio/native/detail/iocp/win_stream_file.hpp +++ b/include/boost/corosio/native/detail/iocp/win_stream_file.hpp @@ -115,12 +115,13 @@ class win_stream_file_internal void close_handle() noexcept; std::uint64_t size() const; - void resize(std::uint64_t new_size); - void sync_data(); - void sync_all(); + std::error_code resize(std::uint64_t new_size) noexcept; + std::error_code sync_data() noexcept; + std::error_code sync_all() noexcept; native_handle_type release(); - void assign(native_handle_type handle); - std::uint64_t seek(std::int64_t offset, file_base::seek_basis origin); + std::error_code assign(native_handle_type handle) noexcept; + capy::io_result + seek(std::int64_t offset, file_base::seek_basis origin) noexcept; }; /** Stream file implementation wrapper for IOCP-based I/O. @@ -159,12 +160,13 @@ class win_stream_file final native_handle_type native_handle() const noexcept override; void cancel() noexcept override; std::uint64_t size() const override; - void resize(std::uint64_t new_size) override; - void sync_data() override; - void sync_all() override; + std::error_code resize(std::uint64_t new_size) noexcept override; + std::error_code sync_data() noexcept override; + std::error_code sync_all() noexcept override; native_handle_type release() override; - void assign(native_handle_type handle) override; - std::uint64_t seek(std::int64_t offset, file_base::seek_basis origin) override; + std::error_code assign(native_handle_type handle) noexcept override; + capy::io_result + seek(std::int64_t offset, file_base::seek_basis origin) noexcept override; win_stream_file_internal* get_internal() const noexcept; }; diff --git a/include/boost/corosio/native/detail/iocp/win_udp_service.hpp b/include/boost/corosio/native/detail/iocp/win_udp_service.hpp index 71a2a39f4..f90d5eaa1 100644 --- a/include/boost/corosio/native/detail/iocp/win_udp_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_udp_service.hpp @@ -868,6 +868,29 @@ win_udp_socket::native_handle() const noexcept return static_cast(internal_->native_handle()); } +inline std::error_code +win_udp_socket::shutdown(udp_socket::shutdown_type what) noexcept +{ + int how; + switch (what) + { + case udp_socket::shutdown_receive: + how = SD_RECEIVE; + break; + case udp_socket::shutdown_send: + how = SD_SEND; + break; + case udp_socket::shutdown_both: + how = SD_BOTH; + break; + default: + return make_err(WSAEINVAL); + } + if (::shutdown(internal_->native_handle(), how) != 0) + return make_err(WSAGetLastError()); + return {}; +} + inline native_handle_type win_udp_socket::release_socket() noexcept { diff --git a/include/boost/corosio/native/detail/iocp/win_udp_socket.hpp b/include/boost/corosio/native/detail/iocp/win_udp_socket.hpp index b76fd9cd5..b0cd41dcd 100644 --- a/include/boost/corosio/native/detail/iocp/win_udp_socket.hpp +++ b/include/boost/corosio/native/detail/iocp/win_udp_socket.hpp @@ -320,6 +320,8 @@ class win_udp_socket final native_handle_type native_handle() const noexcept override; + std::error_code shutdown(udp_socket::shutdown_type what) noexcept override; + native_handle_type release_socket() noexcept override; std::error_code set_option( diff --git a/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp b/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp index 95885750d..5eb156d40 100644 --- a/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp +++ b/include/boost/corosio/native/detail/kqueue/kqueue_scheduler.hpp @@ -148,10 +148,10 @@ class BOOST_COROSIO_DECL kqueue_scheduler final : public reactor_scheduler void deregister_descriptor(int fd) const; /// Watch the read end of the POSIX signal self-pipe (see scheduler.hpp). - void register_signal_reader(int read_fd) override + [[nodiscard]] std::error_code + register_signal_reader(int read_fd) override { - if (auto ec = register_descriptor(read_fd, signal_pipe_reader_.arm())) - detail::throw_system_error(ec, "kevent (register)"); + return register_descriptor(read_fd, signal_pipe_reader_.arm()); } private: diff --git a/include/boost/corosio/native/detail/kqueue/kqueue_types.hpp b/include/boost/corosio/native/detail/kqueue/kqueue_types.hpp index 1cce4bedd..049175105 100644 --- a/include/boost/corosio/native/detail/kqueue/kqueue_types.hpp +++ b/include/boost/corosio/native/detail/kqueue/kqueue_types.hpp @@ -101,6 +101,11 @@ class kqueue_udp_socket final explicit kqueue_udp_socket(kqueue_udp_service& svc) noexcept : base_type(svc) {} + std::error_code shutdown(corosio::shutdown_type what) noexcept override + { + return this->do_shutdown(static_cast(what)); + } + native_handle_type release_socket() noexcept override { return this->do_release_socket(); diff --git a/include/boost/corosio/native/detail/posix/posix_random_access_file.hpp b/include/boost/corosio/native/detail/posix/posix_random_access_file.hpp index 3f3087fae..14171106b 100644 --- a/include/boost/corosio/native/detail/posix/posix_random_access_file.hpp +++ b/include/boost/corosio/native/detail/posix/posix_random_access_file.hpp @@ -165,11 +165,11 @@ class posix_random_access_file final } std::uint64_t size() const override; - void resize(std::uint64_t new_size) override; - void sync_data() override; - void sync_all() override; + std::error_code resize(std::uint64_t new_size) noexcept override; + std::error_code sync_data() noexcept override; + std::error_code sync_all() noexcept override; native_handle_type release() override; - void assign(native_handle_type handle) override; + std::error_code assign(native_handle_type handle) noexcept override; std::error_code open_file( std::filesystem::path const& path, file_base::flags mode); @@ -251,31 +251,35 @@ posix_random_access_file::size() const return static_cast(st.st_size); } -inline void -posix_random_access_file::resize(std::uint64_t new_size) +inline std::error_code +posix_random_access_file::resize(std::uint64_t new_size) noexcept { - if (new_size > static_cast(std::numeric_limits::max())) - throw_system_error(make_err(EOVERFLOW), "random_access_file::resize"); + if (new_size > + static_cast((std::numeric_limits::max)())) + return make_err(EOVERFLOW); if (::ftruncate(fd_, static_cast(new_size)) < 0) - throw_system_error(make_err(errno), "random_access_file::resize"); + return make_err(errno); + return {}; } -inline void -posix_random_access_file::sync_data() +inline std::error_code +posix_random_access_file::sync_data() noexcept { #if BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO if (::fdatasync(fd_) < 0) #else // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO if (::fsync(fd_) < 0) #endif // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO - throw_system_error(make_err(errno), "random_access_file::sync_data"); + return make_err(errno); + return {}; } -inline void -posix_random_access_file::sync_all() +inline std::error_code +posix_random_access_file::sync_all() noexcept { if (::fsync(fd_) < 0) - throw_system_error(make_err(errno), "random_access_file::sync_all"); + return make_err(errno); + return {}; } inline native_handle_type @@ -286,11 +290,12 @@ posix_random_access_file::release() return fd; } -inline void -posix_random_access_file::assign(native_handle_type handle) +inline std::error_code +posix_random_access_file::assign(native_handle_type handle) noexcept { close_file(); fd_ = handle; + return {}; } // read_some_at, write_some_at are defined in diff --git a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp index 048e82ee9..a4dbd6c3b 100644 --- a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp @@ -202,7 +202,8 @@ class BOOST_COROSIO_DECL posix_signal_service final // service, so every io_context that waits on a signal can drain the pipe. // A once_flag (not a bool under mutex_) because registration must run // without holding mutex_ or the signal-state mutex — see add_signal. - std::once_flag reader_once_; + std::mutex reader_mutex_; + bool reader_registered_ = false; intrusive_list impl_list_; @@ -557,9 +558,18 @@ posix_signal_service::add_signal( if (!posix_signal_detail::open_signal_pipe(state)) return make_error_code(std::errc::io_error); } - std::call_once(reader_once_, [this, state] { - sched_->register_signal_reader(state->read_fd); - }); + { + // Success-latched so a failed environmental registration + // (epoll_ctl ENOMEM/ENOSPC) is retried by the next add() + // instead of being lost; the code travels the return channel. + std::lock_guard reg_lock(reader_mutex_); + if (!reader_registered_) + { + if (auto ec = sched_->register_signal_reader(state->read_fd)) + return ec; + reader_registered_ = true; + } + } std::lock_guard state_lock(state->mutex); std::lock_guard lock(mutex_); diff --git a/include/boost/corosio/native/detail/posix/posix_stream_file.hpp b/include/boost/corosio/native/detail/posix/posix_stream_file.hpp index ca5b40c4d..73144f722 100644 --- a/include/boost/corosio/native/detail/posix/posix_stream_file.hpp +++ b/include/boost/corosio/native/detail/posix/posix_stream_file.hpp @@ -200,12 +200,13 @@ class posix_stream_file final } std::uint64_t size() const override; - void resize(std::uint64_t new_size) override; - void sync_data() override; - void sync_all() override; + std::error_code resize(std::uint64_t new_size) noexcept override; + std::error_code sync_data() noexcept override; + std::error_code sync_all() noexcept override; native_handle_type release() override; - void assign(native_handle_type handle) override; - std::uint64_t seek(std::int64_t offset, file_base::seek_basis origin) override; + std::error_code assign(native_handle_type handle) noexcept override; + capy::io_result + seek(std::int64_t offset, file_base::seek_basis origin) noexcept override; // -- Internal -- @@ -317,31 +318,35 @@ posix_stream_file::size() const return static_cast(st.st_size); } -inline void -posix_stream_file::resize(std::uint64_t new_size) +inline std::error_code +posix_stream_file::resize(std::uint64_t new_size) noexcept { - if (new_size > static_cast(std::numeric_limits::max())) - throw_system_error(make_err(EOVERFLOW), "stream_file::resize"); + if (new_size > + static_cast((std::numeric_limits::max)())) + return make_err(EOVERFLOW); if (::ftruncate(fd_, static_cast(new_size)) < 0) - throw_system_error(make_err(errno), "stream_file::resize"); + return make_err(errno); + return {}; } -inline void -posix_stream_file::sync_data() +inline std::error_code +posix_stream_file::sync_data() noexcept { #if BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO if (::fdatasync(fd_) < 0) #else // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO if (::fsync(fd_) < 0) #endif // BOOST_COROSIO_HAS_POSIX_SYNCHRONIZED_IO - throw_system_error(make_err(errno), "stream_file::sync_data"); + return make_err(errno); + return {}; } -inline void -posix_stream_file::sync_all() +inline std::error_code +posix_stream_file::sync_all() noexcept { if (::fsync(fd_) < 0) - throw_system_error(make_err(errno), "stream_file::sync_all"); + return make_err(errno); + return {}; } inline native_handle_type @@ -353,16 +358,18 @@ posix_stream_file::release() return fd; } -inline void -posix_stream_file::assign(native_handle_type handle) +inline std::error_code +posix_stream_file::assign(native_handle_type handle) noexcept { close_file(); fd_ = handle; offset_ = 0; + return {}; } -inline std::uint64_t -posix_stream_file::seek(std::int64_t offset, file_base::seek_basis origin) +inline capy::io_result +posix_stream_file::seek( + std::int64_t offset, file_base::seek_basis origin) noexcept { // We track offset_ ourselves (not the kernel fd offset) // because preadv/pwritev use explicit offsets. @@ -380,18 +387,19 @@ posix_stream_file::seek(std::int64_t offset, file_base::seek_basis origin) { struct stat st; if (::fstat(fd_, &st) < 0) - throw_system_error(make_err(errno), "stream_file::seek"); + return {make_err(errno), 0}; new_pos = st.st_size + offset; } if (new_pos < 0) - throw_system_error(make_err(EINVAL), "stream_file::seek"); - if (new_pos > static_cast(std::numeric_limits::max())) - throw_system_error(make_err(EOVERFLOW), "stream_file::seek"); + return {make_err(EINVAL), 0}; + if (new_pos > + static_cast((std::numeric_limits::max)())) + return {make_err(EOVERFLOW), 0}; offset_ = static_cast(new_pos); - return offset_; + return {std::error_code{}, offset_}; } // -- file_op completion handler -- diff --git a/include/boost/corosio/native/detail/select/select_scheduler.hpp b/include/boost/corosio/native/detail/select/select_scheduler.hpp index 6390e5dc8..470765342 100644 --- a/include/boost/corosio/native/detail/select/select_scheduler.hpp +++ b/include/boost/corosio/native/detail/select/select_scheduler.hpp @@ -132,10 +132,10 @@ class BOOST_COROSIO_DECL select_scheduler final : public reactor_scheduler void notify_reactor() const; /// Watch the read end of the POSIX signal self-pipe (see scheduler.hpp). - void register_signal_reader(int read_fd) override + [[nodiscard]] std::error_code + register_signal_reader(int read_fd) override { - if (auto ec = register_descriptor(read_fd, signal_pipe_reader_.arm())) - detail::throw_system_error(ec, "select: register"); + return register_descriptor(read_fd, signal_pipe_reader_.arm()); } private: diff --git a/include/boost/corosio/native/detail/select/select_types.hpp b/include/boost/corosio/native/detail/select/select_types.hpp index 4651dd86d..67994e2af 100644 --- a/include/boost/corosio/native/detail/select/select_types.hpp +++ b/include/boost/corosio/native/detail/select/select_types.hpp @@ -101,6 +101,11 @@ class select_udp_socket final explicit select_udp_socket(select_udp_service& svc) noexcept : base_type(svc) {} + std::error_code shutdown(corosio::shutdown_type what) noexcept override + { + return this->do_shutdown(static_cast(what)); + } + native_handle_type release_socket() noexcept override { return this->do_release_socket(); diff --git a/include/boost/corosio/native/native_local_datagram_socket.hpp b/include/boost/corosio/native/native_local_datagram_socket.hpp index 8a1be0895..a075476bc 100644 --- a/include/boost/corosio/native/native_local_datagram_socket.hpp +++ b/include/boost/corosio/native/native_local_datagram_socket.hpp @@ -194,7 +194,9 @@ class native_local_datagram_socket : public local_datagram_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. auto-open). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -426,9 +428,10 @@ class native_local_datagram_socket : public local_datagram_socket */ auto connect(corosio::local_endpoint ep) { + native_connect_awaitable aw(*this, ep); if (!is_open()) - open(); - return native_connect_awaitable(*this, ep); + aw.ec_ = open(); + return aw; } /** Send a datagram to the connected peer. diff --git a/include/boost/corosio/native/native_local_stream_socket.hpp b/include/boost/corosio/native/native_local_stream_socket.hpp index eeb92ff5a..1792e5847 100644 --- a/include/boost/corosio/native/native_local_stream_socket.hpp +++ b/include/boost/corosio/native/native_local_stream_socket.hpp @@ -175,7 +175,9 @@ class native_local_stream_socket : public local_stream_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. auto-open). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -306,14 +308,15 @@ class native_local_stream_socket : public local_stream_socket @return An awaitable yielding `io_result<>`. - @throws std::system_error if the socket needs to be opened - and the open fails. + If the socket needs to be opened and the open fails, the + awaitable completes immediately with that error. */ auto connect(corosio::local_endpoint ep) { + native_connect_awaitable aw(*this, ep); if (!is_open()) - open(); - return native_connect_awaitable(*this, ep); + aw.ec_ = open(); + return aw; } /** Asynchronously wait for the socket to be ready. diff --git a/include/boost/corosio/native/native_udp_socket.hpp b/include/boost/corosio/native/native_udp_socket.hpp index 564e438e5..7c08a8c88 100644 --- a/include/boost/corosio/native/native_udp_socket.hpp +++ b/include/boost/corosio/native/native_udp_socket.hpp @@ -192,7 +192,9 @@ class native_udp_socket : public udp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. auto-open). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -432,14 +434,15 @@ class native_udp_socket : public udp_socket @return An awaitable yielding `io_result<>`. - @throws std::system_error if the socket needs to be opened - and the open fails. + If the socket needs to be opened and the open fails, the + awaitable completes immediately with that error. */ auto connect(endpoint ep) { + native_connect_awaitable aw(*this, ep); if (!is_open()) - open(ep.is_v6() ? udp::v6() : udp::v4()); - return native_connect_awaitable(*this, ep); + aw.ec_ = open(ep.is_v6() ? udp::v6() : udp::v4()); + return aw; } /** Send a datagram to the connected peer. diff --git a/include/boost/corosio/random_access_file.hpp b/include/boost/corosio/random_access_file.hpp index 9d15e0bc6..5d11ac593 100644 --- a/include/boost/corosio/random_access_file.hpp +++ b/include/boost/corosio/random_access_file.hpp @@ -55,7 +55,8 @@ namespace boost::corosio { @code io_context ioc; random_access_file f(ioc); - f.open("data.bin", file_base::read_only); + if (auto ec = f.open("data.bin", file_base::read_only)) + co_return; // report the error char buf[4096]; auto [ec, n] = co_await f.read_some_at( @@ -121,19 +122,19 @@ class BOOST_COROSIO_DECL random_access_file : public io_object virtual std::uint64_t size() const = 0; /// Resize the file to @p new_size bytes. - virtual void resize(std::uint64_t new_size) = 0; + virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; /// Synchronize file data to stable storage. - virtual void sync_data() = 0; + virtual std::error_code sync_data() noexcept = 0; /// Synchronize file data and metadata to stable storage. - virtual void sync_all() = 0; + virtual std::error_code sync_all() noexcept = 0; /// Release ownership of the native handle. virtual native_handle_type release() = 0; /// Adopt an existing native handle. - virtual void assign(native_handle_type handle) = 0; + virtual std::error_code assign(native_handle_type handle) noexcept = 0; }; /** Awaitable for async read-at operations. */ @@ -264,15 +265,20 @@ class BOOST_COROSIO_DECL random_access_file : public io_object /** Open a file. + Failures such as a missing file or insufficient permissions + are expected runtime conditions and are reported through the + returned error code. If the file is already open, it is + closed first. + @param path The filesystem path to open. @param mode Bitmask of @ref file_base::flags specifying access mode and creation behavior. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open( + [[nodiscard]] std::error_code open( std::filesystem::path const& path, - file_base::flags mode = file_base::read_only); + file_base::flags mode = file_base::read_only) noexcept; /** Close the file. @@ -331,17 +337,44 @@ class BOOST_COROSIO_DECL random_access_file : public io_object /** Get the native file descriptor or handle. */ native_handle_type native_handle() const noexcept; - /** Return the file size in bytes. */ + /** Return the file size in bytes. + + @throws std::system_error If the file is not open, or if the + underlying size query fails. + */ std::uint64_t size() const; - /** Resize the file. */ - void resize(std::uint64_t new_size); + /** Resize the file to @p new_size bytes. + + Failures such as insufficient disk space are reported + through the returned error code. A closed file reports + `errc::bad_file_descriptor`. + + @param new_size The new file size. + + @return The error code, empty on success. + */ + [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; + + /** Synchronize file data to stable storage. + + Write-back failures such as device I/O errors surface here + and are reported through the returned error code. A closed + file reports `errc::bad_file_descriptor`. + + @return The error code, empty on success. + */ + [[nodiscard]] std::error_code sync_data() noexcept; - /** Synchronize file data to stable storage. */ - void sync_data(); + /** Synchronize file data and metadata to stable storage. - /** Synchronize file data and metadata to stable storage. */ - void sync_all(); + Write-back failures such as device I/O errors surface here + and are reported through the returned error code. A closed + file reports `errc::bad_file_descriptor`. + + @return The error code, empty on success. + */ + [[nodiscard]] std::error_code sync_all() noexcept; /** Release ownership of the native handle. @@ -355,11 +388,15 @@ class BOOST_COROSIO_DECL random_access_file : public io_object /** Adopt an existing native handle. Closes any currently open file before adopting. - The file object takes ownership of the handle. + The file object takes ownership of the handle. Handles + created elsewhere may be unsuitable for asynchronous I/O; + such failures are reported through the returned error code. @param handle The native file descriptor or handle. + + @return The error code, empty on success. */ - void assign(native_handle_type handle); + [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; protected: /// Construct from a pre-built handle (for native_random_access_file). diff --git a/include/boost/corosio/signal_set.hpp b/include/boost/corosio/signal_set.hpp index d6458d52f..858c1f149 100644 --- a/include/boost/corosio/signal_set.hpp +++ b/include/boost/corosio/signal_set.hpp @@ -304,7 +304,7 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set Returns `errc::invalid_argument` if the signal is already registered with different flags. */ - std::error_code add(int signal_number, flags_t flags); + [[nodiscard]] std::error_code add(int signal_number, flags_t flags); /** Add a signal to the signal set with default flags. @@ -314,7 +314,7 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set @return Success, or an error if the signal could not be added. */ - std::error_code add(int signal_number) + [[nodiscard]] std::error_code add(int signal_number) { return add(signal_number, none); } @@ -328,7 +328,7 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set @return Success, or an error if the signal could not be removed. */ - std::error_code remove(int signal_number); + [[nodiscard]] std::error_code remove(int signal_number); /** Remove all signals from the signal set. @@ -337,7 +337,7 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set @return Success, or an error if resetting any signal handler fails. */ - std::error_code clear(); + [[nodiscard]] std::error_code clear(); protected: explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {} diff --git a/include/boost/corosio/stream_file.hpp b/include/boost/corosio/stream_file.hpp index 292908ae2..bba649de5 100644 --- a/include/boost/corosio/stream_file.hpp +++ b/include/boost/corosio/stream_file.hpp @@ -18,10 +18,12 @@ #include #include #include +#include #include #include #include +#include namespace boost::corosio { @@ -48,7 +50,8 @@ namespace boost::corosio { @code io_context ioc; stream_file f(ioc); - f.open("data.bin", file_base::read_only); + if (auto ec = f.open("data.bin", file_base::read_only)) + co_return; // report the error char buf[4096]; auto [ec, n] = co_await f.read_some( @@ -78,28 +81,28 @@ class BOOST_COROSIO_DECL stream_file : public io_stream virtual std::uint64_t size() const = 0; /// Resize the file to @p new_size bytes. - virtual void resize(std::uint64_t new_size) = 0; + virtual std::error_code resize(std::uint64_t new_size) noexcept = 0; /// Synchronize file data to stable storage. - virtual void sync_data() = 0; + virtual std::error_code sync_data() noexcept = 0; /// Synchronize file data and metadata to stable storage. - virtual void sync_all() = 0; + virtual std::error_code sync_all() noexcept = 0; /// Release ownership of the native handle. virtual native_handle_type release() = 0; /// Adopt an existing native handle. - virtual void assign(native_handle_type handle) = 0; + virtual std::error_code assign(native_handle_type handle) noexcept = 0; /** Move the file position. @param offset Signed offset from @p origin. @param origin The reference point for the seek. - @return The new absolute position. + @return The error code and new absolute position. */ - virtual std::uint64_t - seek(std::int64_t offset, file_base::seek_basis origin) = 0; + virtual capy::io_result + seek(std::int64_t offset, file_base::seek_basis origin) noexcept = 0; }; /** Destructor. @@ -153,15 +156,20 @@ class BOOST_COROSIO_DECL stream_file : public io_stream /** Open a file. + Failures such as a missing file or insufficient permissions + are expected runtime conditions and are reported through the + returned error code. If the file is already open, it is + closed first. + @param path The filesystem path to open. @param mode Bitmask of @ref file_base::flags specifying access mode and creation behavior. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open( + [[nodiscard]] std::error_code open( std::filesystem::path const& path, - file_base::flags mode = file_base::read_only); + file_base::flags mode = file_base::read_only) noexcept; /** Close the file. @@ -199,28 +207,42 @@ class BOOST_COROSIO_DECL stream_file : public io_stream /** Return the file size in bytes. - @throws std::system_error on failure. + @throws std::system_error If the file is not open, or if the + underlying size query fails. */ std::uint64_t size() const; /** Resize the file to @p new_size bytes. + Failures such as insufficient disk space are reported + through the returned error code. A closed file reports + `errc::bad_file_descriptor`. + @param new_size The new file size. - @throws std::system_error on failure. + + @return The error code, empty on success. */ - void resize(std::uint64_t new_size); + [[nodiscard]] std::error_code resize(std::uint64_t new_size) noexcept; /** Synchronize file data to stable storage. - @throws std::system_error on failure. + Write-back failures such as device I/O errors surface here + and are reported through the returned error code. A closed + file reports `errc::bad_file_descriptor`. + + @return The error code, empty on success. */ - void sync_data(); + [[nodiscard]] std::error_code sync_data() noexcept; /** Synchronize file data and metadata to stable storage. - @throws std::system_error on failure. + Write-back failures such as device I/O errors surface here + and are reported through the returned error code. A closed + file reports `errc::bad_file_descriptor`. + + @return The error code, empty on success. */ - void sync_all(); + [[nodiscard]] std::error_code sync_all() noexcept; /** Release ownership of the native handle. @@ -234,23 +256,31 @@ class BOOST_COROSIO_DECL stream_file : public io_stream /** Adopt an existing native handle. Closes any currently open file before adopting. - The file object takes ownership of the handle. + The file object takes ownership of the handle. Handles + created elsewhere may be unsuitable for asynchronous I/O; + such failures are reported through the returned error code. @param handle The native file descriptor or handle. - @throws std::system_error on failure. + + @return The error code, empty on success. */ - void assign(native_handle_type handle); + [[nodiscard]] std::error_code assign(native_handle_type handle) noexcept; /** Move the file position. + Positions beyond the end of the file are allowed. A + resulting negative position is reported through the error + code, as offsets often originate from file contents. A + closed file reports `errc::bad_file_descriptor`. + @param offset Signed offset from @p origin. @param origin The reference point for the seek. - @return The new absolute position. - @throws std::system_error on failure. + + @return The error code and new absolute position. */ - std::uint64_t + [[nodiscard]] capy::io_result seek(std::int64_t offset, - file_base::seek_basis origin = file_base::seek_set); + file_base::seek_basis origin = file_base::seek_set) noexcept; protected: /// Default-construct (for derived types that initialize io_object directly). diff --git a/include/boost/corosio/tcp_acceptor.hpp b/include/boost/corosio/tcp_acceptor.hpp index 7dde39951..533da25f7 100644 --- a/include/boost/corosio/tcp_acceptor.hpp +++ b/include/boost/corosio/tcp_acceptor.hpp @@ -290,8 +290,10 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @endcode @see bind, listen + + @return The error code, empty on success. */ - void open(tcp proto = tcp::v4()); + [[nodiscard]] std::error_code open(tcp proto = tcp::v4()) noexcept; /** Bind to a local endpoint. @@ -509,10 +511,11 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @param fd The native socket to adopt. On success the object owns it and will close it. - @throws std::system_error On validation or registration - failure. + @return The error code, empty on success. Validation and + registration failures are normal runtime conditions when + adopting foreign descriptors. */ - void assign(native_handle_type fd); + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; /** Release ownership of the native socket handle. diff --git a/include/boost/corosio/tcp_server.hpp b/include/boost/corosio/tcp_server.hpp index cfac7f78d..620c02ace 100644 --- a/include/boost/corosio/tcp_server.hpp +++ b/include/boost/corosio/tcp_server.hpp @@ -635,7 +635,7 @@ class BOOST_COROSIO_DECL tcp_server @return The error code if binding fails. */ - std::error_code bind(endpoint ep); + [[nodiscard]] std::error_code bind(endpoint ep); /** Set the worker pool. diff --git a/include/boost/corosio/tcp_socket.hpp b/include/boost/corosio/tcp_socket.hpp index 349eaca15..1768b6c25 100644 --- a/include/boost/corosio/tcp_socket.hpp +++ b/include/boost/corosio/tcp_socket.hpp @@ -302,12 +302,17 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream so explicit `open()` is only needed when socket options must be set before connecting. + Failures such as descriptor exhaustion are normal runtime + conditions and are reported through the returned error code. + Opening an already-open socket is a no-op that reports + success. + @param proto The protocol (IPv4 or IPv6). Defaults to `tcp::v4()`. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open(tcp proto = tcp::v4()); + [[nodiscard]] std::error_code open(tcp proto = tcp::v4()) noexcept; /** Bind the socket to a local endpoint. @@ -373,8 +378,8 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream - operation_canceled: Cancelled via stop_token or cancel(). Check `ec == cond::canceled` for portable comparison. - @throws std::system_error if the socket needs to be opened - and the open fails. + If the socket needs to be opened and the open fails, the + awaitable completes immediately with that error. @par Preconditions This socket must outlive the returned awaitable. @@ -388,9 +393,10 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream */ auto connect(endpoint ep) { + connect_awaitable aw(*this, ep); if (!is_open()) - open(ep.is_v6() ? tcp::v6() : tcp::v4()); - return connect_awaitable(*this, ep); + aw.ec_ = open(ep.is_v6() ? tcp::v6() : tcp::v4()); + return aw; } /** Wait for the socket to become ready in a given direction. @@ -466,10 +472,11 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @param fd The native socket to adopt. On success the object owns it and will close it. - @throws std::system_error On validation or registration - failure. + @return The error code, empty on success. Validation and + registration failures are normal runtime conditions when + adopting foreign descriptors. */ - void assign(native_handle_type fd); + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; /** Release ownership of the native socket handle. @@ -519,12 +526,16 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream } @endcode - Any error from the underlying system call is silently discarded - because it is unlikely to be helpful. + Failures such as a peer that already disconnected are + normal runtime conditions and are reported through the + returned error code. A closed socket reports + `errc::bad_file_descriptor`. @param what Determines what operations will no longer be allowed. + + @return The error code, empty on success. */ - void shutdown(shutdown_type what); + [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept; /** Set a socket option. @@ -626,7 +637,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream friend class tcp_acceptor; /// Open the socket for the given protocol triple. - void open_for_family(int family, int type, int protocol); + std::error_code open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/test/mocket.hpp b/include/boost/corosio/test/mocket.hpp index 59a927b83..7db8a9ce0 100644 --- a/include/boost/corosio/test/mocket.hpp +++ b/include/boost/corosio/test/mocket.hpp @@ -563,7 +563,8 @@ make_mocket_pair( bool connect_done = false; Acceptor acc(ctx); - acc.open(); + if (auto open_ec = acc.open()) + throw std::runtime_error("mocket open failed: " + open_ec.message()); acc.set_option(socket_option::reuse_address(true)); if (auto bind_ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) throw std::runtime_error("mocket bind failed: " + bind_ec.message()); @@ -572,7 +573,8 @@ make_mocket_pair( "mocket listen failed: " + listen_ec.message()); auto port = acc.local_endpoint().port(); - peer.open(); + if (auto open_ec = peer.open()) + throw std::runtime_error("mocket open failed: " + open_ec.message()); Socket accepted_socket(ctx); diff --git a/include/boost/corosio/test/socket_pair.hpp b/include/boost/corosio/test/socket_pair.hpp index 0abf6a45e..512f4ad52 100644 --- a/include/boost/corosio/test/socket_pair.hpp +++ b/include/boost/corosio/test/socket_pair.hpp @@ -52,7 +52,8 @@ make_socket_pair(io_context& ctx) bool connect_done = false; Acceptor acc(ctx); - acc.open(); + if (auto open_ec = acc.open()) + throw std::runtime_error("socket_pair open failed: " + open_ec.message()); acc.set_option(socket_option::reuse_address(true)); if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) throw std::runtime_error("socket_pair bind failed: " + ec.message()); @@ -62,7 +63,8 @@ make_socket_pair(io_context& ctx) Socket s1(ctx); Socket s2(ctx); - s2.open(); + if (auto open_ec = s2.open()) + throw std::runtime_error("socket_pair open failed: " + open_ec.message()); capy::run_async(ex)( [](Acceptor& a, Socket& s, std::error_code& ec_out, diff --git a/include/boost/corosio/tls_context.hpp b/include/boost/corosio/tls_context.hpp index 588f143da..51927b3d9 100644 --- a/include/boost/corosio/tls_context.hpp +++ b/include/boost/corosio/tls_context.hpp @@ -337,7 +337,7 @@ class BOOST_COROSIO_DECL tls_context @see use_certificate_file @see use_private_key */ - std::error_code + [[nodiscard]] std::error_code use_certificate(std::string_view certificate, tls_file_format format); /** Load the entity certificate from a file. @@ -361,7 +361,7 @@ class BOOST_COROSIO_DECL tls_context @see use_certificate @see use_private_key_file */ - std::error_code + [[nodiscard]] std::error_code use_certificate_file(std::string_view filename, tls_file_format format); /** Load a certificate chain from a memory buffer. @@ -377,7 +377,7 @@ class BOOST_COROSIO_DECL tls_context @see use_certificate_chain_file */ - std::error_code use_certificate_chain(std::string_view chain); + [[nodiscard]] std::error_code use_certificate_chain(std::string_view chain); /** Load a certificate chain from a file. @@ -397,7 +397,7 @@ class BOOST_COROSIO_DECL tls_context @see use_certificate_chain */ - std::error_code use_certificate_chain_file(std::string_view filename); + [[nodiscard]] std::error_code use_certificate_chain_file(std::string_view filename); /** Load the private key from a memory buffer. @@ -419,7 +419,7 @@ class BOOST_COROSIO_DECL tls_context @see use_private_key_file @see set_password_callback */ - std::error_code + [[nodiscard]] std::error_code use_private_key(std::string_view private_key, tls_file_format format); /** Load the private key from a file. @@ -446,7 +446,7 @@ class BOOST_COROSIO_DECL tls_context @see use_private_key @see set_password_callback */ - std::error_code + [[nodiscard]] std::error_code use_private_key_file(std::string_view filename, tls_file_format format); /** Load credentials from a PKCS#12 bundle in memory. @@ -469,7 +469,7 @@ class BOOST_COROSIO_DECL tls_context @see use_pkcs12_file */ - std::error_code + [[nodiscard]] std::error_code use_pkcs12(std::string_view data, std::string_view passphrase); /** Load credentials from a PKCS#12 file. @@ -498,7 +498,7 @@ class BOOST_COROSIO_DECL tls_context @see use_pkcs12 */ - std::error_code + [[nodiscard]] std::error_code use_pkcs12_file(std::string_view filename, std::string_view passphrase); // @@ -518,7 +518,7 @@ class BOOST_COROSIO_DECL tls_context @see load_verify_file @see set_default_verify_paths */ - std::error_code add_certificate_authority(std::string_view ca); + [[nodiscard]] std::error_code add_certificate_authority(std::string_view ca); /** Load CA certificates from a file. @@ -538,7 +538,7 @@ class BOOST_COROSIO_DECL tls_context @see add_certificate_authority @see add_verify_path */ - std::error_code load_verify_file(std::string_view filename); + [[nodiscard]] std::error_code load_verify_file(std::string_view filename); /** Add a directory of CA certificates for verification. @@ -566,7 +566,7 @@ class BOOST_COROSIO_DECL tls_context @see load_verify_file @see set_default_verify_paths */ - std::error_code add_verify_path(std::string_view path); + [[nodiscard]] std::error_code add_verify_path(std::string_view path); /** Use the system default CA certificate store. @@ -602,7 +602,7 @@ class BOOST_COROSIO_DECL tls_context @see add_verify_path @see set_verify_mode */ - std::error_code set_default_verify_paths(); + [[nodiscard]] std::error_code set_default_verify_paths(); // // Protocol Configuration @@ -626,7 +626,7 @@ class BOOST_COROSIO_DECL tls_context @see set_max_protocol_version */ - std::error_code set_min_protocol_version(tls_version v); + [[nodiscard]] std::error_code set_min_protocol_version(tls_version v); /** Set the maximum TLS protocol version. @@ -645,7 +645,7 @@ class BOOST_COROSIO_DECL tls_context @see set_min_protocol_version */ - std::error_code set_max_protocol_version(tls_version v); + [[nodiscard]] std::error_code set_max_protocol_version(tls_version v); /** Set the allowed cipher suites. @@ -666,7 +666,7 @@ class BOOST_COROSIO_DECL tls_context @note This configures cipher suites for TLS 1.2 and below. For TLS 1.3, use @ref set_ciphersuites_tls13. */ - std::error_code set_ciphersuites(std::string_view ciphers); + [[nodiscard]] std::error_code set_ciphersuites(std::string_view ciphers); /** Set the allowed TLS 1.3 cipher suites. @@ -690,7 +690,7 @@ class BOOST_COROSIO_DECL tls_context @see set_ciphersuites */ - std::error_code set_ciphersuites_tls13(std::string_view ciphers); + [[nodiscard]] std::error_code set_ciphersuites_tls13(std::string_view ciphers); /** Set the ALPN protocol list. @@ -717,7 +717,7 @@ class BOOST_COROSIO_DECL tls_context ctx.set_alpn( { "h2", "http/1.1" } ); @endcode */ - std::error_code set_alpn(std::initializer_list protocols); + [[nodiscard]] std::error_code set_alpn(std::initializer_list protocols); // // Certificate Verification @@ -743,7 +743,7 @@ class BOOST_COROSIO_DECL tls_context @see tls_verify_mode */ - std::error_code set_verify_mode(tls_verify_mode mode); + [[nodiscard]] std::error_code set_verify_mode(tls_verify_mode mode); /** Set the maximum certificate chain verification depth. @@ -755,7 +755,7 @@ class BOOST_COROSIO_DECL tls_context @return Success, or an error if the depth is invalid. */ - std::error_code set_verify_depth(int depth); + [[nodiscard]] std::error_code set_verify_depth(int depth); /** Set a custom certificate verification callback. @@ -818,7 +818,7 @@ class BOOST_COROSIO_DECL tls_context @see set_verify_mode */ template - std::error_code set_verify_callback(Callback callback); + [[nodiscard]] std::error_code set_verify_callback(Callback callback); /** Set a callback for Server Name Indication (SNI). @@ -888,7 +888,7 @@ class BOOST_COROSIO_DECL tls_context @see add_crl_file @see set_revocation_policy */ - std::error_code add_crl(std::string_view crl); + [[nodiscard]] std::error_code add_crl(std::string_view crl); /** Add a Certificate Revocation List from a file. @@ -912,7 +912,7 @@ class BOOST_COROSIO_DECL tls_context @see add_crl @see set_revocation_policy */ - std::error_code add_crl_file(std::string_view filename); + [[nodiscard]] std::error_code add_crl_file(std::string_view filename); /** Set the certificate revocation checking policy. diff --git a/include/boost/corosio/udp_socket.hpp b/include/boost/corosio/udp_socket.hpp index 87e702aba..a5a6ce179 100644 --- a/include/boost/corosio/udp_socket.hpp +++ b/include/boost/corosio/udp_socket.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -89,6 +90,9 @@ namespace boost::corosio { class BOOST_COROSIO_DECL udp_socket : public io_object { public: + using shutdown_type = corosio::shutdown_type; + using enum corosio::shutdown_type; + /** Define backend hooks for UDP socket operations. Platform backends (epoll, kqueue, select) derive from @@ -162,6 +166,9 @@ class BOOST_COROSIO_DECL udp_socket : public io_object */ virtual void cancel() noexcept = 0; + /// Shut down the socket in one or both directions. + virtual std::error_code shutdown(shutdown_type what) noexcept = 0; + /** Set a socket option. @param level The protocol level (e.g. `SOL_SOCKET`). @@ -463,12 +470,17 @@ class BOOST_COROSIO_DECL udp_socket : public io_object Creates a UDP socket and associates it with the platform reactor. + Failures such as descriptor exhaustion are normal runtime + conditions and are reported through the returned error code. + Opening an already-open socket is a no-op that reports + success. + @param proto The protocol (IPv4 or IPv6). Defaults to `udp::v4()`. - @throws std::system_error on failure. + @return The error code, empty on success. */ - void open(udp proto = udp::v4()); + [[nodiscard]] std::error_code open(udp proto = udp::v4()) noexcept; /** Close the socket. @@ -503,6 +515,19 @@ class BOOST_COROSIO_DECL udp_socket : public io_object */ [[nodiscard]] std::error_code bind(endpoint ep); + /** Disable sends or receives on the socket. + + Failures such as an unconnected socket are normal runtime + conditions and are reported through the returned error + code. A closed socket reports `errc::bad_file_descriptor`. + + @param what Determines what operations will no longer be + allowed. + + @return The error code, empty on success. + */ + [[nodiscard]] std::error_code shutdown(shutdown_type what) noexcept; + /** Cancel any pending asynchronous operations. All outstanding operations complete with @@ -541,10 +566,11 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @param fd The native socket to adopt. On success the object owns it and will close it. - @throws std::system_error On validation or registration - failure. + @return The error code, empty on success. Validation and + registration failures are normal runtime conditions when + adopting foreign descriptors. */ - void assign(native_handle_type fd); + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept; /** Release ownership of the native socket handle. @@ -676,14 +702,15 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return An awaitable that completes with `io_result<>`. - @throws std::system_error if the socket needs to be opened - and the open fails. + If the socket needs to be opened and the open fails, the + awaitable completes immediately with that error. */ auto connect(endpoint ep) { + connect_awaitable aw(*this, ep); if (!is_open()) - open(ep.is_v6() ? udp::v6() : udp::v4()); - return connect_awaitable(*this, ep); + aw.ec_ = open(ep.is_v6() ? udp::v6() : udp::v4()); + return aw; } /** Wait for the socket to become ready in a given direction. @@ -776,7 +803,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object private: /// Open the socket for the given protocol triple. - void open_for_family(int family, int type, int protocol); + std::error_code open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/perf/bench/corosio/accept_churn_bench.cpp b/perf/bench/corosio/accept_churn_bench.cpp index 1a50304a5..17724216f 100644 --- a/perf/bench/corosio/accept_churn_bench.cpp +++ b/perf/bench/corosio/accept_churn_bench.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "../../common/native_includes.hpp" @@ -52,7 +53,7 @@ bench_sequential_churn(bench::state& state) corosio::native_io_context ioc; acceptor_type acc(ioc); - acc.open(); + std::ignore = acc.open(); acc.set_option(corosio::native_socket_option::reuse_address(true)); if (auto ec = @@ -76,7 +77,7 @@ bench_sequential_churn(bench::state& state) socket_type client(ioc); socket_type server(ioc); - client.open(); + std::ignore = client.open(); configure_churn_socket(client); capy::run_async(ioc.get_executor())( @@ -135,7 +136,7 @@ bench_sequential_churn_lockless(bench::state& state) opts.locking = corosio::locking_mode::unsafe; corosio::native_io_context ioc(opts, 1); acceptor_type acc(ioc); - acc.open(); + std::ignore = acc.open(); acc.set_option(corosio::native_socket_option::reuse_address(true)); if (auto ec = @@ -159,7 +160,7 @@ bench_sequential_churn_lockless(bench::state& state) socket_type client(ioc); socket_type server(ioc); - client.open(); + std::ignore = client.open(); configure_churn_socket(client); capy::run_async(ioc.get_executor())( @@ -226,7 +227,7 @@ bench_concurrent_churn(bench::state& state) { acceptors.emplace_back(ioc); auto& acc = acceptors.back(); - acc.open(); + std::ignore = acc.open(); acc.set_option(corosio::native_socket_option::reuse_address(true)); if (auto ec = acc.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0))) @@ -251,7 +252,7 @@ bench_concurrent_churn(bench::state& state) socket_type client(ioc); socket_type server(ioc); - client.open(); + std::ignore = client.open(); configure_churn_socket(client); capy::run_async(ioc.get_executor())( @@ -314,7 +315,7 @@ bench_burst_churn(bench::state& state) corosio::native_io_context ioc; acceptor_type acc(ioc); - acc.open(); + std::ignore = acc.open(); acc.set_option(corosio::native_socket_option::reuse_address(true)); if (auto ec = @@ -344,7 +345,7 @@ bench_burst_churn(bench::state& state) for (int i = 0; i < burst_size; ++i) { clients.emplace_back(ioc); - clients.back().open(); + (void)clients.back().open(); configure_churn_socket(clients.back()); capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { @@ -400,7 +401,7 @@ bench_burst_churn_lockless(bench::state& state) opts.locking = corosio::locking_mode::unsafe; corosio::native_io_context ioc(opts, 1); acceptor_type acc(ioc); - acc.open(); + std::ignore = acc.open(); acc.set_option(corosio::native_socket_option::reuse_address(true)); if (auto ec = @@ -430,7 +431,7 @@ bench_burst_churn_lockless(bench::state& state) for (int i = 0; i < burst_size; ++i) { clients.emplace_back(ioc); - clients.back().open(); + (void)clients.back().open(); configure_churn_socket(clients.back()); capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { diff --git a/perf/bench/corosio/http_server_bench.cpp b/perf/bench/corosio/http_server_bench.cpp index c135572a6..ecb28dde8 100644 --- a/perf/bench/corosio/http_server_bench.cpp +++ b/perf/bench/corosio/http_server_bench.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "../common/http_protocol.hpp" #include "../../common/native_includes.hpp" @@ -138,7 +139,7 @@ client_task( buf.erase(0, total_size); } - sock.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = sock.shutdown(corosio::tcp_socket::shutdown_send); } template diff --git a/perf/bench/corosio/local_socket_latency_bench.cpp b/perf/bench/corosio/local_socket_latency_bench.cpp index f26085604..6987a85f2 100644 --- a/perf/bench/corosio/local_socket_latency_bench.cpp +++ b/perf/bench/corosio/local_socket_latency_bench.cpp @@ -29,6 +29,7 @@ #include #include #include +#include namespace corosio = boost::corosio; namespace capy = boost::capy; @@ -72,7 +73,7 @@ unix_pingpong_client_task( co_return; } - client.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = client.shutdown(corosio::local_stream_socket::shutdown_send); } template diff --git a/perf/bench/corosio/local_socket_throughput_bench.cpp b/perf/bench/corosio/local_socket_throughput_bench.cpp index e1efa3dc9..f3282b044 100644 --- a/perf/bench/corosio/local_socket_throughput_bench.cpp +++ b/perf/bench/corosio/local_socket_throughput_bench.cpp @@ -27,6 +27,7 @@ #include #include #include +#include namespace corosio = boost::corosio; namespace capy = boost::capy; @@ -62,7 +63,7 @@ bench_unix_throughput(bench::state& state) if (ec) break; } - writer.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = writer.shutdown(corosio::local_stream_socket::shutdown_send); }; auto read_task = [&]() -> capy::task<> { @@ -125,7 +126,7 @@ bench_unix_bidirectional_throughput(bench::state& state) if (ec) break; } - sock1.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = sock1.shutdown(corosio::local_stream_socket::shutdown_send); }; auto read1_task = [&]() -> capy::task<> { @@ -148,7 +149,7 @@ bench_unix_bidirectional_throughput(bench::state& state) if (ec) break; } - sock2.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = sock2.shutdown(corosio::local_stream_socket::shutdown_send); }; auto read2_task = [&]() -> capy::task<> { @@ -215,7 +216,7 @@ bench_unix_throughput_lockless(bench::state& state) if (ec) break; } - writer.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = writer.shutdown(corosio::local_stream_socket::shutdown_send); }; auto read_task = [&]() -> capy::task<> { @@ -280,7 +281,7 @@ bench_unix_bidirectional_throughput_lockless(bench::state& state) if (ec) break; } - sock1.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = sock1.shutdown(corosio::local_stream_socket::shutdown_send); }; auto read1_task = [&]() -> capy::task<> { @@ -303,7 +304,7 @@ bench_unix_bidirectional_throughput_lockless(bench::state& state) if (ec) break; } - sock2.shutdown(corosio::local_stream_socket::shutdown_send); + std::ignore = sock2.shutdown(corosio::local_stream_socket::shutdown_send); }; auto read2_task = [&]() -> capy::task<> { diff --git a/perf/bench/corosio/socket_latency_bench.cpp b/perf/bench/corosio/socket_latency_bench.cpp index be9bccc69..a9505d250 100644 --- a/perf/bench/corosio/socket_latency_bench.cpp +++ b/perf/bench/corosio/socket_latency_bench.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "../../common/native_includes.hpp" @@ -69,7 +70,7 @@ pingpong_client_task( co_return; } - client.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = client.shutdown(corosio::tcp_socket::shutdown_send); } template diff --git a/perf/bench/corosio/socket_throughput_bench.cpp b/perf/bench/corosio/socket_throughput_bench.cpp index e135cc859..ff096f4b1 100644 --- a/perf/bench/corosio/socket_throughput_bench.cpp +++ b/perf/bench/corosio/socket_throughput_bench.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #endif #include "../../common/native_includes.hpp" @@ -83,7 +84,7 @@ bench_throughput(bench::state& state) if (ec) break; } - writer.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = writer.shutdown(corosio::tcp_socket::shutdown_send); }; auto read_task = [&]() -> capy::task<> { @@ -148,7 +149,7 @@ bench_bidirectional_throughput(bench::state& state) if (ec) break; } - sock1.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = sock1.shutdown(corosio::tcp_socket::shutdown_send); }; auto read1_task = [&]() -> capy::task<> { @@ -171,7 +172,7 @@ bench_bidirectional_throughput(bench::state& state) if (ec) break; } - sock2.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = sock2.shutdown(corosio::tcp_socket::shutdown_send); }; auto read2_task = [&]() -> capy::task<> { @@ -223,7 +224,7 @@ mt_write_coro( if (ec) break; } - sock.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = sock.shutdown(corosio::tcp_socket::shutdown_send); } template @@ -276,7 +277,7 @@ bench_throughput_lockless(bench::state& state) if (ec) break; } - writer.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = writer.shutdown(corosio::tcp_socket::shutdown_send); }; auto read_task = [&]() -> capy::task<> { @@ -343,7 +344,7 @@ bench_bidirectional_throughput_lockless(bench::state& state) if (ec) break; } - sock1.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = sock1.shutdown(corosio::tcp_socket::shutdown_send); }; auto read1_task = [&]() -> capy::task<> { @@ -366,7 +367,7 @@ bench_bidirectional_throughput_lockless(bench::state& state) if (ec) break; } - sock2.shutdown(corosio::tcp_socket::shutdown_send); + std::ignore = sock2.shutdown(corosio::tcp_socket::shutdown_send); }; auto read2_task = [&]() -> capy::task<> { diff --git a/src/corosio/src/local_connect_pair.cpp b/src/corosio/src/local_connect_pair.cpp index 422ac7d4d..a04327474 100644 --- a/src/corosio/src/local_connect_pair.cpp +++ b/src/corosio/src/local_connect_pair.cpp @@ -74,26 +74,18 @@ template std::error_code assign_pair(Socket& a, Socket& b, int a_fd, int b_fd) noexcept { - try - { - a.assign(a_fd); - } - catch (std::system_error const& e) + if (auto ec = a.assign(a_fd)) { ::close(a_fd); ::close(b_fd); - return e.code(); + return ec; } - try - { - b.assign(b_fd); - } - catch (std::system_error const& e) + if (auto ec = b.assign(b_fd)) { a.close(); ::close(b_fd); - return e.code(); + return ec; } return {}; @@ -250,26 +242,18 @@ assign_pair( SOCKET a_sock, SOCKET b_sock) noexcept { - try - { - a.assign(static_cast(a_sock)); - } - catch (std::system_error const& e) + if (auto ec = a.assign(static_cast(a_sock))) { ::closesocket(a_sock); ::closesocket(b_sock); - return e.code(); + return ec; } - try - { - b.assign(static_cast(b_sock)); - } - catch (std::system_error const& e) + if (auto ec = b.assign(static_cast(b_sock))) { a.close(); ::closesocket(b_sock); - return e.code(); + return ec; } return {}; diff --git a/src/corosio/src/local_datagram_socket.cpp b/src/corosio/src/local_datagram_socket.cpp index 2b3cb435f..e2a5b4f9e 100644 --- a/src/corosio/src/local_datagram_socket.cpp +++ b/src/corosio/src/local_datagram_socket.cpp @@ -29,23 +29,22 @@ local_datagram_socket::local_datagram_socket(capy::execution_context& ctx) { } -void -local_datagram_socket::open(local_datagram proto) +std::error_code +local_datagram_socket::open(local_datagram proto) noexcept { if (is_open()) - return; - open_for_family(proto.family(), proto.type(), proto.protocol()); + return {}; + return open_for_family(proto.family(), proto.type(), proto.protocol()); } -void -local_datagram_socket::open_for_family(int family, int type, int protocol) +std::error_code +local_datagram_socket::open_for_family(int family, int type, int protocol) noexcept { auto& svc = static_cast(h_.service()); std::error_code ec = svc.open_socket( static_cast(*h_.get()), family, type, protocol); - if (ec) - detail::throw_system_error(ec, "local_datagram_socket::open"); + return ec; } void @@ -75,32 +74,21 @@ local_datagram_socket::cancel() get().cancel(); } -void -local_datagram_socket::shutdown(shutdown_type what) -{ - if (is_open()) - { - // Best-effort: errors like ENOTCONN are expected and unhelpful - [[maybe_unused]] auto ec = get().shutdown(what); - } -} - -void -local_datagram_socket::shutdown(shutdown_type what, std::error_code& ec) noexcept +std::error_code +local_datagram_socket::shutdown(shutdown_type what) noexcept { - ec = {}; - if (is_open()) - ec = get().shutdown(what); + if (!is_open()) + return make_error_code(std::errc::bad_file_descriptor); + return get().shutdown(what); } -void -local_datagram_socket::assign(native_handle_type fd) +std::error_code +local_datagram_socket::assign(native_handle_type fd) noexcept { auto& svc = static_cast(h_.service()); std::error_code ec = svc.assign_socket( static_cast(*h_.get()), fd); - if (ec) - detail::throw_system_error(ec, "local_datagram_socket::assign"); + return ec; } native_handle_type diff --git a/src/corosio/src/local_stream_acceptor.cpp b/src/corosio/src/local_stream_acceptor.cpp index 39ea54614..e6f8d2e3c 100644 --- a/src/corosio/src/local_stream_acceptor.cpp +++ b/src/corosio/src/local_stream_acceptor.cpp @@ -39,29 +39,27 @@ local_stream_acceptor::local_stream_acceptor(capy::execution_context& ctx) { } -void -local_stream_acceptor::open(local_stream proto) +std::error_code +local_stream_acceptor::open(local_stream proto) noexcept { if (is_open()) - return; + return {}; auto& svc = static_cast(h_.service()); auto ec = svc.open_acceptor_socket( static_cast(*h_.get()), proto.family(), proto.type(), proto.protocol()); - if (ec) - detail::throw_system_error(ec, "local_stream_acceptor::open"); + return ec; } -void -local_stream_acceptor::assign(native_handle_type fd) +std::error_code +local_stream_acceptor::assign(native_handle_type fd) noexcept { auto& svc = static_cast(h_.service()); auto ec = svc.assign_socket( static_cast(*h_.get()), fd); - if (ec) - detail::throw_system_error(ec, "local_stream_acceptor::assign"); + return ec; } native_handle_type diff --git a/src/corosio/src/local_stream_socket.cpp b/src/corosio/src/local_stream_socket.cpp index 4579c4db0..79c929ca3 100644 --- a/src/corosio/src/local_stream_socket.cpp +++ b/src/corosio/src/local_stream_socket.cpp @@ -33,23 +33,22 @@ local_stream_socket::local_stream_socket(capy::execution_context& ctx) { } -void -local_stream_socket::open(local_stream proto) +std::error_code +local_stream_socket::open(local_stream proto) noexcept { if (is_open()) - return; - open_for_family(proto.family(), proto.type(), proto.protocol()); + return {}; + return open_for_family(proto.family(), proto.type(), proto.protocol()); } -void -local_stream_socket::open_for_family(int family, int type, int protocol) +std::error_code +local_stream_socket::open_for_family(int family, int type, int protocol) noexcept { auto& svc = static_cast(h_.service()); std::error_code ec = svc.open_socket( static_cast(*h_.get()), family, type, protocol); - if (ec) - detail::throw_system_error(ec, "local_stream_socket::open"); + return ec; } void @@ -68,32 +67,21 @@ local_stream_socket::cancel() get().cancel(); } -void -local_stream_socket::shutdown(shutdown_type what) -{ - if (is_open()) - { - // Best-effort: errors like ENOTCONN are expected and unhelpful - [[maybe_unused]] auto ec = get().shutdown(what); - } -} - -void -local_stream_socket::shutdown(shutdown_type what, std::error_code& ec) noexcept +std::error_code +local_stream_socket::shutdown(shutdown_type what) noexcept { - ec = {}; - if (is_open()) - ec = get().shutdown(what); + if (!is_open()) + return make_error_code(std::errc::bad_file_descriptor); + return get().shutdown(what); } -void -local_stream_socket::assign(native_handle_type fd) +std::error_code +local_stream_socket::assign(native_handle_type fd) noexcept { auto& svc = static_cast(h_.service()); std::error_code ec = svc.assign_socket( static_cast(*h_.get()), fd); - if (ec) - detail::throw_system_error(ec, "local_stream_socket::assign"); + return ec; } native_handle_type diff --git a/src/corosio/src/random_access_file.cpp b/src/corosio/src/random_access_file.cpp index 04e3d1f3b..94c58180d 100644 --- a/src/corosio/src/random_access_file.cpp +++ b/src/corosio/src/random_access_file.cpp @@ -33,16 +33,14 @@ random_access_file::random_access_file(capy::execution_context& ctx) { } -void +std::error_code random_access_file::open( - std::filesystem::path const& path, file_base::flags mode) + std::filesystem::path const& path, file_base::flags mode) noexcept { if (is_open()) close(); auto& svc = static_cast(h_.service()); - std::error_code ec = svc.open_file(get(), path, mode); - if (ec) - detail::throw_system_error(ec, "random_access_file::open"); + return svc.open_file(get(), path, mode); } void @@ -85,34 +83,28 @@ random_access_file::size() const return get().size(); } -void -random_access_file::resize(std::uint64_t new_size) +std::error_code +random_access_file::resize(std::uint64_t new_size) noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "random_access_file::resize"); - get().resize(new_size); + return make_error_code(std::errc::bad_file_descriptor); + return get().resize(new_size); } -void -random_access_file::sync_data() +std::error_code +random_access_file::sync_data() noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "random_access_file::sync_data"); - get().sync_data(); + return make_error_code(std::errc::bad_file_descriptor); + return get().sync_data(); } -void -random_access_file::sync_all() +std::error_code +random_access_file::sync_all() noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "random_access_file::sync_all"); - get().sync_all(); + return make_error_code(std::errc::bad_file_descriptor); + return get().sync_all(); } native_handle_type @@ -125,12 +117,12 @@ random_access_file::release() return get().release(); } -void -random_access_file::assign(native_handle_type handle) +std::error_code +random_access_file::assign(native_handle_type handle) noexcept { if (is_open()) close(); - get().assign(handle); + return get().assign(handle); } } // namespace boost::corosio diff --git a/src/corosio/src/stream_file.cpp b/src/corosio/src/stream_file.cpp index d0003c064..fa289c519 100644 --- a/src/corosio/src/stream_file.cpp +++ b/src/corosio/src/stream_file.cpp @@ -33,16 +33,14 @@ stream_file::stream_file(capy::execution_context& ctx) { } -void +std::error_code stream_file::open( - std::filesystem::path const& path, file_base::flags mode) + std::filesystem::path const& path, file_base::flags mode) noexcept { if (is_open()) close(); - auto& svc = static_cast(h_.service()); - std::error_code ec = svc.open_file(get(), path, mode); - if (ec) - detail::throw_system_error(ec, "stream_file::open"); + auto& svc = static_cast(h_.service()); + return svc.open_file(get(), path, mode); } void @@ -85,34 +83,28 @@ stream_file::size() const return get().size(); } -void -stream_file::resize(std::uint64_t new_size) +std::error_code +stream_file::resize(std::uint64_t new_size) noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "stream_file::resize"); - get().resize(new_size); + return make_error_code(std::errc::bad_file_descriptor); + return get().resize(new_size); } -void -stream_file::sync_data() +std::error_code +stream_file::sync_data() noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "stream_file::sync_data"); - get().sync_data(); + return make_error_code(std::errc::bad_file_descriptor); + return get().sync_data(); } -void -stream_file::sync_all() +std::error_code +stream_file::sync_all() noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "stream_file::sync_all"); - get().sync_all(); + return make_error_code(std::errc::bad_file_descriptor); + return get().sync_all(); } native_handle_type @@ -125,21 +117,19 @@ stream_file::release() return get().release(); } -void -stream_file::assign(native_handle_type handle) +std::error_code +stream_file::assign(native_handle_type handle) noexcept { if (is_open()) close(); - get().assign(handle); + return get().assign(handle); } -std::uint64_t -stream_file::seek(std::int64_t offset, file_base::seek_basis origin) +capy::io_result +stream_file::seek(std::int64_t offset, file_base::seek_basis origin) noexcept { if (!is_open()) - detail::throw_system_error( - make_error_code(std::errc::bad_file_descriptor), - "stream_file::seek"); + return {make_error_code(std::errc::bad_file_descriptor), 0}; return get().seek(offset, origin); } diff --git a/src/corosio/src/tcp_acceptor.cpp b/src/corosio/src/tcp_acceptor.cpp index db0cb15d0..dac70dc3f 100644 --- a/src/corosio/src/tcp_acceptor.cpp +++ b/src/corosio/src/tcp_acceptor.cpp @@ -40,7 +40,8 @@ tcp_acceptor::tcp_acceptor( capy::execution_context& ctx, endpoint ep, int backlog) : tcp_acceptor(ctx) { - open(ep.is_v6() ? tcp::v6() : tcp::v4()); + if (auto ec = open(ep.is_v6() ? tcp::v6() : tcp::v4())) + detail::throw_system_error(ec, "tcp_acceptor"); set_option(socket_option::reuse_address(true)); if (auto ec = bind(ep)) detail::throw_system_error(ec, "tcp_acceptor"); @@ -48,11 +49,11 @@ tcp_acceptor::tcp_acceptor( detail::throw_system_error(ec, "tcp_acceptor"); } -void -tcp_acceptor::open(tcp proto) +std::error_code +tcp_acceptor::open(tcp proto) noexcept { if (is_open()) - return; + return {}; #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); @@ -62,12 +63,11 @@ tcp_acceptor::open(tcp proto) std::error_code ec = svc.open_acceptor_socket( *static_cast(h_.get()), proto.family(), proto.type(), proto.protocol()); - if (ec) - detail::throw_system_error(ec, "tcp_acceptor::open"); + return ec; } -void -tcp_acceptor::assign(native_handle_type fd) +std::error_code +tcp_acceptor::assign(native_handle_type fd) noexcept { #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); @@ -76,8 +76,7 @@ tcp_acceptor::assign(native_handle_type fd) #endif std::error_code ec = svc.assign_socket( *static_cast(h_.get()), fd); - if (ec) - detail::throw_system_error(ec, "tcp_acceptor::assign"); + return ec; } native_handle_type diff --git a/src/corosio/src/tcp_socket.cpp b/src/corosio/src/tcp_socket.cpp index f93947d9d..795cfdb19 100644 --- a/src/corosio/src/tcp_socket.cpp +++ b/src/corosio/src/tcp_socket.cpp @@ -34,16 +34,16 @@ tcp_socket::tcp_socket(capy::execution_context& ctx) { } -void -tcp_socket::open(tcp proto) +std::error_code +tcp_socket::open(tcp proto) noexcept { if (is_open()) - return; - open_for_family(proto.family(), proto.type(), proto.protocol()); + return {}; + return open_for_family(proto.family(), proto.type(), proto.protocol()); } -void -tcp_socket::open_for_family(int family, int type, int protocol) +std::error_code +tcp_socket::open_for_family(int family, int type, int protocol) noexcept { #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); @@ -57,12 +57,11 @@ tcp_socket::open_for_family(int family, int type, int protocol) static_cast(*h_.get()), family, type, protocol); #endif - if (ec) - detail::throw_system_error(ec, "tcp_socket::open"); + return ec; } -void -tcp_socket::assign(native_handle_type fd) +std::error_code +tcp_socket::assign(native_handle_type fd) noexcept { #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); @@ -74,8 +73,7 @@ tcp_socket::assign(native_handle_type fd) std::error_code ec = svc.assign_socket( static_cast(*h_.get()), fd); #endif - if (ec) - detail::throw_system_error(ec, "tcp_socket::assign"); + return ec; } native_handle_type @@ -119,14 +117,12 @@ tcp_socket::cancel() get().cancel(); } -void -tcp_socket::shutdown(shutdown_type what) +std::error_code +tcp_socket::shutdown(shutdown_type what) noexcept { - if (is_open()) - { - // Best-effort: errors like ENOTCONN are expected and unhelpful - [[maybe_unused]] auto ec = get().shutdown(what); - } + if (!is_open()) + return make_error_code(std::errc::bad_file_descriptor); + return get().shutdown(what); } native_handle_type diff --git a/src/corosio/src/udp_socket.cpp b/src/corosio/src/udp_socket.cpp index 2a624a42d..4be2fb9a6 100644 --- a/src/corosio/src/udp_socket.cpp +++ b/src/corosio/src/udp_socket.cpp @@ -25,33 +25,31 @@ udp_socket::udp_socket(capy::execution_context& ctx) { } -void -udp_socket::open(udp proto) +std::error_code +udp_socket::open(udp proto) noexcept { if (is_open()) - return; - open_for_family(proto.family(), proto.type(), proto.protocol()); + return {}; + return open_for_family(proto.family(), proto.type(), proto.protocol()); } -void -udp_socket::open_for_family(int family, int type, int protocol) +std::error_code +udp_socket::open_for_family(int family, int type, int protocol) noexcept { auto& svc = static_cast(h_.service()); std::error_code ec = svc.open_datagram_socket( static_cast(*h_.get()), family, type, protocol); - if (ec) - detail::throw_system_error(ec, "udp_socket::open"); + return ec; } -void -udp_socket::assign(native_handle_type fd) +std::error_code +udp_socket::assign(native_handle_type fd) noexcept { auto& svc = static_cast(h_.service()); std::error_code ec = svc.assign_socket( static_cast(*h_.get()), fd); - if (ec) - detail::throw_system_error(ec, "udp_socket::assign"); + return ec; } native_handle_type @@ -80,6 +78,14 @@ udp_socket::bind(endpoint ep) static_cast(*h_.get()), ep); } +std::error_code +udp_socket::shutdown(shutdown_type what) noexcept +{ + if (!is_open()) + return make_error_code(std::errc::bad_file_descriptor); + return get().shutdown(what); +} + void udp_socket::cancel() { diff --git a/test/doc/programs/index_page_connect.cpp b/test/doc/programs/index_page_connect.cpp index f3d0a7581..1e4c555a6 100644 --- a/test/doc/programs/index_page_connect.cpp +++ b/test/doc/programs/index_page_connect.cpp @@ -22,8 +22,8 @@ namespace capy = boost::capy; capy::task connect_example(corosio::io_context& ioc) { + // connect() opens the socket automatically corosio::tcp_socket s(ioc); - s.open(); // Connect using structured bindings auto [ec] = co_await s.connect( diff --git a/test/doc/snippets/3b_http_client.cpp b/test/doc/snippets/3b_http_client.cpp index dc5adaca1..c399308ac 100644 --- a/test/doc/snippets/3b_http_client.cpp +++ b/test/doc/snippets/3b_http_client.cpp @@ -95,7 +95,7 @@ struct http_client_test auto ex = ioc.get_executor(); corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(!acc.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0))); BOOST_TEST(!acc.listen()); @@ -103,7 +103,7 @@ struct http_client_test corosio::tcp_socket s(ioc); corosio::tcp_socket peer(ioc); - s.open(); + BOOST_TEST(!s.open()); bool done = false; capy::run_async(ex)(accept_one(acc, peer)); diff --git a/test/doc/snippets/3c_dns_lookup.cpp b/test/doc/snippets/3c_dns_lookup.cpp index 2ce604e83..b726528a6 100644 --- a/test/doc/snippets/3c_dns_lookup.cpp +++ b/test/doc/snippets/3c_dns_lookup.cpp @@ -115,9 +115,8 @@ capy::task connect_to_host( throw std::system_error(resolve_ec); corosio::tcp_socket sock(ioc); - sock.open(); - // Try each address until one works + // Try each address until one works; connect() opens the socket std::error_code last_ec; for (auto const& entry : results) { diff --git a/test/doc/snippets/3d_tls_context.cpp b/test/doc/snippets/3d_tls_context.cpp index 0c1435646..141630b8f 100644 --- a/test/doc/snippets/3d_tls_context.cpp +++ b/test/doc/snippets/3d_tls_context.cpp @@ -88,17 +88,22 @@ typical_setup() tls_context ctx; // 1. Load credentials (for servers, or clients using client certs) - ctx.use_certificate_chain_file( "server.crt" ); - ctx.use_private_key_file( "server.key", tls_file_format::pem ); + if (auto ec = ctx.use_certificate_chain_file( "server.crt" )) + return; + if (auto ec = ctx.use_private_key_file( "server.key", tls_file_format::pem )) + return; // 2. Configure trust anchors (for verifying peer certificates) - ctx.set_default_verify_paths(); // Use system CA store + if (auto ec = ctx.set_default_verify_paths()) // Use system CA store + return; // 3. Set verification mode - ctx.set_verify_mode( tls_verify_mode::peer ); + if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) + return; // 4. Configure protocol options (optional) - ctx.set_min_protocol_version( tls_version::tls_1_2 ); + if (auto ec = ctx.set_min_protocol_version( tls_version::tls_1_2 )) + return; // end::typical_setup[] } @@ -107,10 +112,12 @@ load_separate(tls_context& ctx) { // tag::load_separate[] // Load certificate chain (leaf + intermediates) - ctx.use_certificate_chain_file( "fullchain.pem" ); + if (auto ec = ctx.use_certificate_chain_file( "fullchain.pem" )) + return; // Load the matching private key - ctx.use_private_key_file( "privkey.key", tls_file_format::pem ); + if (auto ec = ctx.use_private_key_file( "privkey.key", tls_file_format::pem )) + return; // end::load_separate[] } @@ -118,8 +125,10 @@ void load_single(tls_context& ctx) { // tag::load_single[] - ctx.use_certificate_file( "server.crt", tls_file_format::pem ); - ctx.use_private_key_file( "server.key", tls_file_format::pem ); + if (auto ec = ctx.use_certificate_file( "server.crt", tls_file_format::pem )) + return; + if (auto ec = ctx.use_private_key_file( "server.key", tls_file_format::pem )) + return; // end::load_single[] } @@ -127,7 +136,8 @@ void pkcs12_bundle(tls_context& ctx) { // tag::pkcs12_file[] - ctx.use_pkcs12_file( "credentials.pfx", "bundle-password" ); + if (auto ec = ctx.use_pkcs12_file( "credentials.pfx", "bundle-password" )) + return; // end::pkcs12_file[] } @@ -150,8 +160,10 @@ load_memory(tls_context& ctx) std::string cert_pem = fetch_certificate_from_vault(); std::string key_pem = fetch_key_from_vault(); - ctx.use_certificate_chain( cert_pem ); - ctx.use_private_key( key_pem, tls_file_format::pem ); + if (auto ec = ctx.use_certificate_chain( cert_pem )) + return; + if (auto ec = ctx.use_private_key( key_pem, tls_file_format::pem )) + return; // end::load_memory[] } @@ -159,8 +171,10 @@ void der_files(tls_context& ctx) { // tag::der_files[] - ctx.use_certificate_file( "server.der", tls_file_format::der ); - ctx.use_private_key_file( "server.key.der", tls_file_format::der ); + if (auto ec = ctx.use_certificate_file( "server.der", tls_file_format::der )) + return; + if (auto ec = ctx.use_private_key_file( "server.key.der", tls_file_format::der )) + return; // end::der_files[] } @@ -168,7 +182,8 @@ void system_trust(tls_context& ctx) { // tag::system_trust[] - ctx.set_default_verify_paths(); + if (auto ec = ctx.set_default_verify_paths()) + return; // end::system_trust[] } @@ -177,7 +192,8 @@ ca_bundle(tls_context& ctx) { // tag::ca_bundle[] // Load CA bundle file (may contain multiple CAs) - ctx.load_verify_file( "/path/to/ca-bundle.crt" ); + if (auto ec = ctx.load_verify_file( "/path/to/ca-bundle.crt" )) + return; // end::ca_bundle[] } @@ -185,7 +201,8 @@ void ca_directory(tls_context& ctx) { // tag::ca_directory[] - ctx.add_verify_path( "/etc/ssl/certs" ); + if (auto ec = ctx.add_verify_path( "/etc/ssl/certs" )) + return; // end::ca_directory[] } @@ -204,11 +221,14 @@ ca_individual( // tag::ca_individual[] // From memory std::string internal_ca = load_ca_from_config(); - ctx.add_certificate_authority( internal_ca ); + if (auto ec = ctx.add_certificate_authority( internal_ca )) + return; // Multiple CAs - ctx.add_certificate_authority( root_ca_pem ); - ctx.add_certificate_authority( intermediate_ca_pem ); + if (auto ec = ctx.add_certificate_authority( root_ca_pem )) + return; + if (auto ec = ctx.add_certificate_authority( intermediate_ca_pem )) + return; // end::ca_individual[] } @@ -217,10 +237,12 @@ combine_trust(tls_context& ctx, std::string const& corporate_ca_pem) { // tag::combine_trust[] // Start with system trust store - ctx.set_default_verify_paths(); + if (auto ec = ctx.set_default_verify_paths()) + return; // Add an internal CA for corporate servers - ctx.add_certificate_authority( corporate_ca_pem ); + if (auto ec = ctx.add_certificate_authority( corporate_ca_pem )) + return; // end::combine_trust[] } @@ -229,11 +251,14 @@ version_bounds(tls_context& ctx) { // tag::version_bounds[] // Require TLS 1.2 or newer (default) - ctx.set_min_protocol_version( tls_version::tls_1_2 ); + if (auto ec = ctx.set_min_protocol_version( tls_version::tls_1_2 )) + return; // Require TLS 1.3 only - ctx.set_min_protocol_version( tls_version::tls_1_3 ); - ctx.set_max_protocol_version( tls_version::tls_1_3 ); + if (auto ec = ctx.set_min_protocol_version( tls_version::tls_1_3 )) + return; + if (auto ec = ctx.set_max_protocol_version( tls_version::tls_1_3 )) + return; // end::version_bounds[] } @@ -242,10 +267,12 @@ cipher_suites(tls_context& ctx) { // tag::cipher_suites[] // TLS 1.2 and below - ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" ); + if (auto ec = ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" )) + return; // TLS 1.3 (distinct API and suite names) - ctx.set_ciphersuites_tls13( "TLS_AES_256_GCM_SHA384" ); + if (auto ec = ctx.set_ciphersuites_tls13( "TLS_AES_256_GCM_SHA384" )) + return; // end::cipher_suites[] } @@ -254,10 +281,12 @@ alpn_offer(tls_context& ctx) { // tag::alpn_offer[] // HTTP/2 with HTTP/1.1 fallback - ctx.set_alpn( { "h2", "http/1.1" } ); + if (auto ec = ctx.set_alpn( { "h2", "http/1.1" } )) + return; // gRPC - ctx.set_alpn( { "h2" } ); + if (auto ec = ctx.set_alpn( { "h2" } )) + return; // end::alpn_offer[] } @@ -276,13 +305,16 @@ verify_modes(tls_context& ctx) { // tag::verify_modes[] // Don't verify peer (not recommended for production) - ctx.set_verify_mode( tls_verify_mode::none ); + if (auto ec = ctx.set_verify_mode( tls_verify_mode::none )) + return; // Verify peer if certificate is presented - ctx.set_verify_mode( tls_verify_mode::peer ); + if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) + return; // Require and verify peer certificate (mTLS server-side) - ctx.set_verify_mode( tls_verify_mode::require_peer ); + if (auto ec = ctx.set_verify_mode( tls_verify_mode::require_peer )) + return; // end::verify_modes[] } @@ -301,7 +333,8 @@ verify_depth(tls_context& ctx) { // tag::verify_depth[] // Allow up to 3 intermediates (leaf -> 3 intermediates -> root) - ctx.set_verify_depth( 3 ); + if (auto ec = ctx.set_verify_depth( 3 )) + return; // end::verify_depth[] } @@ -311,7 +344,7 @@ void verify_callback(tls_context& ctx) { // tag::verify_callback[] - ctx.set_verify_callback( + if (auto ec = ctx.set_verify_callback( []( bool preverified, corosio::verify_context& verify_ctx ) -> bool { if( !preverified ) @@ -320,7 +353,8 @@ verify_callback(tls_context& ctx) auto der = verify_ctx.certificate(); // DER of the current cert return der.size() == expected_pin.size() && std::equal( der.begin(), der.end(), expected_pin.begin() ); - }); + })) + return; // end::verify_callback[] } @@ -350,11 +384,13 @@ crl_load(tls_context& ctx, std::string_view crl_url) { // tag::crl_load[] // From file - ctx.add_crl_file( "/path/to/issuer.crl" ); + if (auto ec = ctx.add_crl_file( "/path/to/issuer.crl" )) + return; // From memory (e.g., fetched via HTTP) std::string crl_data = fetch_crl_from_url( crl_url ); - ctx.add_crl( crl_data ); + if (auto ec = ctx.add_crl( crl_data )) + return; ctx.set_revocation_policy( tls_revocation_policy::hard_fail ); // end::crl_load[] @@ -392,7 +428,8 @@ password_callback(tls_context& ctx) }); // Now load encrypted private key - ctx.use_private_key_file( "encrypted.key", tls_file_format::pem ); + if (auto ec = ctx.use_private_key_file( "encrypted.key", tls_file_format::pem )) + return; // end::password_callback[] } @@ -423,7 +460,8 @@ void pkcs12_memory(tls_context& ctx, std::string_view pkcs12_data) { // tag::pkcs12_memory[] - ctx.use_pkcs12( pkcs12_data, "bundle-password" ); + if (auto ec = ctx.use_pkcs12( pkcs12_data, "bundle-password" )) + return; // end::pkcs12_memory[] } diff --git a/test/doc/snippets/4d_sockets.cpp b/test/doc/snippets/4d_sockets.cpp index e20afdc3d..be67188f7 100644 --- a/test/doc/snippets/4d_sockets.cpp +++ b/test/doc/snippets/4d_sockets.cpp @@ -83,8 +83,8 @@ overview_fragment(corosio::io_context& ioc) { // tag::overview[] corosio::tcp_socket s(ioc); - s.open(); + // connect() opens the socket automatically auto [ec] = co_await s.connect( corosio::endpoint(corosio::ipv4_address::loopback(), 8080)); @@ -111,8 +111,10 @@ void open_fragment(corosio::tcp_socket& s) { // tag::open[] - s.open(); // Creates IPv4 TCP socket, associates with the platform - // reactor (IOCP on Windows, epoll/kqueue/select on POSIX) + // Creates an IPv4 TCP socket and associates it with the platform + // reactor (IOCP on Windows, epoll/kqueue/select on POSIX) + if (auto ec = s.open()) + return; // report the error // end::open[] } @@ -350,7 +352,6 @@ buffer_sequences_fragment(corosio::tcp_socket& s) capy::task echo_client(corosio::io_context& ioc) { corosio::tcp_socket s(ioc); - s.open(); if (auto [ec] = co_await s.connect( corosio::endpoint(corosio::ipv4_address::loopback(), 8080)); ec) @@ -500,7 +501,7 @@ struct sockets_test { corosio::io_context ioc; corosio::tcp_socket s(ioc); - s.open(); + BOOST_TEST(!s.open()); cancel_fragment(s); s.close(); } @@ -511,7 +512,7 @@ struct sockets_test corosio::io_context ioc; corosio::tcp_socket s1(ioc); corosio::tcp_socket s2(ioc); - s2.open(); + BOOST_TEST(!s2.open()); move_assign_fragment(s1, s2); BOOST_TEST(s1.is_open()); s1.close(); diff --git a/test/doc/snippets/4e_tcp_acceptor.cpp b/test/doc/snippets/4e_tcp_acceptor.cpp index 29e4e5458..e6325095e 100644 --- a/test/doc/snippets/4e_tcp_acceptor.cpp +++ b/test/doc/snippets/4e_tcp_acceptor.cpp @@ -105,7 +105,8 @@ bind_listen(corosio::io_context& ioc) { // tag::bind_listen[] corosio::tcp_acceptor acc(ioc); - acc.open(); // create an IPv4 TCP socket + if (auto ec = acc.open()) // create an IPv4 TCP socket + return ec; if (auto ec = acc.bind(corosio::endpoint(8080))) { @@ -275,7 +276,8 @@ capy::task accept_loop( capy::task run_server(corosio::io_context& ioc) { corosio::tcp_acceptor acc(ioc); - acc.open(); + if (auto ec = acc.open()) + co_return; if (auto ec = acc.bind(corosio::endpoint(8080))) { std::cerr << "Bind failed: " << ec.message() << "\n"; @@ -310,7 +312,7 @@ connect_client( std::error_code& out) { corosio::tcp_socket s(ioc); - s.open(); + BOOST_TEST(!s.open()); auto [ec] = co_await s.connect(ep); out = ec; } @@ -332,7 +334,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(!acc.bind(corosio::endpoint( corosio::ipv4_address::loopback(), 0))); BOOST_TEST(!acc.listen()); @@ -367,7 +369,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(!acc.bind(corosio::endpoint( corosio::ipv4_address::loopback(), 0))); BOOST_TEST(!acc.listen()); @@ -391,7 +393,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(!acc.bind(corosio::endpoint( corosio::ipv4_address::loopback(), 0))); BOOST_TEST(!acc.listen()); diff --git a/test/doc/snippets/4f_endpoints.cpp b/test/doc/snippets/4f_endpoints.cpp index a720eccc6..243216e55 100644 --- a/test/doc/snippets/4f_endpoints.cpp +++ b/test/doc/snippets/4f_endpoints.cpp @@ -64,8 +64,8 @@ capy::task<> connecting(corosio::io_context& ioc, bool& done) { // tag::connecting[] + // connect() opens the socket automatically corosio::tcp_socket s(ioc); - s.open(); corosio::endpoint target( corosio::ipv4_address::loopback(), 8080); diff --git a/test/doc/snippets/4g_composed_operations.cpp b/test/doc/snippets/4g_composed_operations.cpp index cef5f9776..073663fd3 100644 --- a/test/doc/snippets/4g_composed_operations.cpp +++ b/test/doc/snippets/4g_composed_operations.cpp @@ -255,7 +255,7 @@ capy::task<> send_and_close( auto [ec, n] = co_await capy::write( s, capy::const_buffer(text.data(), text.size())); BOOST_TEST(!ec); - s.shutdown(corosio::shutdown_send); + BOOST_TEST(!s.shutdown(corosio::shutdown_send)); } struct composed_operations_test diff --git a/test/doc/snippets/4j_resolver.cpp b/test/doc/snippets/4j_resolver.cpp index de2f5da0c..3792850f9 100644 --- a/test/doc/snippets/4j_resolver.cpp +++ b/test/doc/snippets/4j_resolver.cpp @@ -188,8 +188,9 @@ capy::task connect_to_service( if (results.empty()) throw std::runtime_error("No addresses found"); + // connect() opens the socket automatically, and re-opens it + // with each candidate's address family after close() corosio::tcp_socket sock(ioc); - sock.open(); std::error_code last_error; for (auto const& entry : results) @@ -200,7 +201,6 @@ capy::task connect_to_service( last_error = ec; sock.close(); - sock.open(); } throw std::system_error(last_error); @@ -275,9 +275,8 @@ capy::task http_get( co_return; } - // Connect to first address + // Connect to first address; connect() opens the socket corosio::tcp_socket sock(ioc); - sock.open(); for (auto const& entry : results) { diff --git a/test/doc/snippets/4k_tcp_server.cpp b/test/doc/snippets/4k_tcp_server.cpp index aad0aa7bf..f2981851f 100644 --- a/test/doc/snippets/4k_tcp_server.cpp +++ b/test/doc/snippets/4k_tcp_server.cpp @@ -238,8 +238,10 @@ bind_one(corosio::tcp_server& server) bind_many(corosio::tcp_server& server) { // tag::bind_many[] - server.bind(corosio::endpoint(80)); - server.bind(corosio::endpoint(443)); + if (auto ec = server.bind(corosio::endpoint(80))) + return; // report the error + if (auto ec = server.bind(corosio::endpoint(443))) + return; // end::bind_many[] } @@ -255,8 +257,10 @@ start_server(corosio::tcp_server& server) multiple_ports(corosio::tcp_server& server) { // tag::multiple_ports[] - server.bind(corosio::endpoint(80)); // HTTP - server.bind(corosio::endpoint(443)); // HTTPS + if (auto ec = server.bind(corosio::endpoint(80))) // HTTP + return; + if (auto ec = server.bind(corosio::endpoint(443))) // HTTPS + return; server.start(); // end::multiple_ports[] } diff --git a/test/doc/snippets/4l_tls.cpp b/test/doc/snippets/4l_tls.cpp index c0014b7ff..e4561b585 100644 --- a/test/doc/snippets/4l_tls.cpp +++ b/test/doc/snippets/4l_tls.cpp @@ -101,9 +101,8 @@ typical_flow( if (auto ec = ctx.set_verify_mode(tls_verify_mode::peer); ec) throw std::system_error(ec); - // 2. Connect a socket + // 2. Connect a socket (connect() opens it automatically) corosio::tcp_socket sock(ioc); - sock.open(); if (auto [ec] = co_await sock.connect(endpoint); ec) throw std::system_error(ec); @@ -310,9 +309,8 @@ capy::task https_get( if (resolve_ec) throw std::system_error(resolve_ec); - // Connect TCP socket + // Connect TCP socket (connect() opens it automatically) corosio::tcp_socket sock(ioc); - sock.open(); for (auto const& entry : results) { diff --git a/test/doc/snippets/4m_error_handling.cpp b/test/doc/snippets/4m_error_handling.cpp index 2269a1cdf..293bd894b 100644 --- a/test/doc/snippets/4m_error_handling.cpp +++ b/test/doc/snippets/4m_error_handling.cpp @@ -68,7 +68,7 @@ corosio::endpoint closed_endpoint(corosio::io_context& ioc) { corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(corosio::socket_option::reuse_address(true)); if (auto ec = acc.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0))) @@ -84,7 +84,7 @@ refused_connect_ec(corosio::io_context& ioc) { std::error_code out; corosio::tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); capy::run_async(ioc.get_executor())( [](corosio::tcp_socket& s, corosio::endpoint ep, std::error_code& o) -> capy::task<> { @@ -337,7 +337,7 @@ struct exception_safety_fixture : sock(ioc) , endpoint(ep) { - sock.open(); + BOOST_TEST(!sock.open()); } // tag::exception_safety[] @@ -368,7 +368,7 @@ capy::task connect_with_retry( for (int attempt = 0; attempt < max_retries; ++attempt) { - sock.open(); + // connect() re-opens the socket after the close() below auto [ec] = co_await sock.connect(ep); if (!ec) @@ -416,7 +416,7 @@ struct error_handling_test { corosio::io_context ioc; corosio::tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::error_code connect_ec; capy::run_async(ioc.get_executor())( bindings_void_result(sock, closed_endpoint(ioc), connect_ec)); @@ -446,7 +446,7 @@ struct error_handling_test { corosio::io_context ioc; corosio::tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::error_code out; capy::run_async(ioc.get_executor())( direct_members(sock, closed_endpoint(ioc), out)); @@ -493,7 +493,7 @@ struct error_handling_test { corosio::io_context ioc; corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(corosio::socket_option::reuse_address(true)); BOOST_TEST(!acc.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0))); @@ -502,7 +502,7 @@ struct error_handling_test corosio::tcp_socket psock(ioc); corosio::tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::string request_text = "GET /\r\n"; char received[16] = {}; std::size_t got = 0; @@ -559,7 +559,7 @@ struct error_handling_test // race reports cancellation rather than a timeout. corosio::io_context ioc; corosio::tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::stop_source source; source.request_stop(); std::error_code out; @@ -668,7 +668,7 @@ struct error_handling_test { corosio::io_context ioc; corosio::tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(corosio::socket_option::reuse_address(true)); BOOST_TEST(!acc.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0))); diff --git a/test/doc/snippets/4o_file_io.cpp b/test/doc/snippets/4o_file_io.cpp index 6b6f6d361..1df9d4b0c 100644 --- a/test/doc/snippets/4o_file_io.cpp +++ b/test/doc/snippets/4o_file_io.cpp @@ -62,6 +62,7 @@ namespace capy = boost::capy; #include #include #include +#include #include "test_suite.hpp" @@ -73,7 +74,8 @@ stream_read( { // tag::stream_read[] corosio::stream_file f(ioc); - f.open("data.bin", corosio::file_base::read_only); + if (auto ec = f.open("data.bin", corosio::file_base::read_only)) + co_return; // open failed char buf[4096]; auto [ec, n] = co_await f.read_some( @@ -92,10 +94,11 @@ stream_write( { // tag::stream_write[] corosio::stream_file f(ioc); - f.open("output.bin", - corosio::file_base::write_only - | corosio::file_base::create - | corosio::file_base::truncate); + if (auto ec = f.open("output.bin", + corosio::file_base::write_only + | corosio::file_base::create + | corosio::file_base::truncate)) + co_return; // open failed std::string data = "hello world"; auto [ec, n] = co_await f.write_some( @@ -109,11 +112,16 @@ std::uint64_t reposition(corosio::stream_file& f) { // tag::seek[] - f.seek(0, corosio::file_base::seek_set); // beginning - f.seek(100, corosio::file_base::seek_cur); // forward 100 bytes - f.seek(-10, corosio::file_base::seek_end); // 10 bytes before end + auto [ec, pos] = f.seek(0, corosio::file_base::seek_set); // beginning + if (! ec) + std::tie(ec, pos) = + f.seek(100, corosio::file_base::seek_cur); // forward 100 bytes + if (! ec) + std::tie(ec, pos) = + f.seek(-10, corosio::file_base::seek_end); // 10 before end // end::seek[] - return f.seek(0, corosio::file_base::seek_cur); + BOOST_TEST(!ec); + return pos; } capy::task<> @@ -123,7 +131,8 @@ read_at( { // tag::read_at[] corosio::random_access_file f(ioc); - f.open("data.bin", corosio::file_base::read_only); + if (auto ec = f.open("data.bin", corosio::file_base::read_only)) + co_return; // open failed char buf[256]; auto [ec, n] = co_await f.read_some_at( @@ -141,7 +150,8 @@ write_at( { // tag::write_at[] corosio::random_access_file f(ioc); - f.open("data.bin", corosio::file_base::read_write); + if (auto ec = f.open("data.bin", corosio::file_base::read_write)) + co_return; // open failed auto [ec, n] = co_await f.write_some_at( 512, capy::const_buffer("patched", 7)); @@ -154,10 +164,11 @@ void open_log(corosio::stream_file& f) { // tag::open_flags[] - f.open("log.txt", - corosio::file_base::write_only - | corosio::file_base::create - | corosio::file_base::append); + if (auto ec = f.open("log.txt", + corosio::file_base::write_only + | corosio::file_base::create + | corosio::file_base::append)) + return; // report the error // end::open_flags[] } @@ -165,10 +176,13 @@ void inspect_metadata(corosio::stream_file& f) { // tag::metadata[] - auto bytes = f.size(); // file size in bytes - f.resize(1024); // truncate or extend - f.sync_data(); // flush data to stable storage - f.sync_all(); // flush data and metadata + auto bytes = f.size(); // file size in bytes + if (auto ec = f.resize(1024)) // truncate or extend + return; + if (auto ec = f.sync_data()) // flush data to stable storage + return; + if (auto ec = f.sync_all()) // flush data and metadata + return; // end::metadata[] } @@ -218,9 +232,9 @@ adopt_handle( // Adopt a handle obtained from the platform's file API — // the file object takes ownership corosio::random_access_file f2(ioc); - f2.assign(native_handle); + auto ec = f2.assign(native_handle); // end::native_adopt[] - adopted = f2.is_open(); + adopted = !ec && f2.is_open(); f2.close(); } @@ -280,7 +294,7 @@ struct file_io_test { corosio::io_context ioc; corosio::stream_file f(ioc); - f.open("data.bin", corosio::file_base::read_only); + BOOST_TEST(!f.open("data.bin", corosio::file_base::read_only)); // 2048-byte file: 10 bytes before end is position 2038 BOOST_TEST_EQ(reposition(f), 2038u); } @@ -333,7 +347,7 @@ struct file_io_test { corosio::io_context ioc; corosio::random_access_file f(ioc); - f.open("data.bin", corosio::file_base::read_only); + BOOST_TEST(!f.open("data.bin", corosio::file_base::read_only)); release_handle(f); bool adopted = false; @@ -346,8 +360,9 @@ struct file_io_test { corosio::io_context ioc; corosio::stream_file f(ioc); - f.open("data.bin", corosio::file_base::read_only); - f.seek(0, corosio::file_base::seek_end); + BOOST_TEST(!f.open("data.bin", corosio::file_base::read_only)); + auto [sec, spos] = f.seek(0, corosio::file_base::seek_end); + BOOST_TEST(!sec); char buf[64]; std::error_code ec; capy::run_async(ioc.get_executor())(read_at_eof( diff --git a/test/doc/snippets/4p_unix_sockets.cpp b/test/doc/snippets/4p_unix_sockets.cpp index a439f86dd..ed82ce0d4 100644 --- a/test/doc/snippets/4p_unix_sockets.cpp +++ b/test/doc/snippets/4p_unix_sockets.cpp @@ -78,13 +78,14 @@ remove_stale(char const* path) capy::task<> server(corosio::io_context& ioc) { corosio::local_stream_acceptor acc(ioc); - acc.open(); + if (auto ec = acc.open()) + co_return; - auto ec = acc.bind(corosio::local_endpoint("/tmp/my_app.sock")); - if (ec) co_return; + if (auto ec = acc.bind(corosio::local_endpoint("/tmp/my_app.sock"))) + co_return; - ec = acc.listen(); - if (ec) co_return; + if (auto ec = acc.listen()) + co_return; corosio::local_stream_socket peer(ioc); auto [accept_ec] = co_await acc.accept(peer); @@ -118,10 +119,11 @@ void unlink_then_bind(corosio::io_context& ioc, bool& bound) { corosio::local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); // tag::unlink_bind[] ::unlink("/tmp/my_app.sock"); // remove stale socket - acc.bind(corosio::local_endpoint("/tmp/my_app.sock")); + if (auto ec = acc.bind(corosio::local_endpoint("/tmp/my_app.sock"))) + return; // report the error // end::unlink_bind[] bound = std::filesystem::exists("/tmp/my_app.sock"); acc.close(); @@ -159,8 +161,10 @@ datagram_connectionless( char buf[64]; // tag::datagram_connectionless[] corosio::local_datagram_socket s(ioc); - s.open(); - s.bind(corosio::local_endpoint("/tmp/my_dgram.sock")); + if (auto ec = s.open()) + co_return; + if (auto ec = s.bind(corosio::local_endpoint("/tmp/my_dgram.sock"))) + co_return; // Send to a specific peer co_await s.send_to( @@ -262,7 +266,7 @@ struct unix_sockets_test // A live peer bound to the page's literal path receives the // fragment's datagram and answers it. corosio::local_datagram_socket peer(ioc); - peer.open(); + BOOST_TEST(!peer.open()); auto bec = peer.bind(corosio::local_endpoint("/tmp/peer.sock")); BOOST_TEST(!bec); diff --git a/test/doc/snippets/4q_udp.cpp b/test/doc/snippets/4q_udp.cpp index d5dd29339..f03843e3f 100644 --- a/test/doc/snippets/4q_udp.cpp +++ b/test/doc/snippets/4q_udp.cpp @@ -65,9 +65,9 @@ open_by_family(corosio::io_context& ioc) { // tag::protocol[] corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); // SOCK_DGRAM, AF_INET - // or - sock.open(corosio::udp::v6()); // SOCK_DGRAM, AF_INET6 + if (auto ec = sock.open(corosio::udp::v4())) // SOCK_DGRAM, AF_INET + return; // report the error + // or open(corosio::udp::v6()) for SOCK_DGRAM, AF_INET6 // end::protocol[] } @@ -76,11 +76,12 @@ open_and_bind(corosio::io_context& ioc) { // tag::open_bind[] corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + if (auto ec = sock.open(corosio::udp::v4())) + return; // report the error - auto ec = sock.bind( - corosio::endpoint(corosio::ipv4_address::any(), 9000)); - if (ec) /* handle bind failure */; + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv4_address::any(), 9000))) + return; // handle bind failure // end::open_bind[] } @@ -124,10 +125,11 @@ receive_datagram( capy::task<> echo(corosio::io_context& ioc) { corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); - auto ec = sock.bind( - corosio::endpoint(corosio::ipv4_address::any(), 9000)); - if (ec) co_return; + if (auto ec = sock.open(corosio::udp::v4())) + co_return; + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv4_address::any(), 9000))) + co_return; char buf[1500]; for (;;) @@ -204,12 +206,13 @@ multicast_join(corosio::io_context& ioc) { // tag::multicast[] corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + if (auto ec = sock.open(corosio::udp::v4())) + co_return; sock.set_option(corosio::socket_option::reuse_address(true)); - auto ec = sock.bind( - corosio::endpoint(corosio::ipv4_address::any(), 30001)); - if (ec) co_return; + if (auto ec = sock.bind( + corosio::endpoint(corosio::ipv4_address::any(), 30001))) + co_return; sock.set_option(corosio::socket_option::join_group_v4( corosio::ipv4_address("239.255.0.1"))); @@ -240,7 +243,7 @@ struct udp_test { corosio::io_context ioc; corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + BOOST_TEST(!sock.open(corosio::udp::v4())); std::error_code ec; std::size_t n = 0; @@ -257,13 +260,13 @@ struct udp_test auto ex = ioc.get_executor(); corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + BOOST_TEST(!sock.open(corosio::udp::v4())); auto bec = sock.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0)); BOOST_TEST(!bec); corosio::udp_socket helper(ioc); - helper.open(corosio::udp::v4()); + BOOST_TEST(!helper.open(corosio::udp::v4())); std::error_code ec; std::size_t n = 0; @@ -311,13 +314,13 @@ struct udp_test auto ex = ioc.get_executor(); corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + BOOST_TEST(!sock.open(corosio::udp::v4())); auto bec = sock.bind( corosio::endpoint(corosio::ipv4_address::loopback(), 0)); BOOST_TEST(!bec); corosio::udp_socket helper(ioc); - helper.open(corosio::udp::v4()); + BOOST_TEST(!helper.open(corosio::udp::v4())); std::size_t peeked = 0; std::size_t drained = 0; @@ -340,7 +343,7 @@ struct udp_test { corosio::io_context ioc; corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + BOOST_TEST(!sock.open(corosio::udp::v4())); BOOST_TEST(tune_options(sock)); } @@ -349,7 +352,7 @@ struct udp_test { corosio::io_context ioc; corosio::udp_socket sock(ioc); - sock.open(corosio::udp::v4()); + BOOST_TEST(!sock.open(corosio::udp::v4())); cancel_all(sock); BOOST_TEST_PASS(); } @@ -363,7 +366,7 @@ struct udp_test auto my_task = [&]() -> capy::task<> { corosio::udp_socket s(ioc); - s.open(corosio::udp::v4()); + BOOST_TEST(!s.open(corosio::udp::v4())); if (auto bec = s.bind(corosio::endpoint( corosio::ipv4_address::loopback(), 0))) co_return; diff --git a/test/doc/snippets/4r_wait.cpp b/test/doc/snippets/4r_wait.cpp index 5e50e91eb..4fd2f5b60 100644 --- a/test/doc/snippets/4r_wait.cpp +++ b/test/doc/snippets/4r_wait.cpp @@ -105,7 +105,8 @@ drive_foreign(corosio::io_context& ioc, foreign_conn* conn) // the duplicate can never close the library's descriptor. // Readiness travels through the shared open file description. corosio::tcp_socket sock(ioc); - sock.assign(::dup(foreign_socket(conn))); + if (auto ec = sock.assign(::dup(foreign_socket(conn)))) + co_return ec; // Read side: wake, then let the library take the bytes itself. while (foreign_wants_read(conn)) { @@ -189,7 +190,7 @@ struct wait_test auto ex = ioc.get_executor(); corosio::tcp_acceptor acceptor(ioc); - acceptor.open(); + BOOST_TEST(!acceptor.open()); acceptor.set_option(corosio::socket_option::reuse_address(true)); auto bec = acceptor.bind(corosio::endpoint( corosio::ipv4_address::loopback(), 0)); @@ -203,7 +204,7 @@ struct wait_test capy::run_async(ex)(wait_then_accept(ioc, acceptor, wec, aec)); corosio::tcp_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); auto connecter = [&]() -> capy::task<> { co_await client.connect(corosio::endpoint( diff --git a/test/unit/connect.cpp b/test/unit/connect.cpp index 9bc43ff7e..8a4077789 100644 --- a/test/unit/connect.cpp +++ b/test/unit/connect.cpp @@ -39,7 +39,7 @@ struct connect_test Caller keeps the acceptor alive. */ static std::uint16_t open_listener(tcp_acceptor& acc, tcp proto = tcp::v4()) { - acc.open(proto); + BOOST_TEST(!acc.open(proto)); acc.set_option(socket_option::reuse_address(true)); std::error_code ec; if (proto == tcp::v6()) @@ -60,7 +60,7 @@ struct connect_test static std::uint16_t pick_closed_port(io_context& ioc) { tcp_acceptor tmp(ioc); - tmp.open(); + BOOST_TEST(!tmp.open()); tmp.set_option(socket_option::reuse_address(true)); auto ec = tmp.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); diff --git a/test/unit/cross_ssl_stream.cpp b/test/unit/cross_ssl_stream.cpp index 45b4708b4..5c27fd691 100644 --- a/test/unit/cross_ssl_stream.cpp +++ b/test/unit/cross_ssl_stream.cpp @@ -134,7 +134,7 @@ struct cross_ssl_stream_test { auto client_ctx = make_client_context(); auto server_ctx = make_anon_context(); - server_ctx.set_ciphersuites(""); + (void)server_ctx.set_ciphersuites(""); run_tls_test_fail( ioc, client_ctx, server_ctx, make_openssl, make_wolfssl); ioc.restart(); @@ -144,7 +144,7 @@ struct cross_ssl_stream_test { auto client_ctx = make_client_context(); auto server_ctx = make_anon_context(); - server_ctx.set_ciphersuites(""); + (void)server_ctx.set_ciphersuites(""); run_tls_test_fail( ioc, client_ctx, server_ctx, make_wolfssl, make_openssl); } diff --git a/test/unit/datagram_paths.cpp b/test/unit/datagram_paths.cpp index 529a3706c..065e6f60a 100644 --- a/test/unit/datagram_paths.cpp +++ b/test/unit/datagram_paths.cpp @@ -53,8 +53,8 @@ struct datagram_paths_test // Connected UDP pair; both sockets bound to ephemeral loopback ports. static void make_udp_pair(io_context& ioc, udp_socket& a, udp_socket& b) { - a.open(udp::v4()); - b.open(udp::v4()); + BOOST_TEST(!a.open(udp::v4())); + BOOST_TEST(!b.open(udp::v4())); auto ec1 = a.bind(endpoint(ipv4_address::loopback(), 0)); auto ec2 = b.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec1); @@ -120,8 +120,8 @@ struct datagram_paths_test auto ex = ioc.get_executor(); udp_socket recv_sock(ioc), send_sock(ioc); - recv_sock.open(udp::v4()); - send_sock.open(udp::v4()); + BOOST_TEST(!recv_sock.open(udp::v4())); + BOOST_TEST(!send_sock.open(udp::v4())); auto ec1 = recv_sock.bind(endpoint(ipv4_address::loopback(), 0)); auto ec2 = send_sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec1); @@ -165,7 +165,7 @@ struct datagram_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -199,7 +199,7 @@ struct datagram_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -272,8 +272,8 @@ struct datagram_paths_test test::temp_socket_dir tmp2; local_datagram_socket s1(ioc), s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); auto ec1 = s1.bind(local_endpoint(tmp1.path())); auto ec2 = s2.bind(local_endpoint(tmp2.path())); BOOST_TEST(!ec1); @@ -394,8 +394,8 @@ struct datagram_paths_test test::temp_socket_dir tmp2; local_datagram_socket s1(ioc), s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); auto ec1 = s1.bind(local_endpoint(tmp1.path())); auto ec2 = s2.bind(local_endpoint(tmp2.path())); BOOST_TEST(!ec1); @@ -510,7 +510,7 @@ struct datagram_paths_test test::temp_socket_dir tmp; local_datagram_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto bec = sock.bind(local_endpoint(tmp.path())); BOOST_TEST(!bec); @@ -610,8 +610,8 @@ struct datagram_paths_test test::temp_socket_dir tmp2; local_datagram_socket s1(ioc), s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); auto ec1 = s1.bind(local_endpoint(tmp1.path())); auto ec2 = s2.bind(local_endpoint(tmp2.path())); BOOST_TEST(!ec1); diff --git a/test/unit/error_conditions.cpp b/test/unit/error_conditions.cpp index 2ce8a2f17..1b20ada02 100644 --- a/test/unit/error_conditions.cpp +++ b/test/unit/error_conditions.cpp @@ -185,7 +185,7 @@ struct error_conditions_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::error_code connect_ec; diff --git a/test/unit/local_connect_pair.cpp b/test/unit/local_connect_pair.cpp index ff847f0fb..8fa2aabbf 100644 --- a/test/unit/local_connect_pair.cpp +++ b/test/unit/local_connect_pair.cpp @@ -81,7 +81,7 @@ struct local_connect_pair_test { io_context ioc(Backend); local_stream_socket a(ioc), b(ioc); - a.open(); + BOOST_TEST(!a.open()); // a is open; connect_pair must refuse and leave both sockets // in their original state (a open, b closed). auto ec = connect_pair(a, b); @@ -95,7 +95,7 @@ struct local_connect_pair_test { io_context ioc(Backend); local_datagram_socket a(ioc), b(ioc); - b.open(); + BOOST_TEST(!b.open()); auto ec = connect_pair(a, b); BOOST_TEST(static_cast(ec)); BOOST_TEST(!a.is_open()); diff --git a/test/unit/local_datagram_socket.cpp b/test/unit/local_datagram_socket.cpp index 380b10344..4817a70f7 100644 --- a/test/unit/local_datagram_socket.cpp +++ b/test/unit/local_datagram_socket.cpp @@ -58,7 +58,7 @@ struct local_datagram_socket_test io_context ioc(Backend); local_datagram_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST_EQ(sock.is_open(), true); sock.close(); @@ -69,7 +69,7 @@ struct local_datagram_socket_test { io_context ioc(Backend); local_datagram_socket s1(ioc); - s1.open(); + BOOST_TEST(!s1.open()); BOOST_TEST_EQ(s1.is_open(), true); local_datagram_socket s2(std::move(s1)); @@ -130,7 +130,7 @@ struct local_datagram_socket_test { io_context ioc(Backend); local_datagram_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); test::temp_socket_dir tmp; auto path = tmp.path(); @@ -150,8 +150,8 @@ struct local_datagram_socket_test local_datagram_socket s1(ioc); local_datagram_socket s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); auto ec1 = s1.bind(local_endpoint(path1)); auto ec2 = s2.bind(local_endpoint(path2)); @@ -209,7 +209,7 @@ struct local_datagram_socket_test { io_context ioc(Backend); local_datagram_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); // Bind to a path under a nonexistent directory auto ec = sock.bind(local_endpoint("/tmp/nonexistent_dir_corosio/sock")); @@ -315,8 +315,8 @@ struct local_datagram_socket_test local_datagram_socket s1(ioc); local_datagram_socket s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); auto ec1 = s1.bind(local_endpoint(abs_path1)); auto ec2 = s2.bind(local_endpoint(abs_path2)); @@ -448,8 +448,8 @@ struct local_datagram_socket_test local_datagram_socket s1(ioc); local_datagram_socket s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); auto ec1 = s1.bind(local_endpoint(path1)); auto ec2 = s2.bind(local_endpoint(path2)); @@ -529,7 +529,7 @@ struct local_datagram_socket_test io_context ioc(Backend); local_datagram_socket s1(ioc); local_datagram_socket s2(ioc); - s1.open(); + BOOST_TEST(!s1.open()); BOOST_TEST_EQ(s1.is_open(), true); s2 = std::move(s1); @@ -554,7 +554,7 @@ struct local_datagram_socket_test BOOST_TEST_EQ(sock.native_handle() < 0, true); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.native_handle() >= 0); } @@ -571,7 +571,7 @@ struct local_datagram_socket_test { io_context ioc(Backend); local_datagram_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); test::temp_socket_dir tmp; auto path = tmp.path(); @@ -589,20 +589,13 @@ struct local_datagram_socket_test if (auto ec = connect_pair(s1, s2)) throw std::system_error(ec, "connect_pair"); - // Throwing overload (best-effort, may report ENOTCONN). - s1.shutdown(shutdown_send); + BOOST_TEST(!s1.shutdown(shutdown_send)); + BOOST_TEST(!s2.shutdown(shutdown_send)); - // Non-throwing overload - std::error_code ec; - s2.shutdown(shutdown_send, ec); - - // Closed-socket no-ops + // Closed socket reports bad_file_descriptor local_datagram_socket closed(ioc); - closed.shutdown(shutdown_send); - - std::error_code ec2; - closed.shutdown(shutdown_send, ec2); - BOOST_TEST_EQ(!ec2, true); + BOOST_TEST(closed.shutdown(shutdown_send) + == std::errc::bad_file_descriptor); } void testBindClosedThrows() @@ -681,7 +674,7 @@ struct local_datagram_socket_test io_context ioc(Backend); auto ex = ioc.get_executor(); local_datagram_socket d1(ioc), d2(ioc); - connect_pair(d1, d2); + BOOST_TEST(!connect_pair(d1, d2)); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0); @@ -699,7 +692,7 @@ struct local_datagram_socket_test recv_done = true; }; auto assigner = [&]() -> capy::task<> { - d1.assign(static_cast(fds[0])); + BOOST_TEST(!d1.assign(static_cast(fds[0]))); co_return; }; capy::run_async(ex)(reader()); @@ -735,16 +728,7 @@ struct local_datagram_socket_test { io_context ioc(Backend); local_datagram_socket sock(ioc); - bool threw = false; - try - { - sock.assign((native_handle_type)-1); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(sock.assign((native_handle_type)-1)); BOOST_TEST(!sock.is_open()); } @@ -756,16 +740,7 @@ struct local_datagram_socket_test local_datagram_socket sock(ioc); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); - bool threw = false; - try - { - sock.assign((native_handle_type)fds[0]); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(sock.assign((native_handle_type)fds[0])); BOOST_TEST(::fcntl(fds[0], F_GETFD) >= 0); BOOST_TEST(!sock.is_open()); ::close(fds[0]); diff --git a/test/unit/local_stream_socket.cpp b/test/unit/local_stream_socket.cpp index 5403fb50f..ad523d947 100644 --- a/test/unit/local_stream_socket.cpp +++ b/test/unit/local_stream_socket.cpp @@ -73,7 +73,7 @@ struct local_stream_socket_test io_context ioc(Backend); local_stream_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST_EQ(sock.is_open(), true); sock.close(); @@ -84,7 +84,7 @@ struct local_stream_socket_test { io_context ioc(Backend); local_stream_socket s1(ioc); - s1.open(); + BOOST_TEST(!s1.open()); BOOST_TEST_EQ(s1.is_open(), true); local_stream_socket s2(std::move(s1)); @@ -100,7 +100,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -111,7 +111,7 @@ struct local_stream_socket_test local_stream_socket server(ioc); local_stream_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); capy::run_async(ex)( [](local_stream_acceptor& a, local_stream_socket& s, @@ -148,7 +148,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -159,7 +159,7 @@ struct local_stream_socket_test bool server_open = false; local_stream_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); capy::run_async(ex)( [](local_stream_acceptor& a, @@ -258,7 +258,7 @@ struct local_stream_socket_test // First bind creates the socket file { local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); } @@ -266,7 +266,7 @@ struct local_stream_socket_test // Second bind without unlink_existing should fail { local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!!ec, true); } @@ -274,7 +274,7 @@ struct local_stream_socket_test // Third bind with unlink_existing should succeed { local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind( local_endpoint(path), bind_option::unlink_existing); BOOST_TEST_EQ(!ec, true); @@ -290,7 +290,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind( local_endpoint(path), bind_option::unlink_existing); BOOST_TEST_EQ(!ec, true); @@ -331,7 +331,7 @@ struct local_stream_socket_test io_context ioc(Backend); local_stream_socket s1(ioc); local_stream_socket s2(ioc); - s1.open(); + BOOST_TEST(!s1.open()); BOOST_TEST_EQ(s1.is_open(), true); BOOST_TEST_EQ(s2.is_open(), false); @@ -367,7 +367,7 @@ struct local_stream_socket_test #endif BOOST_TEST(sock.native_handle() == invalid); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.native_handle() != invalid); sock.close(); } @@ -389,7 +389,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -434,22 +434,13 @@ struct local_stream_socket_test if (auto ec = connect_pair(s1, s2)) throw std::system_error(ec, "connect_pair"); - // Throwing overload (best-effort) - s1.shutdown(shutdown_send); + BOOST_TEST(!s1.shutdown(shutdown_send)); + BOOST_TEST(!s2.shutdown(shutdown_send)); - // Non-throwing overload - std::error_code ec; - s2.shutdown(shutdown_send, ec); - // ec may be unset or ENOTCONN depending on backend; we just want - // the code path exercised. The doc says best-effort. - - // Closed-socket variants + // Closed socket reports bad_file_descriptor local_stream_socket closed(ioc); - closed.shutdown(shutdown_send); - - std::error_code ec2; - closed.shutdown(shutdown_send, ec2); - BOOST_TEST_EQ(!ec2, true); + BOOST_TEST(closed.shutdown(shutdown_send) + == std::errc::bad_file_descriptor); } #if BOOST_COROSIO_POSIX @@ -460,7 +451,7 @@ struct local_stream_socket_test io_context ioc(Backend); auto ex = ioc.get_executor(); local_stream_socket s1(ioc), s2(ioc); - connect_pair(s1, s2); + BOOST_TEST(!connect_pair(s1, s2)); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); @@ -478,7 +469,7 @@ struct local_stream_socket_test read_done = true; }; auto assigner = [&]() -> capy::task<> { - s1.assign(static_cast(fds[0])); + BOOST_TEST(!s1.assign(static_cast(fds[0]))); co_return; }; capy::run_async(ex)(reader()); @@ -515,21 +506,12 @@ struct local_stream_socket_test io_context ioc(Backend); auto ex = ioc.get_executor(); local_stream_socket s1(ioc), s2(ioc); - connect_pair(s1, s2); + BOOST_TEST(!connect_pair(s1, s2)); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0); - bool threw = false; - try - { - s1.assign(static_cast(fds[0])); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(s1.assign(static_cast(fds[0]))); BOOST_TEST(::fcntl(fds[0], F_GETFD) >= 0); // caller keeps it BOOST_TEST(s1.is_open()); @@ -566,16 +548,7 @@ struct local_stream_socket_test { io_context ioc(Backend); local_stream_socket sock(ioc); - bool threw = false; - try - { - sock.assign((native_handle_type)-1); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(sock.assign((native_handle_type)-1)); BOOST_TEST(!sock.is_open()); } @@ -587,16 +560,7 @@ struct local_stream_socket_test local_stream_socket sock(ioc); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0); - bool threw = false; - try - { - sock.assign((native_handle_type)fds[0]); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(sock.assign((native_handle_type)fds[0])); // fd still valid: fcntl succeeds BOOST_TEST(::fcntl(fds[0], F_GETFD) >= 0); BOOST_TEST(!sock.is_open()); @@ -620,17 +584,8 @@ struct local_stream_socket_test int fds[2]; BOOST_TEST(::socketpair( AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0, fds) == 0); - a.assign((native_handle_type)fds[0]); - bool threw = false; - try - { - b.assign((native_handle_type)fds[0]); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(!a.assign((native_handle_type)fds[0])); + BOOST_TEST(b.assign((native_handle_type)fds[0])); BOOST_TEST(a.is_open()); ::close(fds[1]); } @@ -717,7 +672,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -758,7 +713,7 @@ struct local_stream_socket_test test::temp_socket_dir tmp; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(tmp.path())); BOOST_TEST(!ec); ec = acc.listen(); @@ -961,7 +916,7 @@ struct local_stream_socket_test test::temp_socket_dir tmp; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); // AF_UNIX option support varies by platform; the point is to // drive the set/get paths, so accept a system error as a @@ -995,7 +950,7 @@ struct local_stream_socket_test test::temp_socket_dir tmp; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(tmp.path())); BOOST_TEST(!ec); ec = acc.listen(); @@ -1025,7 +980,7 @@ struct local_stream_socket_test test::temp_socket_dir tmp; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(tmp.path())); BOOST_TEST(!ec); ec = acc.listen(); @@ -1065,7 +1020,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST(!ec); ec = acc.listen(); @@ -1110,7 +1065,7 @@ struct local_stream_socket_test test::temp_socket_dir tmp; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(tmp.path())); BOOST_TEST(!ec); @@ -1153,7 +1108,7 @@ struct local_stream_socket_test test::temp_socket_dir tmp; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(tmp.path())); BOOST_TEST(!ec); ec = acc.listen(); @@ -1280,7 +1235,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -1310,7 +1265,7 @@ struct local_stream_socket_test #endif BOOST_TEST(acc.native_handle() == invalid); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(acc.native_handle() != invalid); acc.close(); BOOST_TEST(acc.native_handle() == invalid); @@ -1363,7 +1318,7 @@ struct local_stream_socket_test test::temp_socket_dir adopted_dir; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(held_dir.path())); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -1371,14 +1326,14 @@ struct local_stream_socket_test // Source the replacement descriptor from a second acceptor. local_stream_acceptor donor(ioc); - donor.open(); + BOOST_TEST(!donor.open()); ec = donor.bind(local_endpoint(adopted_dir.path())); BOOST_TEST_EQ(!ec, true); ec = donor.listen(); BOOST_TEST_EQ(!ec, true); auto h = donor.release(); - acc.assign(h); + BOOST_TEST(!acc.assign(h)); BOOST_TEST_EQ(acc.is_open(), true); BOOST_TEST(acc.native_handle() == h); BOOST_TEST_EQ(acc.local_endpoint().path(), adopted_dir.path()); @@ -1396,7 +1351,7 @@ struct local_stream_socket_test test::temp_socket_dir second_dir; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(first_dir.path())); BOOST_TEST_EQ(!ec, true); ec = acc.listen(); @@ -1412,13 +1367,13 @@ struct local_stream_socket_test #endif local_stream_acceptor donor(ioc); - donor.open(); + BOOST_TEST(!donor.open()); ec = donor.bind(local_endpoint(second_dir.path())); BOOST_TEST_EQ(!ec, true); ec = donor.listen(); BOOST_TEST_EQ(!ec, true); - acc.assign(donor.release()); + BOOST_TEST(!acc.assign(donor.release())); BOOST_TEST_EQ(acc.is_open(), true); BOOST_TEST_EQ(acc.local_endpoint().path(), second_dir.path()); @@ -1434,7 +1389,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor first(ioc); - first.open(); + BOOST_TEST(!first.open()); auto ec = first.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); ec = first.listen(); @@ -1444,7 +1399,7 @@ struct local_stream_socket_test BOOST_TEST_EQ(first.is_open(), false); local_stream_acceptor acc(ioc); - acc.assign(h); + BOOST_TEST(!acc.assign(h)); BOOST_TEST_EQ(acc.is_open(), true); BOOST_TEST(acc.native_handle() == h); BOOST_TEST_EQ(acc.local_endpoint().path(), path); @@ -1486,7 +1441,7 @@ struct local_stream_socket_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(!ec, true); @@ -1555,7 +1510,7 @@ struct local_stream_socket_test abs_path += "corosio_test_abstract_bind"; local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(abs_path)); BOOST_TEST(ec == std::errc::operation_not_supported); } diff --git a/test/unit/native/native_io.cpp b/test/unit/native/native_io.cpp index c00acba33..2486c0898 100644 --- a/test/unit/native/native_io.cpp +++ b/test/unit/native/native_io.cpp @@ -31,7 +31,7 @@ struct native_io_test auto ex = ctx.get_executor(); native_tcp_acceptor acc(ctx); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(native_socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -41,7 +41,7 @@ struct native_io_test tcp_socket peer(ctx); native_tcp_socket client(ctx); - client.open(); + BOOST_TEST(!client.open()); bool done = false; std::error_code io_ec; diff --git a/test/unit/native/native_io_uring_specific.cpp b/test/unit/native/native_io_uring_specific.cpp index e629ae28c..407029e2d 100644 --- a/test/unit/native/native_io_uring_specific.cpp +++ b/test/unit/native/native_io_uring_specific.cpp @@ -76,7 +76,7 @@ struct native_io_uring_specific_test { tcp_acceptor acc(ctx); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); BOOST_TEST(!acc.bind(endpoint(0))); // listen() calls start_multishot(), submitting a multishot diff --git a/test/unit/native/native_local_datagram_socket.cpp b/test/unit/native/native_local_datagram_socket.cpp index c28c646ca..df8239445 100644 --- a/test/unit/native/native_local_datagram_socket.cpp +++ b/test/unit/native/native_local_datagram_socket.cpp @@ -97,7 +97,7 @@ struct native_local_datagram_socket_test { io_context ioc(Backend); native_local_datagram_socket s(ioc); - s.open(); + BOOST_TEST(!s.open()); BOOST_TEST(s.is_open()); s.close(); BOOST_TEST_EQ(s.is_open(), false); @@ -107,7 +107,7 @@ struct native_local_datagram_socket_test { io_context ioc(Backend); native_local_datagram_socket s(ioc); - s.open(); + BOOST_TEST(!s.open()); local_datagram_socket& base = s; BOOST_TEST(base.is_open()); } @@ -122,8 +122,8 @@ struct native_local_datagram_socket_test native_local_datagram_socket sender(ioc); native_local_datagram_socket receiver(ioc); - sender.open(); - receiver.open(); + BOOST_TEST(!sender.open()); + BOOST_TEST(!receiver.open()); auto ec1 = sender.bind(local_endpoint(path1)); auto ec2 = receiver.bind(local_endpoint(path2)); @@ -164,8 +164,8 @@ struct native_local_datagram_socket_test native_local_datagram_socket a(ioc); native_local_datagram_socket b(ioc); - a.open(); - b.open(); + BOOST_TEST(!a.open()); + BOOST_TEST(!b.open()); auto eca = a.bind(local_endpoint(path_a)); auto ecb = b.bind(local_endpoint(path_b)); @@ -212,8 +212,8 @@ struct native_local_datagram_socket_test native_local_datagram_socket sender(ioc); native_local_datagram_socket receiver(ioc); - sender.open(); - receiver.open(); + BOOST_TEST(!sender.open()); + BOOST_TEST(!receiver.open()); auto ec1 = sender.bind(local_endpoint(path1)); auto ec2 = receiver.bind(local_endpoint(path2)); @@ -253,12 +253,12 @@ struct native_local_datagram_socket_test auto rx_path = rx_tmp.path(); native_local_datagram_socket recv(ioc); - recv.open(); + BOOST_TEST(!recv.open()); auto bec = recv.bind(local_endpoint(rx_path)); BOOST_TEST(!bec); native_local_datagram_socket send(ioc); - send.open(); + BOOST_TEST(!send.open()); std::error_code wait_ec; bool wait_done = false; diff --git a/test/unit/native/native_random_access_file.cpp b/test/unit/native/native_random_access_file.cpp index 4b45ee32b..444de6479 100644 --- a/test/unit/native/native_random_access_file.cpp +++ b/test/unit/native/native_random_access_file.cpp @@ -101,7 +101,7 @@ struct native_random_access_file_test io_context ioc(Backend); temp_file tmp("native_raf_slice_", "x"); native_random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); random_access_file& base = f; BOOST_TEST(base.is_open()); @@ -114,7 +114,7 @@ struct native_random_access_file_test io_context ioc(Backend); native_random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); char buf[5] = {}; std::size_t n_out = 0; @@ -138,9 +138,9 @@ struct native_random_access_file_test io_context ioc(Backend); native_random_access_file f(ioc); - f.open( + BOOST_TEST(!f.open( tmp.path, - file_base::read_write | file_base::create | file_base::truncate); + file_base::read_write | file_base::create | file_base::truncate)); std::size_t written = 0; auto task = [&]() -> capy::task<> { @@ -169,7 +169,7 @@ struct native_random_access_file_test io_context ioc(Backend); native_random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); random_access_file& base = f; diff --git a/test/unit/native/native_stream_file.cpp b/test/unit/native/native_stream_file.cpp index fc9771137..ec56bffc8 100644 --- a/test/unit/native/native_stream_file.cpp +++ b/test/unit/native/native_stream_file.cpp @@ -95,7 +95,7 @@ struct native_stream_file_test io_context ioc(Backend); temp_file tmp("native_sf_slice_", "x"); native_stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); stream_file& base = f; BOOST_TEST(base.is_open()); @@ -108,7 +108,7 @@ struct native_stream_file_test io_context ioc(Backend); native_stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); char buf[64] = {}; std::size_t n_out = 0; @@ -132,9 +132,9 @@ struct native_stream_file_test io_context ioc(Backend); native_stream_file f(ioc); - f.open( + BOOST_TEST(!f.open( tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + file_base::write_only | file_base::create | file_base::truncate)); char const msg[] = "native write"; std::size_t written = 0; @@ -165,7 +165,7 @@ struct native_stream_file_test io_context ioc(Backend); native_stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); stream_file& base = f; diff --git a/test/unit/native/native_tcp_socket.cpp b/test/unit/native/native_tcp_socket.cpp index 9808981d1..f0687968a 100644 --- a/test/unit/native/native_tcp_socket.cpp +++ b/test/unit/native/native_tcp_socket.cpp @@ -72,7 +72,7 @@ struct native_tcp_socket_test { io_context ctx(Backend); native_tcp_socket s1(ctx); - s1.open(); + BOOST_TEST(!s1.open()); BOOST_TEST(s1.is_open()); native_tcp_socket s2(std::move(s1)); @@ -83,7 +83,7 @@ struct native_tcp_socket_test { io_context ctx(Backend); native_tcp_socket ns(ctx); - ns.open(); + BOOST_TEST(!ns.open()); tcp_socket& base = ns; BOOST_TEST(base.is_open()); @@ -131,7 +131,7 @@ struct native_tcp_socket_test { io_context ctx(Backend); native_tcp_socket s(ctx); - s.open(); + BOOST_TEST(!s.open()); s.set_option(native_socket_option::no_delay(true)); auto nd = s.template get_option(); diff --git a/test/unit/native/native_udp_socket.cpp b/test/unit/native/native_udp_socket.cpp index 090b75b0f..76d9b90e5 100644 --- a/test/unit/native/native_udp_socket.cpp +++ b/test/unit/native/native_udp_socket.cpp @@ -87,7 +87,7 @@ struct native_udp_socket_test { io_context ctx(Backend); native_udp_socket s1(ctx); - s1.open(); + BOOST_TEST(!s1.open()); BOOST_TEST(s1.is_open()); native_udp_socket s2(std::move(s1)); @@ -98,7 +98,7 @@ struct native_udp_socket_test { io_context ctx(Backend); native_udp_socket ns(ctx); - ns.open(); + BOOST_TEST(!ns.open()); udp_socket& base = ns; BOOST_TEST(base.is_open()); @@ -113,8 +113,8 @@ struct native_udp_socket_test native_udp_socket sender(ioc); native_udp_socket receiver(ioc); - sender.open(); - receiver.open(); + BOOST_TEST(!sender.open()); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -150,7 +150,7 @@ struct native_udp_socket_test io_context ioc(Backend); native_udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -188,7 +188,7 @@ struct native_udp_socket_test io_context ioc(Backend); native_udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -227,7 +227,7 @@ struct native_udp_socket_test native_udp_socket a(ioc); native_udp_socket b(ioc); - b.open(); + BOOST_TEST(!b.open()); auto ec = b.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto b_ep = b.local_endpoint(); @@ -279,7 +279,7 @@ struct native_udp_socket_test io_context ioc(Backend); native_udp_socket receiver(ioc); - receiver.open(); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto recv_ep = receiver.local_endpoint(); @@ -307,8 +307,8 @@ struct native_udp_socket_test native_udp_socket sender(ioc); native_udp_socket receiver(ioc); - sender.open(); - receiver.open(); + BOOST_TEST(!sender.open()); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -345,13 +345,13 @@ struct native_udp_socket_test auto ex = ioc.get_executor(); native_udp_socket recv(ioc); - recv.open(udp::v4()); + BOOST_TEST(!recv.open(udp::v4())); auto bec = recv.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); auto port = recv.local_endpoint().port(); native_udp_socket send(ioc); - send.open(udp::v4()); + BOOST_TEST(!send.open(udp::v4())); std::error_code wait_ec; bool wait_done = false; @@ -386,7 +386,7 @@ struct native_udp_socket_test { io_context ioc(Backend); native_udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(native_socket_option::broadcast(true)); auto bc = @@ -440,7 +440,7 @@ struct native_udp_socket_test { io_context ioc(Backend); native_udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); try { @@ -474,7 +474,7 @@ struct native_udp_socket_test { io_context ioc(Backend); native_udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); sock.set_option(native_socket_option::multicast_loop_v6(true)); sock.set_option(native_socket_option::multicast_hops_v6(4)); diff --git a/test/unit/openssl_engine.cpp b/test/unit/openssl_engine.cpp index 1f24f19b0..9b1b6ce10 100644 --- a/test/unit/openssl_engine.cpp +++ b/test/unit/openssl_engine.cpp @@ -615,15 +615,14 @@ struct openssl_engine_test testPasswordTruncation() { tls_context ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_certificate(test::server_cert_pem, tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) + BOOST_TEST( + !ctx.use_certificate(test::server_cert_pem, tls_file_format::pem)); ctx.set_password_callback( [](std::size_t, tls_password_purpose) { return std::string(4096, 'x'); }); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_private_key( + // The oversized password may fail here or latch for init(). + (void)ctx.use_private_key( test::encrypted_server_key_pem, tls_file_format::pem); ossl_engine eng; @@ -637,10 +636,10 @@ struct openssl_engine_test testGarbageDerCertificateFailsSetup() { tls_context ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_private_key(test::server_key_pem, tls_file_format::pem); + // Whether the garbage surfaces here or at init() is + // backend-dependent; the init failure below is what matters. + (void)ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); + (void)ctx.use_private_key(test::server_key_pem, tls_file_format::pem); ossl_engine eng; BOOST_TEST(!eng.init(ctx)); diff --git a/test/unit/openssl_stream.cpp b/test/unit/openssl_stream.cpp index 08de60e23..608dcb1ea 100644 --- a/test/unit/openssl_stream.cpp +++ b/test/unit/openssl_stream.cpp @@ -158,10 +158,8 @@ struct openssl_stream_test { io_context ioc; tls_context client_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.add_verify_path(dir.string()); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_mode(tls_verify_mode::peer); + BOOST_TEST(!client_ctx.add_verify_path(dir.string())); + BOOST_TEST(!client_ctx.set_verify_mode(tls_verify_mode::peer)); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); diff --git a/test/unit/precancel.cpp b/test/unit/precancel.cpp index a35720370..54d70063a 100644 --- a/test/unit/precancel.cpp +++ b/test/unit/precancel.cpp @@ -103,7 +103,7 @@ struct precancel_test auto ex = ioc.get_executor(); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::stop_source ss; ss.request_stop(); @@ -131,7 +131,7 @@ struct precancel_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); BOOST_TEST(!acc.bind(endpoint(0))); BOOST_TEST(!acc.listen()); @@ -163,8 +163,8 @@ struct precancel_test auto ex = ioc.get_executor(); udp_socket s1(ioc), s2(ioc); - s1.open(udp::v4()); - s2.open(udp::v4()); + BOOST_TEST(!s1.open(udp::v4())); + BOOST_TEST(!s2.open(udp::v4())); BOOST_TEST(!s1.bind(endpoint(ipv4_address::loopback(), 0))); BOOST_TEST(!s2.bind(endpoint(ipv4_address::loopback(), 0))); auto peer_ep = s2.local_endpoint(); @@ -310,7 +310,7 @@ struct precancel_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(!acc.bind(local_endpoint(path))); BOOST_TEST(!acc.listen()); @@ -318,7 +318,7 @@ struct precancel_test ss.request_stop(); local_stream_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); local_stream_socket server(ioc); std::error_code conn_ec, accept_ec; @@ -400,8 +400,8 @@ struct precancel_test test::temp_socket_dir tmp2; local_datagram_socket s1(ioc), s2(ioc); - s1.open(); - s2.open(); + BOOST_TEST(!s1.open()); + BOOST_TEST(!s2.open()); BOOST_TEST(!s1.bind(local_endpoint(tmp1.path()))); BOOST_TEST(!s2.bind(local_endpoint(tmp2.path()))); diff --git a/test/unit/random_access_file.cpp b/test/unit/random_access_file.cpp index aa5ad7581..9e71c1290 100644 --- a/test/unit/random_access_file.cpp +++ b/test/unit/random_access_file.cpp @@ -129,7 +129,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST(f.is_open()); f.close(); @@ -141,17 +141,10 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - bool threw = false; - try - { - f.open("/tmp/corosio_nonexistent_raf_zzz_12345", - file_base::read_only); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto ec = f.open("/tmp/corosio_nonexistent_raf_zzz_12345", + file_base::read_only); + BOOST_TEST(ec == std::errc::no_such_file_or_directory); + BOOST_TEST(!f.is_open()); } // File metadata @@ -163,7 +156,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST_EQ(f.size(), static_cast(data.size())); } @@ -173,22 +166,13 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_write); - f.resize(5); + BOOST_TEST(!f.open(tmp.path, file_base::read_write)); + BOOST_TEST(!f.resize(5)); BOOST_TEST_EQ(f.size(), 5u); #if BOOST_COROSIO_POSIX // Larger than off_t can represent: rejected with EOVERFLOW. - bool caught = false; - try - { - f.resize((std::numeric_limits::max)()); - } - catch (std::system_error const&) - { - caught = true; - } - BOOST_TEST(caught); + BOOST_TEST(f.resize((std::numeric_limits::max)())); #endif } @@ -201,7 +185,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool completed = false; char buf[5] = {}; @@ -230,7 +214,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool completed = false; char buf[5] = {}; @@ -257,7 +241,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool got_eof = false; @@ -284,8 +268,8 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::read_write | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::read_write | file_base::create | file_base::truncate)); bool completed = false; @@ -318,8 +302,8 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::read_write | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::read_write | file_base::create | file_base::truncate)); bool completed = false; @@ -378,7 +362,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); int read_count = 0; @@ -410,7 +394,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); f.cancel(); BOOST_TEST_PASS(); @@ -439,7 +423,7 @@ struct random_access_file_test #endif BOOST_TEST(f.native_handle() == invalid); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST(f.native_handle() != invalid); } @@ -450,11 +434,11 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp1.path, file_base::read_only); + BOOST_TEST(!f.open(tmp1.path, file_base::read_only)); BOOST_TEST(f.is_open()); // Reopen on an already-open file closes the previous handle. - f.open(tmp2.path, file_base::read_only); + BOOST_TEST(!f.open(tmp2.path, file_base::read_only)); BOOST_TEST(f.is_open()); } @@ -466,8 +450,8 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create | file_base::truncate)); bool completed = false; @@ -476,7 +460,7 @@ struct random_access_file_test auto [ec, n] = co_await f_ref.write_some_at( 0, capy::const_buffer("sync", 4)); BOOST_TEST(!ec); - f_ref.sync_data(); + BOOST_TEST(!f_ref.sync_data()); done = true; }; capy::run_async(ioc.get_executor())(task(f, completed)); @@ -496,7 +480,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); int completed = 0; @@ -530,9 +514,9 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::read_write | file_base::create | file_base::truncate); - f.resize(16); + BOOST_TEST(!f.open(tmp.path, + file_base::read_write | file_base::create | file_base::truncate)); + BOOST_TEST(!f.resize(16)); int completed = 0; @@ -573,7 +557,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_write); + BOOST_TEST(!f.open(tmp.path, file_base::read_write)); bool read_done = false; bool write_done = false; @@ -620,7 +604,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); std::atomic completed{0}; @@ -683,7 +667,7 @@ struct random_access_file_test testSyncAll(); testRelease(); testAssign(); - testClosedFileThrows(); + testClosedFileErrors(); testOpenSyncAllOnWrite(); testOpenExclusiveExistingFails(); testOpenExclusiveNewFile(); @@ -695,12 +679,13 @@ struct random_access_file_test // Operations on closed file - void testClosedFileThrows() + void testClosedFileErrors() { io_context ioc(Backend); random_access_file f(ioc); BOOST_TEST(!f.is_open()); + // Exceptional-only operations throw on a closed file auto expect_throw = [](auto fn) { bool threw = false; try { fn(); } @@ -709,10 +694,12 @@ struct random_access_file_test }; expect_throw([&] { f.size(); }); - expect_throw([&] { f.resize(0); }); - expect_throw([&] { f.sync_data(); }); - expect_throw([&] { f.sync_all(); }); expect_throw([&] { f.release(); }); + + // Error-returning operations report bad_file_descriptor + BOOST_TEST(f.resize(0) == std::errc::bad_file_descriptor); + BOOST_TEST(f.sync_data() == std::errc::bad_file_descriptor); + BOOST_TEST(f.sync_all() == std::errc::bad_file_descriptor); } // Open flag variants @@ -724,9 +711,9 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create - | file_base::truncate | file_base::sync_all_on_write); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create + | file_base::truncate | file_base::sync_all_on_write)); BOOST_TEST(f.is_open()); bool done = false; @@ -751,18 +738,10 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - bool threw = false; - try - { - f.open(tmp.path, - file_base::write_only | file_base::create - | file_base::exclusive); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto ec = f.open(tmp.path, + file_base::write_only | file_base::create + | file_base::exclusive); + BOOST_TEST(ec == std::errc::file_exists); BOOST_TEST(!f.is_open()); } @@ -774,9 +753,9 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create - | file_base::exclusive); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create + | file_base::exclusive)); BOOST_TEST(f.is_open()); f.close(); } @@ -789,7 +768,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_write); + BOOST_TEST(!f.open(tmp.path, file_base::read_write)); bool done = false; auto task = [](random_access_file& f_ref, bool& d) -> capy::task<> { @@ -819,7 +798,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); constexpr std::uint64_t num_ops = 16; std::atomic completed{0}; @@ -853,7 +832,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool done = false; auto task = [](random_access_file& f_ref, bool& d) -> capy::task<> { @@ -877,7 +856,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); std::stop_source stop_src; stop_src.request_stop(); @@ -911,8 +890,8 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create | file_base::truncate)); bool completed = false; @@ -921,7 +900,7 @@ struct random_access_file_test auto [ec, n] = co_await f_ref.write_some_at( 0, capy::const_buffer("sync_all", 8)); BOOST_TEST(!ec); - f_ref.sync_all(); + BOOST_TEST(!f_ref.sync_all()); done = true; }; capy::run_async(ioc.get_executor())(task(f, completed)); @@ -939,7 +918,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST(f.is_open()); auto handle = f.release(); @@ -1001,7 +980,7 @@ struct random_access_file_test io_context ioc(Backend); random_access_file f(ioc); - f.assign(raw_handle); + BOOST_TEST(!f.assign(raw_handle)); BOOST_TEST(f.is_open()); bool completed = false; diff --git a/test/unit/reactor_paths.cpp b/test/unit/reactor_paths.cpp index ff2afa20a..f66f2bb4f 100644 --- a/test/unit/reactor_paths.cpp +++ b/test/unit/reactor_paths.cpp @@ -123,7 +123,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); std::error_code conn_ec; bool conn_done = false; @@ -164,7 +164,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); // Use port 1 which is well-known reserved and very unlikely to // be listening; the resulting connect will get RST or fail. @@ -424,7 +424,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto bec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -455,7 +455,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto bec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -491,7 +491,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -519,7 +519,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -552,7 +552,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -583,8 +583,8 @@ struct reactor_paths_test // Use a connected pair to avoid sendto address issues. udp_socket s1(ioc); udp_socket s2(ioc); - s1.open(udp::v4()); - s2.open(udp::v4()); + BOOST_TEST(!s1.open(udp::v4())); + BOOST_TEST(!s2.open(udp::v4())); auto e1 = s1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!e1); auto e2 = s2.bind(endpoint(ipv4_address::loopback(), 0)); @@ -633,8 +633,8 @@ struct reactor_paths_test udp_socket s1(ioc); udp_socket s2(ioc); - s1.open(udp::v4()); - s2.open(udp::v4()); + BOOST_TEST(!s1.open(udp::v4())); + BOOST_TEST(!s2.open(udp::v4())); auto e1 = s1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!e1); auto e2 = s2.bind(endpoint(ipv4_address::loopback(), 0)); @@ -679,8 +679,8 @@ struct reactor_paths_test udp_socket s1(ioc); udp_socket s2(ioc); - s1.open(udp::v4()); - s2.open(udp::v4()); + BOOST_TEST(!s1.open(udp::v4())); + BOOST_TEST(!s2.open(udp::v4())); auto e1 = s1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!e1); auto e2 = s2.bind(endpoint(ipv4_address::loopback(), 0)); @@ -717,7 +717,7 @@ struct reactor_paths_test bool ok = false; auto task = [&]() -> capy::task<> { - s1.shutdown(shutdown_both); + BOOST_TEST(!s1.shutdown(shutdown_both)); ok = true; co_return; }; @@ -735,7 +735,7 @@ struct reactor_paths_test bool ok = false; auto task = [&]() -> capy::task<> { - s1.shutdown(shutdown_receive); + BOOST_TEST(!s1.shutdown(shutdown_receive)); ok = true; co_return; }; @@ -752,7 +752,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -797,14 +797,14 @@ struct reactor_paths_test for (int i = 0; i < N; ++i) { accs.emplace_back(ioc); - accs.back().open(); + BOOST_TEST(!accs.back().open()); accs.back().set_option(socket_option::reuse_address(true)); BOOST_TEST(!accs.back().bind(endpoint(ipv4_address::loopback(), 0))); BOOST_TEST(!accs.back().listen()); ports.push_back(accs.back().local_endpoint().port()); peers.emplace_back(ioc); clients.emplace_back(ioc); - clients.back().open(); + BOOST_TEST(!clients.back().open()); } std::array accept_done{}; @@ -908,8 +908,8 @@ struct reactor_paths_test udp_socket s1(ioc); udp_socket s2(ioc); - s1.open(udp::v4()); - s2.open(udp::v4()); + BOOST_TEST(!s1.open(udp::v4())); + BOOST_TEST(!s2.open(udp::v4())); auto e1 = s1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!e1); auto e2 = s2.bind(endpoint(ipv4_address::loopback(), 0)); @@ -962,7 +962,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -999,7 +999,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); BOOST_TEST(!acc.bind(endpoint(ipv4_address::loopback(), 0))); BOOST_TEST(!acc.listen()); @@ -1047,7 +1047,7 @@ struct reactor_paths_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); BOOST_TEST(!acc.bind(endpoint(ipv4_address::loopback(), 0))); BOOST_TEST(!acc.listen()); @@ -1112,7 +1112,7 @@ struct reactor_paths_test auto port = ntohs(addr.sin_port); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto [t1, t2] = test::make_socket_pair(ioc); @@ -1277,12 +1277,12 @@ struct reactor_paths_test auto ex = ioc.get_executor(); udp_socket rsock(ioc); - rsock.open(udp::v4()); + BOOST_TEST(!rsock.open(udp::v4())); auto bec = rsock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); udp_socket ssock(ioc); - ssock.open(udp::v4()); + BOOST_TEST(!ssock.open(udp::v4())); std::error_code wait_ec; bool wait_done = false; @@ -1356,19 +1356,10 @@ struct reactor_paths_test BOOST_TEST(tcp_fd >= 0); local_stream_socket sock(ioc); - bool threw = false; - try - { - sock.assign(tcp_fd); - } - catch (std::system_error const&) - { - threw = true; - } - // assign may throw on wrong type/family; cleanup if owned. + BOOST_TEST(sock.assign(tcp_fd)); + // rejection leaves ownership with the caller if (!sock.is_open()) ::close(tcp_fd); - BOOST_TEST(threw); } // Assign a stream-type fd (AF_UNIX SOCK_STREAM) to local_datagram_socket: @@ -1381,18 +1372,9 @@ struct reactor_paths_test BOOST_TEST(fd >= 0); local_datagram_socket sock(ioc); - bool threw = false; - try - { - sock.assign(fd); - } - catch (std::system_error const&) - { - threw = true; - } + BOOST_TEST(sock.assign(fd)); if (!sock.is_open()) ::close(fd); - BOOST_TEST(threw); } // Local stream socket wait_type::error then cancel. Exercises the // local-endpoint specialization of reactor_stream_socket. @@ -1655,7 +1637,7 @@ struct reactor_paths_test std::error_code ec; auto task = [&]() -> capy::task<> { - s1.shutdown(shutdown_both, ec); + ec = s1.shutdown(shutdown_both); co_return; }; capy::run_async(ioc.get_executor())(task()); @@ -1673,7 +1655,7 @@ struct reactor_paths_test std::error_code ec; auto task = [&]() -> capy::task<> { - s1.shutdown(shutdown_receive, ec); + ec = s1.shutdown(shutdown_receive); co_return; }; capy::run_async(ioc.get_executor())(task()); @@ -1793,7 +1775,7 @@ struct reactor_paths_test test::make_socket_pair(ioc); udp_socket u1(ioc); - u1.open(udp::v4()); + BOOST_TEST(!u1.open(udp::v4())); auto bec = u1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); diff --git a/test/unit/socket_option.cpp b/test/unit/socket_option.cpp index 96db6a404..69596143c 100644 --- a/test/unit/socket_option.cpp +++ b/test/unit/socket_option.cpp @@ -44,7 +44,7 @@ struct socket_option_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::no_delay(true)); BOOST_TEST(sock.get_option().value()); @@ -81,7 +81,7 @@ struct socket_option_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -98,7 +98,7 @@ struct socket_option_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); sock.set_option(socket_option::broadcast(true)); BOOST_TEST(sock.get_option().value()); @@ -120,7 +120,7 @@ struct socket_option_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); sock.set_option(socket_option::multicast_loop_v4(false)); BOOST_TEST( @@ -140,7 +140,7 @@ struct socket_option_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); sock.set_option(socket_option::v6_only(true)); BOOST_TEST(sock.get_option().value()); @@ -183,7 +183,7 @@ struct socket_option_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); bool threw = false; try diff --git a/test/unit/socket_stress.cpp b/test/unit/socket_stress.cpp index 2fa7a8597..d38adf811 100644 --- a/test/unit/socket_stress.cpp +++ b/test/unit/socket_stress.cpp @@ -68,7 +68,7 @@ make_stress_pair(io_context& ctx) bool connect_done = false; tcp_acceptor acc(ctx); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) throw std::runtime_error("stress_pair bind failed: " + ec.message()); @@ -78,7 +78,7 @@ make_stress_pair(io_context& ctx) tcp_socket s1(ctx); tcp_socket s2(ctx); - s2.open(); + BOOST_TEST(!s2.open()); capy::run_async(ex)( [](tcp_acceptor& a, tcp_socket& s, std::error_code& ec_out, @@ -623,7 +623,7 @@ struct accept_stress_test std::atomic stop_flag{false}; tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); if (auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0))) { @@ -659,7 +659,7 @@ struct accept_stress_test while (!stop_flag.load(std::memory_order_relaxed)) { tcp_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); auto [ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); (void)ec; diff --git a/test/unit/stream_file.cpp b/test/unit/stream_file.cpp index f313174ca..6fbababbc 100644 --- a/test/unit/stream_file.cpp +++ b/test/unit/stream_file.cpp @@ -142,7 +142,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST(f.is_open()); f.close(); @@ -155,7 +155,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::write_only | file_base::create); + BOOST_TEST(!f.open(tmp.path, file_base::write_only | file_base::create)); BOOST_TEST(f.is_open()); f.close(); @@ -168,17 +168,9 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - bool threw = false; - try - { - f.open("/tmp/corosio_nonexistent_file_zzz_12345", - file_base::read_only); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto ec = f.open("/tmp/corosio_nonexistent_file_zzz_12345", + file_base::read_only); + BOOST_TEST(ec == std::errc::no_such_file_or_directory); BOOST_TEST(!f.is_open()); } @@ -189,18 +181,10 @@ struct stream_file_test stream_file f(ioc); // Opening with create|exclusive on an existing file should fail - bool threw = false; - try - { - f.open(tmp.path, - file_base::write_only | file_base::create - | file_base::exclusive); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto ec = f.open(tmp.path, + file_base::write_only | file_base::create + | file_base::exclusive); + BOOST_TEST(ec == std::errc::file_exists); } void testOpenSyncAllOnWrite() @@ -210,9 +194,9 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create - | file_base::truncate | file_base::sync_all_on_write); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create + | file_base::truncate | file_base::sync_all_on_write)); BOOST_TEST(f.is_open()); bool done = false; @@ -237,7 +221,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST_EQ(f.size(), static_cast(data.size())); } @@ -247,22 +231,13 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_write); - f.resize(5); + BOOST_TEST(!f.open(tmp.path, file_base::read_write)); + BOOST_TEST(!f.resize(5)); BOOST_TEST_EQ(f.size(), 5u); #if BOOST_COROSIO_POSIX // Larger than off_t can represent: rejected with EOVERFLOW. - bool caught = false; - try - { - f.resize((std::numeric_limits::max)()); - } - catch (std::system_error const&) - { - caught = true; - } - BOOST_TEST(caught); + BOOST_TEST(f.resize((std::numeric_limits::max)())); #endif } @@ -272,16 +247,24 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); - auto pos = f.seek(5, file_base::seek_set); - BOOST_TEST_EQ(pos, 5u); + auto [ec1, pos1] = f.seek(5, file_base::seek_set); + BOOST_TEST(!ec1); + BOOST_TEST_EQ(pos1, 5u); - pos = f.seek(3, file_base::seek_cur); - BOOST_TEST_EQ(pos, 8u); + auto [ec2, pos2] = f.seek(3, file_base::seek_cur); + BOOST_TEST(!ec2); + BOOST_TEST_EQ(pos2, 8u); - pos = f.seek(-2, file_base::seek_end); - BOOST_TEST_EQ(pos, 8u); // size=10, 10-2=8 + auto [ec3, pos3] = f.seek(-2, file_base::seek_end); + BOOST_TEST(!ec3); + BOOST_TEST_EQ(pos3, 8u); // size=10, 10-2=8 + + // Seeking past EOF is allowed + auto [ec4, pos4] = f.seek(100, file_base::seek_set); + BOOST_TEST(!ec4); + BOOST_TEST_EQ(pos4, 100u); } // Async read @@ -293,7 +276,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool completed = false; std::error_code result_ec; @@ -326,7 +309,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool got_eof = false; @@ -359,8 +342,8 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create | file_base::truncate)); std::string data = "written by corosio"; bool completed = false; @@ -396,8 +379,8 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::read_write | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::read_write | file_base::create | file_base::truncate)); bool completed = false; @@ -417,7 +400,8 @@ struct stream_file_test } // Seek back to start - f_ref.seek(0, file_base::seek_set); + auto [sec, spos] = f_ref.seek(0, file_base::seek_set); + BOOST_TEST(!sec); // Read back char buf[6] = {}; @@ -446,8 +430,8 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create | file_base::truncate)); bool completed = false; @@ -455,7 +439,7 @@ struct stream_file_test auto [ec, n] = co_await f_ref.write_some( capy::const_buffer("data", 4)); BOOST_TEST(!ec); - f_ref.sync_data(); + BOOST_TEST(!f_ref.sync_data()); done = true; }; capy::run_async(ioc.get_executor())(task(f, completed)); @@ -473,7 +457,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); f.cancel(); // Should not crash BOOST_TEST_PASS(); @@ -503,7 +487,7 @@ struct stream_file_test // Closed: returns the platform sentinel. BOOST_TEST(f.native_handle() == invalid); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST(f.native_handle() != invalid); } @@ -514,11 +498,11 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp1.path, file_base::read_only); + BOOST_TEST(!f.open(tmp1.path, file_base::read_only)); BOOST_TEST(f.is_open()); // Reopen on an already-open file closes the previous handle. - f.open(tmp2.path, file_base::read_only); + BOOST_TEST(!f.open(tmp2.path, file_base::read_only)); BOOST_TEST(f.is_open()); } @@ -540,16 +524,8 @@ struct stream_file_test io_context ioc(Backend, opts, 1); stream_file f(ioc); - bool caught = false; - try - { - f.open(tmp.path, file_base::read_only); - } - catch (std::system_error const& e) - { - caught = (e.code() == std::errc::operation_not_supported); - } - BOOST_TEST(caught); + auto ec = f.open(tmp.path, file_base::read_only); + BOOST_TEST(ec == std::errc::operation_not_supported); } void testOpenUnsafeIoStillSupported() @@ -568,19 +544,8 @@ struct stream_file_test io_context ioc(Backend, opts, 1); stream_file f(ioc); - bool opened = false; - std::error_code caught_ec; - try - { - f.open(tmp.path, file_base::read_only); - opened = true; - } - catch (std::system_error const& e) - { - caught_ec = e.code(); - } - BOOST_TEST(opened); - BOOST_TEST(caught_ec != std::errc::operation_not_supported); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); + BOOST_TEST(f.is_open()); } #endif @@ -592,7 +557,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); bool completed = false; @@ -615,8 +580,8 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create | file_base::truncate)); bool completed = false; @@ -640,7 +605,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::write_only | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, file_base::write_only | file_base::truncate)); BOOST_TEST_EQ(f.size(), 0u); } @@ -652,8 +617,8 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::append); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::append)); bool completed = false; @@ -687,8 +652,8 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, - file_base::write_only | file_base::create | file_base::truncate); + BOOST_TEST(!f.open(tmp.path, + file_base::write_only | file_base::create | file_base::truncate)); bool completed = false; @@ -696,7 +661,7 @@ struct stream_file_test auto [ec, n] = co_await f_ref.write_some( capy::const_buffer("data", 4)); BOOST_TEST(!ec); - f_ref.sync_all(); + BOOST_TEST(!f_ref.sync_all()); done = true; }; capy::run_async(ioc.get_executor())(task(f, completed)); @@ -714,7 +679,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); BOOST_TEST(f.is_open()); auto handle = f.release(); @@ -766,7 +731,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.assign(raw_handle); + BOOST_TEST(!f.assign(raw_handle)); BOOST_TEST(f.is_open()); bool completed = false; @@ -789,13 +754,13 @@ struct stream_file_test // Operations on closed file - void testClosedFileThrows() + void testClosedFileErrors() { io_context ioc(Backend); stream_file f(ioc); BOOST_TEST(!f.is_open()); - // Each operation on a closed file should throw + // Exceptional-only operations throw on a closed file auto expect_throw = [](auto fn) { bool threw = false; try { fn(); } @@ -804,40 +769,43 @@ struct stream_file_test }; expect_throw([&] { f.size(); }); - expect_throw([&] { f.resize(0); }); - expect_throw([&] { f.sync_data(); }); - expect_throw([&] { f.sync_all(); }); expect_throw([&] { f.release(); }); - expect_throw([&] { f.seek(0, file_base::seek_set); }); + + // Error-returning operations report bad_file_descriptor + BOOST_TEST(f.resize(0) == std::errc::bad_file_descriptor); + BOOST_TEST(f.sync_data() == std::errc::bad_file_descriptor); + BOOST_TEST(f.sync_all() == std::errc::bad_file_descriptor); + auto [ec, pos] = f.seek(0, file_base::seek_set); + BOOST_TEST(ec == std::errc::bad_file_descriptor); } // Negative seek validation - void testSeekNegativeThrows() + void testSeekNegative() { temp_file tmp("sf_seekneg_", "0123456789"); io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); // seek_set with negative offset - bool threw = false; - try { f.seek(-1, file_base::seek_set); } - catch (std::system_error const&) { threw = true; } - BOOST_TEST(threw); + { + auto [ec, pos] = f.seek(-1, file_base::seek_set); + BOOST_TEST(ec); + } // seek_end past beginning - threw = false; - try { f.seek(-100, file_base::seek_end); } - catch (std::system_error const&) { threw = true; } - BOOST_TEST(threw); + { + auto [ec, pos] = f.seek(-100, file_base::seek_end); + BOOST_TEST(ec); + } // seek_cur past beginning - threw = false; - try { f.seek(-100, file_base::seek_cur); } - catch (std::system_error const&) { threw = true; } - BOOST_TEST(threw); + { + auto [ec, pos] = f.seek(-100, file_base::seek_cur); + BOOST_TEST(ec); + } } void run() @@ -879,8 +847,8 @@ struct stream_file_test testAppendMode(); testRelease(); testAssign(); - testClosedFileThrows(); - testSeekNegativeThrows(); + testClosedFileErrors(); + testSeekNegative(); testCancelWithStoppedToken(); } @@ -892,7 +860,7 @@ struct stream_file_test io_context ioc(Backend); stream_file f(ioc); - f.open(tmp.path, file_base::read_only); + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); // Pre-stop the source so the token is already cancelled // when the coroutine starts diff --git a/test/unit/tcp_acceptor.cpp b/test/unit/tcp_acceptor.cpp index cd8675760..83d591676 100644 --- a/test/unit/tcp_acceptor.cpp +++ b/test/unit/tcp_acceptor.cpp @@ -183,7 +183,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(0)); BOOST_TEST(!ec); @@ -201,7 +201,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto opt = acc.get_option(); BOOST_TEST(opt.value()); @@ -220,7 +220,7 @@ struct tcp_acceptor_test { io_context ioc(Backend); tcp_acceptor acc1(ioc); - acc1.open(); + BOOST_TEST(!acc1.open()); acc1.set_option(socket_option::reuse_address(true)); auto ec = acc1.bind(endpoint(0)); BOOST_TEST(!ec); @@ -241,7 +241,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc1(ioc); tcp_acceptor acc2(ioc); - acc1.open(); + BOOST_TEST(!acc1.open()); acc1.set_option(socket_option::reuse_address(true)); auto ec = acc1.bind(endpoint(0)); BOOST_TEST(!ec); @@ -267,7 +267,7 @@ struct tcp_acceptor_test // acceptor impl alive until IOCP delivers the cancellation. io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(0)); BOOST_TEST(!ec); @@ -315,7 +315,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); // Bind to loopback explicitly: connecting to a wildcard-bound // listener's 0.0.0.0 address only works on some platforms. @@ -363,7 +363,7 @@ struct tcp_acceptor_test // The acceptor_ptr shared_ptr in accept_op ensures this. io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(0)); BOOST_TEST(!ec); @@ -408,7 +408,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST(!ec); @@ -426,7 +426,7 @@ struct tcp_acceptor_test { io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST(!ec); @@ -481,7 +481,7 @@ struct tcp_acceptor_test // associated with the acceptor's execution context. io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST(!ec); @@ -539,7 +539,7 @@ struct tcp_acceptor_test tcp_acceptor acc(ioc); // Default v6only=false gives dual-stack - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::any(), 0)); BOOST_TEST(!ec); @@ -594,7 +594,7 @@ struct tcp_acceptor_test tcp_acceptor acc(ioc); // Explicit v6only restricts to IPv6 - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); acc.set_option(socket_option::v6_only(true)); auto ec = acc.bind(endpoint(ipv6_address::any(), 0)); @@ -642,7 +642,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST_EQ(acc.is_open(), true); acc.set_option(socket_option::reuse_address(true)); @@ -697,7 +697,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); acc.set_option(socket_option::reuse_port(true)); @@ -720,11 +720,11 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST_EQ(acc.is_open(), true); // Second open should be a no-op - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST_EQ(acc.is_open(), true); acc.close(); @@ -758,7 +758,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -829,14 +829,14 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc1(ioc); - acc1.open(); + BOOST_TEST(!acc1.open()); acc1.set_option(socket_option::reuse_address(true)); auto ec = acc1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto port = acc1.local_endpoint().port(); tcp_acceptor acc2(ioc); - acc2.open(); + BOOST_TEST(!acc2.open()); ec = acc2.bind(endpoint(ipv4_address::loopback(), port)); BOOST_TEST(ec); @@ -849,7 +849,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); // Bind to an address not assigned to any local interface auto ec = acc.bind(endpoint(ipv4_address("1.2.3.4"), 0)); @@ -930,7 +930,7 @@ struct tcp_acceptor_test io_context ioc(Backend); auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(0)); BOOST_TEST(!ec); @@ -968,7 +968,7 @@ struct tcp_acceptor_test io_context ioc(Backend); auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1023,7 +1023,7 @@ struct tcp_acceptor_test io_context ioc(Backend); auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1065,7 +1065,7 @@ struct tcp_acceptor_test io_context ioc(Backend); auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(0)); BOOST_TEST(!ec); @@ -1093,7 +1093,7 @@ struct tcp_acceptor_test // Closed: returns the platform sentinel. BOOST_TEST(acc.native_handle() == invalid_native_socket); - acc.open(); + BOOST_TEST(!acc.open()); BOOST_TEST(acc.native_handle() != invalid_native_socket); acc.close(); BOOST_TEST(acc.native_handle() == invalid_native_socket); @@ -1149,7 +1149,7 @@ struct tcp_acceptor_test BOOST_TEST(port != 0); tcp_acceptor acc(ioc); - acc.assign(lfd); + BOOST_TEST(!acc.assign(lfd)); BOOST_TEST(acc.is_open()); BOOST_TEST(acc.native_handle() == lfd); BOOST_TEST_EQ(acc.local_endpoint().port(), port); @@ -1166,7 +1166,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1242,7 +1242,7 @@ struct tcp_acceptor_test BOOST_TEST(native_connect_loopback(client, port, false)); tcp_acceptor acc(ioc); - acc.assign(lfd); + BOOST_TEST(!acc.assign(lfd)); BOOST_TEST(acc.is_open()); // Pump once with nothing parked so the registration-time @@ -1298,7 +1298,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1341,7 +1341,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1359,7 +1359,7 @@ struct tcp_acceptor_test BOOST_TEST(lfd != invalid_native_socket); BOOST_TEST(port != old_port); - acc.assign(lfd); + BOOST_TEST(!acc.assign(lfd)); BOOST_TEST(acc.is_open()); BOOST_TEST(acc.native_handle() == lfd); BOOST_TEST_EQ(acc.local_endpoint().port(), port); @@ -1375,7 +1375,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1391,7 +1391,7 @@ struct tcp_acceptor_test auto lfd = make_native_listener(false, port); BOOST_TEST(lfd != invalid_native_socket); - acc.assign(lfd); + BOOST_TEST(!acc.assign(lfd)); BOOST_TEST(acc.is_open()); BOOST_TEST(acc.native_handle() == lfd); BOOST_TEST_EQ(acc.local_endpoint().port(), port); @@ -1408,7 +1408,7 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1425,7 +1425,7 @@ struct tcp_acceptor_test BOOST_TEST(released != invalid_native_socket); close_native_socket(released); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1447,7 +1447,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1505,7 +1505,7 @@ struct tcp_acceptor_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1526,7 +1526,7 @@ struct tcp_acceptor_test close_native_socket(released); close_native_socket(stale); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1578,38 +1578,29 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - auto expect_throw = [&](native_handle_type h) { - bool threw = false; - try - { - acc.assign(h); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto expect_error = [&](native_handle_type h) { + BOOST_TEST(acc.assign(h)); }; - expect_throw(invalid_native_socket); + expect_error(invalid_native_socket); BOOST_TEST(!acc.is_open()); auto dg = make_native_socket(AF_INET, SOCK_DGRAM); BOOST_TEST(dg != invalid_native_socket); - expect_throw(dg); + expect_error(dg); BOOST_TEST(native_socket_valid(dg)); // caller keeps it close_native_socket(dg); #if BOOST_COROSIO_POSIX auto un = make_native_socket(AF_UNIX, SOCK_STREAM); BOOST_TEST(un != invalid_native_socket); - expect_throw(un); + expect_error(un); BOOST_TEST(native_socket_valid(un)); close_native_socket(un); #endif - acc.open(); - expect_throw(acc.native_handle()); + BOOST_TEST(!acc.open()); + expect_error(acc.native_handle()); BOOST_TEST(acc.is_open()); acc.close(); } @@ -1619,7 +1610,7 @@ struct tcp_acceptor_test { io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1672,7 +1663,7 @@ struct tcp_acceptor_test return; // no IPv6 loopback on this host tcp_acceptor acc(ioc); - acc.assign(lfd); + BOOST_TEST(!acc.assign(lfd)); BOOST_TEST(acc.is_open()); BOOST_TEST(acc.local_endpoint().is_v6()); BOOST_TEST_EQ(acc.local_endpoint().port(), port); diff --git a/test/unit/tcp_server.cpp b/test/unit/tcp_server.cpp index dfa4e8f25..f9c27d6ad 100644 --- a/test/unit/tcp_server.cpp +++ b/test/unit/tcp_server.cpp @@ -129,7 +129,7 @@ struct tcp_server_test std::atomic* connection_handled, std::atomic* stop_requested) -> capy::task<> { tcp_socket client(*ioc); - client.open(); + BOOST_TEST(!client.open()); auto [connect_ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); @@ -241,7 +241,7 @@ struct tcp_server_test auto task1 = [](io_context* ioc, std::uint16_t port, int* count) -> capy::task<> { tcp_socket client(*ioc); - client.open(); + BOOST_TEST(!client.open()); auto [connect_ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); if (!connect_ec) @@ -279,7 +279,7 @@ struct tcp_server_test auto task2 = [](io_context* ioc, std::uint16_t port, int* count) -> capy::task<> { tcp_socket client(*ioc); - client.open(); + BOOST_TEST(!client.open()); auto [connect_ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); if (!connect_ec) @@ -357,7 +357,7 @@ struct tcp_server_test // Test success case tcp_acceptor acc1(ioc); - acc1.open(); + BOOST_TEST(!acc1.open()); acc1.set_option(socket_option::reuse_address(true)); auto ec1 = acc1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec1); @@ -369,7 +369,7 @@ struct tcp_server_test // Test with explicit backlog tcp_acceptor acc2(ioc); - acc2.open(); + BOOST_TEST(!acc2.open()); acc2.set_option(socket_option::reuse_address(true)); auto ec2 = acc2.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec2); @@ -398,7 +398,7 @@ struct tcp_server_test // 192.0.2.1 is from TEST-NET-1 (RFC 5737), reserved for documentation // and never assigned to real interfaces. tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(endpoint(ipv4_address({192, 0, 2, 1}), 0)); BOOST_TEST(ec); acc.close(); @@ -420,7 +420,7 @@ struct tcp_server_test tcp_acceptor acc(ioc); // First listen - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec1 = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec1); @@ -430,7 +430,7 @@ struct tcp_server_test // Close and re-listen acc.close(); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec2 = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec2); @@ -533,7 +533,7 @@ struct tcp_server_test for (int i = 0; i < 2; ++i) { tcp_socket client(*ioc); - client.open(); + BOOST_TEST(!client.open()); auto [cec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); (void)cec; @@ -615,7 +615,6 @@ struct tcp_server_test std::atomic* connected, multi_server* srv) -> capy::task<> { tcp_socket c1(*ioc), c2(*ioc), c3(*ioc); - c1.open(); c2.open(); c3.open(); auto [e1] = co_await c1.connect( endpoint(ipv4_address::loopback(), port)); @@ -719,7 +718,7 @@ struct tcp_server_test auto client_task = [](io_context* ioc, std::uint16_t port, one_worker_server* srv) -> capy::task<> { tcp_socket client(*ioc); - client.open(); + BOOST_TEST(!client.open()); auto [cec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); (void)cec; @@ -781,7 +780,7 @@ struct tcp_server_test for (int i = 0; i < 2; ++i) { tcp_socket client(*ioc); - client.open(); + BOOST_TEST(!client.open()); auto [cec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); if (cec) diff --git a/test/unit/tcp_socket.cpp b/test/unit/tcp_socket.cpp index 1f87d3cb8..8b2aaad73 100644 --- a/test/unit/tcp_socket.cpp +++ b/test/unit/tcp_socket.cpp @@ -137,9 +137,12 @@ struct tcp_socket_test tcp_socket sock(ioc); // Open the tcp_socket - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST_EQ(sock.is_open(), true); + // Opening an already-open socket is a successful no-op + BOOST_TEST(!sock.open()); + // Close it sock.close(); BOOST_TEST_EQ(sock.is_open(), false); @@ -149,7 +152,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); // Bind to loopback with ephemeral port auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); @@ -168,11 +171,11 @@ struct tcp_socket_test io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.is_open()); // Second open() on an already-open socket is a no-op. - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.is_open()); } @@ -199,7 +202,7 @@ struct tcp_socket_test // Closed: returns the platform sentinel. BOOST_TEST(sock.native_handle() == invalid); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.native_handle() != invalid); sock.close(); } @@ -209,7 +212,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -219,7 +222,7 @@ struct tcp_socket_test tcp_socket client(ioc); tcp_socket server(ioc); - client.open(); + BOOST_TEST(!client.open()); // Bind client to specific local address before connecting ec = client.bind(endpoint(ipv4_address::loopback(), 0)); @@ -276,7 +279,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(tcp::v6()); + BOOST_TEST(!sock.open(tcp::v6())); auto ec = sock.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST(!ec); @@ -294,14 +297,14 @@ struct tcp_socket_test // Bind first socket to a specific port tcp_socket sock1(ioc); - sock1.open(); + BOOST_TEST(!sock1.open()); auto ec = sock1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto port = sock1.local_endpoint().port(); // Second bind to same port should fail tcp_socket sock2(ioc); - sock2.open(); + BOOST_TEST(!sock2.open()); ec = sock2.bind(endpoint(ipv4_address::loopback(), port)); BOOST_TEST(ec); @@ -313,7 +316,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address("1.2.3.4"), 0)); BOOST_TEST(ec); @@ -325,7 +328,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock1(ioc); - sock1.open(); + BOOST_TEST(!sock1.open()); BOOST_TEST_EQ(sock1.is_open(), true); // Move construct @@ -341,7 +344,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_socket sock1(ioc); tcp_socket sock2(ioc); - sock1.open(); + BOOST_TEST(!sock1.open()); BOOST_TEST_EQ(sock1.is_open(), true); BOOST_TEST_EQ(sock2.is_open(), false); @@ -986,7 +989,7 @@ struct tcp_socket_test // Write data then shutdown send // (unqualified: using enum avoids GCC 11 ICE in tsubst_copy) (void)co_await a.write_some(capy::const_buffer("hello", 5)); - a.shutdown(shutdown_send); + BOOST_TEST(!a.shutdown(shutdown_send)); // Read the data char buf[32] = {}; @@ -1015,7 +1018,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Shutdown receive on b - b.shutdown(shutdown_receive); + BOOST_TEST(!b.shutdown(shutdown_receive)); // b can still send (void)co_await b.write_some(capy::const_buffer("from_b", 6)); @@ -1038,10 +1041,13 @@ struct tcp_socket_test io_context ioc(Backend); tcp_socket sock(ioc); - // Shutdown on closed tcp_socket should not crash - sock.shutdown(shutdown_send); - sock.shutdown(shutdown_receive); - sock.shutdown(shutdown_both); + // Closed socket reports bad_file_descriptor + BOOST_TEST(sock.shutdown(shutdown_send) + == std::errc::bad_file_descriptor); + BOOST_TEST(sock.shutdown(shutdown_receive) + == std::errc::bad_file_descriptor); + BOOST_TEST(sock.shutdown(shutdown_both) + == std::errc::bad_file_descriptor); } void testShutdownBothSendDirection() @@ -1053,7 +1059,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Write data then shutdown both (void)co_await a.write_some(capy::const_buffer("goodbye", 7)); - a.shutdown(shutdown_both); + BOOST_TEST(!a.shutdown(shutdown_both)); // Peer should receive the data char buf[32] = {}; @@ -1080,7 +1086,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::no_delay(true)); BOOST_TEST_EQ(sock.get_option().value(), true); @@ -1099,7 +1105,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::keep_alive(true)); BOOST_TEST_EQ( @@ -1120,7 +1126,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); int initial_size = sock.get_option().value(); @@ -1138,7 +1144,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); int initial_size = sock.get_option().value(); @@ -1156,7 +1162,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::linger(true, 5)); auto opts = sock.get_option(); @@ -1214,7 +1220,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::no_delay(true)); BOOST_TEST(sock.get_option().value()); @@ -1229,7 +1235,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::receive_buffer_size(32768)); int sz = sock.get_option().value(); @@ -1242,7 +1248,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::linger(true, 5)); auto lg = sock.get_option(); @@ -1256,7 +1262,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); // boolean assignment and negation socket_option::no_delay nd(false); @@ -1385,7 +1391,7 @@ struct tcp_socket_test tcp_acceptor acc(ioc); // Bind to loopback with port 0 (ephemeral) - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto listen_ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); if (!listen_ec) @@ -1399,7 +1405,7 @@ struct tcp_socket_test tcp_socket client(ioc); tcp_socket server(ioc); - client.open(); + BOOST_TEST(!client.open()); auto task = [&]() -> capy::task<> { // Connect to the acceptor @@ -1458,7 +1464,7 @@ struct tcp_socket_test bool found = false; for (int attempt = 0; attempt < 100; ++attempt) { - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); if (!acc.bind(endpoint(ipv4_address::loopback(), test_port)) && !acc.listen()) @@ -1484,7 +1490,7 @@ struct tcp_socket_test tcp_socket client(ioc); tcp_socket server(ioc); - client.open(); + BOOST_TEST(!client.open()); auto task = [&]() -> capy::task<> { auto [ec] = co_await client.connect( @@ -1532,7 +1538,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); // Open but unconnected tcp_socket should return default endpoint BOOST_TEST(sock.local_endpoint() == endpoint{}); @@ -1545,7 +1551,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto task = [&]() -> capy::task<> { // Connect to an unreachable address (localhost on unlikely port) @@ -1667,7 +1673,7 @@ struct tcp_socket_test BOOST_TEST(s1.remote_endpoint() == endpoint{}); // Reopen the tcp_socket - s1.open(); + BOOST_TEST(!s1.open()); // After reopen (but before connect), endpoints should still be default BOOST_TEST(s1.local_endpoint() == endpoint{}); @@ -1779,7 +1785,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::loopback(), 0)); if (!ec) @@ -1837,7 +1843,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); if (!ec) @@ -1891,7 +1897,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); if (!ec) @@ -1901,7 +1907,7 @@ struct tcp_socket_test tcp_socket s1(ioc); tcp_socket s2(ioc); - s2.open(); + BOOST_TEST(!s2.open()); s2.set_option(socket_option::no_delay(true)); BOOST_TEST(s2.get_option()); @@ -1947,7 +1953,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::loopback(), 0)); if (!ec) @@ -2037,7 +2043,7 @@ struct tcp_socket_test { io_context ioc(Backend); tcp_socket sock(ioc); - sock.open(tcp::v6()); // IPv6 + BOOST_TEST(!sock.open(tcp::v6())); // IPv6 // Default is v6only=true (kernel default after open_socket sets it) BOOST_TEST_EQ(sock.get_option().value(), true); @@ -2057,7 +2063,7 @@ struct tcp_socket_test // Dual-stack listener (v6only=false is the default) tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::any(), 0)); if (!ec) @@ -2067,7 +2073,7 @@ struct tcp_socket_test tcp_socket s1(ioc); tcp_socket s2(ioc); - s2.open(tcp::v6()); // IPv6 socket + BOOST_TEST(!s2.open(tcp::v6())); // IPv6 socket s2.set_option(socket_option::v6_only(false)); bool accept_done = false; @@ -2115,7 +2121,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -2129,7 +2135,7 @@ struct tcp_socket_test make_native_adoptable(nfd); tcp_socket adopted(ioc); - adopted.assign(nfd); + BOOST_TEST(!adopted.assign(nfd)); BOOST_TEST(adopted.is_open()); BOOST_TEST(adopted.native_handle() == nfd); BOOST_TEST_EQ(adopted.remote_endpoint().port(), port); @@ -2172,36 +2178,18 @@ struct tcp_socket_test io_context ioc(Backend); tcp_socket sock(ioc); - auto expect_throw = [&](native_handle_type h) { - bool threw = false; - try - { - sock.assign(h); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto expect_error = [&](native_handle_type h) { + BOOST_TEST(sock.assign(h)); }; - expect_throw(invalid_native_socket); + expect_error(invalid_native_socket); BOOST_TEST(!sock.is_open()); auto dg = make_native_socket(AF_INET, SOCK_DGRAM); BOOST_TEST(dg != invalid_native_socket); { // The rejection code is part of the portable contract. - std::error_code rejected; - try - { - sock.assign(dg); - } - catch (std::system_error const& e) - { - rejected = e.code(); - } - BOOST_TEST(rejected == std::errc::wrong_protocol_type); + BOOST_TEST(sock.assign(dg) == std::errc::wrong_protocol_type); } BOOST_TEST(native_socket_valid(dg)); // caller keeps it close_native_socket(dg); @@ -2209,13 +2197,13 @@ struct tcp_socket_test #if BOOST_COROSIO_POSIX auto un = make_native_socket(AF_UNIX, SOCK_STREAM); BOOST_TEST(un != invalid_native_socket); - expect_throw(un); + expect_error(un); BOOST_TEST(native_socket_valid(un)); close_native_socket(un); #endif - sock.open(tcp::v4()); - expect_throw(sock.native_handle()); + BOOST_TEST(!sock.open(tcp::v4())); + expect_error(sock.native_handle()); BOOST_TEST(sock.is_open()); sock.close(); } @@ -2231,16 +2219,7 @@ struct tcp_socket_test auto dg = make_native_socket(AF_INET, SOCK_DGRAM); BOOST_TEST(dg != invalid_native_socket); - bool threw = false; - try - { - s1.assign(dg); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(s1.assign(dg)); BOOST_TEST(native_socket_valid(dg)); close_native_socket(dg); @@ -2274,7 +2253,7 @@ struct tcp_socket_test tcp_socket& s1 = pair.first; tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -2300,7 +2279,7 @@ struct tcp_socket_test read_done = true; }; auto adopter = [&]() -> capy::task<> { - s1.assign(nfd); + BOOST_TEST(!s1.assign(nfd)); auto [aec, peer] = co_await acc.accept(); BOOST_TEST(!aec); char const out[] = "ping"; @@ -2409,7 +2388,7 @@ struct tcp_socket_test io_context ioc(Backend); tcp_acceptor acc(ioc); - acc.open(tcp::v6()); + BOOST_TEST(!acc.open(tcp::v6())); acc.set_option(socket_option::reuse_address(true)); auto ec = acc.bind(endpoint(ipv6_address::loopback(), 0)); if (ec) @@ -2429,7 +2408,7 @@ struct tcp_socket_test make_native_adoptable(nfd); tcp_socket adopted(ioc); - adopted.assign(nfd); + BOOST_TEST(!adopted.assign(nfd)); BOOST_TEST(adopted.is_open()); BOOST_TEST(adopted.local_endpoint().is_v6()); BOOST_TEST(adopted.remote_endpoint().is_v6()); diff --git a/test/unit/test_utils.hpp b/test/unit/test_utils.hpp index 1b58aaa99..889849cbc 100644 --- a/test/unit/test_utils.hpp +++ b/test/unit/test_utils.hpp @@ -50,6 +50,13 @@ inline constexpr int failsafe_scale = 1; namespace boost::corosio::test { +/// Fail the current test if a setup call reports an error. +inline void +require_ok(std::error_code ec) +{ + BOOST_TEST(!ec); +} + // // Raw native sockets for the assign()/release() adoption tests // @@ -745,10 +752,10 @@ inline tls_context make_anon_context() { tls_context ctx; - ctx.set_verify_mode( - tls_verify_mode::none); // NOLINT(bugprone-unused-return-value) - ctx.set_ciphersuites( - "aNULL:eNULL:@SECLEVEL=0"); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.set_verify_mode( + tls_verify_mode::none)); + require_ok(ctx.set_ciphersuites( + "aNULL:eNULL:@SECLEVEL=0")); return ctx; } @@ -757,14 +764,14 @@ inline tls_context make_server_context() { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( server_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( server_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::none); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::none)); return ctx; } @@ -773,10 +780,10 @@ inline tls_context make_client_context() { tls_context ctx; - ctx.add_certificate_authority( - ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + ca_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -785,10 +792,10 @@ inline tls_context make_wrong_ca_context() { tls_context ctx; - ctx.add_certificate_authority( - wrong_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + wrong_ca_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -1443,19 +1450,19 @@ inline tls_context make_encrypted_key_server_context(bool& callback_invoked) { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( server_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); ctx.set_password_callback( [&callback_invoked](std::size_t, tls_password_purpose) { callback_invoked = true; return std::string(encrypted_key_password); }); - ctx.use_private_key( + require_ok(ctx.use_private_key( encrypted_server_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::none); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::none)); return ctx; } @@ -1464,8 +1471,8 @@ inline tls_context make_verify_no_cert_context() { tls_context ctx; - ctx.set_verify_mode( - tls_verify_mode::require_peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.set_verify_mode( + tls_verify_mode::require_peer)); return ctx; } @@ -1491,8 +1498,8 @@ make_contexts(context_mode mode) case context_mode::shared_cert: { auto ctx = make_server_context(); - ctx.add_certificate_authority( - ca_cert_pem); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + ca_cert_pem)); return {ctx, ctx}; } case context_mode::separate_cert: @@ -2029,14 +2036,14 @@ inline tls_context make_chain_server_context() { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( chain_server_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( chain_server_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::none); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::none)); return ctx; } @@ -2049,13 +2056,13 @@ make_fullchain_server_context() { tls_context ctx; // use_certificate_chain expects entity cert followed by intermediate(s) - ctx.use_certificate_chain( - server_fullchain_pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + require_ok(ctx.use_certificate_chain( + server_fullchain_pem)); + require_ok(ctx.use_private_key( chain_server_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::none); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::none)); return ctx; } @@ -2065,10 +2072,10 @@ inline tls_context make_rootonly_client_context() { tls_context ctx; - ctx.add_certificate_authority( - root_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + root_ca_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -2078,12 +2085,12 @@ make_chain_client_context() { tls_context ctx; // Trust both root and intermediate CA for chain verification - ctx.add_certificate_authority( - root_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.add_certificate_authority( - intermediate_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + root_ca_cert_pem)); + require_ok(ctx.add_certificate_authority( + intermediate_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -2093,12 +2100,12 @@ inline tls_context make_expired_server_context() { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( expired_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( expired_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); return ctx; } @@ -2109,10 +2116,10 @@ make_expired_client_context() { tls_context ctx; // Trust the expired cert as its own CA (self-signed) - ctx.add_certificate_authority( - expired_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + expired_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -2121,14 +2128,14 @@ inline tls_context make_wrong_host_server_context() { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( wrong_host_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( wrong_host_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::none); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::none)); return ctx; } @@ -2137,19 +2144,19 @@ inline tls_context make_mtls_client_context() { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( client_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( client_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); // Trust both root and intermediate CA for chain verification - ctx.add_certificate_authority( - root_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.add_certificate_authority( - intermediate_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + root_ca_cert_pem)); + require_ok(ctx.add_certificate_authority( + intermediate_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -2158,19 +2165,19 @@ inline tls_context make_mtls_server_context() { tls_context ctx; - ctx.use_certificate( + require_ok(ctx.use_certificate( chain_server_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( chain_server_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); // Trust both root and intermediate CA for chain verification - ctx.add_certificate_authority( - root_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.add_certificate_authority( - intermediate_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::require_peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + root_ca_cert_pem)); + require_ok(ctx.add_certificate_authority( + intermediate_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::require_peer)); return ctx; } @@ -2179,10 +2186,10 @@ inline tls_context make_untrusted_ca_client_context() { tls_context ctx; - ctx.add_certificate_authority( - untrusted_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + untrusted_ca_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } @@ -2194,19 +2201,19 @@ make_invalid_mtls_client_context() { tls_context ctx; // Use the self-signed server cert as client cert - server won't trust it - ctx.use_certificate( + require_ok(ctx.use_certificate( server_cert_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) - ctx.use_private_key( + tls_file_format::pem)); + require_ok(ctx.use_private_key( server_key_pem, - tls_file_format::pem); // NOLINT(bugprone-unused-return-value) + tls_file_format::pem)); // Trust the chain CAs so we can verify server - ctx.add_certificate_authority( - root_ca_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.add_certificate_authority( - intermediate_cert_pem); // NOLINT(bugprone-unused-return-value) - ctx.set_verify_mode( - tls_verify_mode::peer); // NOLINT(bugprone-unused-return-value) + require_ok(ctx.add_certificate_authority( + root_ca_cert_pem)); + require_ok(ctx.add_certificate_authority( + intermediate_cert_pem)); + require_ok(ctx.set_verify_mode( + tls_verify_mode::peer)); return ctx; } diff --git a/test/unit/tls_stream_tests.hpp b/test/unit/tls_stream_tests.hpp index 31fafe121..97cf46dad 100644 --- a/test/unit/tls_stream_tests.hpp +++ b/test/unit/tls_stream_tests.hpp @@ -306,7 +306,7 @@ testFailureCases(StreamFactory make_stream) { auto client_ctx = make_client_context(); auto server_ctx = make_anon_context(); - server_ctx.set_ciphersuites(""); // NOLINT(bugprone-unused-return-value) + (void)server_ctx.set_ciphersuites(""); run_tls_test_fail( ioc, client_ctx, server_ctx, make_stream, make_stream); ioc.restart(); @@ -848,15 +848,15 @@ testHostnameIpLiteral(StreamFactory make_stream, bool ip_supported) // NOLINTBEGIN(bugprone-unused-return-value) tls_context client_ctx; - client_ctx.add_certificate_authority(test::server_ip_cert_pem); - client_ctx.set_verify_mode(tls_verify_mode::peer); + require_ok(client_ctx.add_certificate_authority(test::server_ip_cert_pem)); + require_ok(client_ctx.set_verify_mode(tls_verify_mode::peer)); tls_context server_ctx; - server_ctx.use_certificate( - test::server_ip_cert_pem, tls_file_format::pem); - server_ctx.use_private_key( - test::server_ip_key_pem, tls_file_format::pem); - server_ctx.set_verify_mode(tls_verify_mode::none); + require_ok(server_ctx.use_certificate( + test::server_ip_cert_pem, tls_file_format::pem)); + require_ok(server_ctx.use_private_key( + test::server_ip_key_pem, tls_file_format::pem)); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); // NOLINTEND(bugprone-unused-return-value) std::size_t sni_count = 0; @@ -954,22 +954,17 @@ testCrlRevocation(StreamFactory make_stream, bool crl_supported) { auto revoked_server = []() { tls_context ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_certificate(revoked_leaf_cert_pem, tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_private_key(revoked_leaf_key_pem, tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.set_verify_mode(tls_verify_mode::none); + require_ok(ctx.use_certificate(revoked_leaf_cert_pem, tls_file_format::pem)); + require_ok(ctx.use_private_key(revoked_leaf_key_pem, tls_file_format::pem)); + require_ok(ctx.set_verify_mode(tls_verify_mode::none)); return ctx; }; auto revoking_client = [](tls_revocation_policy policy, bool load_crl) { tls_context ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.add_certificate_authority(root_ca_cert_pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.set_verify_mode(tls_verify_mode::peer); + require_ok(ctx.add_certificate_authority(root_ca_cert_pem)); + require_ok(ctx.set_verify_mode(tls_verify_mode::peer)); if (load_crl) - ctx.add_crl(revoked_crl_pem); // NOLINT(bugprone-unused-return-value) + (void)ctx.add_crl(revoked_crl_pem); ctx.set_revocation_policy(policy); return ctx; }; @@ -1014,12 +1009,9 @@ testCrlRevocation(StreamFactory make_stream, bool crl_supported) { io_context ioc; tls_context client_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.add_certificate_authority(root_ca_cert_pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_mode(tls_verify_mode::peer); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.add_crl("this is not a valid PEM or DER CRL"); + require_ok(client_ctx.add_certificate_authority(root_ca_cert_pem)); + require_ok(client_ctx.set_verify_mode(tls_verify_mode::peer)); + (void)client_ctx.add_crl("this is not a valid PEM or DER CRL"); client_ctx.set_revocation_policy(tls_revocation_policy::soft_fail); auto server_ctx = revoked_server(); run_tls_test_fail( @@ -1033,8 +1025,7 @@ testCrlRevocation(StreamFactory make_stream, bool crl_supported) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.add_crl(revoked_crl_pem); // policy left disabled + (void)client_ctx.add_crl(revoked_crl_pem); // policy left disabled auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1065,10 +1056,8 @@ testPkcs12(StreamFactory make_stream) io_context ioc; auto client_ctx = make_client_context(); tls_context server_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_pkcs12(p12, p12_password); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_verify_mode(tls_verify_mode::none); + require_ok(server_ctx.use_pkcs12(p12, p12_password)); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1077,10 +1066,8 @@ testPkcs12(StreamFactory make_stream) io_context ioc; auto client_ctx = make_client_context(); tls_context server_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_pkcs12(p12, "wrong-password"); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_verify_mode(tls_verify_mode::none); + require_ok(server_ctx.use_pkcs12(p12, "wrong-password")); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); run_tls_test_fail( ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1094,14 +1081,10 @@ testPkcs12(StreamFactory make_stream) io_context ioc; auto client_ctx = make_client_context(); tls_context server_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_pkcs12(p12, p12_password); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_certificate(expired_cert_pem, tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_private_key(expired_key_pem, tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_verify_mode(tls_verify_mode::none); + require_ok(server_ctx.use_pkcs12(p12, p12_password)); + require_ok(server_ctx.use_certificate(expired_cert_pem, tls_file_format::pem)); + require_ok(server_ctx.use_private_key(expired_key_pem, tls_file_format::pem)); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1113,11 +1096,9 @@ testPkcs12(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.use_pkcs12(p12, "wrong-password"); + require_ok(client_ctx.use_pkcs12(p12, "wrong-password")); auto server_ctx = make_server_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_verify_mode(tls_verify_mode::peer); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::peer)); run_tls_test_fail( ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1142,10 +1123,8 @@ testPkcs12Chain(StreamFactory make_stream) io_context ioc; auto client_ctx = make_rootonly_client_context(); tls_context server_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_pkcs12(p12, p12_password); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_verify_mode(tls_verify_mode::none); + require_ok(server_ctx.use_pkcs12(p12, p12_password)); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1204,8 +1183,7 @@ testDefaultVerifyPaths(StreamFactory make_stream) auto client_ctx = make_client_context(); // Adding the system store on top of the explicit CA must not break // context creation or verification. - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_default_verify_paths(); + require_ok(client_ctx.set_default_verify_paths()); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); @@ -1229,10 +1207,8 @@ testCiphersuitesTls13( { auto make_ctx = [&](auto base, char const* suite) { auto ctx = base(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.set_min_protocol_version(tls_version::tls_1_3); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.set_ciphersuites_tls13(suite); + require_ok(ctx.set_min_protocol_version(tls_version::tls_1_3)); + require_ok(ctx.set_ciphersuites_tls13(suite)); return ctx; }; @@ -1281,11 +1257,9 @@ testProtocolVersion(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_min_protocol_version(tls_version::tls_1_3); + require_ok(client_ctx.set_min_protocol_version(tls_version::tls_1_3)); auto server_ctx = make_server_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_min_protocol_version(tls_version::tls_1_3); + require_ok(server_ctx.set_min_protocol_version(tls_version::tls_1_3)); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1293,11 +1267,9 @@ testProtocolVersion(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_max_protocol_version(tls_version::tls_1_2); + require_ok(client_ctx.set_max_protocol_version(tls_version::tls_1_2)); auto server_ctx = make_server_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_min_protocol_version(tls_version::tls_1_3); + require_ok(server_ctx.set_min_protocol_version(tls_version::tls_1_3)); run_tls_test_fail( ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1308,10 +1280,8 @@ testProtocolVersion(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_min_protocol_version(tls_version::tls_1_3); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_max_protocol_version(tls_version::tls_1_2); + require_ok(client_ctx.set_min_protocol_version(tls_version::tls_1_3)); + require_ok(client_ctx.set_max_protocol_version(tls_version::tls_1_2)); auto server_ctx = make_server_context(); run_tls_test_fail( ioc, client_ctx, server_ctx, make_stream, make_stream); @@ -1336,11 +1306,9 @@ testAlpn(StreamFactory make_stream, bool alpn_supported) auto [m1, m2] = corosio::test::make_mocket_pair(ioc); auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_alpn({"h2", "http/1.1"}); + require_ok(client_ctx.set_alpn({"h2", "http/1.1"})); auto server_ctx = make_server_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_alpn({"h2", "http/1.1"}); + require_ok(server_ctx.set_alpn({"h2", "http/1.1"})); auto client = make_stream(m1, client_ctx); auto server = make_stream(m2, server_ctx); @@ -1399,11 +1367,9 @@ testAlpnNoOverlap(StreamFactory make_stream, bool alpn_supported) io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_alpn({"h2"}); + require_ok(client_ctx.set_alpn({"h2"})); auto server_ctx = make_server_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_alpn({"http/1.1"}); + require_ok(server_ctx.set_alpn({"http/1.1"})); run_tls_test_fail(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1440,11 +1406,10 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( + require_ok(client_ctx.set_verify_callback( [](bool preverified, verify_context&) -> bool { return preverified; - }); + })); auto server_ctx = make_server_context(); std::error_code client_ec; run_tls_test_fail( @@ -1462,11 +1427,10 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) auto [m1, m2] = corosio::test::make_mocket_pair(ioc); (void)m2; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( + require_ok(client_ctx.set_verify_callback( [](bool preverified, verify_context&) -> bool { return preverified; - }); + })); auto client = make_stream(m1, client_ctx); std::error_code ec1; @@ -1498,8 +1462,7 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) bool saw_unverified = false; auto client_ctx = make_wrong_ca_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( + require_ok(client_ctx.set_verify_callback( [&saw_unverified](bool preverified, verify_context& vc) -> bool { if (!preverified) { @@ -1515,7 +1478,7 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) BOOST_TEST(der[0] == 0x30); } return true; - }); + })); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); @@ -1526,9 +1489,8 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) { io_context ioc; auto client_ctx = make_wrong_ca_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( - [](bool, verify_context&) -> bool { return false; }); + require_ok(client_ctx.set_verify_callback( + [](bool, verify_context&) -> bool { return false; })); auto server_ctx = make_server_context(); run_tls_test_fail( @@ -1557,15 +1519,14 @@ testVerifyCallbackOnSuccess(StreamFactory make_stream) bool saw_cert = false; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( + require_ok(client_ctx.set_verify_callback( [&](bool preverified, verify_context& vc) -> bool { invoked = true; if (preverified && !vc.certificate().empty() && vc.certificate()[0] == 0x30) saw_cert = true; return preverified; - }); + })); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); @@ -1577,9 +1538,8 @@ testVerifyCallbackOnSuccess(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( - [](bool, verify_context&) -> bool { return false; }); + require_ok(client_ctx.set_verify_callback( + [](bool, verify_context&) -> bool { return false; })); auto server_ctx = make_server_context(); run_tls_test_fail( @@ -1592,14 +1552,13 @@ testVerifyCallbackOnSuccess(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_callback( + require_ok(client_ctx.set_verify_callback( [](bool preverified, verify_context& vc) -> bool { if (!preverified) return false; auto der = vc.certificate(); return der.size() == 1 && der[0] == 0xFF; // never matches - }); + })); auto server_ctx = make_server_context(); run_tls_test_fail( @@ -1821,12 +1780,11 @@ testInvalidContextHandshake(StreamFactory make_stream) auto client_ctx = make_client_context(); tls_context server_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_certificate("not a certificate", tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.use_private_key("not a key", tls_file_format::pem); - // NOLINTNEXTLINE(bugprone-unused-return-value) - server_ctx.set_verify_mode(tls_verify_mode::none); + // The setters may reject the garbage eagerly or defer to the + // handshake; the handshake failure below is what is asserted. + (void)server_ctx.use_certificate("not a certificate", tls_file_format::pem); + (void)server_ctx.use_private_key("not a key", tls_file_format::pem); + require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); auto client = make_stream(m1, client_ctx); auto server = make_stream(m2, server_ctx); @@ -3862,7 +3820,7 @@ testShutdownTruncation(StreamFactory make_stream) // inbound bytes (e.g. a post-handshake session ticket), which // would otherwise turn the FIN into an RST and hit a different // (already-propagated) error path than the one under test. - m1.socket().shutdown(shutdown_send); + BOOST_TEST(!m1.socket().shutdown(shutdown_send)); bool shutdown_done = false; bool failsafe_hit = false; diff --git a/test/unit/udp_socket.cpp b/test/unit/udp_socket.cpp index 9adcd863d..481ff253d 100644 --- a/test/unit/udp_socket.cpp +++ b/test/unit/udp_socket.cpp @@ -141,7 +141,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST_EQ(sock.is_open(), true); sock.close(); @@ -153,7 +153,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); BOOST_TEST_EQ(sock.is_open(), true); sock.close(); @@ -164,7 +164,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock1(ioc); - sock1.open(); + BOOST_TEST(!sock1.open()); BOOST_TEST_EQ(sock1.is_open(), true); udp_socket sock2(std::move(sock1)); @@ -179,7 +179,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock1(ioc); udp_socket sock2(ioc); - sock1.open(); + BOOST_TEST(!sock1.open()); BOOST_TEST_EQ(sock1.is_open(), true); BOOST_TEST_EQ(sock2.is_open(), false); @@ -194,7 +194,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -210,7 +210,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); auto ec = sock.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -353,13 +353,13 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock1(ioc); - sock1.open(); + BOOST_TEST(!sock1.open()); auto ec = sock1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto port = sock1.local_endpoint().port(); udp_socket sock2(ioc); - sock2.open(); + BOOST_TEST(!sock2.open()); ec = sock2.bind(endpoint(ipv4_address::loopback(), port)); BOOST_TEST(ec); @@ -400,11 +400,11 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.is_open()); auto nh = sock.native_handle(); - sock.open(); + BOOST_TEST(!sock.open()); BOOST_TEST(sock.is_open()); BOOST_TEST_EQ(sock.native_handle(), nh); @@ -415,7 +415,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address("1.2.3.4"), 0)); BOOST_TEST(ec); @@ -427,7 +427,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::receive_buffer_size(65536)); auto opt = sock.get_option(); @@ -448,8 +448,8 @@ struct udp_socket_test udp_socket sender(ioc); udp_socket receiver(ioc); - sender.open(); - receiver.open(); + BOOST_TEST(!sender.open()); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -489,8 +489,8 @@ struct udp_socket_test udp_socket sender(ioc); udp_socket receiver(ioc); - sender.open(udp::v6()); - receiver.open(udp::v6()); + BOOST_TEST(!sender.open(udp::v6())); + BOOST_TEST(!receiver.open(udp::v6())); auto ec = receiver.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -525,8 +525,8 @@ struct udp_socket_test udp_socket a(ioc); udp_socket b(ioc); - a.open(); - b.open(); + BOOST_TEST(!a.open()); + BOOST_TEST(!b.open()); auto ec1 = a.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec1, std::error_code{}); @@ -563,8 +563,8 @@ struct udp_socket_test udp_socket sender(ioc); udp_socket receiver(ioc); - sender.open(); - receiver.open(); + BOOST_TEST(!sender.open()); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -599,12 +599,30 @@ struct udp_socket_test ioc.run(); } + void testShutdown() + { + io_context ioc(Backend); + + // Closed socket reports bad_file_descriptor + udp_socket closed(ioc); + BOOST_TEST(closed.shutdown(shutdown_send) + == std::errc::bad_file_descriptor); + + // Open socket: outcome is platform-dependent for an + // unconnected datagram socket; only the path is exercised. + udp_socket sock(ioc); + BOOST_TEST(!sock.open()); + auto ec = sock.shutdown(shutdown_send); + (void)ec; + sock.close(); + } + void testCancelRecv() { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -641,7 +659,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -681,8 +699,8 @@ struct udp_socket_test udp_socket reader(ioc); udp_socket signal_sock(ioc); - reader.open(); - signal_sock.open(); + BOOST_TEST(!reader.open()); + BOOST_TEST(!signal_sock.open()); auto ec1 = reader.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec1, std::error_code{}); @@ -752,8 +770,8 @@ struct udp_socket_test udp_socket a(ioc); udp_socket b(ioc); - a.open(); - b.open(); + BOOST_TEST(!a.open()); + BOOST_TEST(!b.open()); auto ec1 = a.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec1, std::error_code{}); @@ -822,7 +840,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -844,7 +862,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -864,7 +882,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); sock.set_option(socket_option::multicast_loop_v4(true)); auto loop = sock.get_option(); @@ -885,7 +903,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); sock.set_option(socket_option::multicast_loop_v6(true)); auto loop = sock.get_option(); @@ -909,12 +927,12 @@ struct udp_socket_test udp_socket sender(ioc); udp_socket receiver(ioc); - receiver.open(); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto recv_ep = receiver.local_endpoint(); - sender.open(); + BOOST_TEST(!sender.open()); auto task = [](udp_socket& s, endpoint dest) -> capy::task<> { auto [ec] = co_await s.connect(dest); @@ -933,7 +951,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket receiver(ioc); - receiver.open(); + BOOST_TEST(!receiver.open()); auto ec = receiver.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto recv_ep = receiver.local_endpoint(); @@ -959,7 +977,7 @@ struct udp_socket_test udp_socket a(ioc); udp_socket b(ioc); - b.open(); + BOOST_TEST(!b.open()); auto ec = b.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto b_ep = b.local_endpoint(); @@ -1016,7 +1034,7 @@ struct udp_socket_test udp_socket a(ioc); udp_socket b(ioc); - b.open(udp::v6()); + BOOST_TEST(!b.open(udp::v6())); auto ec = b.bind(endpoint(ipv6_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto b_ep = b.local_endpoint(); @@ -1053,7 +1071,7 @@ struct udp_socket_test udp_socket a(ioc); udp_socket b(ioc); - b.open(); + BOOST_TEST(!b.open()); auto ec = b.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); auto b_ep = b.local_endpoint(); @@ -1094,8 +1112,8 @@ struct udp_socket_test udp_socket receiver(ioc); udp_socket sender(ioc); - receiver.open(); - sender.open(); + BOOST_TEST(!receiver.open()); + BOOST_TEST(!sender.open()); auto ec = receiver.bind(endpoint(ipv4_address::any(), 0)); BOOST_TEST_EQ(ec, std::error_code{}); @@ -1156,7 +1174,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); try { @@ -1177,7 +1195,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); try { @@ -1198,7 +1216,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); try { @@ -1217,7 +1235,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(udp::v6()); + BOOST_TEST(!sock.open(udp::v6())); try { @@ -1237,7 +1255,7 @@ struct udp_socket_test { io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); // Linux clamps SO_RCVBUF=0 to a minimum and reports success; // BSD platforms (macOS, FreeBSD) reject 0 with EINVAL. @@ -1282,7 +1300,7 @@ struct udp_socket_test // TCP_NODELAY is meaningful only on TCP; setting on UDP must error. io_context ioc(Backend); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); bool caught = false; try @@ -1305,7 +1323,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket peer(ioc); - peer.open(); + BOOST_TEST(!peer.open()); auto ec = peer.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto peer_ep = peer.local_endpoint(); @@ -1317,7 +1335,7 @@ struct udp_socket_test make_native_adoptable(nfd); udp_socket adopted(ioc); - adopted.assign(nfd); + BOOST_TEST(!adopted.assign(nfd)); BOOST_TEST(adopted.is_open()); BOOST_TEST(adopted.native_handle() == nfd); BOOST_TEST_EQ(adopted.local_endpoint().port(), nport); @@ -1364,36 +1382,18 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - auto expect_throw = [&](native_handle_type h) { - bool threw = false; - try - { - sock.assign(h); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + auto expect_error = [&](native_handle_type h) { + BOOST_TEST(sock.assign(h)); }; - expect_throw(invalid_native_socket); + expect_error(invalid_native_socket); BOOST_TEST(!sock.is_open()); auto st = make_native_socket(AF_INET, SOCK_STREAM); BOOST_TEST(st != invalid_native_socket); { // The rejection code is part of the portable contract. - std::error_code rejected; - try - { - sock.assign(st); - } - catch (std::system_error const& e) - { - rejected = e.code(); - } - BOOST_TEST(rejected == std::errc::wrong_protocol_type); + BOOST_TEST(sock.assign(st) == std::errc::wrong_protocol_type); } BOOST_TEST(native_socket_valid(st)); // caller keeps it close_native_socket(st); @@ -1401,13 +1401,13 @@ struct udp_socket_test #if BOOST_COROSIO_POSIX auto un = make_native_socket(AF_UNIX, SOCK_DGRAM); BOOST_TEST(un != invalid_native_socket); - expect_throw(un); + expect_error(un); BOOST_TEST(native_socket_valid(un)); close_native_socket(un); #endif - sock.open(udp::v4()); - expect_throw(sock.native_handle()); + BOOST_TEST(!sock.open(udp::v4())); + expect_error(sock.native_handle()); BOOST_TEST(sock.is_open()); sock.close(); } @@ -1418,29 +1418,20 @@ struct udp_socket_test io_context ioc(Backend); udp_socket peer(ioc); - peer.open(); + BOOST_TEST(!peer.open()); auto ec = peer.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto peer_ep = peer.local_endpoint(); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto before = sock.native_handle(); auto st = make_native_socket(AF_INET, SOCK_STREAM); BOOST_TEST(st != invalid_native_socket); - bool threw = false; - try - { - sock.assign(st); - } - catch (std::system_error const&) - { - threw = true; - } - BOOST_TEST(threw); + BOOST_TEST(sock.assign(st)); BOOST_TEST(native_socket_valid(st)); close_native_socket(st); @@ -1474,13 +1465,13 @@ struct udp_socket_test auto ex = ioc.get_executor(); udp_socket peer(ioc); - peer.open(); + BOOST_TEST(!peer.open()); auto ec = peer.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto peer_ep = peer.local_endpoint(); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -1504,7 +1495,7 @@ struct udp_socket_test recv_done = true; }; auto adopter = [&]() -> capy::task<> { - sock.assign(nfd); + BOOST_TEST(!sock.assign(nfd)); char const msg[] = "after"; auto [ec1, n1] = co_await sock.send_to( capy::const_buffer(msg, sizeof(msg)), peer_ep); @@ -1541,13 +1532,13 @@ struct udp_socket_test auto ex = ioc.get_executor(); udp_socket peer(ioc); - peer.open(); + BOOST_TEST(!peer.open()); auto ec = peer.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto peer_port = peer.local_endpoint().port(); udp_socket sock(ioc); - sock.open(); + BOOST_TEST(!sock.open()); ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); auto sock_port = sock.local_endpoint().port(); @@ -1623,7 +1614,7 @@ struct udp_socket_test io_context ioc(Backend); udp_socket peer(ioc); - peer.open(udp::v6()); + BOOST_TEST(!peer.open(udp::v6())); auto ec = peer.bind(endpoint(ipv6_address::loopback(), 0)); if (ec) return; // no IPv6 loopback on this host @@ -1641,7 +1632,7 @@ struct udp_socket_test make_native_adoptable(nfd); udp_socket adopted(ioc); - adopted.assign(nfd); + BOOST_TEST(!adopted.assign(nfd)); BOOST_TEST(adopted.is_open()); BOOST_TEST(adopted.local_endpoint().is_v6()); BOOST_TEST_EQ(adopted.local_endpoint().port(), nport); @@ -1691,6 +1682,7 @@ struct udp_socket_test testSendRecvV6Loopback(); testEchoLoopback(); testMultipleDatagrams(); + testShutdown(); testCancelRecv(); testCloseWhileRecving(); testStopTokenCancellation(); diff --git a/test/unit/wait.cpp b/test/unit/wait.cpp index 2cc302151..003dfe3f1 100644 --- a/test/unit/wait.cpp +++ b/test/unit/wait.cpp @@ -185,7 +185,7 @@ make_backpressured_pair(io_context& ioc) bool connect_done = false; tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); shrink_socket_buffer(acc.native_handle(), SO_SNDBUF); shrink_socket_buffer(acc.native_handle(), SO_RCVBUF); @@ -197,7 +197,7 @@ make_backpressured_pair(io_context& ioc) tcp_socket s1(ioc); tcp_socket s2(ioc); - s2.open(); + BOOST_TEST(!s2.open()); shrink_socket_buffer(s2.native_handle(), SO_SNDBUF); shrink_socket_buffer(s2.native_handle(), SO_RCVBUF); @@ -476,13 +476,13 @@ struct wait_test auto ex = ioc.get_executor(); udp_socket recv(ioc); - recv.open(udp::v4()); + BOOST_TEST(!recv.open(udp::v4())); auto bec = recv.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); auto port = recv.local_endpoint().port(); udp_socket send(ioc); - send.open(udp::v4()); + BOOST_TEST(!send.open(udp::v4())); std::error_code wait_ec; bool wait_done = false; @@ -516,7 +516,7 @@ struct wait_test auto ex = ioc.get_executor(); tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(socket_option::reuse_address(true)); auto bec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -564,7 +564,7 @@ struct wait_test auto path = tmp.path(); local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto bec = acc.bind(local_endpoint(path)); BOOST_TEST(!bec); auto lec = acc.listen(); @@ -572,7 +572,7 @@ struct wait_test local_stream_socket server(ioc); local_stream_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); auto accept_task = [&]() -> capy::task<> { auto [ec] = co_await acc.accept(server); @@ -649,7 +649,7 @@ struct wait_test auto ex = ioc.get_executor(); udp_socket sock(ioc); - sock.open(udp::v4()); + BOOST_TEST(!sock.open(udp::v4())); auto bec = sock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -799,13 +799,13 @@ struct wait_test auto ex = ioc.get_executor(); udp_socket recv(ioc); - recv.open(udp::v4()); + BOOST_TEST(!recv.open(udp::v4())); auto bec = recv.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); auto port = recv.local_endpoint().port(); udp_socket send(ioc); - send.open(udp::v4()); + BOOST_TEST(!send.open(udp::v4())); std::size_t first_n = 0; std::error_code wait_ec; @@ -854,12 +854,12 @@ struct wait_test auto ex = ioc.get_executor(); udp_socket rsock(ioc); - rsock.open(udp::v4()); + BOOST_TEST(!rsock.open(udp::v4())); auto bec = rsock.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); udp_socket ssock(ioc); - ssock.open(udp::v4()); + BOOST_TEST(!ssock.open(udp::v4())); auto [t1, t2] = test::make_socket_pair(ioc); diff --git a/test/unit/wolfssl_engine.cpp b/test/unit/wolfssl_engine.cpp index e0b8c9d84..057647012 100644 --- a/test/unit/wolfssl_engine.cpp +++ b/test/unit/wolfssl_engine.cpp @@ -595,10 +595,10 @@ struct wolfssl_engine_test testGarbageDerCertificateFailsSetup() { tls_context ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); - // NOLINTNEXTLINE(bugprone-unused-return-value) - ctx.use_private_key(test::server_key_pem, tls_file_format::pem); + // Whether the garbage surfaces here or at init() is + // backend-dependent; the init failure below is what matters. + (void)ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); + (void)ctx.use_private_key(test::server_key_pem, tls_file_format::pem); wssl_engine eng; // Unlike the OpenSSL engine, wolfSSL surfaces setup_error_ diff --git a/test/unit/wolfssl_stream.cpp b/test/unit/wolfssl_stream.cpp index a12972405..8405dab8d 100644 --- a/test/unit/wolfssl_stream.cpp +++ b/test/unit/wolfssl_stream.cpp @@ -145,10 +145,8 @@ struct wolfssl_stream_test { io_context ioc; tls_context client_ctx; - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.add_verify_path(dir.string()); - // NOLINTNEXTLINE(bugprone-unused-return-value) - client_ctx.set_verify_mode(tls_verify_mode::peer); + BOOST_TEST(!client_ctx.add_verify_path(dir.string())); + BOOST_TEST(!client_ctx.set_verify_mode(tls_verify_mode::peer)); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); From c2e84032e9edc9c1c1dfa80cc027cbf8a7f3d427 Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Fri, 21 Aug 2026 17:44:31 +0200 Subject: [PATCH 2/5] refactor: one code for closed-object misuse across every channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A closed object reports errc::bad_file_descriptor everywhere, delivered through whichever channel the operation already uses: - bind() and listen() return the code instead of throwing logic_error, and become noexcept. - release(), available(), set_option(), and get_option() throw std::system_error carrying the code instead of logic_error — misuse and runtime failure share one exception type. - Async initiators (datagram send/recv, acceptor accept/wait, file read_some_at/write_some_at, and the native wrappers) no longer throw: the returned awaitable completes immediately with the code, matching how a racing close() already surfaces to in-flight operations. The op_base and native awaitables treat a pre-set error as ready-with-result. - close() is noexcept on every io object: idempotent, callable from destructors, and the descriptor is released even when the underlying close reports an error. Durability belongs to sync_*. - host_name() throws std::system_error rather than runtime_error. Genuine logic errors unrelated to object state — service not installed, launcher invoked twice, zero-sized thread pool — keep throwing logic_error. local_endpoint's out-param constructor — the library's last error_code& signature — is removed: an over-long path is a precondition violation with a public pre-check (max_path_length), so the throwing constructor reporting errc::filename_too_long is the whole API. The sockaddr conversion path applies the documented pre-check, since a foreign sun_path may legitimately exceed the cap. The lockdown suite pins the portable codes the normalized API promises across all reactor backends: closed-object completions and returns assert bad_file_descriptor by equality, genuine runtime failures are exercised (wrong-direction I/O, huge offsets, bind conflicts, pipe sync), deterministic corosio-generated codes assert equality while platform-varying codes assert only that a genuine error arrived, and deliberate discards use std::ignore. Lockdowns added from the coverage pass: io_context's documented invalid_argument on thread_pool_size < 1, and ipv4_address::to_buffer's length_error (matching the existing ipv6 test). --- .../ROOT/pages/4.guide/4e.tcp-acceptor.adoc | 7 +- include/boost/corosio/detail/op_base.hpp | 4 +- include/boost/corosio/host_name.hpp | 2 +- .../boost/corosio/local_datagram_socket.hpp | 63 +++--- include/boost/corosio/local_endpoint.hpp | 14 +- .../boost/corosio/local_stream_acceptor.hpp | 68 ++++--- include/boost/corosio/local_stream_socket.hpp | 25 ++- .../native/detail/endpoint_convert.hpp | 13 +- .../native/native_local_datagram_socket.hpp | 44 +++-- .../native/native_local_stream_acceptor.hpp | 30 ++- .../native/native_local_stream_socket.hpp | 12 +- .../native/native_random_access_file.hpp | 8 +- .../corosio/native/native_stream_file.hpp | 8 +- .../corosio/native/native_tcp_acceptor.hpp | 30 ++- .../corosio/native/native_tcp_socket.hpp | 23 ++- .../corosio/native/native_udp_socket.hpp | 48 +++-- include/boost/corosio/random_access_file.hpp | 24 ++- include/boost/corosio/stream_file.hpp | 2 +- include/boost/corosio/tcp_acceptor.hpp | 107 ++++++---- include/boost/corosio/tcp_socket.hpp | 25 ++- include/boost/corosio/udp_socket.hpp | 57 +++--- src/corosio/src/host_name.cpp | 41 ++-- src/corosio/src/local_datagram_socket.cpp | 14 +- src/corosio/src/local_endpoint.cpp | 13 -- src/corosio/src/local_stream_acceptor.cpp | 14 +- src/corosio/src/local_stream_socket.cpp | 10 +- src/corosio/src/random_access_file.cpp | 2 +- src/corosio/src/stream_file.cpp | 2 +- src/corosio/src/tcp_acceptor.cpp | 14 +- src/corosio/src/tcp_socket.cpp | 10 +- src/corosio/src/udp_socket.cpp | 10 +- test/unit/cross_ssl_stream.cpp | 5 +- test/unit/io_context.cpp | 21 ++ test/unit/ipv4_address.cpp | 13 +- test/unit/ipv6_address.cpp | 2 +- test/unit/local_datagram_socket.cpp | 122 ++++++++---- test/unit/local_endpoint.cpp | 21 +- test/unit/local_stream_socket.cpp | 187 +++++++++++++----- .../native/native_local_datagram_socket.cpp | 77 +++----- .../native/native_local_stream_socket.cpp | 62 +++--- test/unit/native/native_tcp_acceptor.cpp | 56 +++++- test/unit/native/native_tcp_socket.cpp | 20 ++ test/unit/native/native_udp_socket.cpp | 36 ++++ test/unit/openssl_engine.cpp | 7 +- test/unit/random_access_file.cpp | 158 ++++++++++++++- test/unit/reactor_paths.cpp | 6 +- test/unit/signal_set.cpp | 6 +- test/unit/socket_option.cpp | 16 +- test/unit/stream_file.cpp | 122 +++++++++++- test/unit/tcp_acceptor.cpp | 183 +++++++++++------ test/unit/tcp_socket.cpp | 61 ++++-- test/unit/tls_stream_tests.hpp | 12 +- test/unit/udp_socket.cpp | 176 ++++++++--------- test/unit/wolfssl_engine.cpp | 5 +- 54 files changed, 1428 insertions(+), 690 deletions(-) diff --git a/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc b/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc index 2cee07fd5..fb306c9cb 100644 --- a/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc +++ b/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc @@ -149,13 +149,12 @@ Common accept errors: | System limit reached (file descriptors, memory) |=== -Calling `accept()` on an acceptor that is not listening is a precondition -violation: it throws `std::logic_error` rather than completing with an error -code. +Calling `accept()` on a closed acceptor completes with +`errc::bad_file_descriptor` — the same code every corosio operation +reports for a closed object. === Preconditions -* The tcp_acceptor must be listening (`is_open() == true`) * For `accept(tcp_socket&)`, the peer socket must be associated with the same execution context as the acceptor (the returning overload guarantees this) diff --git a/include/boost/corosio/detail/op_base.hpp b/include/boost/corosio/detail/op_base.hpp index 066c64b4a..65ec57d17 100644 --- a/include/boost/corosio/detail/op_base.hpp +++ b/include/boost/corosio/detail/op_base.hpp @@ -45,7 +45,9 @@ class bytes_op_base bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before dispatch + // (e.g. a closed object); complete immediately with that error. + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept diff --git a/include/boost/corosio/host_name.hpp b/include/boost/corosio/host_name.hpp index d3372b468..12198f55b 100644 --- a/include/boost/corosio/host_name.hpp +++ b/include/boost/corosio/host_name.hpp @@ -37,7 +37,7 @@ namespace boost::corosio { @return The hostname as a UTF-8 string. - @throws std::runtime_error If the underlying system call fails. + @throws std::system_error If the underlying system call fails. */ BOOST_COROSIO_DECL std::string diff --git a/include/boost/corosio/local_datagram_socket.hpp b/include/boost/corosio/local_datagram_socket.hpp index f99c7f420..1dd13ce62 100644 --- a/include/boost/corosio/local_datagram_socket.hpp +++ b/include/boost/corosio/local_datagram_socket.hpp @@ -525,7 +525,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @post is_open() == false */ - void close(); + void close() noexcept; /** Check if the socket is open. @@ -550,9 +550,9 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return Error code on failure, empty on success. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ - [[nodiscard]] std::error_code bind(corosio::local_endpoint ep); + [[nodiscard]] std::error_code bind(corosio::local_endpoint ep) noexcept; /** Initiate an asynchronous connect to set the default peer. @@ -614,7 +614,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return An awaitable that completes with io_result. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto send_to( @@ -622,10 +622,10 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object corosio::local_endpoint dest, corosio::message_flags flags) { + send_to_awaitable aw(*this, buf, dest, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send_to: socket not open"); - return send_to_awaitable( - *this, buf, dest, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -654,7 +654,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return An awaitable that completes with io_result. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto recv_from( @@ -662,10 +662,10 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object corosio::local_endpoint& source, corosio::message_flags flags) { + recv_from_awaitable aw(*this, buf, source, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv_from: socket not open"); - return recv_from_awaitable( - *this, buf, source, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -688,15 +688,15 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return An awaitable that completes with io_result. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto send(Buffers const& buf, corosio::message_flags flags) { + send_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send: socket not open"); - return send_awaitable( - *this, buf, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -719,15 +719,15 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return An awaitable that completes with io_result. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto recv(Buffers const& buf, corosio::message_flags flags) { + recv_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv: socket not open"); - return recv_awaitable( - *this, buf, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -757,9 +757,10 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object operations without closing the fd. The caller takes ownership of the returned descriptor. - @return The native handle, or -1 if not open. + @return The native handle. - @throws std::logic_error if the socket is not open. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open. */ native_handle_type release(); @@ -767,8 +768,8 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return The number of bytes that can be read without blocking. - @throws std::logic_error if the socket is not open. - @throws std::system_error on ioctl failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on ioctl failure. */ std::size_t available() const; @@ -792,14 +793,16 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @param opt The option to set. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template void set_option(Option const& opt) { if (!is_open()) - detail::throw_logic_error("set_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_datagram_socket::set_option"); std::error_code ec = get().set_option( Option::level(), Option::name(), opt.data(), opt.size()); if (ec) @@ -815,14 +818,16 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return The current option value. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template Option get_option() const { if (!is_open()) - detail::throw_logic_error("get_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_datagram_socket::get_option"); Option opt{}; std::size_t sz = opt.size(); std::error_code ec = diff --git a/include/boost/corosio/local_endpoint.hpp b/include/boost/corosio/local_endpoint.hpp index 3f0241c54..a213ac697 100644 --- a/include/boost/corosio/local_endpoint.hpp +++ b/include/boost/corosio/local_endpoint.hpp @@ -55,19 +55,19 @@ class BOOST_COROSIO_DECL local_endpoint /** Construct from a path. + An over-long path is a precondition violation: the limit is + the public @ref max_path_length constant, so callers with + runtime-derived paths can check + `path.size() <= max_path_length` before constructing. + @param path The filesystem path for the socket. Must not exceed @ref max_path_length bytes. - @throws std::system_error if the path is too long. + @throws std::system_error `errc::filename_too_long` if the + path is too long. */ explicit local_endpoint(std::string_view path); - /** Construct from a path (no-throw). - - @param path The filesystem path for the socket. - @param ec Set to an error if the path is too long. - */ - local_endpoint(std::string_view path, std::error_code& ec) noexcept; /** Return the socket path. diff --git a/include/boost/corosio/local_stream_acceptor.hpp b/include/boost/corosio/local_stream_acceptor.hpp index ef7f46511..0fbbc2a2b 100644 --- a/include/boost/corosio/local_stream_acceptor.hpp +++ b/include/boost/corosio/local_stream_acceptor.hpp @@ -68,11 +68,14 @@ enum class bind_option @code io_context ioc; local_stream_acceptor acc(ioc); - acc.open(); - acc.bind(local_endpoint("/tmp/my.sock"), - bind_option::unlink_existing); - acc.listen(); - auto [ec, peer] = co_await acc.accept(); + if (auto ec = acc.open()) + return ec; + if (auto ec = acc.bind(local_endpoint("/tmp/my.sock"), + bind_option::unlink_existing)) + return ec; + if (auto ec = acc.listen()) + return ec; + auto [aec, peer] = co_await acc.accept(); @endcode */ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @@ -108,7 +111,9 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -151,7 +156,9 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -266,11 +273,11 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return An error code on failure, empty on success. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. */ [[nodiscard]] std::error_code bind(corosio::local_endpoint ep, - bind_option opt = bind_option::none); + bind_option opt = bind_option::none) noexcept; /** Start listening for incoming connections. @@ -278,9 +285,9 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return An error code on failure, empty on success. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. */ - [[nodiscard]] std::error_code listen(int backlog = 128); + [[nodiscard]] std::error_code listen(int backlog = 128) noexcept; /** Close the acceptor. @@ -290,7 +297,7 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @post is_open() == false */ - void close(); + void close() noexcept; /// Check if the acceptor has an open socket handle. bool is_open() const noexcept @@ -313,13 +320,14 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return An awaitable that completes with io_result<>. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. */ auto accept(local_stream_socket& peer) { + accept_awaitable aw(*this, peer); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return accept_awaitable(*this, peer); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Wait for an incoming connection or readiness condition. @@ -345,9 +353,10 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object */ [[nodiscard]] auto wait(wait_type w) { + wait_awaitable aw(*this, w); if (!is_open()) - detail::throw_logic_error("wait: acceptor not listening"); - return wait_awaitable(*this, w); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Initiate an asynchronous accept, returning the socket. @@ -363,13 +372,14 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return An awaitable that completes with io_result. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. */ auto accept() { + move_accept_awaitable aw(*this); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return move_accept_awaitable(*this); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Cancel pending asynchronous accept operations. @@ -388,7 +398,7 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return The native handle. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. @post is_open() == false */ @@ -456,14 +466,16 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object `level()` and `name()` members, and `data()` / `size()` accessors. - @throws std::logic_error if the acceptor is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + acceptor is not open; otherwise thrown on failure. */ template void set_option(Option const& opt) { if (!is_open()) - detail::throw_logic_error("set_option: acceptor not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_acceptor::set_option"); std::error_code ec = get().set_option( Option::level(), Option::name(), opt.data(), opt.size()); if (ec) @@ -480,14 +492,16 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object `level()` and `name()` members, and `data()` / `size()` / `resize()` members. - @throws std::logic_error if the acceptor is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + acceptor is not open; otherwise thrown on failure. */ template Option get_option() const { if (!is_open()) - detail::throw_logic_error("get_option: acceptor not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_acceptor::get_option"); Option opt{}; std::size_t sz = opt.size(); std::error_code ec = diff --git a/include/boost/corosio/local_stream_socket.hpp b/include/boost/corosio/local_stream_socket.hpp index aa98d628d..65cc82508 100644 --- a/include/boost/corosio/local_stream_socket.hpp +++ b/include/boost/corosio/local_stream_socket.hpp @@ -313,7 +313,7 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream Releases socket resources. Any pending operations complete with `errc::operation_canceled`. */ - void close(); + void close() noexcept; /** Check if the socket is open. @@ -387,8 +387,8 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @return The number of bytes that can be read without blocking. - @throws std::logic_error if the socket is not open. - @throws std::system_error on ioctl failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on ioctl failure. */ std::size_t available() const; @@ -400,7 +400,8 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @return The native handle. - @throws std::logic_error if the socket is not open. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open. @post is_open() == false */ @@ -432,14 +433,16 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @param opt The option to set. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template void set_option(Option const& opt) { if (!is_open()) - detail::throw_logic_error("set_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_socket::set_option"); std::error_code ec = get().set_option( Option::level(), Option::name(), opt.data(), opt.size()); if (ec) @@ -452,14 +455,16 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @return The current option value. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template Option get_option() const { if (!is_open()) - detail::throw_logic_error("get_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_socket::get_option"); Option opt{}; std::size_t sz = opt.size(); std::error_code ec = diff --git a/include/boost/corosio/native/detail/endpoint_convert.hpp b/include/boost/corosio/native/detail/endpoint_convert.hpp index 53548131f..8988ac3f8 100644 --- a/include/boost/corosio/native/detail/endpoint_convert.hpp +++ b/include/boost/corosio/native/detail/endpoint_convert.hpp @@ -315,7 +315,10 @@ from_sockaddr_local( if (static_cast(len) <= path_offset) return local_endpoint{}; - auto path_len = static_cast(len) - path_offset; + // Clamp to the buffer: a foreign len may overstate the payload, + // and sun_path is the struct's last member. + auto path_len = (std::min)( + static_cast(len) - path_offset, sizeof(sa.sun_path)); // Non-abstract paths may be null-terminated by the kernel if (path_len > 0 && sa.sun_path[0] != '\0') @@ -326,11 +329,11 @@ from_sockaddr_local( path_len = static_cast(end - sa.sun_path); } - std::error_code ec; - local_endpoint ep(std::string_view(sa.sun_path, path_len), ec); - if (ec) + // A foreign sun_path may exceed corosio's cap; the length + // pre-check keeps the throwing constructor unreachable. + if (path_len > local_endpoint::max_path_length) return local_endpoint{}; - return ep; + return local_endpoint(std::string_view(sa.sun_path, path_len)); } //---------------------------------------------------------- diff --git a/include/boost/corosio/native/native_local_datagram_socket.hpp b/include/boost/corosio/native/native_local_datagram_socket.hpp index a075476bc..dffd0bfae 100644 --- a/include/boost/corosio/native/native_local_datagram_socket.hpp +++ b/include/boost/corosio/native/native_local_datagram_socket.hpp @@ -113,7 +113,9 @@ class native_local_datagram_socket : public local_datagram_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -158,7 +160,9 @@ class native_local_datagram_socket : public local_datagram_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -232,7 +236,9 @@ class native_local_datagram_socket : public local_datagram_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -273,7 +279,9 @@ class native_local_datagram_socket : public local_datagram_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -315,7 +323,9 @@ class native_local_datagram_socket : public local_datagram_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -382,10 +392,10 @@ class native_local_datagram_socket : public local_datagram_socket corosio::local_endpoint dest, corosio::message_flags flags) { + native_send_to_awaitable aw(*this, buffers, dest, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send_to: socket not open"); - return native_send_to_awaitable( - *this, buffers, dest, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -406,10 +416,10 @@ class native_local_datagram_socket : public local_datagram_socket corosio::local_endpoint& source, corosio::message_flags flags) { + native_recv_from_awaitable aw(*this, buffers, source, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv_from: socket not open"); - return native_recv_from_awaitable( - *this, buffers, source, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -442,10 +452,10 @@ class native_local_datagram_socket : public local_datagram_socket template auto send(CB const& buffers, corosio::message_flags flags) { + native_send_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send: socket not open"); - return native_send_awaitable( - *this, buffers, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -463,10 +473,10 @@ class native_local_datagram_socket : public local_datagram_socket template auto recv(MB const& buffers, corosio::message_flags flags) { + native_recv_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv: socket not open"); - return native_recv_awaitable( - *this, buffers, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload diff --git a/include/boost/corosio/native/native_local_stream_acceptor.hpp b/include/boost/corosio/native/native_local_stream_acceptor.hpp index 016f07a7a..e69fed3c1 100644 --- a/include/boost/corosio/native/native_local_stream_acceptor.hpp +++ b/include/boost/corosio/native/native_local_stream_acceptor.hpp @@ -91,7 +91,9 @@ class native_local_stream_acceptor : public local_stream_acceptor bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -128,7 +130,9 @@ class native_local_stream_acceptor : public local_stream_acceptor bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -164,7 +168,9 @@ class native_local_stream_acceptor : public local_stream_acceptor bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result> @@ -238,16 +244,17 @@ class native_local_stream_acceptor : public local_stream_acceptor @return An awaitable yielding `io_result<>`. - @throws std::logic_error if the acceptor is not listening. + A closed acceptor reports `errc::bad_file_descriptor`. Both this acceptor and @p peer must outlive the returned awaitable. */ auto accept(local_stream_socket& peer) { + native_accept_awaitable aw(*this, peer); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return native_accept_awaitable(*this, peer); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Asynchronously accept an incoming connection, returning the peer. @@ -260,15 +267,20 @@ class native_local_stream_acceptor : public local_stream_acceptor @return An awaitable yielding `io_result>`. - @throws std::logic_error if the acceptor is not listening. + A closed acceptor reports `errc::bad_file_descriptor`. This acceptor must outlive the returned awaitable. */ auto accept() { + // The awaitable builds the peer from context(), which a + // moved-from acceptor no longer has. + if (!h_) + detail::throw_logic_error("accept: acceptor moved-from"); + native_move_accept_awaitable aw(*this); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return native_move_accept_awaitable(*this); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Asynchronously wait for the acceptor to be ready. diff --git a/include/boost/corosio/native/native_local_stream_socket.hpp b/include/boost/corosio/native/native_local_stream_socket.hpp index 1792e5847..5022d850c 100644 --- a/include/boost/corosio/native/native_local_stream_socket.hpp +++ b/include/boost/corosio/native/native_local_stream_socket.hpp @@ -102,7 +102,9 @@ class native_local_stream_socket : public local_stream_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -140,7 +142,9 @@ class native_local_stream_socket : public local_stream_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -213,7 +217,9 @@ class native_local_stream_socket : public local_stream_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept diff --git a/include/boost/corosio/native/native_random_access_file.hpp b/include/boost/corosio/native/native_random_access_file.hpp index 96244d18e..5172a9213 100644 --- a/include/boost/corosio/native/native_random_access_file.hpp +++ b/include/boost/corosio/native/native_random_access_file.hpp @@ -107,7 +107,9 @@ class native_random_access_file : public random_access_file bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -149,7 +151,9 @@ class native_random_access_file : public random_access_file bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept diff --git a/include/boost/corosio/native/native_stream_file.hpp b/include/boost/corosio/native/native_stream_file.hpp index 4f4291b6c..b456e9e77 100644 --- a/include/boost/corosio/native/native_stream_file.hpp +++ b/include/boost/corosio/native/native_stream_file.hpp @@ -103,7 +103,9 @@ class native_stream_file : public stream_file bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -141,7 +143,9 @@ class native_stream_file : public stream_file bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept diff --git a/include/boost/corosio/native/native_tcp_acceptor.hpp b/include/boost/corosio/native/native_tcp_acceptor.hpp index 6b1b5d510..a04b4dede 100644 --- a/include/boost/corosio/native/native_tcp_acceptor.hpp +++ b/include/boost/corosio/native/native_tcp_acceptor.hpp @@ -84,7 +84,9 @@ class native_tcp_acceptor : public tcp_acceptor bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -120,7 +122,9 @@ class native_tcp_acceptor : public tcp_acceptor bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -157,7 +161,9 @@ class native_tcp_acceptor : public tcp_acceptor bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() noexcept @@ -234,16 +240,17 @@ class native_tcp_acceptor : public tcp_acceptor @return An awaitable yielding `io_result<>`. - @throws std::logic_error if the acceptor is not listening. + A closed acceptor reports `errc::bad_file_descriptor`. Both this acceptor and @p peer must outlive the returned awaitable. */ auto accept(tcp_socket& peer) { + native_accept_awaitable aw(*this, peer); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return native_accept_awaitable(*this, peer); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Asynchronously accept an incoming connection, returning the peer. @@ -253,15 +260,20 @@ class native_tcp_acceptor : public tcp_acceptor @return An awaitable yielding `io_result`. - @throws std::logic_error if the acceptor is not listening. + A closed acceptor reports `errc::bad_file_descriptor`. This acceptor must outlive the returned awaitable. */ auto accept() { + // The awaitable builds the peer from context(), which a + // moved-from acceptor no longer has. + if (!h_) + detail::throw_logic_error("accept: acceptor moved-from"); + native_accept_value_awaitable aw(*this); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return native_accept_value_awaitable(*this); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Asynchronously wait for the acceptor to be ready. diff --git a/include/boost/corosio/native/native_tcp_socket.hpp b/include/boost/corosio/native/native_tcp_socket.hpp index f871ee48b..9c04641e6 100644 --- a/include/boost/corosio/native/native_tcp_socket.hpp +++ b/include/boost/corosio/native/native_tcp_socket.hpp @@ -101,7 +101,9 @@ class native_tcp_socket : public tcp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -138,7 +140,9 @@ class native_tcp_socket : public tcp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -172,7 +176,9 @@ class native_tcp_socket : public tcp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -206,7 +212,9 @@ class native_tcp_socket : public tcp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -321,15 +329,16 @@ class native_tcp_socket : public tcp_socket @return An awaitable yielding `io_result<>`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. This socket must outlive the returned awaitable. */ auto connect(endpoint ep) { + native_connect_awaitable aw(*this, ep); if (!is_open()) - detail::throw_logic_error("connect: socket not open"); - return native_connect_awaitable(*this, ep); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Asynchronously wait for the socket to be ready. diff --git a/include/boost/corosio/native/native_udp_socket.hpp b/include/boost/corosio/native/native_udp_socket.hpp index 7c08a8c88..2bda3cfc4 100644 --- a/include/boost/corosio/native/native_udp_socket.hpp +++ b/include/boost/corosio/native/native_udp_socket.hpp @@ -112,7 +112,9 @@ class native_udp_socket : public udp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -157,7 +159,9 @@ class native_udp_socket : public udp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -228,7 +232,9 @@ class native_udp_socket : public udp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -269,7 +275,9 @@ class native_udp_socket : public udp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -311,7 +319,9 @@ class native_udp_socket : public udp_socket bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -378,10 +388,10 @@ class native_udp_socket : public udp_socket endpoint dest, corosio::message_flags flags) { + native_send_to_awaitable aw(*this, buffers, dest, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send_to: socket not open"); - return native_send_to_awaitable( - *this, buffers, dest, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -409,10 +419,10 @@ class native_udp_socket : public udp_socket endpoint& source, corosio::message_flags flags) { + native_recv_from_awaitable aw(*this, buffers, source, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv_from: socket not open"); - return native_recv_from_awaitable( - *this, buffers, source, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -455,15 +465,15 @@ class native_udp_socket : public udp_socket @return An awaitable yielding `(error_code, std::size_t)`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto send(CB const& buffers, corosio::message_flags flags) { + native_send_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send: socket not open"); - return native_send_awaitable( - *this, buffers, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -483,15 +493,15 @@ class native_udp_socket : public udp_socket @return An awaitable yielding `(error_code, std::size_t)`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto recv(MB const& buffers, corosio::message_flags flags) { + native_recv_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv: socket not open"); - return native_recv_awaitable( - *this, buffers, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload diff --git a/include/boost/corosio/random_access_file.hpp b/include/boost/corosio/random_access_file.hpp index 5d11ac593..300e61395 100644 --- a/include/boost/corosio/random_access_file.hpp +++ b/include/boost/corosio/random_access_file.hpp @@ -161,7 +161,9 @@ class BOOST_COROSIO_DECL random_access_file : public io_object bool await_ready() const noexcept { - return false; + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -202,7 +204,9 @@ class BOOST_COROSIO_DECL random_access_file : public io_object bool await_ready() const noexcept { - return false; + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_); } [[nodiscard]] capy::io_result await_resume() const noexcept @@ -285,7 +289,7 @@ class BOOST_COROSIO_DECL random_access_file : public io_object Releases file resources. Any pending operations complete with `errc::operation_canceled`. */ - void close(); + void close() noexcept; /** Check if the file is open. */ bool is_open() const noexcept @@ -304,14 +308,15 @@ class BOOST_COROSIO_DECL random_access_file : public io_object @return An awaitable yielding `(error_code, std::size_t)`. - @throws std::logic_error if the file is not open. + A closed file reports `errc::bad_file_descriptor`. */ template auto read_some_at(std::uint64_t offset, MB const& buffers) { + read_some_at_awaitable aw(*this, offset, buffers); if (!is_open()) - detail::throw_logic_error("read_some_at: file not open"); - return read_some_at_awaitable(*this, offset, buffers); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Write data at the given offset. @@ -321,14 +326,15 @@ class BOOST_COROSIO_DECL random_access_file : public io_object @return An awaitable yielding `(error_code, std::size_t)`. - @throws std::logic_error if the file is not open. + A closed file reports `errc::bad_file_descriptor`. */ template auto write_some_at(std::uint64_t offset, CB const& buffers) { + write_some_at_awaitable aw(*this, offset, buffers); if (!is_open()) - detail::throw_logic_error("write_some_at: file not open"); - return write_some_at_awaitable(*this, offset, buffers); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Cancel pending asynchronous operations. */ diff --git a/include/boost/corosio/stream_file.hpp b/include/boost/corosio/stream_file.hpp index bba649de5..ae1e34e80 100644 --- a/include/boost/corosio/stream_file.hpp +++ b/include/boost/corosio/stream_file.hpp @@ -176,7 +176,7 @@ class BOOST_COROSIO_DECL stream_file : public io_stream Releases file resources. Any pending operations complete with `errc::operation_canceled`. */ - void close(); + void close() noexcept; /** Check if the file is open. diff --git a/include/boost/corosio/tcp_acceptor.hpp b/include/boost/corosio/tcp_acceptor.hpp index 533da25f7..ae2ad26cc 100644 --- a/include/boost/corosio/tcp_acceptor.hpp +++ b/include/boost/corosio/tcp_acceptor.hpp @@ -72,7 +72,8 @@ namespace boost::corosio { @code // Fine-grained setup tcp_acceptor acc( ioc ); - acc.open( tcp::v6() ); + if ( auto ec = acc.open( tcp::v6() ) ) + return ec; acc.set_option( socket_option::reuse_address( true ) ); acc.set_option( socket_option::v6_only( true ) ); if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) @@ -115,7 +116,9 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result<> await_resume() const noexcept @@ -140,31 +143,36 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object struct accept_value_awaitable { tcp_acceptor& acc_; - tcp_socket peer_; std::stop_token token_; mutable std::error_code ec_; mutable io_object::implementation* peer_impl_ = nullptr; - explicit accept_value_awaitable(tcp_acceptor& acc) + explicit accept_value_awaitable(tcp_acceptor& acc) noexcept : acc_(acc) - , peer_(acc.context()) { } bool await_ready() const noexcept { - return token_.stop_requested(); + // A pre-set ec_ means the initiator failed before + // dispatch (e.g. a closed object). + return static_cast(ec_) || token_.stop_requested(); } [[nodiscard]] capy::io_result await_resume() noexcept { + // The peer is built only on success: error paths must not + // touch acc_.context(), which a moved-from acceptor lacks. if (token_.stop_requested()) return {make_error_code(std::errc::operation_canceled), - std::move(peer_)}; + tcp_socket()}; - if (!ec_ && peer_impl_) - peer_.h_.reset(peer_impl_); - return {ec_, std::move(peer_)}; + if (ec_ || !peer_impl_) + return {ec_, tcp_socket()}; + + tcp_socket peer(acc_.context()); + peer.h_.reset(peer_impl_); + return {ec_, std::move(peer)}; } auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) @@ -276,17 +284,21 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object If the acceptor is already open, this function is a no-op. + Failures such as descriptor exhaustion are normal runtime + conditions and are reported through the returned error code. + @param proto The protocol (IPv4 or IPv6). Defaults to `tcp::v4()`. - @throws std::system_error on failure. - @par Example @code - acc.open( tcp::v6() ); + if (auto ec = acc.open( tcp::v6() )) + return; // report the error acc.set_option( socket_option::reuse_address( true ) ); - acc.bind( endpoint( ipv6_address::any(), 8080 ) ); - acc.listen(); + if (auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) )) + return; + if (auto ec = acc.listen()) + return; @endcode @see bind, listen @@ -313,9 +325,9 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @li `errc::permission_denied`: Insufficient privileges to bind to the endpoint (e.g., privileged port). - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. */ - [[nodiscard]] std::error_code bind(endpoint ep); + [[nodiscard]] std::error_code bind(endpoint ep) noexcept; /** Start listening for incoming connections. @@ -328,16 +340,16 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @return An error code indicating success or the reason for failure. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. */ - [[nodiscard]] std::error_code listen(int backlog = 128); + [[nodiscard]] std::error_code listen(int backlog = 128) noexcept; /** Close the acceptor. Releases acceptor resources. Any pending operations complete with `errc::operation_canceled`. */ - void close(); + void close() noexcept; /** Check if the acceptor is listening. @@ -368,8 +380,9 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object - operation_canceled: Cancelled via stop_token or cancel(). Check `ec == cond::canceled` for portable comparison. + A closed acceptor completes with `errc::bad_file_descriptor`. + @par Preconditions - The acceptor must be listening (`is_open() == true`). The peer socket must be associated with the same execution context. Both this acceptor and @p peer must outlive the returned @@ -388,9 +401,10 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object */ auto accept(tcp_socket& peer) { + accept_awaitable aw(*this, peer); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return accept_awaitable(*this, peer); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Initiate an asynchronous accept operation, returning the peer. @@ -414,9 +428,10 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object - operation_canceled: Cancelled via stop_token or cancel(). Check `ec == cond::canceled` for portable comparison. + A closed acceptor completes with `errc::bad_file_descriptor`. + @par Preconditions - The acceptor must be listening (`is_open() == true`). This acceptor - must outlive the returned awaitable. + This acceptor must outlive the returned awaitable. @par Example @code @@ -430,9 +445,10 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object */ auto accept() { + accept_value_awaitable aw(*this); if (!is_open()) - detail::throw_logic_error("accept: acceptor not listening"); - return accept_value_awaitable(*this); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Wait for an incoming connection or readiness condition. @@ -453,15 +469,17 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @return An awaitable that completes with `io_result<>`. + A closed acceptor completes with `errc::bad_file_descriptor`. + @par Preconditions - The acceptor must be listening. This acceptor must - outlive the returned awaitable. + This acceptor must outlive the returned awaitable. */ [[nodiscard]] auto wait(wait_type w) { + wait_awaitable aw(*this, w); if (!is_open()) - detail::throw_logic_error("wait: acceptor not listening"); - return wait_awaitable(*this, w); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /** Cancel any pending asynchronous operations. @@ -525,7 +543,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @return The native handle. - @throws std::logic_error if the acceptor is not open. + A closed acceptor reports `errc::bad_file_descriptor`. @post is_open() == false */ @@ -558,22 +576,27 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @par Example @code - acc.open( tcp::v6() ); + if ( auto ec = acc.open( tcp::v6() ) ) + return ec; acc.set_option( socket_option::reuse_port( true ) ); - acc.bind( endpoint( ipv6_address::any(), 8080 ) ); - acc.listen(); + if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) + return ec; + if ( auto ec = acc.listen() ) + return ec; @endcode @param opt The option to set. - @throws std::logic_error if the acceptor is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + acceptor is not open; otherwise thrown on failure. */ template void set_option(Option const& opt) { if (!is_open()) - detail::throw_logic_error("set_option: acceptor not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "tcp_acceptor::set_option"); std::error_code ec = get().set_option( Option::level(), Option::name(), opt.data(), opt.size()); if (ec) @@ -591,14 +614,16 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @return The current option value. - @throws std::logic_error if the acceptor is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + acceptor is not open; otherwise thrown on failure. */ template Option get_option() const { if (!is_open()) - detail::throw_logic_error("get_option: acceptor not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "tcp_acceptor::get_option"); Option opt{}; std::size_t sz = opt.size(); std::error_code ec = diff --git a/include/boost/corosio/tcp_socket.hpp b/include/boost/corosio/tcp_socket.hpp index 1768b6c25..5a9cded2c 100644 --- a/include/boost/corosio/tcp_socket.hpp +++ b/include/boost/corosio/tcp_socket.hpp @@ -332,16 +332,16 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @li `errc::permission_denied`: Insufficient privileges to bind to the endpoint (e.g., privileged port). - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ - [[nodiscard]] std::error_code bind(endpoint ep); + [[nodiscard]] std::error_code bind(endpoint ep) noexcept; /** Close the socket. Releases socket resources. Any pending operations complete with `errc::operation_canceled`. */ - void close(); + void close() noexcept; /** Check if the socket is open. @@ -486,7 +486,8 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @return The native handle. - @throws std::logic_error if the socket is not open. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open. @post is_open() == false */ @@ -550,14 +551,16 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @param opt The option to set. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template void set_option(Option const& opt) { if (!is_open()) - detail::throw_logic_error("set_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "tcp_socket::set_option"); std::error_code ec = get().set_option( Option::level(), Option::name(), opt.data(), opt.size()); if (ec) @@ -577,14 +580,16 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @return The current option value. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template Option get_option() const { if (!is_open()) - detail::throw_logic_error("get_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "tcp_socket::get_option"); Option opt{}; std::size_t sz = opt.size(); std::error_code ec = diff --git a/include/boost/corosio/udp_socket.hpp b/include/boost/corosio/udp_socket.hpp index a5a6ce179..77d2ea2f3 100644 --- a/include/boost/corosio/udp_socket.hpp +++ b/include/boost/corosio/udp_socket.hpp @@ -487,7 +487,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object Releases socket resources. Any pending operations complete with `errc::operation_canceled`. */ - void close(); + void close() noexcept; /** Check if the socket is open. @@ -511,9 +511,9 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return Error code on failure, empty on success. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ - [[nodiscard]] std::error_code bind(endpoint ep); + [[nodiscard]] std::error_code bind(endpoint ep) noexcept; /** Disable sends or receives on the socket. @@ -580,7 +580,8 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return The native handle. - @throws std::logic_error if the socket is not open. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open. @post is_open() == false */ @@ -590,14 +591,16 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @param opt The option to set. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template void set_option(Option const& opt) { if (!is_open()) - detail::throw_logic_error("set_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "udp_socket::set_option"); std::error_code ec = get().set_option( Option::level(), Option::name(), opt.data(), opt.size()); if (ec) @@ -608,14 +611,16 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return The current option value. - @throws std::logic_error if the socket is not open. - @throws std::system_error on failure. + @throws std::system_error `errc::bad_file_descriptor` if the + socket is not open; otherwise thrown on failure. */ template Option get_option() const { if (!is_open()) - detail::throw_logic_error("get_option: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "udp_socket::get_option"); Option opt{}; std::size_t sz = opt.size(); std::error_code ec = @@ -641,7 +646,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return An awaitable that completes with `io_result`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto send_to( @@ -649,10 +654,10 @@ class BOOST_COROSIO_DECL udp_socket : public io_object endpoint dest, corosio::message_flags flags) { + send_to_awaitable aw(*this, buf, dest, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send_to: socket not open"); - return send_to_awaitable( - *this, buf, dest, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -672,7 +677,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return An awaitable that completes with `io_result`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto recv_from( @@ -680,10 +685,10 @@ class BOOST_COROSIO_DECL udp_socket : public io_object endpoint& source, corosio::message_flags flags) { + recv_from_awaitable aw(*this, buf, source, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv_from: socket not open"); - return recv_from_awaitable( - *this, buf, source, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -742,15 +747,15 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return An awaitable that completes with `io_result`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto send(Buffers const& buf, corosio::message_flags flags) { + send_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("send: socket not open"); - return send_awaitable( - *this, buf, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload @@ -768,15 +773,15 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return An awaitable that completes with `io_result`. - @throws std::logic_error if the socket is not open. + A closed socket reports `errc::bad_file_descriptor`. */ template auto recv(Buffers const& buf, corosio::message_flags flags) { + recv_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) - detail::throw_logic_error("recv: socket not open"); - return recv_awaitable( - *this, buf, static_cast(flags)); + aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + return aw; } /// @overload diff --git a/src/corosio/src/host_name.cpp b/src/corosio/src/host_name.cpp index 5c6b6e80d..15bd0533b 100644 --- a/src/corosio/src/host_name.cpp +++ b/src/corosio/src/host_name.cpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #if BOOST_COROSIO_POSIX #include @@ -33,14 +33,15 @@ host_name() char buf[256]; if (::gethostname(buf, sizeof(buf)) != 0) { - int e = errno; - throw std::runtime_error( - std::string("gethostname failed: ") + std::strerror(e)); + throw std::system_error( + std::error_code(errno, std::generic_category()), "gethostname"); } // POSIX does not guarantee NUL termination on truncation. if (std::memchr(buf, '\0', sizeof(buf)) == nullptr) - throw std::runtime_error("gethostname: hostname truncated"); + throw std::system_error( + make_error_code(std::errc::value_too_large), + "gethostname: hostname truncated"); return std::string(buf); } @@ -58,14 +59,15 @@ host_name() DWORD err = ::GetLastError(); if (ok) { - throw std::runtime_error( + throw std::system_error( + make_error_code(std::errc::protocol_error), "GetComputerNameExW (size query) unexpectedly succeeded"); } if (err != ERROR_MORE_DATA) { - throw std::runtime_error( - "GetComputerNameExW (size query) failed: error " + - std::to_string(err)); + throw std::system_error( + std::error_code(static_cast(err), std::system_category()), + "GetComputerNameExW (size query)"); } // On success, GetComputerNameExW rewrites `size` to the count @@ -74,9 +76,10 @@ host_name() if (!::GetComputerNameExW( ComputerNameDnsHostname, wide.data(), &size)) { - throw std::runtime_error( - "GetComputerNameExW failed: error " + - std::to_string(::GetLastError())); + throw std::system_error( + std::error_code( + static_cast(::GetLastError()), std::system_category()), + "GetComputerNameExW"); } wide.resize(size); @@ -85,9 +88,10 @@ host_name() nullptr, 0, nullptr, nullptr); if (needed <= 0) { - throw std::runtime_error( - "WideCharToMultiByte (size query) failed: error " + - std::to_string(::GetLastError())); + throw std::system_error( + std::error_code( + static_cast(::GetLastError()), std::system_category()), + "WideCharToMultiByte (size query)"); } std::string out(static_cast(needed), '\0'); @@ -96,9 +100,10 @@ host_name() out.data(), needed, nullptr, nullptr); if (written != needed) { - throw std::runtime_error( - "WideCharToMultiByte failed: error " + - std::to_string(::GetLastError())); + throw std::system_error( + std::error_code( + static_cast(::GetLastError()), std::system_category()), + "WideCharToMultiByte"); } return out; } diff --git a/src/corosio/src/local_datagram_socket.cpp b/src/corosio/src/local_datagram_socket.cpp index e2a5b4f9e..fa7b774b3 100644 --- a/src/corosio/src/local_datagram_socket.cpp +++ b/src/corosio/src/local_datagram_socket.cpp @@ -48,7 +48,7 @@ local_datagram_socket::open_for_family(int family, int type, int protocol) noexc } void -local_datagram_socket::close() +local_datagram_socket::close() noexcept { if (!is_open()) return; @@ -56,10 +56,10 @@ local_datagram_socket::close() } std::error_code -local_datagram_socket::bind(corosio::local_endpoint ep) +local_datagram_socket::bind(corosio::local_endpoint ep) noexcept { if (!is_open()) - detail::throw_logic_error("bind: socket not open"); + return make_error_code(std::errc::bad_file_descriptor); auto& svc = static_cast(h_.service()); return svc.bind_socket( static_cast(*h_.get()), @@ -103,7 +103,9 @@ native_handle_type local_datagram_socket::release() { if (!is_open()) - detail::throw_logic_error("release: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_datagram_socket::release"); return get().release_socket(); } @@ -111,7 +113,9 @@ std::size_t local_datagram_socket::available() const { if (!is_open()) - detail::throw_logic_error("available: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_datagram_socket::available"); int value = 0; if (::ioctl(native_handle(), FIONREAD, &value) < 0) detail::throw_system_error( diff --git a/src/corosio/src/local_endpoint.cpp b/src/corosio/src/local_endpoint.cpp index c09025b15..058f1042f 100644 --- a/src/corosio/src/local_endpoint.cpp +++ b/src/corosio/src/local_endpoint.cpp @@ -26,19 +26,6 @@ local_endpoint::local_endpoint(std::string_view path) len_ = static_cast(path.size()); } -local_endpoint::local_endpoint( - std::string_view path, std::error_code& ec) noexcept -{ - if (path.size() > max_path_length) - { - ec = std::make_error_code(std::errc::filename_too_long); - return; - } - ec = {}; - std::memcpy(path_, path.data(), path.size()); - len_ = static_cast(path.size()); -} - std::ostream& operator<<(std::ostream& os, local_endpoint const& ep) { diff --git a/src/corosio/src/local_stream_acceptor.cpp b/src/corosio/src/local_stream_acceptor.cpp index e6f8d2e3c..c19eda34e 100644 --- a/src/corosio/src/local_stream_acceptor.cpp +++ b/src/corosio/src/local_stream_acceptor.cpp @@ -77,10 +77,10 @@ local_stream_acceptor::native_handle() const noexcept } std::error_code -local_stream_acceptor::bind(corosio::local_endpoint ep, bind_option opt) +local_stream_acceptor::bind(corosio::local_endpoint ep, bind_option opt) noexcept { if (!is_open()) - detail::throw_logic_error("bind: acceptor not open"); + return make_error_code(std::errc::bad_file_descriptor); if (opt == bind_option::unlink_existing && !ep.empty() && !ep.is_abstract()) @@ -105,10 +105,10 @@ local_stream_acceptor::bind(corosio::local_endpoint ep, bind_option opt) } std::error_code -local_stream_acceptor::listen(int backlog) +local_stream_acceptor::listen(int backlog) noexcept { if (!is_open()) - detail::throw_logic_error("listen: acceptor not open"); + return make_error_code(std::errc::bad_file_descriptor); auto& svc = static_cast(h_.service()); return svc.listen_acceptor( @@ -117,7 +117,7 @@ local_stream_acceptor::listen(int backlog) } void -local_stream_acceptor::close() +local_stream_acceptor::close() noexcept { if (!is_open()) return; @@ -128,7 +128,9 @@ native_handle_type local_stream_acceptor::release() { if (!is_open()) - detail::throw_logic_error("release: acceptor not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_acceptor::release"); return get().release_socket(); } diff --git a/src/corosio/src/local_stream_socket.cpp b/src/corosio/src/local_stream_socket.cpp index 79c929ca3..3d4d70cde 100644 --- a/src/corosio/src/local_stream_socket.cpp +++ b/src/corosio/src/local_stream_socket.cpp @@ -52,7 +52,7 @@ local_stream_socket::open_for_family(int family, int type, int protocol) noexcep } void -local_stream_socket::close() +local_stream_socket::close() noexcept { if (!is_open()) return; @@ -100,7 +100,9 @@ native_handle_type local_stream_socket::release() { if (!is_open()) - detail::throw_logic_error("release: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_socket::release"); return get().release_socket(); } @@ -108,7 +110,9 @@ std::size_t local_stream_socket::available() const { if (!is_open()) - detail::throw_logic_error("available: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "local_stream_socket::available"); #if BOOST_COROSIO_HAS_IOCP u_long value = 0; if (::ioctlsocket( diff --git a/src/corosio/src/random_access_file.cpp b/src/corosio/src/random_access_file.cpp index 94c58180d..c2f93b811 100644 --- a/src/corosio/src/random_access_file.cpp +++ b/src/corosio/src/random_access_file.cpp @@ -44,7 +44,7 @@ random_access_file::open( } void -random_access_file::close() +random_access_file::close() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/stream_file.cpp b/src/corosio/src/stream_file.cpp index fa289c519..af1f22625 100644 --- a/src/corosio/src/stream_file.cpp +++ b/src/corosio/src/stream_file.cpp @@ -44,7 +44,7 @@ stream_file::open( } void -stream_file::close() +stream_file::close() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/tcp_acceptor.cpp b/src/corosio/src/tcp_acceptor.cpp index dac70dc3f..0c1905f7c 100644 --- a/src/corosio/src/tcp_acceptor.cpp +++ b/src/corosio/src/tcp_acceptor.cpp @@ -83,7 +83,9 @@ native_handle_type tcp_acceptor::release() { if (!is_open()) - detail::throw_logic_error("release: acceptor not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "tcp_acceptor::release"); return get().release_socket(); } @@ -102,10 +104,10 @@ tcp_acceptor::native_handle() const noexcept } std::error_code -tcp_acceptor::bind(endpoint ep) +tcp_acceptor::bind(endpoint ep) noexcept { if (!is_open()) - detail::throw_logic_error("bind: acceptor not open"); + return make_error_code(std::errc::bad_file_descriptor); #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); #else @@ -116,10 +118,10 @@ tcp_acceptor::bind(endpoint ep) } std::error_code -tcp_acceptor::listen(int backlog) +tcp_acceptor::listen(int backlog) noexcept { if (!is_open()) - detail::throw_logic_error("listen: acceptor not open"); + return make_error_code(std::errc::bad_file_descriptor); #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); #else @@ -130,7 +132,7 @@ tcp_acceptor::listen(int backlog) } void -tcp_acceptor::close() +tcp_acceptor::close() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/tcp_socket.cpp b/src/corosio/src/tcp_socket.cpp index 795cfdb19..c80438ffa 100644 --- a/src/corosio/src/tcp_socket.cpp +++ b/src/corosio/src/tcp_socket.cpp @@ -80,15 +80,17 @@ native_handle_type tcp_socket::release() { if (!is_open()) - detail::throw_logic_error("release: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "tcp_socket::release"); return get().release_socket(); } std::error_code -tcp_socket::bind(endpoint ep) +tcp_socket::bind(endpoint ep) noexcept { if (!is_open()) - detail::throw_logic_error("bind: socket not open"); + return make_error_code(std::errc::bad_file_descriptor); #if BOOST_COROSIO_HAS_IOCP auto& svc = static_cast(h_.service()); auto& wrapper = static_cast(*h_.get()); @@ -102,7 +104,7 @@ tcp_socket::bind(endpoint ep) } void -tcp_socket::close() +tcp_socket::close() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/udp_socket.cpp b/src/corosio/src/udp_socket.cpp index 4be2fb9a6..893e7becd 100644 --- a/src/corosio/src/udp_socket.cpp +++ b/src/corosio/src/udp_socket.cpp @@ -56,12 +56,14 @@ native_handle_type udp_socket::release() { if (!is_open()) - detail::throw_logic_error("release: socket not open"); + detail::throw_system_error( + make_error_code(std::errc::bad_file_descriptor), + "udp_socket::release"); return get().release_socket(); } void -udp_socket::close() +udp_socket::close() noexcept { if (!is_open()) return; @@ -69,10 +71,10 @@ udp_socket::close() } std::error_code -udp_socket::bind(endpoint ep) +udp_socket::bind(endpoint ep) noexcept { if (!is_open()) - detail::throw_logic_error("bind: socket not open"); + return make_error_code(std::errc::bad_file_descriptor); auto& svc = static_cast(h_.service()); return svc.bind_datagram( static_cast(*h_.get()), ep); diff --git a/test/unit/cross_ssl_stream.cpp b/test/unit/cross_ssl_stream.cpp index 5c27fd691..748874ba5 100644 --- a/test/unit/cross_ssl_stream.cpp +++ b/test/unit/cross_ssl_stream.cpp @@ -22,6 +22,7 @@ #include "test_utils.hpp" #include "test_suite.hpp" #include +#include /* Cross-Implementation TLS Tests ================================ @@ -134,7 +135,7 @@ struct cross_ssl_stream_test { auto client_ctx = make_client_context(); auto server_ctx = make_anon_context(); - (void)server_ctx.set_ciphersuites(""); + std::ignore = server_ctx.set_ciphersuites(""); run_tls_test_fail( ioc, client_ctx, server_ctx, make_openssl, make_wolfssl); ioc.restart(); @@ -144,7 +145,7 @@ struct cross_ssl_stream_test { auto client_ctx = make_client_context(); auto server_ctx = make_anon_context(); - (void)server_ctx.set_ciphersuites(""); + std::ignore = server_ctx.set_ciphersuites(""); run_tls_test_fail( ioc, client_ctx, server_ctx, make_wolfssl, make_openssl); } diff --git a/test/unit/io_context.cpp b/test/unit/io_context.cpp index a12233aff..2c2cf350a 100644 --- a/test/unit/io_context.cpp +++ b/test/unit/io_context.cpp @@ -962,8 +962,29 @@ struct io_context_test BOOST_TEST_EQ(counter.load(), 8); } +#if BOOST_COROSIO_POSIX + void testZeroThreadPoolSizeThrows() + { + io_context_options opts; + opts.thread_pool_size = 0; + bool threw = false; + try + { + io_context ioc(opts); + } + catch (std::invalid_argument const&) + { + threw = true; + } + BOOST_TEST(threw); + } +#endif + void run() { +#if BOOST_COROSIO_POSIX + testZeroThreadPoolSizeThrows(); +#endif testConstruction(); testConstructionWithOptions(); testConstructionWithThreadPoolSize(); diff --git a/test/unit/ipv4_address.cpp b/test/unit/ipv4_address.cpp index d7e502773..786370e77 100644 --- a/test/unit/ipv4_address.cpp +++ b/test/unit/ipv4_address.cpp @@ -75,7 +75,7 @@ struct ipv4_address_test auto check_invalid = [](std::string_view s) { ipv4_address addr; auto ec = parse_ipv4_address(s, addr); - BOOST_TEST(bool(ec)); + BOOST_TEST(ec == std::errc::invalid_argument); }; check_invalid(""); @@ -126,6 +126,16 @@ struct ipv4_address_test BOOST_TEST_EQ(sv, "1.2.3.4"); } + void testToBufferTooSmallThrows() + { + // to_buffer must throw length_error when the buffer is smaller + // than max_str_len, even if the formatted address would fit. + char small[4]; + BOOST_TEST_THROWS( + ipv4_address(0x01020304).to_buffer(small, sizeof(small)), + std::length_error); + } + void testPredicates() { // Loopback @@ -182,6 +192,7 @@ struct ipv4_address_test testToBytes(); testToString(); testToBuffer(); + testToBufferTooSmallThrows(); testPredicates(); testStaticFactories(); testComparison(); diff --git a/test/unit/ipv6_address.cpp b/test/unit/ipv6_address.cpp index 52df5aaf8..00b5ee307 100644 --- a/test/unit/ipv6_address.cpp +++ b/test/unit/ipv6_address.cpp @@ -99,7 +99,7 @@ struct ipv6_address_test auto check_invalid = [](std::string_view s) { ipv6_address addr; auto ec = parse_ipv6_address(s, addr); - BOOST_TEST(bool(ec)); + BOOST_TEST(ec == std::errc::invalid_argument); }; check_invalid(""); diff --git a/test/unit/local_datagram_socket.cpp b/test/unit/local_datagram_socket.cpp index 4817a70f7..c24517ef7 100644 --- a/test/unit/local_datagram_socket.cpp +++ b/test/unit/local_datagram_socket.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -598,14 +599,13 @@ struct local_datagram_socket_test == std::errc::bad_file_descriptor); } - void testBindClosedThrows() + void testBindClosed() { io_context ioc(Backend); local_datagram_socket sock(ioc); - // NOLINTNEXTLINE(bugprone-unused-return-value) - BOOST_TEST_THROWS(sock.bind(local_endpoint("/tmp/never")), - std::logic_error); + BOOST_TEST(sock.bind(local_endpoint("/tmp/never")) + == std::errc::bad_file_descriptor); } void testReleaseClosedThrows() @@ -613,16 +613,16 @@ struct local_datagram_socket_test io_context ioc(Backend); local_datagram_socket sock(ioc); - bool caught = false; + std::error_code caught; try { (void)sock.release(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } void testAvailableClosedThrows() @@ -630,16 +630,16 @@ struct local_datagram_socket_test io_context ioc(Backend); local_datagram_socket sock(ioc); - bool caught = false; + std::error_code caught; try { (void)sock.available(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } void testAvailable() @@ -728,7 +728,8 @@ struct local_datagram_socket_test { io_context ioc(Backend); local_datagram_socket sock(ioc); - BOOST_TEST(sock.assign((native_handle_type)-1)); + BOOST_TEST(sock.assign((native_handle_type)-1) + == std::errc::bad_file_descriptor); BOOST_TEST(!sock.is_open()); } @@ -740,7 +741,8 @@ struct local_datagram_socket_test local_datagram_socket sock(ioc); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0); - BOOST_TEST(sock.assign((native_handle_type)fds[0])); + BOOST_TEST(sock.assign((native_handle_type)fds[0]) + == std::errc::wrong_protocol_type); BOOST_TEST(::fcntl(fds[0], F_GETFD) >= 0); BOOST_TEST(!sock.is_open()); ::close(fds[0]); @@ -762,58 +764,91 @@ struct local_datagram_socket_test ::close(fd); } - void testSendOnClosedThrows() + void testOptionErrors() { io_context ioc(Backend); - local_datagram_socket sock(ioc); - char const m[] = "x"; - bool caught_send = false; + // Closed socket: option access throws bad_file_descriptor + local_datagram_socket closed(ioc); + std::error_code set_ec, get_ec; try { - (void)sock.send(capy::const_buffer(m, 1)); + closed.set_option(socket_option::send_buffer_size(4096)); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught_send = true; + set_ec = e.code(); } - BOOST_TEST(caught_send); - - bool caught_send_to = false; + BOOST_TEST(set_ec == std::errc::bad_file_descriptor); try { - (void)sock.send_to( - capy::const_buffer(m, 1), local_endpoint("/tmp/x")); + (void)closed.get_option(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught_send_to = true; + get_ec = e.code(); } - BOOST_TEST(caught_send_to); + BOOST_TEST(get_ec == std::errc::bad_file_descriptor); - char buf[1]; - bool caught_recv = false; + // Open socket: a TCP-level option on an AF_UNIX socket fails + // with a genuine (platform-specific) error code + local_datagram_socket sock(ioc); + BOOST_TEST(!sock.open()); + bool threw = false; try { - (void)sock.recv(capy::mutable_buffer(buf, 1)); + sock.set_option(socket_option::no_delay(true)); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught_recv = true; + threw = bool(e.code()); } - BOOST_TEST(caught_recv); + BOOST_TEST(threw); - local_endpoint src; - bool caught_recv_from = false; + bool get_threw = false; try { - (void)sock.recv_from(capy::mutable_buffer(buf, 1), src); + (void)sock.get_option(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught_recv_from = true; + get_threw = bool(e.code()); } - BOOST_TEST(caught_recv_from); + BOOST_TEST(get_threw); + sock.close(); + } + + void testClosedOpsComplete() + { + // Datagram operations on a closed socket complete with + // bad_file_descriptor instead of throwing. + io_context ioc(Backend); + local_datagram_socket sock(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char const m[] = "x"; + char buf[1]; + local_endpoint src; + + auto [e1, n1] = co_await sock.send(capy::const_buffer(m, 1)); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + + auto [e2, n2] = co_await sock.send_to( + capy::const_buffer(m, 1), local_endpoint("/tmp/x")); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + + auto [e3, n3] = co_await sock.recv(capy::mutable_buffer(buf, 1)); + BOOST_TEST(e3 == std::errc::bad_file_descriptor); + + auto [e4, n4] = co_await sock.recv_from( + capy::mutable_buffer(buf, 1), src); + BOOST_TEST(e4 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } void testCancelPendingRecv() @@ -862,7 +897,7 @@ struct local_datagram_socket_test testEndpointsClosed(); testEndpointsBound(); testShutdown(); - testBindClosedThrows(); + testBindClosed(); testReleaseClosedThrows(); testAvailableClosedThrows(); testAvailable(); @@ -870,7 +905,8 @@ struct local_datagram_socket_test testAssignBadFdThrows(); testAssignRejectedFdStaysOpen(); testRelease(); - testSendOnClosedThrows(); + testClosedOpsComplete(); + testOptionErrors(); testCancelPendingRecv(); testSendRecvConnected(); testExplicitBind(); diff --git a/test/unit/local_endpoint.cpp b/test/unit/local_endpoint.cpp index 9bef4b679..ef5c64cf7 100644 --- a/test/unit/local_endpoint.cpp +++ b/test/unit/local_endpoint.cpp @@ -105,16 +105,23 @@ struct local_endpoint_test std::string too_long(local_endpoint::max_path_length + 1, 'x'); BOOST_TEST_THROWS(local_endpoint(too_long), std::system_error); - std::error_code ec; - local_endpoint ep(too_long, ec); - BOOST_TEST(bool(ec)); - BOOST_TEST(ep.empty()); + std::error_code caught; + try + { + local_endpoint ep(too_long); + } + catch (std::system_error const& e) + { + caught = e.code(); + } + BOOST_TEST(caught == std::errc::filename_too_long); + + // The documented pre-check pattern for runtime-derived paths + BOOST_TEST(too_long.size() > local_endpoint::max_path_length); // The maximum-length path is accepted. std::string at_max(local_endpoint::max_path_length, 'x'); - std::error_code ec2; - local_endpoint ok(at_max, ec2); - BOOST_TEST(!ec2); + local_endpoint ok(at_max); BOOST_TEST_EQ(ok.path().size(), local_endpoint::max_path_length); } diff --git a/test/unit/local_stream_socket.cpp b/test/unit/local_stream_socket.cpp index ad523d947..6c6373caa 100644 --- a/test/unit/local_stream_socket.cpp +++ b/test/unit/local_stream_socket.cpp @@ -511,7 +511,8 @@ struct local_stream_socket_test int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0); - BOOST_TEST(s1.assign(static_cast(fds[0]))); + BOOST_TEST(s1.assign(static_cast(fds[0])) + == std::errc::wrong_protocol_type); BOOST_TEST(::fcntl(fds[0], F_GETFD) >= 0); // caller keeps it BOOST_TEST(s1.is_open()); @@ -548,7 +549,8 @@ struct local_stream_socket_test { io_context ioc(Backend); local_stream_socket sock(ioc); - BOOST_TEST(sock.assign((native_handle_type)-1)); + BOOST_TEST(sock.assign((native_handle_type)-1) + == std::errc::bad_file_descriptor); BOOST_TEST(!sock.is_open()); } @@ -560,7 +562,8 @@ struct local_stream_socket_test local_stream_socket sock(ioc); int fds[2]; BOOST_TEST(::socketpair(AF_UNIX, SOCK_DGRAM, 0, fds) == 0); - BOOST_TEST(sock.assign((native_handle_type)fds[0])); + BOOST_TEST(sock.assign((native_handle_type)fds[0]) + == std::errc::wrong_protocol_type); // fd still valid: fcntl succeeds BOOST_TEST(::fcntl(fds[0], F_GETFD) >= 0); BOOST_TEST(!sock.is_open()); @@ -597,16 +600,16 @@ struct local_stream_socket_test io_context ioc(Backend); local_stream_socket sock(ioc); - bool caught = false; + std::error_code caught; try { (void)sock.release(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } void testAvailableClosedThrows() @@ -614,16 +617,16 @@ struct local_stream_socket_test io_context ioc(Backend); local_stream_socket sock(ioc); - bool caught = false; + std::error_code caught; try { (void)sock.available(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } void testConnectToNonexistent() @@ -902,12 +905,17 @@ struct local_stream_socket_test } local_stream_socket closed(ioc); - BOOST_TEST_THROWS( - closed.set_option(socket_option::send_buffer_size(4096)), - std::logic_error); - BOOST_TEST_THROWS( - (void)closed.get_option(), - std::logic_error); + auto expect_bad_fd = [](auto fn) { + std::error_code caught; + try { fn(); } + catch (std::system_error const& e) { caught = e.code(); } + BOOST_TEST(caught == std::errc::bad_file_descriptor); + }; + expect_bad_fd([&] { + closed.set_option(socket_option::send_buffer_size(4096)); }); + expect_bad_fd([&] { + std::ignore = + closed.get_option(); }); } void testAcceptorOptions() @@ -933,12 +941,17 @@ struct local_stream_socket_test acc.close(); local_stream_acceptor closed(ioc); - BOOST_TEST_THROWS( - closed.set_option(socket_option::reuse_address(true)), - std::logic_error); - BOOST_TEST_THROWS( - (void)closed.get_option(), - std::logic_error); + auto expect_bad_fd = [](auto fn) { + std::error_code caught; + try { fn(); } + catch (std::system_error const& e) { caught = e.code(); } + BOOST_TEST(caught == std::errc::bad_file_descriptor); + }; + expect_bad_fd([&] { + closed.set_option(socket_option::reuse_address(true)); }); + expect_bad_fd([&] { + std::ignore = + closed.get_option(); }); } // Acceptor wait(wait_type::write) fails uniformly on every @@ -1168,46 +1181,114 @@ struct local_stream_socket_test { io_context ioc(Backend); local_stream_acceptor acc(ioc); - // NOLINTNEXTLINE(bugprone-unused-return-value) - BOOST_TEST_THROWS(acc.bind(local_endpoint("/tmp/never")), - std::logic_error); + BOOST_TEST(acc.bind(local_endpoint("/tmp/never")) + == std::errc::bad_file_descriptor); } void testAcceptorListenClosedThrows() { io_context ioc(Backend); local_stream_acceptor acc(ioc); - // NOLINTNEXTLINE(bugprone-unused-return-value) - BOOST_TEST_THROWS(acc.listen(), std::logic_error); + BOOST_TEST(acc.listen() == std::errc::bad_file_descriptor); } - void testAcceptorAcceptClosedThrows() + void testOptionFailureThrows() { io_context ioc(Backend); - local_stream_acceptor acc(ioc); - local_stream_socket peer(ioc); - bool caught_peer = false; + // A TCP-level option on an AF_UNIX socket fails with a genuine + // error surfaced as system_error + local_stream_socket s1(ioc), s2(ioc); + if (auto ec = connect_pair(s1, s2)) + throw std::system_error(ec, "connect_pair"); + bool threw = false; + try + { + s1.set_option(socket_option::no_delay(true)); + } + catch (std::system_error const& e) + { + threw = bool(e.code()); + } + BOOST_TEST(threw); + +#if !defined(__APPLE__) + // Darwin's getsockopt accepts TCP_NODELAY on AF_UNIX sockets, + // so the get failure is only asserted where the kernel rejects it. + bool sock_get_threw = false; try { - (void)acc.accept(peer); + (void)s1.get_option(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught_peer = true; + sock_get_threw = bool(e.code()); } - BOOST_TEST(caught_peer); + BOOST_TEST(sock_get_threw); +#endif - bool caught_move = false; + // Same through the acceptor's option surface + local_stream_acceptor acc(ioc); + BOOST_TEST(!acc.open()); + bool acc_threw = false; + try + { + acc.set_option(socket_option::no_delay(true)); + } + catch (std::system_error const& e) + { + acc_threw = bool(e.code()); + } + BOOST_TEST(acc_threw); + bool get_threw = false; try { - (void)acc.accept(); + (void)acc.get_option(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught_move = true; + get_threw = bool(e.code()); } - BOOST_TEST(caught_move); + BOOST_TEST(get_threw); + acc.close(); + } + + void testAcceptorClosedWaitCompletes() + { + io_context ioc(Backend); + local_stream_acceptor acc(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + auto [ec] = co_await acc.wait(wait_type::read); + BOOST_TEST(ec == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + + void testAcceptorAcceptClosedThrows() + { + io_context ioc(Backend); + local_stream_acceptor acc(ioc); + local_stream_socket peer(ioc); + + // Accepts on a closed acceptor complete with + // bad_file_descriptor instead of throwing. + bool done = false; + auto task = [&]() -> capy::task<> { + auto [e1] = co_await acc.accept(peer); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + + auto [e2, moved] = co_await acc.accept(); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } void testAcceptorReleaseClosedThrows() @@ -1220,7 +1301,7 @@ struct local_stream_socket_test { (void)acc.release(); } - catch (std::logic_error const&) + catch (std::system_error const&) { caught = true; } @@ -1465,19 +1546,15 @@ struct local_stream_socket_test BOOST_TEST(caught); } - void testEndpointTooLongNoThrow() + void testEndpointTooLongPrecheck() { + // Runtime-derived paths use the documented pre-check + // instead of a non-throwing parse. std::string too_long(local_endpoint::max_path_length + 1, 'x'); - std::error_code ec; - local_endpoint ep(too_long, ec); - BOOST_TEST(!!ec); - BOOST_TEST_EQ(ep.empty(), true); - - // Successful construction with the no-throw overload clears ec. - std::error_code ec2; - local_endpoint ep2("/tmp/ok", ec2); - BOOST_TEST_EQ(!ec2, true); - BOOST_TEST_EQ(ep2.path(), std::string_view("/tmp/ok")); + BOOST_TEST(too_long.size() > local_endpoint::max_path_length); + + local_endpoint ok("/tmp/ok"); + BOOST_TEST_EQ(ok.path(), std::string_view("/tmp/ok")); } void testEndpointMaxPathLength() @@ -1565,6 +1642,8 @@ struct local_stream_socket_test testAcceptorOnClosedNoOp(); testAcceptorBindClosedThrows(); testAcceptorListenClosedThrows(); + testOptionFailureThrows(); + testAcceptorClosedWaitCompletes(); testAcceptorAcceptClosedThrows(); testAcceptorReleaseClosedThrows(); testAcceptorReleaseOpen(); @@ -1574,7 +1653,7 @@ struct local_stream_socket_test testAcceptorAssignAfterRelease(); testAcceptorLocalEndpoint(); testEndpointTooLongThrows(); - testEndpointTooLongNoThrow(); + testEndpointTooLongPrecheck(); testEndpointMaxPathLength(); testAbstractEndpoint(); #ifdef _WIN32 diff --git a/test/unit/native/native_local_datagram_socket.cpp b/test/unit/native/native_local_datagram_socket.cpp index df8239445..74a7cdf89 100644 --- a/test/unit/native/native_local_datagram_socket.cpp +++ b/test/unit/native/native_local_datagram_socket.cpp @@ -285,58 +285,37 @@ struct native_local_datagram_socket_test BOOST_TEST(!wait_ec); } - void testSendOnClosedThrows() + void testClosedOpsComplete() { + // Datagram operations on a closed socket complete with + // bad_file_descriptor instead of throwing. io_context ioc(Backend); native_local_datagram_socket s(ioc); - char const m[] = "x"; - - bool caught_send = false; - try - { - (void)s.send(capy::const_buffer(m, 1)); - } - catch (std::logic_error const&) - { - caught_send = true; - } - BOOST_TEST(caught_send); - - bool caught_send_to = false; - try - { - (void)s.send_to( + + bool done = false; + auto task = [&]() -> capy::task<> { + char const m[] = "x"; + char buf[1]; + local_endpoint src; + + auto [e1, n1] = co_await s.send(capy::const_buffer(m, 1)); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + + auto [e2, n2] = co_await s.send_to( capy::const_buffer(m, 1), local_endpoint("/tmp/x")); - } - catch (std::logic_error const&) - { - caught_send_to = true; - } - BOOST_TEST(caught_send_to); - - char buf[1]; - bool caught_recv = false; - try - { - (void)s.recv(capy::mutable_buffer(buf, 1)); - } - catch (std::logic_error const&) - { - caught_recv = true; - } - BOOST_TEST(caught_recv); - - local_endpoint src; - bool caught_recv_from = false; - try - { - (void)s.recv_from(capy::mutable_buffer(buf, 1), src); - } - catch (std::logic_error const&) - { - caught_recv_from = true; - } - BOOST_TEST(caught_recv_from); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + + auto [e3, n3] = co_await s.recv(capy::mutable_buffer(buf, 1)); + BOOST_TEST(e3 == std::errc::bad_file_descriptor); + + auto [e4, n4] = co_await s.recv_from( + capy::mutable_buffer(buf, 1), src); + BOOST_TEST(e4 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } void testConnectAutoOpens() @@ -381,7 +360,7 @@ struct native_local_datagram_socket_test testSendRecvConnected(); testVirtualDispatchFallback(); testWait(); - testSendOnClosedThrows(); + testClosedOpsComplete(); testConnectAutoOpens(); } }; diff --git a/test/unit/native/native_local_stream_socket.cpp b/test/unit/native/native_local_stream_socket.cpp index 7a38a27b2..fe0205827 100644 --- a/test/unit/native/native_local_stream_socket.cpp +++ b/test/unit/native/native_local_stream_socket.cpp @@ -91,7 +91,7 @@ struct native_local_stream_socket_test { io_context ioc(Backend); native_local_stream_socket s(ioc); - s.open(); + BOOST_TEST(!s.open()); BOOST_TEST(s.is_open()); s.close(); BOOST_TEST_EQ(s.is_open(), false); @@ -101,7 +101,7 @@ struct native_local_stream_socket_test { io_context ioc(Backend); native_local_stream_socket s(ioc); - s.open(); + BOOST_TEST(!s.open()); local_stream_socket& base = s; BOOST_TEST(base.is_open()); } @@ -113,7 +113,7 @@ struct native_local_stream_socket_test auto path = tmp.path(); native_local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(ec, std::error_code{}); ec = acc.listen(); @@ -160,7 +160,7 @@ struct native_local_stream_socket_test auto path = tmp.path(); native_local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(ec, std::error_code{}); ec = acc.listen(); @@ -206,7 +206,7 @@ struct native_local_stream_socket_test auto path = tmp.path(); native_local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto ec = acc.bind(local_endpoint(path)); BOOST_TEST_EQ(ec, std::error_code{}); ec = acc.listen(); @@ -258,7 +258,7 @@ struct native_local_stream_socket_test auto path = tmp.path(); native_local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto bec = acc.bind(local_endpoint(path)); BOOST_TEST(!bec); auto lec = acc.listen(); @@ -305,7 +305,7 @@ struct native_local_stream_socket_test auto path = tmp.path(); native_local_stream_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); auto bec = acc.bind(local_endpoint(path)); BOOST_TEST(!bec); auto lec = acc.listen(); @@ -333,33 +333,44 @@ struct native_local_stream_socket_test BOOST_TEST(!wait_ec); } - void testAcceptOnClosedThrows() + void testMovedFromValueAcceptThrows() { io_context ioc(Backend); - native_local_stream_acceptor acc(ioc); - native_local_stream_socket peer(ioc); + native_local_stream_acceptor a(ioc); + native_local_stream_acceptor b(std::move(a)); - bool caught_peer = false; + bool threw = false; try { - (void)acc.accept(peer); + (void)a.accept(); } catch (std::logic_error const&) { - caught_peer = true; + threw = true; } - BOOST_TEST(caught_peer); + BOOST_TEST(threw); + } - bool caught_move = false; - try - { - (void)acc.accept(); - } - catch (std::logic_error const&) - { - caught_move = true; - } - BOOST_TEST(caught_move); + void testAcceptOnClosedCompletes() + { + // Accepts on a closed acceptor complete with + // bad_file_descriptor instead of throwing. + io_context ioc(Backend); + native_local_stream_acceptor acc(ioc); + native_local_stream_socket peer(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + auto [e1] = co_await acc.accept(peer); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + + auto [e2, moved] = co_await acc.accept(); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } void testConnectAutoOpens() @@ -400,7 +411,8 @@ struct native_local_stream_socket_test testVirtualDispatchFallback(); testSocketWait(); testAcceptorWait(); - testAcceptOnClosedThrows(); + testAcceptOnClosedCompletes(); + testMovedFromValueAcceptThrows(); testConnectAutoOpens(); } }; diff --git a/test/unit/native/native_tcp_acceptor.cpp b/test/unit/native/native_tcp_acceptor.cpp index 9da42b94a..31f21c941 100644 --- a/test/unit/native/native_tcp_acceptor.cpp +++ b/test/unit/native/native_tcp_acceptor.cpp @@ -57,7 +57,7 @@ struct native_tcp_acceptor_test { io_context ctx(Backend); native_tcp_acceptor a1(ctx); - a1.open(); + BOOST_TEST(!a1.open()); a1.set_option(native_socket_option::reuse_address(true)); auto ec = a1.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -73,7 +73,7 @@ struct native_tcp_acceptor_test { io_context ctx(Backend); native_tcp_acceptor na(ctx); - na.open(); + BOOST_TEST(!na.open()); na.set_option(native_socket_option::reuse_address(true)); auto ec = na.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!ec); @@ -92,7 +92,7 @@ struct native_tcp_acceptor_test auto ex = ioc.get_executor(); native_tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(native_socket_option::reuse_address(true)); auto bec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -101,7 +101,7 @@ struct native_tcp_acceptor_test auto port = acc.local_endpoint().port(); native_tcp_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); std::error_code wait_ec; bool wait_done = false; @@ -133,7 +133,7 @@ struct native_tcp_acceptor_test auto ex = ioc.get_executor(); native_tcp_acceptor acc(ioc); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(native_socket_option::reuse_address(true)); auto bec = acc.bind(endpoint(ipv4_address::loopback(), 0)); BOOST_TEST(!bec); @@ -142,7 +142,7 @@ struct native_tcp_acceptor_test auto port = acc.local_endpoint().port(); native_tcp_socket client(ioc); - client.open(); + BOOST_TEST(!client.open()); std::error_code accept_ec; bool accept_done = false; @@ -175,7 +175,7 @@ struct native_tcp_acceptor_test { io_context ctx(Backend); native_tcp_acceptor acc(ctx); - acc.open(); + BOOST_TEST(!acc.open()); acc.set_option(native_socket_option::reuse_address(true)); acc.set_option(native_socket_option::reuse_port(true)); @@ -187,6 +187,46 @@ struct native_tcp_acceptor_test } #endif + void testClosedAcceptCompletes() + { + // Accepts on a closed (not moved-from) acceptor complete + // with bad_file_descriptor instead of dispatching. + io_context ioc(Backend); + native_tcp_acceptor acc(ioc); + tcp_socket peer(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + auto [e1] = co_await acc.accept(peer); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + + auto [e2, moved] = co_await acc.accept(); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + + void testMovedFromValueAcceptThrows() + { + io_context ioc(Backend); + native_tcp_acceptor a(ioc); + native_tcp_acceptor b(std::move(a)); + + bool threw = false; + try + { + (void)a.accept(); + } + catch (std::logic_error const&) + { + threw = true; + } + BOOST_TEST(threw); + } + void run() { testAcceptorConstruct(); @@ -196,6 +236,8 @@ struct native_tcp_acceptor_test testNativeAcceptReturning(); #ifdef SO_REUSEPORT testNativeReusePort(); + testMovedFromValueAcceptThrows(); + testClosedAcceptCompletes(); #endif } }; diff --git a/test/unit/native/native_tcp_socket.cpp b/test/unit/native/native_tcp_socket.cpp index f0687968a..241b0d8a9 100644 --- a/test/unit/native/native_tcp_socket.cpp +++ b/test/unit/native/native_tcp_socket.cpp @@ -155,8 +155,28 @@ struct native_tcp_socket_test s.close(); } + void testClosedConnectCompletes() + { + // The native socket does not auto-open: connect on a closed + // socket completes with bad_file_descriptor. + io_context ioc(Backend); + native_tcp_socket s(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + auto [ec] = co_await s.connect( + endpoint(ipv4_address::loopback(), 1)); + BOOST_TEST(ec == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + void run() { + testClosedConnectCompletes(); testSocketConstruct(); testSocketMoveConstruct(); testSocketPolymorphicSlice(); diff --git a/test/unit/native/native_udp_socket.cpp b/test/unit/native/native_udp_socket.cpp index 76d9b90e5..87437db8d 100644 --- a/test/unit/native/native_udp_socket.cpp +++ b/test/unit/native/native_udp_socket.cpp @@ -543,8 +543,44 @@ struct native_udp_socket_test BOOST_TEST_EQ(lg2.timeout(), 3); } + void testClosedOpsComplete() + { + // Datagram operations on a closed socket complete with + // bad_file_descriptor instead of dispatching. + io_context ioc(Backend); + native_udp_socket s(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8]; + char const m[] = "x"; + endpoint src; + + auto [e1, n1] = co_await s.send_to( + capy::const_buffer(m, 1), + endpoint(ipv4_address::loopback(), 1)); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + + auto [e2, n2] = co_await s.recv_from( + capy::mutable_buffer(buf, sizeof(buf)), src); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + + auto [e3, n3] = co_await s.send(capy::const_buffer(m, 1)); + BOOST_TEST(e3 == std::errc::bad_file_descriptor); + + auto [e4, n4] = co_await s.recv( + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(e4 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + void run() { + testClosedOpsComplete(); testConstruct(); testMoveConstruct(); testPolymorphicSlice(); diff --git a/test/unit/openssl_engine.cpp b/test/unit/openssl_engine.cpp index 9b1b6ce10..67a67fc40 100644 --- a/test/unit/openssl_engine.cpp +++ b/test/unit/openssl_engine.cpp @@ -34,6 +34,7 @@ #include #include +#include namespace boost::corosio { @@ -622,7 +623,7 @@ struct openssl_engine_test return std::string(4096, 'x'); }); // The oversized password may fail here or latch for init(). - (void)ctx.use_private_key( + std::ignore = ctx.use_private_key( test::encrypted_server_key_pem, tls_file_format::pem); ossl_engine eng; @@ -638,8 +639,8 @@ struct openssl_engine_test tls_context ctx; // Whether the garbage surfaces here or at init() is // backend-dependent; the init failure below is what matters. - (void)ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); - (void)ctx.use_private_key(test::server_key_pem, tls_file_format::pem); + std::ignore = ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); + std::ignore = ctx.use_private_key(test::server_key_pem, tls_file_format::pem); ossl_engine eng; BOOST_TEST(!eng.init(ctx)); diff --git a/test/unit/random_access_file.cpp b/test/unit/random_access_file.cpp index 9e71c1290..5952baa85 100644 --- a/test/unit/random_access_file.cpp +++ b/test/unit/random_access_file.cpp @@ -172,7 +172,8 @@ struct random_access_file_test #if BOOST_COROSIO_POSIX // Larger than off_t can represent: rejected with EOVERFLOW. - BOOST_TEST(f.resize((std::numeric_limits::max)())); + BOOST_TEST(f.resize((std::numeric_limits::max)()) + == std::errc::value_too_large); #endif } @@ -668,6 +669,14 @@ struct random_access_file_test testRelease(); testAssign(); testClosedFileErrors(); + testClosedAtOpsComplete(); +#if BOOST_COROSIO_POSIX + testSyncOnPipeFails(); + testHugeOffsetFails(); +#endif + testWrongDirectionIoFails(); + testResizeReadOnlyFails(); + testAssignOverOpenAdopts(); testOpenSyncAllOnWrite(); testOpenExclusiveExistingFails(); testOpenExclusiveNewFile(); @@ -679,18 +688,157 @@ struct random_access_file_test // Operations on closed file + void testClosedAtOpsComplete() + { + // Offset I/O on a closed file completes with + // bad_file_descriptor instead of throwing. + io_context ioc(Backend); + random_access_file f(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8]; + auto [e1, n1] = co_await f.read_some_at( + 0, capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(n1, 0u); + + auto [e2, n2] = co_await f.write_some_at( + 0, capy::const_buffer("x", 1)); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(n2, 0u); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + + void testResizeReadOnlyFails() + { + // ftruncate on a read-only descriptor reports a genuine + // runtime error through the returned code. + temp_file tmp("raf_resize_ro_", "0123456789"); + io_context ioc(Backend); + random_access_file f(ioc); + + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); + BOOST_TEST(f.resize(4)); + BOOST_TEST_EQ(f.size(), 10u); + } + + void testAssignOverOpenAdopts() + { + temp_file tmp1("raf_assign_a_", "first"); + temp_file tmp2("raf_assign_b_", "second"); + io_context ioc(Backend); + random_access_file f(ioc); + + BOOST_TEST(!f.open(tmp1.path, file_base::read_only)); + +#if BOOST_COROSIO_HAS_IOCP + HANDLE h = ::CreateFileW( + tmp2.path.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + nullptr); + BOOST_TEST(h != INVALID_HANDLE_VALUE); + auto raw = reinterpret_cast(h); +#else + int fd = ::open(tmp2.path.c_str(), O_RDONLY); + BOOST_TEST(fd >= 0); + auto raw = static_cast(fd); +#endif + // Adopting over an open file closes the previous handle first + BOOST_TEST(!f.assign(raw)); + BOOST_TEST(f.is_open()); + BOOST_TEST_EQ(f.size(), 6u); + } + +#if BOOST_COROSIO_POSIX + void testSyncOnPipeFails() + { + io_context ioc(Backend); + random_access_file f(ioc); + + int fds[2]; + BOOST_TEST(::pipe(fds) == 0); + BOOST_TEST(!f.assign(static_cast(fds[1]))); + BOOST_TEST(f.sync_data()); + BOOST_TEST(f.sync_all()); + f.close(); + ::close(fds[0]); + } +#endif + + void testWrongDirectionIoFails() + { + temp_file tmp("raf_wrongdir_", "data"); + io_context ioc(Backend); + random_access_file wo(ioc), ro(ioc); + + BOOST_TEST(!wo.open(tmp.path, file_base::write_only)); + BOOST_TEST(!ro.open(tmp.path, file_base::read_only)); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8]; + auto [rec, rn] = co_await wo.read_some_at( + 0, capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(bool(rec)); + + auto [wec, wn] = co_await ro.write_some_at( + 0, capy::const_buffer("x", 1)); + BOOST_TEST(bool(wec)); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + +#if BOOST_COROSIO_POSIX + void testHugeOffsetFails() + { + // An offset beyond off_t's range is rejected through the + // async completion. + temp_file tmp("raf_hugeoff_", "data"); + io_context ioc(Backend); + random_access_file f(ioc); + + BOOST_TEST(!f.open(tmp.path, file_base::read_write)); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8]; + // Past off_t's range on the pool backend, and a negative + // offset on io_uring (whose ~0 means "current position"). + auto [ec, n] = co_await f.read_some_at( + std::uint64_t(1) << 63, + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(bool(ec)); + BOOST_TEST_EQ(n, 0u); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } +#endif + void testClosedFileErrors() { io_context ioc(Backend); random_access_file f(ioc); BOOST_TEST(!f.is_open()); - // Exceptional-only operations throw on a closed file + // Exceptional-only operations throw bad_file_descriptor auto expect_throw = [](auto fn) { - bool threw = false; + std::error_code caught; try { fn(); } - catch (std::system_error const&) { threw = true; } - BOOST_TEST(threw); + catch (std::system_error const& e) { caught = e.code(); } + BOOST_TEST(caught == std::errc::bad_file_descriptor); }; expect_throw([&] { f.size(); }); diff --git a/test/unit/reactor_paths.cpp b/test/unit/reactor_paths.cpp index f66f2bb4f..0efd13632 100644 --- a/test/unit/reactor_paths.cpp +++ b/test/unit/reactor_paths.cpp @@ -1356,7 +1356,8 @@ struct reactor_paths_test BOOST_TEST(tcp_fd >= 0); local_stream_socket sock(ioc); - BOOST_TEST(sock.assign(tcp_fd)); + BOOST_TEST(sock.assign(tcp_fd) + == std::errc::address_family_not_supported); // rejection leaves ownership with the caller if (!sock.is_open()) ::close(tcp_fd); @@ -1372,7 +1373,8 @@ struct reactor_paths_test BOOST_TEST(fd >= 0); local_datagram_socket sock(ioc); - BOOST_TEST(sock.assign(fd)); + BOOST_TEST(sock.assign(fd) + == std::errc::wrong_protocol_type); if (!sock.is_open()) ::close(fd); } diff --git a/test/unit/signal_set.cpp b/test/unit/signal_set.cpp index 82db595e0..0971b80a5 100644 --- a/test/unit/signal_set.cpp +++ b/test/unit/signal_set.cpp @@ -151,8 +151,7 @@ struct signal_set_test // A signal number above the service's table size returns // invalid_argument. - auto result = s.add(100000); - BOOST_TEST(!!result); + BOOST_TEST(s.add(100000) == std::errc::invalid_argument); } void testRemoveInvalidSignal() @@ -160,8 +159,7 @@ struct signal_set_test io_context ioc(Backend); signal_set s(ioc); - auto result = s.remove(-1); - BOOST_TEST(!!result); + BOOST_TEST(s.remove(-1) == std::errc::invalid_argument); } void testRemove() diff --git a/test/unit/socket_option.cpp b/test/unit/socket_option.cpp index 69596143c..4b028fe52 100644 --- a/test/unit/socket_option.cpp +++ b/test/unit/socket_option.cpp @@ -155,27 +155,27 @@ struct socket_option_test io_context ioc(Backend); tcp_socket sock(ioc); - bool set_threw = false; + std::error_code set_caught; try { sock.set_option(socket_option::no_delay(true)); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - set_threw = true; + set_caught = e.code(); } - BOOST_TEST(set_threw); + BOOST_TEST(set_caught == std::errc::bad_file_descriptor); - bool get_threw = false; + std::error_code get_caught; try { (void)sock.get_option(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - get_threw = true; + get_caught = e.code(); } - BOOST_TEST(get_threw); + BOOST_TEST(get_caught == std::errc::bad_file_descriptor); } // An option the protocol does not support reports a system error. diff --git a/test/unit/stream_file.cpp b/test/unit/stream_file.cpp index 6fbababbc..b4a988cb9 100644 --- a/test/unit/stream_file.cpp +++ b/test/unit/stream_file.cpp @@ -237,7 +237,8 @@ struct stream_file_test #if BOOST_COROSIO_POSIX // Larger than off_t can represent: rejected with EOVERFLOW. - BOOST_TEST(f.resize((std::numeric_limits::max)())); + BOOST_TEST(f.resize((std::numeric_limits::max)()) + == std::errc::value_too_large); #endif } @@ -754,18 +755,109 @@ struct stream_file_test // Operations on closed file + void testResizeReadOnlyFails() + { + // ftruncate on a read-only descriptor reports a genuine + // runtime error through the returned code. + temp_file tmp("sf_resize_ro_", "0123456789"); + io_context ioc(Backend); + stream_file f(ioc); + + BOOST_TEST(!f.open(tmp.path, file_base::read_only)); + BOOST_TEST(f.resize(4)); + BOOST_TEST_EQ(f.size(), 10u); + } + + void testAssignOverOpenAdopts() + { + temp_file tmp1("sf_assign_a_", "first"); + temp_file tmp2("sf_assign_b_", "second"); + io_context ioc(Backend); + stream_file f(ioc); + + BOOST_TEST(!f.open(tmp1.path, file_base::read_only)); + +#if BOOST_COROSIO_HAS_IOCP + HANDLE h = ::CreateFileW( + tmp2.path.c_str(), GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED + | FILE_FLAG_SEQUENTIAL_SCAN, + nullptr); + BOOST_TEST(h != INVALID_HANDLE_VALUE); + auto raw = reinterpret_cast(h); +#else + int fd = ::open(tmp2.path.c_str(), O_RDONLY); + BOOST_TEST(fd >= 0); + auto raw = static_cast(fd); +#endif + // Adopting over an open file closes the previous handle first + BOOST_TEST(!f.assign(raw)); + BOOST_TEST(f.is_open()); + BOOST_TEST_EQ(f.size(), 6u); + } + +#if BOOST_COROSIO_POSIX + void testSyncOnPipeFails() + { + // fsync/fdatasync on a pipe reports a genuine runtime error + // through the returned code. + io_context ioc(Backend); + stream_file f(ioc); + + int fds[2]; + BOOST_TEST(::pipe(fds) == 0); + BOOST_TEST(!f.assign(static_cast(fds[1]))); + BOOST_TEST(f.sync_data()); + BOOST_TEST(f.sync_all()); + f.close(); + ::close(fds[0]); + } +#endif + + void testWrongDirectionIoFails() + { + // Reading a write-only file (and writing a read-only one) + // completes with an error from the underlying I/O. + temp_file tmp("sf_wrongdir_", "data"); + io_context ioc(Backend); + stream_file wo(ioc), ro(ioc); + + BOOST_TEST(!wo.open(tmp.path, file_base::write_only)); + BOOST_TEST(!ro.open(tmp.path, file_base::read_only)); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8]; + auto [rec, rn] = co_await wo.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(bool(rec)); + BOOST_TEST_EQ(rn, 0u); + + auto [wec, wn] = co_await ro.write_some( + capy::const_buffer("x", 1)); + BOOST_TEST(bool(wec)); + BOOST_TEST_EQ(wn, 0u); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + void testClosedFileErrors() { io_context ioc(Backend); stream_file f(ioc); BOOST_TEST(!f.is_open()); - // Exceptional-only operations throw on a closed file + // Exceptional-only operations throw bad_file_descriptor auto expect_throw = [](auto fn) { - bool threw = false; + std::error_code caught; try { fn(); } - catch (std::system_error const&) { threw = true; } - BOOST_TEST(threw); + catch (std::system_error const& e) { caught = e.code(); } + BOOST_TEST(caught == std::errc::bad_file_descriptor); }; expect_throw([&] { f.size(); }); @@ -789,22 +881,30 @@ struct stream_file_test BOOST_TEST(!f.open(tmp.path, file_base::read_only)); + auto expect_negative = [](std::error_code ec) { +#if BOOST_COROSIO_POSIX + BOOST_TEST(ec == std::errc::invalid_argument); +#else + BOOST_TEST(bool(ec)); +#endif + }; + // seek_set with negative offset { auto [ec, pos] = f.seek(-1, file_base::seek_set); - BOOST_TEST(ec); + expect_negative(ec); } // seek_end past beginning { auto [ec, pos] = f.seek(-100, file_base::seek_end); - BOOST_TEST(ec); + expect_negative(ec); } // seek_cur past beginning { auto [ec, pos] = f.seek(-100, file_base::seek_cur); - BOOST_TEST(ec); + expect_negative(ec); } } @@ -848,6 +948,12 @@ struct stream_file_test testRelease(); testAssign(); testClosedFileErrors(); + testResizeReadOnlyFails(); +#if BOOST_COROSIO_POSIX + testSyncOnPipeFails(); +#endif + testWrongDirectionIoFails(); + testAssignOverOpenAdopts(); testSeekNegative(); testCancelWithStoppedToken(); } diff --git a/test/unit/tcp_acceptor.cpp b/test/unit/tcp_acceptor.cpp index 83d591676..f75b89d70 100644 --- a/test/unit/tcp_acceptor.cpp +++ b/test/unit/tcp_acceptor.cpp @@ -208,12 +208,8 @@ struct tcp_acceptor_test acc.close(); tcp_acceptor closed(ioc); - BOOST_TEST_THROWS( - closed.set_option(socket_option::reuse_address(true)), - std::logic_error); - BOOST_TEST_THROWS( - (void)closed.get_option(), - std::logic_error); + BOOST_TEST_THROWS(closed.set_option(socket_option::reuse_address(true)), std::system_error); + BOOST_TEST_THROWS((void)closed.get_option(), std::system_error); } void testMoveConstruct() @@ -806,22 +802,13 @@ struct tcp_acceptor_test acc.close(); } - void testBindClosedAcceptorThrows() + void testBindClosedAcceptor() { io_context ioc(Backend); tcp_acceptor acc(ioc); - bool caught = false; - try - { - auto ec = acc.bind(endpoint(ipv4_address::loopback(), 0)); - (void)ec; - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); + BOOST_TEST(acc.bind(endpoint(ipv4_address::loopback(), 0)) + == std::errc::bad_file_descriptor); } void testBindAddressInUse() @@ -838,7 +825,13 @@ struct tcp_acceptor_test tcp_acceptor acc2(ioc); BOOST_TEST(!acc2.open()); ec = acc2.bind(endpoint(ipv4_address::loopback(), port)); - BOOST_TEST(ec); +#if BOOST_COROSIO_HAS_IOCP + // MinGW's libstdc++ lacks the WSA-to-generic condition + // mapping, so the raw code is pinned on Windows. + BOOST_TEST(ec.value() == WSAEADDRINUSE); +#else + BOOST_TEST(ec == std::errc::address_in_use); +#endif acc1.close(); acc2.close(); @@ -853,28 +846,21 @@ struct tcp_acceptor_test // Bind to an address not assigned to any local interface auto ec = acc.bind(endpoint(ipv4_address("1.2.3.4"), 0)); - BOOST_TEST(ec); +#if BOOST_COROSIO_HAS_IOCP + BOOST_TEST(ec.value() == WSAEADDRNOTAVAIL); +#else + BOOST_TEST(ec == std::errc::address_not_available); +#endif acc.close(); } - void testListenClosedThrows() + void testListenClosed() { - // listen() on a closed acceptor throws std::logic_error. io_context ioc(Backend); tcp_acceptor acc(ioc); - bool caught = false; - try - { - auto ec = acc.listen(); - (void)ec; - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); + BOOST_TEST(acc.listen() == std::errc::bad_file_descriptor); } void testClosedAcceptorAccessors() @@ -896,30 +882,108 @@ struct tcp_acceptor_test BOOST_TEST_EQ(acc.is_open(), false); } - // accept()/wait() on a closed acceptor must throw rather than - // initiate an operation on an invalid handle. - void testClosedAcceptorOpsThrow() + // accept()/wait() on a closed acceptor complete with + // bad_file_descriptor instead of initiating on an invalid handle. + void testOptionFailureThrows() { +#if !BOOST_COROSIO_HAS_IOCP + // The IOCP acceptor is created dual-stack, so IPV6_V6ONLY is + // a valid option there even for a v4 open; the rejection is + // only asserted where the listener is genuinely AF_INET. io_context ioc(Backend); tcp_acceptor acc(ioc); - tcp_socket peer(ioc); + BOOST_TEST(!acc.open(tcp::v4())); - auto expect_throw = [](auto fn) { - bool threw = false; - try - { - fn(); - } - catch (std::logic_error const&) - { - threw = true; - } - BOOST_TEST(threw); + bool set_threw = false; + try + { + acc.set_option(socket_option::v6_only(true)); + } + catch (std::system_error const& e) + { + set_threw = bool(e.code()); + } + BOOST_TEST(set_threw); + + bool get_threw = false; + try + { + (void)acc.get_option(); + } + catch (std::system_error const& e) + { + get_threw = bool(e.code()); + } + BOOST_TEST(get_threw); + acc.close(); +#endif + } + + void testConvenienceCtorBindFailureThrows() + { + io_context ioc(Backend); + + // Occupy a loopback port without SO_REUSEADDR conflicts + tcp_acceptor holder(ioc); + BOOST_TEST(!holder.open()); + BOOST_TEST(!holder.bind(endpoint(ipv4_address::loopback(), 0))); + BOOST_TEST(!holder.listen()); + auto ep = holder.local_endpoint(); + + // The convenience constructor surfaces the bind failure by + // throwing system_error + std::error_code caught; + try + { + tcp_acceptor dup(ioc, ep); + } + catch (std::system_error const& e) + { + caught = e.code(); + } + BOOST_TEST(bool(caught)); + holder.close(); + } + + void testMovedFromAcceptorValueAccept() + { + io_context ioc(Backend); + tcp_acceptor a(ioc); + tcp_acceptor b(std::move(a)); + + bool done = false; + auto task = [&]() -> capy::task<> { + auto [ec, peer] = co_await a.accept(); + BOOST_TEST(ec == std::errc::bad_file_descriptor); + BOOST_TEST(!peer.is_open()); + done = true; }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + + void testClosedAcceptorOpsComplete() + { + io_context ioc(Backend); + tcp_acceptor acc(ioc); + tcp_socket peer(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + auto [e1] = co_await acc.accept(peer); + BOOST_TEST(e1 == std::errc::bad_file_descriptor); - expect_throw([&] { (void)acc.accept(peer); }); - expect_throw([&] { (void)acc.accept(); }); - expect_throw([&] { (void)acc.wait(wait_type::read); }); + auto [e2, moved] = co_await acc.accept(); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); + + auto [e3] = co_await acc.wait(wait_type::read); + BOOST_TEST(e3 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } // Stop-token cancel of a parked accept. Unlike testCancelAccept @@ -1639,16 +1703,16 @@ struct tcp_acceptor_test io_context ioc(Backend); tcp_acceptor acc(ioc); - bool caught = false; + std::error_code caught; try { (void)acc.release(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } // Adopting a v6 listener must seed the endpoint cache as v6: the @@ -1710,12 +1774,15 @@ struct tcp_acceptor_test // Explicit bind+listen flow testBindThenListen(); - testBindClosedAcceptorThrows(); + testBindClosedAcceptor(); testBindAddressInUse(); testBindError(); - testListenClosedThrows(); + testListenClosed(); testClosedAcceptorAccessors(); - testClosedAcceptorOpsThrow(); + testClosedAcceptorOpsComplete(); + testMovedFromAcceptorValueAccept(); + testOptionFailureThrows(); + testConvenienceCtorBindFailureThrows(); // Waiter lifecycle testStopTokenAccept(); diff --git a/test/unit/tcp_socket.cpp b/test/unit/tcp_socket.cpp index 8b2aaad73..cd8bc377f 100644 --- a/test/unit/tcp_socket.cpp +++ b/test/unit/tcp_socket.cpp @@ -256,23 +256,49 @@ struct tcp_socket_test acc.close(); } - void testBindClosedSocketThrows() + void testOptionFailureThrows() { +#if !BOOST_COROSIO_HAS_IOCP + // WinSock accepts IPv6-level options on AF_INET stream + // sockets, so the rejection only holds on POSIX backends. io_context ioc(Backend); tcp_socket sock(ioc); + BOOST_TEST(!sock.open(tcp::v4())); - // Bind on a closed socket should throw - bool caught = false; + // An IPv6-level option on an IPv4 socket fails with a genuine + // error surfaced as system_error + bool set_threw = false; try { - auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); - (void)ec; + sock.set_option(socket_option::v6_only(true)); + } + catch (std::system_error const& e) + { + set_threw = bool(e.code()); } - catch (std::logic_error const&) + BOOST_TEST(set_threw); + + bool get_threw = false; + try { - caught = true; + (void)sock.get_option(); } - BOOST_TEST(caught); + catch (std::system_error const& e) + { + get_threw = bool(e.code()); + } + BOOST_TEST(get_threw); + sock.close(); +#endif + } + + void testBindClosedSocket() + { + io_context ioc(Backend); + tcp_socket sock(ioc); + + BOOST_TEST(sock.bind(endpoint(ipv4_address::loopback(), 0)) + == std::errc::bad_file_descriptor); } void testBindV6() @@ -1693,7 +1719,8 @@ struct tcp_socket_test testBind(); testBindThenConnect(); testBindV6(); - testBindClosedSocketThrows(); + testBindClosedSocket(); + testOptionFailureThrows(); testBindAddressInUse(); testBindNonLocalAddress(); testMoveConstruct(); @@ -2182,6 +2209,14 @@ struct tcp_socket_test BOOST_TEST(sock.assign(h)); }; +#if BOOST_COROSIO_HAS_IOCP + BOOST_TEST(sock.assign(invalid_native_socket) + == std::errc::not_a_socket); +#else + BOOST_TEST(sock.assign(invalid_native_socket) + == std::errc::bad_file_descriptor); +#endif + expect_error(invalid_native_socket); BOOST_TEST(!sock.is_open()); @@ -2370,16 +2405,16 @@ struct tcp_socket_test io_context ioc(Backend); tcp_socket sock(ioc); - bool caught = false; + std::error_code caught; try { (void)sock.release(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } // v6 adoption: the cached endpoints must report v6. diff --git a/test/unit/tls_stream_tests.hpp b/test/unit/tls_stream_tests.hpp index 97cf46dad..cd68b0ede 100644 --- a/test/unit/tls_stream_tests.hpp +++ b/test/unit/tls_stream_tests.hpp @@ -306,7 +306,7 @@ testFailureCases(StreamFactory make_stream) { auto client_ctx = make_client_context(); auto server_ctx = make_anon_context(); - (void)server_ctx.set_ciphersuites(""); + std::ignore = server_ctx.set_ciphersuites(""); run_tls_test_fail( ioc, client_ctx, server_ctx, make_stream, make_stream); ioc.restart(); @@ -964,7 +964,7 @@ testCrlRevocation(StreamFactory make_stream, bool crl_supported) require_ok(ctx.add_certificate_authority(root_ca_cert_pem)); require_ok(ctx.set_verify_mode(tls_verify_mode::peer)); if (load_crl) - (void)ctx.add_crl(revoked_crl_pem); + std::ignore = ctx.add_crl(revoked_crl_pem); ctx.set_revocation_policy(policy); return ctx; }; @@ -1011,7 +1011,7 @@ testCrlRevocation(StreamFactory make_stream, bool crl_supported) tls_context client_ctx; require_ok(client_ctx.add_certificate_authority(root_ca_cert_pem)); require_ok(client_ctx.set_verify_mode(tls_verify_mode::peer)); - (void)client_ctx.add_crl("this is not a valid PEM or DER CRL"); + std::ignore = client_ctx.add_crl("this is not a valid PEM or DER CRL"); client_ctx.set_revocation_policy(tls_revocation_policy::soft_fail); auto server_ctx = revoked_server(); run_tls_test_fail( @@ -1025,7 +1025,7 @@ testCrlRevocation(StreamFactory make_stream, bool crl_supported) { io_context ioc; auto client_ctx = make_client_context(); - (void)client_ctx.add_crl(revoked_crl_pem); // policy left disabled + std::ignore = client_ctx.add_crl(revoked_crl_pem); // policy left disabled auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); } @@ -1782,8 +1782,8 @@ testInvalidContextHandshake(StreamFactory make_stream) tls_context server_ctx; // The setters may reject the garbage eagerly or defer to the // handshake; the handshake failure below is what is asserted. - (void)server_ctx.use_certificate("not a certificate", tls_file_format::pem); - (void)server_ctx.use_private_key("not a key", tls_file_format::pem); + std::ignore = server_ctx.use_certificate("not a certificate", tls_file_format::pem); + std::ignore = server_ctx.use_private_key("not a key", tls_file_format::pem); require_ok(server_ctx.set_verify_mode(tls_verify_mode::none)); auto client = make_stream(m1, client_ctx); diff --git a/test/unit/udp_socket.cpp b/test/unit/udp_socket.cpp index 481ff253d..5ccf2d1b2 100644 --- a/test/unit/udp_socket.cpp +++ b/test/unit/udp_socket.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #if BOOST_COROSIO_POSIX // Raw socket creation for the assign()/release() adoption tests. @@ -221,22 +222,13 @@ struct udp_socket_test sock.close(); } - void testBindClosedSocketThrows() + void testBindClosedSocket() { io_context ioc(Backend); udp_socket sock(ioc); - bool caught = false; - try - { - auto ec = sock.bind(endpoint(ipv4_address::loopback(), 0)); - (void)ec; - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); + BOOST_TEST(sock.bind(endpoint(ipv4_address::loopback(), 0)) + == std::errc::bad_file_descriptor); } void testSetOptionClosedThrows() @@ -244,16 +236,16 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - bool caught = false; + std::error_code caught; try { sock.set_option(socket_option::broadcast(true)); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } void testGetOptionClosedThrows() @@ -261,91 +253,52 @@ struct udp_socket_test io_context ioc(Backend); udp_socket sock(ioc); - bool caught = false; + std::error_code caught; try { (void)sock.get_option(); } - catch (std::logic_error const&) + catch (std::system_error const& e) { - caught = true; + caught = e.code(); } - BOOST_TEST(caught); + BOOST_TEST(caught == std::errc::bad_file_descriptor); } - void testSendToClosedThrows() + void testClosedOpsComplete() { + // Datagram operations on a closed socket complete with + // bad_file_descriptor instead of throwing. io_context ioc(Backend); udp_socket sock(ioc); - char const msg[] = "x"; - bool caught = false; - try - { - (void)sock.send_to( + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[16]; + char const msg[] = "x"; + endpoint src; + + auto [e1, n1] = co_await sock.send_to( capy::const_buffer(msg, sizeof(msg)), endpoint(ipv4_address::loopback(), 1)); - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); - } + BOOST_TEST(e1 == std::errc::bad_file_descriptor); - void testRecvFromClosedThrows() - { - io_context ioc(Backend); - udp_socket sock(ioc); + auto [e2, n2] = co_await sock.recv_from( + capy::mutable_buffer(buf, sizeof(buf)), src); + BOOST_TEST(e2 == std::errc::bad_file_descriptor); - char buf[16]; - endpoint src; - bool caught = false; - try - { - (void)sock.recv_from(capy::mutable_buffer(buf, sizeof(buf)), src); - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); - } - - void testSendClosedThrows() - { - io_context ioc(Backend); - udp_socket sock(ioc); + auto [e3, n3] = co_await sock.send( + capy::const_buffer(msg, sizeof(msg))); + BOOST_TEST(e3 == std::errc::bad_file_descriptor); - char const msg[] = "x"; - bool caught = false; - try - { - (void)sock.send(capy::const_buffer(msg, sizeof(msg))); - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); - } - - void testRecvClosedThrows() - { - io_context ioc(Backend); - udp_socket sock(ioc); - - char buf[16]; - bool caught = false; - try - { - (void)sock.recv(capy::mutable_buffer(buf, sizeof(buf))); - } - catch (std::logic_error const&) - { - caught = true; - } - BOOST_TEST(caught); + auto [e4, n4] = co_await sock.recv( + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(e4 == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } void testBindAddressInUse() @@ -599,6 +552,40 @@ struct udp_socket_test ioc.run(); } + void testOptionFailureThrows() + { +#if !BOOST_COROSIO_HAS_IOCP + // WinSock's acceptance of IPv6-level options on AF_INET + // sockets varies; the rejection only holds on POSIX backends. + io_context ioc(Backend); + udp_socket sock(ioc); + BOOST_TEST(!sock.open(udp::v4())); + + bool set_threw = false; + try + { + sock.set_option(socket_option::v6_only(true)); + } + catch (std::system_error const& e) + { + set_threw = bool(e.code()); + } + BOOST_TEST(set_threw); + + bool get_threw = false; + try + { + (void)sock.get_option(); + } + catch (std::system_error const& e) + { + get_threw = bool(e.code()); + } + BOOST_TEST(get_threw); + sock.close(); +#endif + } + void testShutdown() { io_context ioc(Backend); @@ -612,8 +599,7 @@ struct udp_socket_test // unconnected datagram socket; only the path is exercised. udp_socket sock(ioc); BOOST_TEST(!sock.open()); - auto ec = sock.shutdown(shutdown_send); - (void)ec; + std::ignore = sock.shutdown(shutdown_send); sock.close(); } @@ -1386,6 +1372,14 @@ struct udp_socket_test BOOST_TEST(sock.assign(h)); }; +#if BOOST_COROSIO_HAS_IOCP + BOOST_TEST(sock.assign(invalid_native_socket) + == std::errc::not_a_socket); +#else + BOOST_TEST(sock.assign(invalid_native_socket) + == std::errc::bad_file_descriptor); +#endif + expect_error(invalid_native_socket); BOOST_TEST(!sock.is_open()); @@ -1601,7 +1595,7 @@ struct udp_socket_test { (void)sock.release(); } - catch (std::logic_error const&) + catch (std::system_error const&) { caught = true; } @@ -1666,13 +1660,10 @@ struct udp_socket_test testMoveAssign(); testBind(); testBindV6(); - testBindClosedSocketThrows(); + testBindClosedSocket(); testSetOptionClosedThrows(); testGetOptionClosedThrows(); - testSendToClosedThrows(); - testRecvFromClosedThrows(); - testSendClosedThrows(); - testRecvClosedThrows(); + testClosedOpsComplete(); testBindAddressInUse(); testBindNonLocalAddress(); testClosedAccessorsReturnDefaults(); @@ -1683,6 +1674,7 @@ struct udp_socket_test testEchoLoopback(); testMultipleDatagrams(); testShutdown(); + testOptionFailureThrows(); testCancelRecv(); testCloseWhileRecving(); testStopTokenCancellation(); diff --git a/test/unit/wolfssl_engine.cpp b/test/unit/wolfssl_engine.cpp index 057647012..43769b1f6 100644 --- a/test/unit/wolfssl_engine.cpp +++ b/test/unit/wolfssl_engine.cpp @@ -35,6 +35,7 @@ #include #include +#include namespace boost::corosio { @@ -597,8 +598,8 @@ struct wolfssl_engine_test tls_context ctx; // Whether the garbage surfaces here or at init() is // backend-dependent; the init failure below is what matters. - (void)ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); - (void)ctx.use_private_key(test::server_key_pem, tls_file_format::pem); + std::ignore = ctx.use_certificate("\x30\x82\x00\x00", tls_file_format::der); + std::ignore = ctx.use_private_key(test::server_key_pem, tls_file_format::pem); wssl_engine eng; // Unlike the OpenSSL engine, wolfSSL surfaces setup_error_ From 05a738c7ec78586b7898b45a963299e5c25555e9 Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Fri, 21 Aug 2026 17:44:43 +0200 Subject: [PATCH 3/5] fix: make the closed-object and contracted codes deterministic on every backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every backend completes read/write/wait on a closed object with bad_file_descriptor at the initiation entry, contracted codes compare against std::errc on every toolchain, and the openssl engine can no longer lose an error entirely. - reactor (epoll/select/kqueue): read/write on a never-registered socket completed through complete_io_op, which dereferenced the null descriptor scheduler — a segfault. The entry check completes before touching the kernel, and complete_io_op gets the same null-scheduler guard complete_wait_op already had. - io_uring: a closed acceptor's wait parked a waiter node the multishot accept machinery would never signal — a hang. The entry check posts an EBADF completion instead. - iocp: closed sockets reached WSARecv/WSASend/the wait reactor and reported WSAENOTSOCK or toolchain-dependent mappings. Entry checks complete with WSAEBADF (ERROR_INVALID_HANDLE for files), and iocp_make_err normalizes both spellings to bad_file_descriptor — MSVC maps ERROR_INVALID_HANDLE to invalid_argument on its own. - native_tcp_socket::connect pre-failed a closed socket instead of auto-opening like the base class and every sibling facade; it now opens with the family-matched protocol. - WSAEADDRINUSE, WSAEADDRNOTAVAIL, and ERROR_NEGATIVE_SEEK join the make_err contract list so bind and seek failures compare portably; the raw-pin test gates collapse to unconditional equality. - openssl: make_implementation swallowed an engine init failure by returning nullptr, which the constructor stored — every later operation dereferenced a null impl_, and the failed SSL_CTX cache made the crash permanent for the whole tls_context. Construction now only allocates; prepare() runs the deferred init on the first handshake (the wolfssl shape) so setup failures surface through the handshake completion, re-running check_context after the lazy init because the driver's fail-closed gate runs before the native context exists. perform() reports pre-handshake I/O instead of crashing; the OOM trigger itself remains fault-injection-only. The closed-wait suite is promoted from reactor-only to every backend and extended to read/write and all four socket classes; closed-object completions are locked by equality on files, native at-ops, and native acceptor waits. The random-access completion decode joins iocp_make_err — it was the one overlapped op decoding through plain make_err, which left ERROR_INVALID_HANDLE to MSVC's invalid_argument mapping instead of the contracted bad_file_descriptor. Coverage: acceptor wait(wait_type::error) parked and cancelled on both acceptor types — the io_uring error-poll submit path had no test. --- .../io_uring/io_uring_random_access_file.hpp | 20 ++++ .../detail/io_uring/io_uring_stream_file.hpp | 20 ++++ .../native/detail/io_uring/io_uring_types.hpp | 26 ++++ .../native/detail/iocp/win_file_service.hpp | 16 +++ .../win_local_stream_acceptor_service.hpp | 8 ++ .../detail/iocp/win_local_stream_service.hpp | 24 ++++ .../native/detail/iocp/win_overlapped_op.hpp | 5 + .../iocp/win_random_access_file_service.hpp | 29 ++++- .../detail/iocp/win_tcp_acceptor_service.hpp | 32 +++++ .../native/detail/iocp/win_udp_service.hpp | 8 ++ .../boost/corosio/native/detail/make_err.hpp | 8 +- .../posix_random_access_file_service.hpp | 16 +++ .../posix/posix_stream_file_service.hpp | 18 +++ .../detail/reactor/reactor_op_complete.hpp | 6 +- .../detail/reactor/reactor_stream_socket.hpp | 30 +++++ .../corosio/native/native_tcp_socket.hpp | 8 +- src/corosio/src/tcp_acceptor.cpp | 24 ++++ src/openssl/src/detail/engine.cpp | 26 +++- src/openssl/src/detail/engine.hpp | 22 ++-- src/openssl/src/openssl_stream.cpp | 15 +-- test/unit/native/iocp/iocp_error_map.cpp | 7 ++ .../native/native_local_stream_socket.cpp | 54 +++++++++ .../unit/native/native_random_access_file.cpp | 32 +++++ test/unit/native/native_stream_file.cpp | 26 ++++ test/unit/native/native_tcp_acceptor.cpp | 23 ++++ test/unit/native/native_tcp_socket.cpp | 44 ++++++- test/unit/openssl_stream.cpp | 1 + test/unit/signal_set.cpp | 2 +- test/unit/stream_file.cpp | 35 +++++- test/unit/tcp_acceptor.cpp | 14 +-- test/unit/tls_stream_tests.hpp | 40 +++++++ test/unit/udp_socket.cpp | 4 +- test/unit/wait.cpp | 113 ++++++++++++++++-- test/unit/wolfssl_stream.cpp | 1 + 34 files changed, 699 insertions(+), 58 deletions(-) diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp index 66b6e3d34..a41c4551e 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_random_access_file.hpp @@ -230,6 +230,16 @@ io_uring_random_access_file::read_some_at( sched_, shared_from_this(), buffers, token); sched_->work_started(); + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + op_guard->empty_buffer = false; + op_guard->res = -EBADF; + io_uring_scheduler::lock_type lock(sched_->dispatch_mutex()); + sched_->push_completed_locked(op_guard.release()); + return std::noop_coroutine(); + } + if (op_guard->empty_buffer || op_guard->cancelled.load(std::memory_order_acquire)) { @@ -258,6 +268,16 @@ io_uring_random_access_file::write_some_at( sched_, shared_from_this(), buffers, token); sched_->work_started(); + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + op_guard->empty_buffer = false; + op_guard->res = -EBADF; + io_uring_scheduler::lock_type lock(sched_->dispatch_mutex()); + sched_->push_completed_locked(op_guard.release()); + return std::noop_coroutine(); + } + if (op_guard->empty_buffer || op_guard->cancelled.load(std::memory_order_acquire)) { diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp index 1051e7deb..b61e1ac35 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_stream_file.hpp @@ -249,6 +249,16 @@ io_uring_stream_file::read_some( shared_from_this(), buffers, token); sched_->work_started(); + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + rd_.empty_buffer = false; + rd_.res = -EBADF; + io_uring_scheduler::lock_type lock(sched_->dispatch_mutex()); + sched_->push_completed_locked(&rd_); + return std::noop_coroutine(); + } + if (rd_.empty_buffer || rd_.cancelled.load(std::memory_order_acquire)) { @@ -274,6 +284,16 @@ io_uring_stream_file::write_some( shared_from_this(), buffers, token); sched_->work_started(); + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + wr_.empty_buffer = false; + wr_.res = -EBADF; + io_uring_scheduler::lock_type lock(sched_->dispatch_mutex()); + sched_->push_completed_locked(&wr_); + return std::noop_coroutine(); + } + if (wr_.empty_buffer || wr_.cancelled.load(std::memory_order_acquire)) { diff --git a/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp b/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp index 8ee7609ef..b1b7e129b 100644 --- a/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp +++ b/include/boost/corosio/native/detail/io_uring/io_uring_types.hpp @@ -686,6 +686,19 @@ class BOOST_COROSIO_DECL io_uring_tcp_acceptor final std::stop_token token, std::error_code* ec) override { + // Closed-object contract: complete with bad_file_descriptor + // instead of parking a waiter no accept machinery will signal. + if (this->fd_ < 0) + { + // NOLINTNEXTLINE(bugprone-unhandled-exception-at-new) — noexcept-adjacent initiation path: OOM => std::terminate is the intended behavior + auto* op = new uring_accept_op(); + op->h = h; + op->ex = ex; + op->ec_out = ec; + op->err = EBADF; + this->sched_->post(op); + return std::noop_coroutine(); + } // Multishot accepting drains the kernel queue as connections // arrive, so a poll on the listener never reports it // readable; read waits complete from the delivery queue. @@ -1482,6 +1495,19 @@ class BOOST_COROSIO_DECL io_uring_local_stream_acceptor final std::stop_token token, std::error_code* ec) override { + // Closed-object contract: complete with bad_file_descriptor + // instead of parking a waiter no accept machinery will signal. + if (this->fd_ < 0) + { + // NOLINTNEXTLINE(bugprone-unhandled-exception-at-new) — noexcept-adjacent initiation path: OOM => std::terminate is the intended behavior + auto* op = new uring_accept_op(); + op->h = h; + op->ex = ex; + op->ec_out = ec; + op->err = EBADF; + this->sched_->post(op); + return std::noop_coroutine(); + } // Multishot accepting drains the kernel queue as connections // arrive, so a poll on the listener never reports it // readable; read waits complete from the delivery queue. diff --git a/include/boost/corosio/native/detail/iocp/win_file_service.hpp b/include/boost/corosio/native/detail/iocp/win_file_service.hpp index 4352fada6..b4875830e 100644 --- a/include/boost/corosio/native/detail/iocp/win_file_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_file_service.hpp @@ -376,6 +376,14 @@ win_stream_file_internal::read_some( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (handle_ == INVALID_HANDLE_VALUE) + { + svc_.on_completion(&op, ERROR_INVALID_HANDLE, 0); + return std::noop_coroutine(); + } + // Extract first buffer from buffer_param capy::mutable_buffer bufs[max_buffers]; auto count = param.copy_to(bufs, max_buffers); @@ -438,6 +446,14 @@ win_stream_file_internal::write_some( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (handle_ == INVALID_HANDLE_VALUE) + { + svc_.on_completion(&op, ERROR_INVALID_HANDLE, 0); + return std::noop_coroutine(); + } + // Extract first buffer from buffer_param capy::mutable_buffer bufs[max_buffers]; auto count = param.copy_to(bufs, max_buffers); diff --git a/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp b/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp index 0d83183b0..66af42719 100644 --- a/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp @@ -315,6 +315,14 @@ win_local_stream_acceptor_internal::wait( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel or the wait reactor. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + // Writability carries no meaning for a listening socket; the // wait fails the same way on every backend. if (w == wait_type::write) diff --git a/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp b/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp index c1598a380..b8725e23f 100644 --- a/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_local_stream_service.hpp @@ -488,6 +488,14 @@ win_local_stream_socket_internal::read_some( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + capy::mutable_buffer bufs[local_stream_read_op::max_buffers]; op.wsabuf_count = static_cast(param.copy_to(bufs, local_stream_read_op::max_buffers)); @@ -549,6 +557,14 @@ win_local_stream_socket_internal::write_some( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + capy::mutable_buffer bufs[local_stream_write_op::max_buffers]; op.wsabuf_count = static_cast(param.copy_to(bufs, local_stream_write_op::max_buffers)); @@ -607,6 +623,14 @@ win_local_stream_socket_internal::wait( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel or the wait reactor. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + if (w == wait_type::read) { // Zero-byte WSARecv — completes when data is available diff --git a/include/boost/corosio/native/detail/iocp/win_overlapped_op.hpp b/include/boost/corosio/native/detail/iocp/win_overlapped_op.hpp index 94245955b..75d80331e 100644 --- a/include/boost/corosio/native/detail/iocp/win_overlapped_op.hpp +++ b/include/boost/corosio/native/detail/iocp/win_overlapped_op.hpp @@ -93,6 +93,11 @@ iocp_make_err(DWORD dwError, bool accept_path) noexcept return std::make_error_code(std::errc::host_unreachable); case WSAETIMEDOUT: case ERROR_SEM_TIMEOUT: // 10060 / 121 return std::make_error_code(std::errc::timed_out); + // Closed-object contract: MSVC maps ERROR_INVALID_HANDLE to + // invalid_argument and MinGW's WSAEBADF mapping is unreliable, so + // normalize both spellings of "dead handle" here. + case WSAEBADF: case ERROR_INVALID_HANDLE: // 10009 / 6 + return std::make_error_code(std::errc::bad_file_descriptor); default: break; } diff --git a/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp b/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp index ad706443e..870c7ce38 100644 --- a/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_random_access_file_service.hpp @@ -165,7 +165,10 @@ raf_concurrent_op::do_complete( if (op->cancelled.load(std::memory_order_acquire)) *op->ec_out = capy::error::canceled; else if (op->dwError != 0) - *op->ec_out = make_err(op->dwError); + // Through the IOCP normalization point, like every other + // overlapped op — plain make_err would leave codes such + // as ERROR_INVALID_HANDLE to toolchain-dependent mappings. + *op->ec_out = iocp_make_err(op->dwError, /*accept_path=*/false); else if (op->is_read && op->bytes_transferred == 0 && !op->empty_buffer) *op->ec_out = capy::error::eof; else @@ -328,6 +331,18 @@ win_random_access_file_internal::read_some_at( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (handle_ == INVALID_HANDLE_VALUE) + { + { + std::lock_guard lock(ops_mutex_); + outstanding_ops_.push_back(op); + } + svc_.on_completion(op, ERROR_INVALID_HANDLE, 0); + return std::noop_coroutine(); + } + capy::mutable_buffer bufs[max_buffers]; auto count = param.copy_to(bufs, max_buffers); @@ -397,6 +412,18 @@ win_random_access_file_internal::write_some_at( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (handle_ == INVALID_HANDLE_VALUE) + { + { + std::lock_guard lock(ops_mutex_); + outstanding_ops_.push_back(op); + } + svc_.on_completion(op, ERROR_INVALID_HANDLE, 0); + return std::noop_coroutine(); + } + capy::mutable_buffer bufs[max_buffers]; auto count = param.copy_to(bufs, max_buffers); diff --git a/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp b/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp index 0c70035db..0b7678a01 100644 --- a/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp @@ -588,6 +588,14 @@ win_tcp_socket_internal::read_some( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + // Prepare buffers capy::mutable_buffer bufs[read_op::max_buffers]; op.wsabuf_count = @@ -653,6 +661,14 @@ win_tcp_socket_internal::write_some( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + // Prepare buffers capy::mutable_buffer bufs[write_op::max_buffers]; op.wsabuf_count = @@ -714,6 +730,14 @@ win_tcp_socket_internal::wait( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel or the wait reactor. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + if (w == wait_type::read) { // Zero-byte WSARecv: kernel signals completion when data is @@ -1533,6 +1557,14 @@ win_tcp_acceptor_internal::wait( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel or the wait reactor. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + // Writability carries no meaning for a listening socket; the // wait fails the same way on every backend. if (w == wait_type::write) diff --git a/include/boost/corosio/native/detail/iocp/win_udp_service.hpp b/include/boost/corosio/native/detail/iocp/win_udp_service.hpp index f90d5eaa1..b26028862 100644 --- a/include/boost/corosio/native/detail/iocp/win_udp_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_udp_service.hpp @@ -714,6 +714,14 @@ win_udp_socket_internal::wait( svc_.work_started(); + // Closed-object contract: complete with bad_file_descriptor + // without touching the kernel or the wait reactor. + if (socket_ == INVALID_SOCKET) + { + svc_.on_completion(&op, WSAEBADF, 0); + return std::noop_coroutine(); + } + // Every datagram wait routes through the auxiliary select // reactor: there's no IOCP-native primitive for "datagram // readable without dequeuing the message" (zero-byte WSARecvFrom diff --git a/include/boost/corosio/native/detail/make_err.hpp b/include/boost/corosio/native/detail/make_err.hpp index 68c45a952..75dc01bbe 100644 --- a/include/boost/corosio/native/detail/make_err.hpp +++ b/include/boost/corosio/native/detail/make_err.hpp @@ -82,7 +82,7 @@ make_err(unsigned long dwError) noexcept if (dwError == ERROR_HANDLE_EOF) return capy::error::eof; - // Part of the portable wait and adoption contracts; + // Part of the portable wait, adoption, bind, and seek contracts; // system_category's condition mapping for WSA codes varies by // toolchain. if (dwError == WSAEOPNOTSUPP) @@ -94,6 +94,12 @@ make_err(unsigned long dwError) noexcept std::errc::address_family_not_supported); if (dwError == WSAEPROTOTYPE) return std::make_error_code(std::errc::wrong_protocol_type); + if (dwError == WSAEADDRINUSE) + return std::make_error_code(std::errc::address_in_use); + if (dwError == WSAEADDRNOTAVAIL) + return std::make_error_code(std::errc::address_not_available); + if (dwError == ERROR_NEGATIVE_SEEK) + return std::make_error_code(std::errc::invalid_argument); return std::error_code(static_cast(dwError), std::system_category()); } diff --git a/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp b/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp index ecfae2964..ea048a762 100644 --- a/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_random_access_file_service.hpp @@ -168,6 +168,14 @@ posix_random_access_file::read_some_at( std::error_code* ec, std::size_t* bytes_out) { + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + *ec = make_error_code(std::errc::bad_file_descriptor); + *bytes_out = 0; + return h; + } + capy::mutable_buffer bufs[max_buffers]; auto count = param.copy_to(bufs, max_buffers); @@ -223,6 +231,14 @@ posix_random_access_file::write_some_at( std::error_code* ec, std::size_t* bytes_out) { + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + *ec = make_error_code(std::errc::bad_file_descriptor); + *bytes_out = 0; + return h; + } + capy::mutable_buffer bufs[max_buffers]; auto count = param.copy_to(bufs, max_buffers); diff --git a/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp b/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp index 629dbc8f9..39b6a4027 100644 --- a/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_stream_file_service.hpp @@ -169,6 +169,15 @@ posix_stream_file::read_some( op.reset(); op.is_read = true; + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + *ec = make_error_code(std::errc::bad_file_descriptor); + *bytes_out = 0; + op.cont.h = h; + return dispatch_coro(ex, op.cont); + } + capy::mutable_buffer bufs[max_buffers]; op.iovec_count = static_cast(param.copy_to(bufs, max_buffers)); @@ -253,6 +262,15 @@ posix_stream_file::write_some( op.reset(); op.is_read = false; + // Closed-object contract outranks the zero-length no-op. + if (fd_ < 0) + { + *ec = make_error_code(std::errc::bad_file_descriptor); + *bytes_out = 0; + op.cont.h = h; + return dispatch_coro(ex, op.cont); + } + capy::mutable_buffer bufs[max_buffers]; op.iovec_count = static_cast(param.copy_to(bufs, max_buffers)); diff --git a/include/boost/corosio/native/detail/reactor/reactor_op_complete.hpp b/include/boost/corosio/native/detail/reactor/reactor_op_complete.hpp index 8e93bed24..19d354519 100644 --- a/include/boost/corosio/native/detail/reactor/reactor_op_complete.hpp +++ b/include/boost/corosio/native/detail/reactor/reactor_op_complete.hpp @@ -40,7 +40,11 @@ void complete_io_op(Op& op) { op.stop_cb.reset(); - op.socket_impl_->desc_state_.scheduler_->reset_inline_budget(); + // scheduler_ is null until the descriptor is registered; an op + // completed by the closed-object entry check never registered and + // has no budget to reset. + if (auto* sched = op.socket_impl_->desc_state_.scheduler_) + sched->reset_inline_budget(); // is_read_operation() already folds in the empty-buffer case (it // returns false for a zero-length read), so empty_buffer stays false diff --git a/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp b/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp index 26fbeb58a..428348f8e 100644 --- a/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp +++ b/include/boost/corosio/native/detail/reactor/reactor_stream_socket.hpp @@ -441,6 +441,21 @@ reactor_stream_socketfd_ < 0) + { + op.h = h; + op.ex = ex; + op.ec_out = ec; + op.bytes_out = bytes_out; + op.start(token, static_cast(this)); + op.impl_ptr = this->shared_from_this(); + op.complete(EBADF, 0); + this->svc_.post(&op); + return std::noop_coroutine(); + } + capy::mutable_buffer bufs[ReadOp::max_buffers]; op.iovec_count = static_cast(param.copy_to(bufs, ReadOp::max_buffers)); @@ -550,6 +565,21 @@ reactor_stream_socketfd_ < 0) + { + op.h = h; + op.ex = ex; + op.ec_out = ec; + op.bytes_out = bytes_out; + op.start(token, static_cast(this)); + op.impl_ptr = this->shared_from_this(); + op.complete(EBADF, 0); + this->svc_.post(&op); + return std::noop_coroutine(); + } + capy::mutable_buffer bufs[WriteOp::max_buffers]; op.iovec_count = static_cast(param.copy_to(bufs, WriteOp::max_buffers)); diff --git a/include/boost/corosio/native/native_tcp_socket.hpp b/include/boost/corosio/native/native_tcp_socket.hpp index 9c04641e6..84f55deb7 100644 --- a/include/boost/corosio/native/native_tcp_socket.hpp +++ b/include/boost/corosio/native/native_tcp_socket.hpp @@ -325,19 +325,21 @@ class native_tcp_socket : public tcp_socket Calls the backend implementation directly, bypassing virtual dispatch. Otherwise identical to @ref tcp_socket::connect. + If the socket is not open, it is opened automatically using + the protocol matching the endpoint's address family. An open + failure surfaces through the connect completion. + @param ep The remote endpoint to connect to. @return An awaitable yielding `io_result<>`. - A closed socket reports `errc::bad_file_descriptor`. - This socket must outlive the returned awaitable. */ auto connect(endpoint ep) { native_connect_awaitable aw(*this, ep); if (!is_open()) - aw.ec_ = make_error_code(std::errc::bad_file_descriptor); + aw.ec_ = open(ep.is_v6() ? tcp::v6() : tcp::v4()); return aw; } diff --git a/src/corosio/src/tcp_acceptor.cpp b/src/corosio/src/tcp_acceptor.cpp index 0c1905f7c..e88f7dc52 100644 --- a/src/corosio/src/tcp_acceptor.cpp +++ b/src/corosio/src/tcp_acceptor.cpp @@ -22,6 +22,26 @@ namespace boost::corosio { +#if BOOST_COROSIO_HAS_IOCP +namespace { + +// On Windows SO_REUSEADDR grants bind-over ("hijack") rights instead +// of TIME_WAIT reuse, so a second listener would share the port +// silently. SO_EXCLUSIVEADDRUSE restores the POSIX contract: the +// collision surfaces as WSAEADDRINUSE (errc::address_in_use). +struct exclusive_address_use +{ + int value_ = 1; + + static int level() noexcept { return SOL_SOCKET; } + static int name() noexcept { return SO_EXCLUSIVEADDRUSE; } + void const* data() const noexcept { return &value_; } + std::size_t size() const noexcept { return sizeof(value_); } +}; + +} // namespace +#endif + tcp_acceptor::~tcp_acceptor() { close(); @@ -42,7 +62,11 @@ tcp_acceptor::tcp_acceptor( { if (auto ec = open(ep.is_v6() ? tcp::v6() : tcp::v4())) detail::throw_system_error(ec, "tcp_acceptor"); +#if BOOST_COROSIO_HAS_IOCP + set_option(exclusive_address_use{}); +#else set_option(socket_option::reuse_address(true)); +#endif if (auto ec = bind(ep)) detail::throw_system_error(ec, "tcp_acceptor"); if (auto ec = listen(backlog)) diff --git a/src/openssl/src/detail/engine.cpp b/src/openssl/src/detail/engine.cpp index babdb51ba..0c11a1164 100644 --- a/src/openssl/src/detail/engine.cpp +++ b/src/openssl/src/detail/engine.cpp @@ -738,7 +738,9 @@ engine::reset() bool engine::context_setup_failed() const noexcept { - return nc_->setup_failed_; + // Before the deferred init runs there is no native context to + // judge; prepare() re-checks once it exists. + return nc_ && nc_->setup_failed_; } std::error_code @@ -758,8 +760,21 @@ engine::check_session() const noexcept } std::error_code -engine::prepare(tls_context const&, tls_role role, std::string const& hostname) +engine::prepare(tls_context const& ctx, tls_role role, std::string const& hostname) { + // Session creation is deferred from construction so a setup + // failure reports through the handshake completion. + if (!ssl_) + { + if (auto ec = init(ctx)) + return ec; + // The driver's check_context gate ran before this init could + // populate the native context; re-check so a rejected + // configuration still fails closed on the first handshake. + if (auto cec = check_context()) + return cec; + } + // The hostname applies to client handshakes only; a server // handshake clears any name left by a prior client-role // handshake so client certificates are never hostname-matched. @@ -816,6 +831,13 @@ engine::capture_alpn(std::string& out) const engine_result engine::perform(engine_op op, void* data, std::size_t len) { + // No session exists until the first handshake's deferred init; + // report I/O attempted before then instead of crashing on a null + // SSL handle. + if (!ssl_) + return {engine_want::done, + std::make_error_code(std::errc::invalid_argument), 0}; + ERR_clear_error(); int ret = 0; diff --git a/src/openssl/src/detail/engine.hpp b/src/openssl/src/detail/engine.hpp index 14ce3a6f8..89ea7e111 100644 --- a/src/openssl/src/detail/engine.hpp +++ b/src/openssl/src/detail/engine.hpp @@ -93,10 +93,11 @@ class BOOST_COROSIO_DECL engine /** Create the SSL session and BIO pair from a TLS context. - Must succeed before any other member is used. A context whose - native build failed is reported unconditionally: the cache - retains a failed build permanently and the error queue may - already be drained, so a queue-derived code could read as + Called lazily by `prepare` on the first handshake so a setup + failure reports through the handshake completion. A context + whose native build failed is reported unconditionally: the + cache retains a failed build permanently and the error queue + may already be drained, so a queue-derived code could read as success. @param ctx The TLS context supplying the native `SSL_CTX`. @@ -148,13 +149,14 @@ class BOOST_COROSIO_DECL engine /** Prepare the session for a handshake in the given role. - Applies SNI/hostname verification and installs the context's - ALPN offer, both for client handshakes only; a server - handshake clears any name left by a prior client-role - handshake so client certificates are never hostname-matched. - Fails closed rather than handshake without a requested check. + Runs the deferred `init` on first use, then applies + SNI/hostname verification and installs the context's ALPN + offer, both for client handshakes only; a server handshake + clears any name left by a prior client-role handshake so + client certificates are never hostname-matched. Fails closed + rather than handshake without a requested check. - @param ctx Unused; the session was built from it at `init`. + @param ctx The TLS context backing the deferred session build. @param role Handshake role. @param hostname Peer name for SNI/verification; empty for none. diff --git a/src/openssl/src/openssl_stream.cpp b/src/openssl/src/openssl_stream.cpp index d7c12cb34..4342fd8ea 100644 --- a/src/openssl/src/openssl_stream.cpp +++ b/src/openssl/src/openssl_stream.cpp @@ -33,16 +33,11 @@ struct openssl_stream::implementation openssl_stream::implementation* openssl_stream::make_implementation(capy::any_stream& stream, tls_context const& ctx) { - auto* p = new implementation(stream, ctx); - - auto ec = p->engine().init(p->context()); - if (ec) - { - delete p; - return nullptr; - } - - return p; + // Session creation is deferred to handshake time (the engine's + // prepare hook builds it lazily), so a session setup failure + // surfaces through the handshake completion instead of leaving a + // null implementation behind. + return new implementation(stream, ctx); } openssl_stream::~openssl_stream() diff --git a/test/unit/native/iocp/iocp_error_map.cpp b/test/unit/native/iocp/iocp_error_map.cpp index 00c0da4c8..dc293d268 100644 --- a/test/unit/native/iocp/iocp_error_map.cpp +++ b/test/unit/native/iocp/iocp_error_map.cpp @@ -70,6 +70,13 @@ struct iocp_error_map_test // Unmapped codes defer to make_err and stay non-empty. BOOST_TEST(!!iocp_make_err(ERROR_ACCESS_DENIED, false)); + + // Closed-object contract spellings normalize to + // bad_file_descriptor regardless of toolchain mapping. + BOOST_TEST(iocp_make_err(WSAEBADF, false) == + std::errc::bad_file_descriptor); + BOOST_TEST(iocp_make_err(ERROR_INVALID_HANDLE, false) == + std::errc::bad_file_descriptor); } }; diff --git a/test/unit/native/native_local_stream_socket.cpp b/test/unit/native/native_local_stream_socket.cpp index fe0205827..0e883dcfe 100644 --- a/test/unit/native/native_local_stream_socket.cpp +++ b/test/unit/native/native_local_stream_socket.cpp @@ -401,6 +401,58 @@ struct native_local_stream_socket_test BOOST_TEST(s.is_open()); } + // A closed acceptor's wait completes with bad_file_descriptor, + // matching the base-class contract. + // Stream operations on a closed socket complete with + // bad_file_descriptor instead of dispatching. + void testSocketClosedOpsComplete() + { + io_context ioc(Backend); + native_local_stream_socket s(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8] = {}; + + auto [rec, rn] = co_await s.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(rec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(rn, 0u); + + auto [wec, wn] = co_await s.write_some( + capy::const_buffer(buf, sizeof(buf))); + BOOST_TEST(wec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(wn, 0u); + + auto [tec] = co_await s.wait(wait_type::read); + BOOST_TEST(tec == std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + + void testAcceptorWaitOnClosed() + { + io_context ioc(Backend); + native_local_stream_acceptor acc(ioc); + + std::error_code wait_ec; + bool wait_done = false; + + auto waiter = [&]() -> capy::task<> { + auto [ec] = co_await acc.wait(wait_type::read); + wait_ec = ec; + wait_done = true; + }; + capy::run_async(ioc.get_executor())(waiter()); + ioc.run(); + + BOOST_TEST(wait_done); + BOOST_TEST(wait_ec == std::errc::bad_file_descriptor); + } + void run() { testConstruct(); @@ -410,7 +462,9 @@ struct native_local_stream_socket_test testMoveAccept(); testVirtualDispatchFallback(); testSocketWait(); + testSocketClosedOpsComplete(); testAcceptorWait(); + testAcceptorWaitOnClosed(); testAcceptOnClosedCompletes(); testMovedFromValueAcceptThrows(); testConnectAutoOpens(); diff --git a/test/unit/native/native_random_access_file.cpp b/test/unit/native/native_random_access_file.cpp index 444de6479..b4c88c0a4 100644 --- a/test/unit/native/native_random_access_file.cpp +++ b/test/unit/native/native_random_access_file.cpp @@ -162,6 +162,37 @@ struct native_random_access_file_test BOOST_TEST_EQ(contents, std::string("hello")); } + // Closed-object contract on the devirtualized path: the at-ops + // complete with bad_file_descriptor. + void testReadWriteAtOnClosedFile() + { + io_context ioc(Backend); + native_random_access_file f(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[4] = {}; + auto [rec, rn] = co_await f.read_some_at( + 0, capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(rec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(rn, 0u); + auto [wec, wn] = co_await f.write_some_at( + 0, capy::const_buffer(buf, sizeof(buf))); + BOOST_TEST(wec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(wn, 0u); + + // Zero-length at-ops on a closed file still report closed. + auto [zrec, zrn] = co_await f.read_some_at( + 0, capy::mutable_buffer(buf, 0)); + BOOST_TEST(zrec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(zrn, 0u); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + void testVirtualDispatchFallback() { std::string data = "fallback"; @@ -194,6 +225,7 @@ struct native_random_access_file_test testPolymorphicSlice(); testReadSomeAt(); testWriteSomeAt(); + testReadWriteAtOnClosedFile(); testVirtualDispatchFallback(); } }; diff --git a/test/unit/native/native_stream_file.cpp b/test/unit/native/native_stream_file.cpp index ec56bffc8..c57bb7a1a 100644 --- a/test/unit/native/native_stream_file.cpp +++ b/test/unit/native/native_stream_file.cpp @@ -158,6 +158,31 @@ struct native_stream_file_test BOOST_TEST_EQ(contents, std::string(msg)); } + // Closed-object contract on the devirtualized path: read/write + // complete with bad_file_descriptor. + void testReadWriteOnClosedFile() + { + io_context ioc(Backend); + native_stream_file f(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[4] = {}; + auto [rec, rn] = + co_await f.read_some(capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(rec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(rn, 0u); + auto [wec, wn] = + co_await f.write_some(capy::const_buffer(buf, sizeof(buf))); + BOOST_TEST(wec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(wn, 0u); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + } + void testVirtualDispatchFallback() { std::string data = "fallback"; @@ -189,6 +214,7 @@ struct native_stream_file_test testPolymorphicSlice(); testReadSome(); testWriteSome(); + testReadWriteOnClosedFile(); testVirtualDispatchFallback(); } }; diff --git a/test/unit/native/native_tcp_acceptor.cpp b/test/unit/native/native_tcp_acceptor.cpp index 31f21c941..cefe9eccf 100644 --- a/test/unit/native/native_tcp_acceptor.cpp +++ b/test/unit/native/native_tcp_acceptor.cpp @@ -227,12 +227,35 @@ struct native_tcp_acceptor_test BOOST_TEST(threw); } + // A closed acceptor's wait completes with bad_file_descriptor, + // matching the base-class contract. + void testWaitOnClosedAcceptor() + { + io_context ioc(Backend); + native_tcp_acceptor acc(ioc); + + std::error_code wait_ec; + bool wait_done = false; + + auto waiter = [&]() -> capy::task<> { + auto [ec] = co_await acc.wait(wait_type::read); + wait_ec = ec; + wait_done = true; + }; + capy::run_async(ioc.get_executor())(waiter()); + ioc.run(); + + BOOST_TEST(wait_done); + BOOST_TEST(wait_ec == std::errc::bad_file_descriptor); + } + void run() { testAcceptorConstruct(); testAcceptorMoveConstruct(); testAcceptorPolymorphicSlice(); testWait(); + testWaitOnClosedAcceptor(); testNativeAcceptReturning(); #ifdef SO_REUSEPORT testNativeReusePort(); diff --git a/test/unit/native/native_tcp_socket.cpp b/test/unit/native/native_tcp_socket.cpp index 241b0d8a9..e17ad2ea1 100644 --- a/test/unit/native/native_tcp_socket.cpp +++ b/test/unit/native/native_tcp_socket.cpp @@ -155,18 +155,51 @@ struct native_tcp_socket_test s.close(); } - void testClosedConnectCompletes() + void testConnectAutoOpens() { - // The native socket does not auto-open: connect on a closed - // socket completes with bad_file_descriptor. + // connect() auto-opens like the base class: the failure (no + // listener on the target port) comes from the connect attempt, + // not from a closed descriptor, and the socket ends up open. io_context ioc(Backend); native_tcp_socket s(ioc); + BOOST_TEST_EQ(s.is_open(), false); bool done = false; auto task = [&]() -> capy::task<> { auto [ec] = co_await s.connect( endpoint(ipv4_address::loopback(), 1)); - BOOST_TEST(ec == std::errc::bad_file_descriptor); + BOOST_TEST(ec != std::errc::bad_file_descriptor); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + BOOST_TEST(s.is_open()); + } + + // Stream operations on a closed socket complete with + // bad_file_descriptor instead of dispatching. + void testClosedOpsComplete() + { + io_context ioc(Backend); + native_tcp_socket s(ioc); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[8] = {}; + + auto [rec, rn] = co_await s.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(rec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(rn, 0u); + + auto [wec, wn] = co_await s.write_some( + capy::const_buffer(buf, sizeof(buf))); + BOOST_TEST(wec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(wn, 0u); + + auto [tec] = co_await s.wait(wait_type::read); + BOOST_TEST(tec == std::errc::bad_file_descriptor); done = true; }; capy::run_async(ioc.get_executor())(task()); @@ -176,7 +209,8 @@ struct native_tcp_socket_test void run() { - testClosedConnectCompletes(); + testConnectAutoOpens(); + testClosedOpsComplete(); testSocketConstruct(); testSocketMoveConstruct(); testSocketPolymorphicSlice(); diff --git a/test/unit/openssl_stream.cpp b/test/unit/openssl_stream.cpp index 608dcb1ea..109cdf6cc 100644 --- a/test/unit/openssl_stream.cpp +++ b/test/unit/openssl_stream.cpp @@ -170,6 +170,7 @@ struct openssl_stream_test void run() { + test::testIoBeforeHandshake(make_stream); test::testHandshakeFuse(make_stream); test::testReadWriteFuse(make_stream); test::testShutdownFuse(make_stream); diff --git a/test/unit/signal_set.cpp b/test/unit/signal_set.cpp index 0971b80a5..7396ebc70 100644 --- a/test/unit/signal_set.cpp +++ b/test/unit/signal_set.cpp @@ -786,7 +786,7 @@ struct signal_set_test BOOST_TEST(!s1.add(SIGINT, signal_set::restart)); // Second set tries to add with different flag auto result = s2.add(SIGINT, signal_set::no_defer); - BOOST_TEST(!!result); // Should fail + BOOST_TEST(result == std::errc::invalid_argument); } void testMultipleSetsWithDontCare() diff --git a/test/unit/stream_file.cpp b/test/unit/stream_file.cpp index b4a988cb9..36a1c826b 100644 --- a/test/unit/stream_file.cpp +++ b/test/unit/stream_file.cpp @@ -869,6 +869,35 @@ struct stream_file_test BOOST_TEST(f.sync_all() == std::errc::bad_file_descriptor); auto [ec, pos] = f.seek(0, file_base::seek_set); BOOST_TEST(ec == std::errc::bad_file_descriptor); + + // Async operations complete with bad_file_descriptor + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[4] = {}; + auto [rec, rn] = + co_await f.read_some(capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(rec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(rn, 0u); + auto [wec, wn] = + co_await f.write_some(capy::const_buffer(buf, sizeof(buf))); + BOOST_TEST(wec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(wn, 0u); + + // The zero-length no-op does not outrank the closed-object + // contract: closed wins on every backend. + auto [zrec, zrn] = + co_await f.read_some(capy::mutable_buffer(buf, 0)); + BOOST_TEST(zrec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(zrn, 0u); + auto [zwec, zwn] = + co_await f.write_some(capy::const_buffer(buf, 0)); + BOOST_TEST(zwec == std::errc::bad_file_descriptor); + BOOST_TEST_EQ(zwn, 0u); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); } // Negative seek validation @@ -882,11 +911,9 @@ struct stream_file_test BOOST_TEST(!f.open(tmp.path, file_base::read_only)); auto expect_negative = [](std::error_code ec) { -#if BOOST_COROSIO_POSIX + // POSIX lseek reports EINVAL; the Windows guard's + // ERROR_NEGATIVE_SEEK is normalized by make_err. BOOST_TEST(ec == std::errc::invalid_argument); -#else - BOOST_TEST(bool(ec)); -#endif }; // seek_set with negative offset diff --git a/test/unit/tcp_acceptor.cpp b/test/unit/tcp_acceptor.cpp index f75b89d70..32b877393 100644 --- a/test/unit/tcp_acceptor.cpp +++ b/test/unit/tcp_acceptor.cpp @@ -825,13 +825,9 @@ struct tcp_acceptor_test tcp_acceptor acc2(ioc); BOOST_TEST(!acc2.open()); ec = acc2.bind(endpoint(ipv4_address::loopback(), port)); -#if BOOST_COROSIO_HAS_IOCP - // MinGW's libstdc++ lacks the WSA-to-generic condition - // mapping, so the raw code is pinned on Windows. - BOOST_TEST(ec.value() == WSAEADDRINUSE); -#else + // make_err normalizes WSAEADDRINUSE, so the condition compares + // portably on every toolchain. BOOST_TEST(ec == std::errc::address_in_use); -#endif acc1.close(); acc2.close(); @@ -846,11 +842,7 @@ struct tcp_acceptor_test // Bind to an address not assigned to any local interface auto ec = acc.bind(endpoint(ipv4_address("1.2.3.4"), 0)); -#if BOOST_COROSIO_HAS_IOCP - BOOST_TEST(ec.value() == WSAEADDRNOTAVAIL); -#else BOOST_TEST(ec == std::errc::address_not_available); -#endif acc.close(); } @@ -941,7 +933,7 @@ struct tcp_acceptor_test { caught = e.code(); } - BOOST_TEST(bool(caught)); + BOOST_TEST(caught == std::errc::address_in_use); holder.close(); } diff --git a/test/unit/tls_stream_tests.hpp b/test/unit/tls_stream_tests.hpp index cd68b0ede..c5daf1e4c 100644 --- a/test/unit/tls_stream_tests.hpp +++ b/test/unit/tls_stream_tests.hpp @@ -430,6 +430,46 @@ testSocketErrorPropagation(StreamFactory make_stream) } } +/** I/O before any handshake completes with an error, never crashes. + + The engines defer session creation to the first handshake; a + read, write, or shutdown issued before then must surface a code + through the completion. +*/ +template +void +testIoBeforeHandshake(StreamFactory make_stream) +{ + io_context ioc; + auto [m, peer] = corosio::test::make_mocket_pair(ioc); + + auto ctx = make_client_context(); + auto stream = make_stream(m, ctx); + + bool done = false; + auto task = [&]() -> capy::task<> { + char buf[16]; + auto [rec, rn] = co_await stream.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + BOOST_TEST(bool(rec)); + BOOST_TEST_EQ(rn, 0u); + + auto [wec, wn] = co_await stream.write_some( + capy::const_buffer("x", 1)); + BOOST_TEST(bool(wec)); + BOOST_TEST_EQ(wn, 0u); + + auto [sec] = co_await stream.shutdown(); + BOOST_TEST(bool(sec)); + done = true; + }; + capy::run_async(ioc.get_executor())(task()); + ioc.run(); + BOOST_TEST(done); + + peer.close(); +} + /** Test certificate validation. */ template void diff --git a/test/unit/udp_socket.cpp b/test/unit/udp_socket.cpp index 5ccf2d1b2..4ed0f2fe6 100644 --- a/test/unit/udp_socket.cpp +++ b/test/unit/udp_socket.cpp @@ -314,7 +314,9 @@ struct udp_socket_test udp_socket sock2(ioc); BOOST_TEST(!sock2.open()); ec = sock2.bind(endpoint(ipv4_address::loopback(), port)); - BOOST_TEST(ec); + // make_err normalizes WSAEADDRINUSE, so the condition + // compares portably on every toolchain. + BOOST_TEST(ec == std::errc::address_in_use); sock1.close(); sock2.close(); diff --git a/test/unit/wait.cpp b/test/unit/wait.cpp index 003dfe3f1..f589f949c 100644 --- a/test/unit/wait.cpp +++ b/test/unit/wait.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -919,6 +920,52 @@ struct wait_test BOOST_TEST(wait_ec == capy::cond::canceled); } + // wait(wait_type::error) on a listening acceptor parks in the + // error-poll path (aux reactor on IOCP, POLL_ADD on io_uring); + // cancel() completes it with the canceled condition. + template + void checkAcceptorErrorWaitCancel(Endpoint ep) + { + io_context ioc(Backend); + auto ex = ioc.get_executor(); + + Acceptor acc(ioc); + BOOST_TEST(!acc.open()); + BOOST_TEST(!acc.bind(ep)); + BOOST_TEST(!acc.listen()); + + std::error_code wait_ec; + bool wait_done = false; + capy::run_async(ex)( + [](Acceptor& a, std::error_code& ec_out, + bool& done) -> capy::task<> { + auto [ec] = co_await a.wait(wait_type::error); + ec_out = ec; + done = true; + }(acc, wait_ec, wait_done)); + + // Runs after the waiter has parked: coroutines start in + // submission order on the single run thread. + capy::run_async(ex)( + [](Acceptor& a) -> capy::task<> { + a.cancel(); + co_return; + }(acc)); + + ioc.run(); + BOOST_TEST(wait_done); + BOOST_TEST(wait_ec == capy::cond::canceled); + } + + void testAcceptorErrorWaitCancel() + { + checkAcceptorErrorWaitCancel( + endpoint(ipv4_address::loopback(), 0)); + test::temp_socket_dir tmp; + checkAcceptorErrorWaitCancel( + local_endpoint(tmp.path())); + } + void run() { testWaitReadAndNoConsume(); @@ -927,6 +974,7 @@ struct wait_test testWaitWriteCancel(); testWaitWriteCancelDoesNotLeak(); testAcceptorWait(); + testAcceptorErrorWaitCancel(); testWaitOnLocalStream(); testWaitOnUdp(); testWaitReadAfterShortRead(); @@ -940,20 +988,18 @@ struct wait_test COROSIO_BACKEND_TESTS(wait_test, "boost.corosio.wait") -// Reactor-only: the readiness probe is shared by epoll/kqueue/select; -// the proactor backends resolve closed-socket waits through their own -// submission paths. +// Closed-object contract: every backend completes wait/read/write on a +// closed socket with bad_file_descriptor instead of a platform code. template struct wait_closed_test { - // Waiting on a socket that was never opened must fail instead of - // parking forever on a descriptor the reactor has never seen. - void testWaitOnUnopenedSocket() + template + void checkClosedWait() { io_context ioc(Backend); auto ex = ioc.get_executor(); - tcp_socket sock(ioc); + Socket sock(ioc); std::error_code wait_ec; bool wait_done = false; @@ -971,12 +1017,63 @@ struct wait_closed_test BOOST_TEST(wait_ec == std::errc::bad_file_descriptor); } + void testWaitOnUnopenedSocket() + { + checkClosedWait(); + checkClosedWait(); + checkClosedWait(); +#if BOOST_COROSIO_POSIX + // No local datagram sockets on Windows. + checkClosedWait(); +#endif + } + + template + void checkClosedReadWrite() + { + io_context ioc(Backend); + auto ex = ioc.get_executor(); + + Socket sock(ioc); + + std::error_code read_ec; + std::error_code write_ec; + bool done = false; + + auto io = [&]() -> capy::task<> { + char buf[4] = {}; + auto [rec, rn] = + co_await sock.read_some(capy::mutable_buffer(buf, sizeof(buf))); + read_ec = rec; + BOOST_TEST_EQ(rn, std::size_t(0)); + auto [wec, wn] = + co_await sock.write_some(capy::const_buffer(buf, sizeof(buf))); + write_ec = wec; + BOOST_TEST_EQ(wn, std::size_t(0)); + done = true; + }; + + capy::run_async(ex)(io()); + ioc.run(); + + BOOST_TEST(done); + BOOST_TEST(read_ec == std::errc::bad_file_descriptor); + BOOST_TEST(write_ec == std::errc::bad_file_descriptor); + } + + void testReadWriteOnUnopenedSocket() + { + checkClosedReadWrite(); + checkClosedReadWrite(); + } + void run() { testWaitOnUnopenedSocket(); + testReadWriteOnUnopenedSocket(); } }; -COROSIO_REACTOR_BACKEND_TESTS(wait_closed_test, "boost.corosio.wait_closed") +COROSIO_BACKEND_TESTS(wait_closed_test, "boost.corosio.wait_closed") } // namespace boost::corosio diff --git a/test/unit/wolfssl_stream.cpp b/test/unit/wolfssl_stream.cpp index 8405dab8d..806dfacca 100644 --- a/test/unit/wolfssl_stream.cpp +++ b/test/unit/wolfssl_stream.cpp @@ -157,6 +157,7 @@ struct wolfssl_stream_test void run() { + test::testIoBeforeHandshake(make_stream); test::testHandshakeFuse(make_stream); test::testReadWriteFuse(make_stream); test::testShutdownFuse(make_stream); From 7800036ae93a59005bed02d5acaac870fcafcc7a Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Fri, 21 Aug 2026 18:46:32 +0200 Subject: [PATCH 4/5] refactor(api): edge-surface coherence, make_* factories, [[nodiscard]] initiators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the remaining channel inconsistencies the error-handling audit found and completes the discard guard on the async surface: - parse_endpoint, parse_ipv4_address, and parse_ipv6_address were the library's last payload out-params — and with the payload in the return value they are factories, so they take asio's name for the operation: make_endpoint, make_ipv4_address, make_ipv6_address, returning io_result with a documented, equality-tested default-constructed payload on failure. The throwing constructors stay as the convenience over the factories; the parser bodies survive byte-identical as file-local helpers. - ipv4_address and ipv6_address string constructors threw plain std::invalid_argument while endpoint(string_view) threw std::system_error for the same condition; all three now throw system_error carrying the deterministic invalid_argument contract. - connect_pair called closed-ness a Precondition while its noexcept body returned raw EISCONN/WSAEISCONN; the rejection is now a documented expected condition generating errc::already_connected. - mocket::close() was the one close() returning an error code; verification moves to a [[nodiscard]] verify() and close() keeps the library-wide idempotent void noexcept contract. - set_verify_callback joins its three record-and-defer siblings on the void shape. - local_stream_acceptor gains the (ctx, ep, backlog) convenience constructor tcp_acceptor already had — open + bind + listen, no reuse_address (AF_UNIX conflicts are path collisions), no implicit unlink (deleting a file is not a safe constructor default). - Every awaitable-returning initiator is [[nodiscard]] — a discarded awaitable is a silent no-op — and cancel() is noexcept across the public surface, matching the not-actionable classification. - temp_socket_dir includes the collected filesystem error in its thrown message. - Deliberate discards are std::ignore = expr repository-wide (~195 sites: awaited io_results in tests, best-effort setsockopt in the reactor traits, the TLS engine driver) and unused names carry [[maybe_unused]] on the declaration (~225 sites: partially consumed structured bindings, signature-preserving parameters, #if-gated uses) — no void cast remains, so the two intents are never spelled alike. Coverage: make_endpoint's invalid-v4-with-port and unbracketed-v6 arms, detect_endpoint_format on empty input, two embedded-IPv4 octet validator arms, and local_stream_acceptor::open's already-open no-op. - host_name() returns io_result — it was the one remaining throwing-only surface whose failure the caller cannot pre-check (gethostname is a runtime condition), which made it the last obstacle to fully exception-free use of the library. Errors now ride the return; only allocation can throw. --- .../ROOT/pages/4.guide/4f.endpoints.adoc | 15 +- example/client/http_client.cpp | 4 +- example/hash-server/hash_server.cpp | 4 +- example/https-client/https_client.cpp | 4 +- example/reconnect/reconnect.cpp | 4 +- example/tls_context_examples.cpp | 27 +- include/boost/corosio/detail/scheduler.hpp | 3 +- include/boost/corosio/detail/thread_pool.hpp | 5 +- .../boost/corosio/detail/timer_service.hpp | 3 +- include/boost/corosio/endpoint.hpp | 43 +-- include/boost/corosio/host_name.hpp | 16 +- include/boost/corosio/io/io_read_stream.hpp | 2 +- include/boost/corosio/io/io_signal_set.hpp | 8 +- include/boost/corosio/io/io_write_stream.hpp | 2 +- include/boost/corosio/io_context.hpp | 6 +- include/boost/corosio/ipv4_address.hpp | 21 +- include/boost/corosio/ipv6_address.hpp | 23 +- include/boost/corosio/local_connect_pair.hpp | 12 +- .../boost/corosio/local_datagram_socket.hpp | 23 +- .../boost/corosio/local_stream_acceptor.hpp | 38 ++- include/boost/corosio/local_stream_socket.hpp | 7 +- .../native/detail/epoll/epoll_traits.hpp | 5 +- .../corosio/native/detail/iocp/win_signal.hpp | 2 +- .../native/detail/iocp/win_signals.hpp | 2 +- .../detail/iocp/win_tcp_acceptor_service.hpp | 3 +- .../native/detail/kqueue/kqueue_traits.hpp | 5 +- .../native/detail/posix/posix_signal.hpp | 2 +- .../detail/posix/posix_signal_service.hpp | 2 +- .../detail/reactor/reactor_scheduler.hpp | 3 +- .../native/detail/select/select_traits.hpp | 5 +- .../native/native_local_datagram_socket.hpp | 18 +- .../native/native_local_stream_acceptor.hpp | 4 +- .../native/native_local_stream_socket.hpp | 6 +- .../native/native_random_access_file.hpp | 4 +- .../boost/corosio/native/native_resolver.hpp | 8 +- .../corosio/native/native_signal_set.hpp | 2 +- .../corosio/native/native_stream_file.hpp | 4 +- .../corosio/native/native_tcp_acceptor.hpp | 4 +- .../corosio/native/native_tcp_socket.hpp | 6 +- .../corosio/native/native_udp_socket.hpp | 18 +- include/boost/corosio/openssl_stream.hpp | 4 +- include/boost/corosio/random_access_file.hpp | 6 +- include/boost/corosio/resolver.hpp | 10 +- include/boost/corosio/signal_set.hpp | 2 +- include/boost/corosio/stream_file.hpp | 2 +- include/boost/corosio/tcp_acceptor.hpp | 6 +- include/boost/corosio/tcp_socket.hpp | 7 +- include/boost/corosio/test/mocket.hpp | 52 ++-- include/boost/corosio/test/temp_path.hpp | 3 +- include/boost/corosio/tls_context.hpp | 15 +- include/boost/corosio/tls_stream.hpp | 8 +- include/boost/corosio/udp_socket.hpp | 23 +- include/boost/corosio/wolfssl_stream.hpp | 4 +- perf/bench/asio/callback/fan_out_bench.cpp | 4 +- perf/bench/asio/coroutine/fan_out_bench.cpp | 24 +- perf/bench/corosio/accept_churn_bench.cpp | 19 +- perf/bench/corosio/fan_out_bench.cpp | 28 +- src/corosio/src/endpoint.cpp | 25 +- src/corosio/src/host_name.cpp | 47 ++- src/corosio/src/io_context.cpp | 15 +- src/corosio/src/ipv4_address.cpp | 20 +- src/corosio/src/ipv6_address.cpp | 39 ++- src/corosio/src/local_connect_pair.cpp | 10 +- src/corosio/src/local_datagram_socket.cpp | 2 +- src/corosio/src/local_stream_acceptor.cpp | 14 +- src/corosio/src/local_stream_socket.cpp | 2 +- src/corosio/src/random_access_file.cpp | 2 +- src/corosio/src/resolver.cpp | 2 +- src/corosio/src/signal_set.cpp | 2 +- src/corosio/src/stream_file.cpp | 2 +- src/corosio/src/tcp_acceptor.cpp | 2 +- src/corosio/src/tcp_socket.cpp | 2 +- src/corosio/src/tls/detail/engine_driver.hpp | 3 +- src/corosio/src/tls/detail/engine_types.hpp | 5 +- src/corosio/src/udp_socket.cpp | 2 +- src/wolfssl/src/detail/engine.cpp | 4 +- test/doc/snippets/3d_tls_context.cpp | 5 +- test/doc/snippets/4f_endpoints.cpp | 38 ++- test/doc/snippets/5a_mocket.cpp | 21 +- test/doc/snippets/5c_patterns.cpp | 15 +- test/unit/connect.cpp | 18 +- test/unit/datagram_paths.cpp | 45 ++- test/unit/delay.cpp | 35 +-- test/unit/endpoint.cpp | 82 +++-- test/unit/error_conditions.cpp | 12 +- test/unit/host_name.cpp | 19 +- test/unit/io_context.cpp | 7 +- test/unit/ipv4_address.cpp | 26 +- test/unit/ipv6_address.cpp | 67 ++-- test/unit/local_connect_pair.cpp | 4 +- test/unit/local_datagram_socket.cpp | 23 +- test/unit/local_stream_socket.cpp | 105 ++++--- test/unit/native/native_io_context.cpp | 3 +- .../native/native_local_datagram_socket.cpp | 4 +- .../native/native_local_stream_socket.cpp | 14 +- test/unit/native/native_resolver.cpp | 3 +- test/unit/native/native_signal_set.cpp | 6 +- test/unit/native/native_tcp_acceptor.cpp | 9 +- test/unit/native/native_tcp_socket.cpp | 9 +- test/unit/native/native_udp_socket.cpp | 13 +- test/unit/openssl_stream.cpp | 6 +- test/unit/precancel.cpp | 36 +-- test/unit/random_access_file.cpp | 4 +- test/unit/reactor_paths.cpp | 103 +++---- test/unit/resolver.cpp | 27 +- test/unit/signal_set.cpp | 55 ++-- test/unit/socket_option.cpp | 3 +- test/unit/socket_stress.cpp | 40 ++- test/unit/tcp_acceptor.cpp | 58 ++-- test/unit/tcp_server.cpp | 38 ++- test/unit/tcp_socket.cpp | 59 ++-- test/unit/test/mocket.cpp | 19 +- test/unit/test_utils.hpp | 18 +- test/unit/timeout.cpp | 20 +- test/unit/tls_stream_stress.cpp | 5 +- test/unit/tls_stream_tests.hpp | 285 +++++++++--------- test/unit/udp_socket.cpp | 33 +- test/unit/wait.cpp | 77 ++--- test/unit/wolfssl_stream.cpp | 6 +- 119 files changed, 1115 insertions(+), 1150 deletions(-) diff --git a/doc/modules/ROOT/pages/4.guide/4f.endpoints.adoc b/doc/modules/ROOT/pages/4.guide/4f.endpoints.adoc index 175fff91d..d49f521be 100644 --- a/doc/modules/ROOT/pages/4.guide/4f.endpoints.adoc +++ b/doc/modules/ROOT/pages/4.guide/4f.endpoints.adoc @@ -118,21 +118,22 @@ include::example$snippets/4f_endpoints.cpp[tag=broadcast,indent=0] == Parsing Addresses -Parse addresses from strings. Parsing reports failure through an -`std::error_code` out-parameter: +Create addresses from strings. The `make_*` factories destructure into +the error code and the parsed value, the same idiom every corosio +operation uses: [source,cpp] ---- -include::example$snippets/4f_endpoints.cpp[tag=parse_addresses,indent=0] +include::example$snippets/4f_endpoints.cpp[tag=make_addresses,indent=0] ---- -You can also parse a full `address:port` string directly into an endpoint -using `parse_endpoint()`, or the `endpoint` constructor that accepts a -`std::string_view`: +You can also create an endpoint from a full `address:port` string +using `make_endpoint()`, or the `endpoint` constructor that accepts a +`std::string_view` and throws on failure: [source,cpp] ---- -include::example$snippets/4f_endpoints.cpp[tag=parse_endpoint,indent=0] +include::example$snippets/4f_endpoints.cpp[tag=make_endpoint,indent=0] ---- == Comparison diff --git a/example/client/http_client.cpp b/example/client/http_client.cpp index 4f0fbd8a1..90f796790 100644 --- a/example/client/http_client.cpp +++ b/example/client/http_client.cpp @@ -107,8 +107,8 @@ main(int argc, char* argv[]) } // Parse IP address - corosio::ipv4_address addr; - if (auto ec = corosio::parse_ipv4_address(argv[1], addr); ec) + auto [aec, addr] = corosio::make_ipv4_address(argv[1]); + if (aec) { std::cerr << "Invalid IP address: " << argv[1] << "\n"; return EXIT_FAILURE; diff --git a/example/hash-server/hash_server.cpp b/example/hash-server/hash_server.cpp index 786469229..9d3ee7b70 100644 --- a/example/hash-server/hash_server.cpp +++ b/example/hash-server/hash_server.cpp @@ -89,11 +89,9 @@ do_session( // Send hex result back to client (on io_context) auto result = to_hex( hash ) + "\n"; - auto [wec, wn] = co_await capy::write( + [[maybe_unused]] auto [wec, wn] = co_await capy::write( sock, capy::const_buffer( result.data(), result.size() ) ); - (void)wec; - (void)wn; sock.close(); } diff --git a/example/https-client/https_client.cpp b/example/https-client/https_client.cpp index f0d3c905b..9bd7860da 100644 --- a/example/https-client/https_client.cpp +++ b/example/https-client/https_client.cpp @@ -123,8 +123,8 @@ main(int argc, char* argv[]) std::string hostname = (argc == 4) ? argv[3] : argv[1]; // Parse IP address - corosio::ipv4_address addr; - if (auto ec = corosio::parse_ipv4_address(argv[1], addr); ec) + auto [aec, addr] = corosio::make_ipv4_address(argv[1]); + if (aec) { std::cerr << "Invalid IP address: " << argv[1] << "\n"; return EXIT_FAILURE; diff --git a/example/reconnect/reconnect.cpp b/example/reconnect/reconnect.cpp index 9fea849fb..8d6c8780c 100644 --- a/example/reconnect/reconnect.cpp +++ b/example/reconnect/reconnect.cpp @@ -187,8 +187,8 @@ main(int argc, char* argv[]) } // Parse IP address - corosio::ipv4_address addr; - if (auto ec = corosio::parse_ipv4_address(argv[1], addr); ec) + auto [aec, addr] = corosio::make_ipv4_address(argv[1]); + if (aec) { std::cerr << "Invalid IP address: " << argv[1] << "\n"; return EXIT_FAILURE; diff --git a/example/tls_context_examples.cpp b/example/tls_context_examples.cpp index c70ba3756..6265fd123 100644 --- a/example/tls_context_examples.cpp +++ b/example/tls_context_examples.cpp @@ -146,10 +146,9 @@ tls_context make_server_encrypted_key() // Set password callback before loading encrypted key ctx.set_password_callback( - []( std::size_t max_len, tls_password_purpose purpose ) + []( [[maybe_unused]] std::size_t max_len, + [[maybe_unused]] tls_password_purpose purpose ) { - (void)max_len; - (void)purpose; // Read from environment or secret manager char const* pw = std::getenv( "TLS_KEY_PASSWORD" ); return std::string( pw ? pw : "" ); @@ -283,7 +282,7 @@ tls_context make_client_custom_verify( std::span pin ) must(ctx.set_default_verify_paths()); must(ctx.set_verify_mode( tls_verify_mode::peer )); - must(ctx.set_verify_callback( + ctx.set_verify_callback( [pin]( bool preverified, verify_context& verify_ctx ) -> bool { // Require the chain to verify normally first. @@ -297,7 +296,7 @@ tls_context make_client_custom_verify( std::span pin ) auto der = verify_ctx.certificate(); return der.size() == pin.size() && std::equal( der.begin(), der.end(), pin.begin() ); - })); + }); return ctx; } @@ -379,17 +378,14 @@ void demonstrate_sharing() must(original.set_verify_mode( tls_verify_mode::peer )); // Share via copy - both point to same underlying state - tls_context copy1 = original; - tls_context copy2 = original; - (void)copy1; - (void)copy2; + [[maybe_unused]] tls_context copy1 = original; + [[maybe_unused]] tls_context copy2 = original; // Changes to copy1 affect copy2 and original // (they all share the same impl) // Move transfers ownership - tls_context moved = std::move( original ); - (void)moved; + [[maybe_unused]] tls_context moved = std::move( original ); // original is now empty } @@ -458,12 +454,9 @@ int main() // These examples demonstrate API ergonomics try { - auto https = make_https_client(); - auto server = make_basic_server(); - auto mtls = make_mtls_server(); - (void)https; - (void)server; - (void)mtls; + [[maybe_unused]] auto https = make_https_client(); + [[maybe_unused]] auto server = make_basic_server(); + [[maybe_unused]] auto mtls = make_mtls_server(); } catch( std::exception const& e ) { diff --git a/include/boost/corosio/detail/scheduler.hpp b/include/boost/corosio/detail/scheduler.hpp index 59425467f..9ba41b485 100644 --- a/include/boost/corosio/detail/scheduler.hpp +++ b/include/boost/corosio/detail/scheduler.hpp @@ -100,9 +100,8 @@ struct BOOST_COROSIO_DECL scheduler @return The error code, empty on success. */ [[nodiscard]] virtual std::error_code - register_signal_reader(int read_fd) + register_signal_reader([[maybe_unused]] int read_fd) { - (void)read_fd; return {}; } diff --git a/include/boost/corosio/detail/thread_pool.hpp b/include/boost/corosio/detail/thread_pool.hpp index b5e382fcb..d7db11749 100644 --- a/include/boost/corosio/detail/thread_pool.hpp +++ b/include/boost/corosio/detail/thread_pool.hpp @@ -103,9 +103,10 @@ class thread_pool final : public capy::execution_context::service @throws std::logic_error If `num_threads` is 0. */ - explicit thread_pool(capy::execution_context& ctx, unsigned num_threads = 1) + explicit thread_pool( + [[maybe_unused]] capy::execution_context& ctx, + unsigned num_threads = 1) { - (void)ctx; if (!num_threads) throw std::logic_error("thread_pool requires at least 1 thread"); threads_.reserve(num_threads); diff --git a/include/boost/corosio/detail/timer_service.hpp b/include/boost/corosio/detail/timer_service.hpp index f9f599cad..82e7f2b2b 100644 --- a/include/boost/corosio/detail/timer_service.hpp +++ b/include/boost/corosio/detail/timer_service.hpp @@ -238,8 +238,7 @@ struct tl_cache_owner inline void arm_tl_cache_cleanup() noexcept { - thread_local tl_cache_owner owner; - (void)owner; + [[maybe_unused]] thread_local tl_cache_owner owner; } inline timer::implementation* diff --git a/include/boost/corosio/endpoint.hpp b/include/boost/corosio/endpoint.hpp index 972130631..be8bf3b6b 100644 --- a/include/boost/corosio/endpoint.hpp +++ b/include/boost/corosio/endpoint.hpp @@ -15,6 +15,8 @@ #include #include +#include + #include #include #include @@ -46,11 +48,10 @@ namespace boost::corosio { // Port only (defaults to IPv4 any address) endpoint bind_addr(8080); - // Parse from string - endpoint ep; - if (auto ec = parse_endpoint("192.168.1.1:8080", ep); !ec) { - // use ep - } + // Create from string + auto [ec, ep] = make_endpoint("192.168.1.1:8080"); + if (ec) + return; @endcode */ class endpoint @@ -245,7 +246,7 @@ class endpoint /** Endpoint format detection result. - Used internally by parse_endpoint to determine + Used internally by make_endpoint to determine the format of an endpoint string. */ enum class endpoint_format @@ -272,7 +273,7 @@ enum class endpoint_format BOOST_COROSIO_DECL endpoint_format detect_endpoint_format(std::string_view s) noexcept; -/** Parse an endpoint from a string. +/** Create an endpoint from a string. This function parses an endpoint string in one of the following formats: @@ -284,30 +285,30 @@ endpoint_format detect_endpoint_format(std::string_view s) noexcept; @par Example @code - endpoint ep; - if (auto ec = parse_endpoint("192.168.1.1:8080", ep); !ec) { - // ep.is_v4() == true - // ep.port() == 8080 - } + auto [ec, ep] = make_endpoint("192.168.1.1:8080"); + if (ec) + return; + assert( ep.is_v4() && ep.port() == 8080 ); - if (auto ec = parse_endpoint("[::1]:443", ep); !ec) { - // ep.is_v6() == true - // ep.port() == 443 - } + auto [ec6, ep6] = make_endpoint("[::1]:443"); + if (ec6) + return; + assert( ep6.is_v6() && ep6.port() == 443 ); @endcode @param s The string to parse. - @param ep The endpoint to store the result. - @return An error code (empty on success). + @return The error code, empty on success, and the parsed + endpoint — default-constructed on failure. */ -[[nodiscard]] BOOST_COROSIO_DECL std::error_code -parse_endpoint(std::string_view s, endpoint& ep) noexcept; +[[nodiscard]] BOOST_COROSIO_DECL capy::io_result +make_endpoint(std::string_view s) noexcept; inline endpoint::endpoint(std::string_view s) { - auto ec = parse_endpoint(s, *this); + auto [ec, ep] = make_endpoint(s); if (ec) detail::throw_system_error(ec); + *this = ep; } } // namespace boost::corosio diff --git a/include/boost/corosio/host_name.hpp b/include/boost/corosio/host_name.hpp index 12198f55b..76fbbcd6e 100644 --- a/include/boost/corosio/host_name.hpp +++ b/include/boost/corosio/host_name.hpp @@ -12,6 +12,8 @@ #include +#include + #include namespace boost::corosio { @@ -27,20 +29,20 @@ namespace boost::corosio { been initialized. @par Exception Safety - Strong guarantee. + Strong guarantee; throws only on allocation failure. @par Example @code - std::string h = boost::corosio::host_name(); + auto [ec, h] = boost::corosio::host_name(); + if (ec) + return; std::cout << "running on " << h << "\n"; @endcode - @return The hostname as a UTF-8 string. - - @throws std::system_error If the underlying system call fails. + @return The error code, empty on success, and the hostname as a + UTF-8 string — empty on failure. */ -BOOST_COROSIO_DECL -std::string +[[nodiscard]] BOOST_COROSIO_DECL capy::io_result host_name(); } // namespace boost::corosio diff --git a/include/boost/corosio/io/io_read_stream.hpp b/include/boost/corosio/io/io_read_stream.hpp index bea9218ef..d1bd4a0e7 100644 --- a/include/boost/corosio/io/io_read_stream.hpp +++ b/include/boost/corosio/io/io_read_stream.hpp @@ -108,7 +108,7 @@ class BOOST_COROSIO_DECL io_read_stream : virtual public io_object @see io_stream::write_some */ template - auto read_some(MB const& buffers) + [[nodiscard]] auto read_some(MB const& buffers) { return read_some_awaitable(*this, buffers); } diff --git a/include/boost/corosio/io/io_signal_set.hpp b/include/boost/corosio/io/io_signal_set.hpp index 187822f9d..1d7c21b14 100644 --- a/include/boost/corosio/io/io_signal_set.hpp +++ b/include/boost/corosio/io/io_signal_set.hpp @@ -97,7 +97,7 @@ class BOOST_COROSIO_DECL io_signal_set : public io_object Cancelled waiters complete with an error that compares equal to `capy::cond::canceled`. */ - virtual void cancel() = 0; + virtual void cancel() noexcept = 0; }; /** Cancel all operations associated with the signal set. @@ -108,7 +108,7 @@ class BOOST_COROSIO_DECL io_signal_set : public io_object Cancellation does not alter the set of registered signals. */ - void cancel() + void cancel() noexcept { do_cancel(); } @@ -126,14 +126,14 @@ class BOOST_COROSIO_DECL io_signal_set : public io_object Returns the signal number when a signal is delivered, or an error code on failure. */ - auto wait() + [[nodiscard]] auto wait() { return wait_awaitable(*this); } protected: /** Dispatch cancel to the concrete implementation. */ - virtual void do_cancel() = 0; + virtual void do_cancel() noexcept = 0; explicit io_signal_set(handle h) noexcept : io_object(std::move(h)) {} diff --git a/include/boost/corosio/io/io_write_stream.hpp b/include/boost/corosio/io/io_write_stream.hpp index d84ee807c..cc09f2724 100644 --- a/include/boost/corosio/io/io_write_stream.hpp +++ b/include/boost/corosio/io/io_write_stream.hpp @@ -108,7 +108,7 @@ class BOOST_COROSIO_DECL io_write_stream : virtual public io_object @see io_stream::read_some */ template - auto write_some(CB const& buffers) + [[nodiscard]] auto write_some(CB const& buffers) { return write_some_awaitable(*this, buffers); } diff --git a/include/boost/corosio/io_context.hpp b/include/boost/corosio/io_context.hpp index 51f0b32d1..62fdd44ae 100644 --- a/include/boost/corosio/io_context.hpp +++ b/include/boost/corosio/io_context.hpp @@ -292,12 +292,11 @@ class BOOST_COROSIO_DECL io_context : public capy::execution_context template requires requires { Backend::construct; } explicit io_context( - Backend backend, + [[maybe_unused]] Backend backend, unsigned concurrency_hint = std::thread::hardware_concurrency()) : capy::execution_context(this) , sched_(nullptr) { - (void)backend; sched_ = &Backend::construct(*this, concurrency_hint); // Apply threading config only (locking tier). Unlike the options // ctor, the plain path leaves the reactor budget at its defaults. @@ -316,13 +315,12 @@ class BOOST_COROSIO_DECL io_context : public capy::execution_context template requires requires { Backend::construct; } explicit io_context( - Backend backend, + [[maybe_unused]] Backend backend, io_context_options const& opts, unsigned concurrency_hint = std::thread::hardware_concurrency()) : capy::execution_context(this) , sched_(nullptr) { - (void)backend; apply_options_pre_(opts); // Effective hint (1 for lockless tiers); see effective_concurrency_hint. unsigned const eff = diff --git a/include/boost/corosio/ipv4_address.hpp b/include/boost/corosio/ipv4_address.hpp index 53fc91595..5ccb40698 100644 --- a/include/boost/corosio/ipv4_address.hpp +++ b/include/boost/corosio/ipv4_address.hpp @@ -12,6 +12,8 @@ #include +#include + #include #include #include @@ -43,7 +45,7 @@ namespace boost::corosio { >3.2.2. Host (rfc3986) @see - @ref parse_ipv4_address, + @ref make_ipv4_address, @ref ipv6_address. */ class BOOST_COROSIO_DECL ipv4_address @@ -110,12 +112,13 @@ class BOOST_COROSIO_DECL ipv4_address is thrown. @note For a non-throwing parse function, - use @ref parse_ipv4_address. + use @ref make_ipv4_address. @par Exception Safety Exceptions thrown on invalid input. - @throw std::invalid_argument The input failed to parse correctly. + @throws std::system_error `errc::invalid_argument` if the input + failed to parse correctly. @param s The string to parse. @@ -124,7 +127,7 @@ class BOOST_COROSIO_DECL ipv4_address >3.2.2. Host (rfc3986) @see - @ref parse_ipv4_address. + @ref make_ipv4_address. */ explicit ipv4_address(std::string_view s); @@ -249,14 +252,14 @@ class BOOST_COROSIO_DECL ipv4_address std::size_t print_impl(char* dest) const noexcept; }; -/** Return an IPv4 address from an IP address string in dotted decimal form. +/** Create an IPv4 address from an IP address string in dotted decimal form. @param s The string to parse. - @param addr The address to store the result. - @return An error code (empty on success). + @return The error code, empty on success, and the parsed + address — default-constructed on failure. */ -[[nodiscard]] BOOST_COROSIO_DECL std::error_code -parse_ipv4_address(std::string_view s, ipv4_address& addr) noexcept; +[[nodiscard]] BOOST_COROSIO_DECL capy::io_result +make_ipv4_address(std::string_view s) noexcept; } // namespace boost::corosio diff --git a/include/boost/corosio/ipv6_address.hpp b/include/boost/corosio/ipv6_address.hpp index b1e9a8d41..ca5b71ff9 100644 --- a/include/boost/corosio/ipv6_address.hpp +++ b/include/boost/corosio/ipv6_address.hpp @@ -12,6 +12,8 @@ #include +#include + #include #include #include @@ -54,7 +56,7 @@ class ipv4_address; @see @ref ipv4_address, - @ref parse_ipv6_address. + @ref make_ipv6_address. */ class BOOST_COROSIO_DECL ipv6_address { @@ -134,13 +136,13 @@ class BOOST_COROSIO_DECL ipv6_address is thrown. @note For a non-throwing parse function, - use @ref parse_ipv6_address. + use @ref make_ipv6_address. @par Exception Safety Exceptions thrown on invalid input. - @throw std::invalid_argument - The input failed to parse correctly. + @throws std::system_error `errc::invalid_argument` if the input + failed to parse correctly. @param s The string to parse. @@ -149,7 +151,7 @@ class BOOST_COROSIO_DECL ipv6_address >3.2.2. Host (rfc3986) @see - @ref parse_ipv6_address. + @ref make_ipv6_address. */ explicit ipv6_address(std::string_view s); @@ -316,7 +318,7 @@ class BOOST_COROSIO_DECL ipv6_address std::size_t print_impl(char* dest) const noexcept; }; -/** Parse a string containing an IPv6 address. +/** Create an IPv6 address from a string. This function attempts to parse the string as an IPv6 address and returns an error code @@ -325,13 +327,12 @@ class BOOST_COROSIO_DECL ipv6_address @par Exception Safety Throws nothing. - @return An error code (empty on success). - @param s The string to parse. - @param addr The address to store the result. + @return The error code, empty on success, and the parsed + address — default-constructed on failure. */ -[[nodiscard]] BOOST_COROSIO_DECL std::error_code -parse_ipv6_address(std::string_view s, ipv6_address& addr) noexcept; +[[nodiscard]] BOOST_COROSIO_DECL capy::io_result +make_ipv6_address(std::string_view s) noexcept; } // namespace boost::corosio diff --git a/include/boost/corosio/local_connect_pair.hpp b/include/boost/corosio/local_connect_pair.hpp index ffafc3259..66b740366 100644 --- a/include/boost/corosio/local_connect_pair.hpp +++ b/include/boost/corosio/local_connect_pair.hpp @@ -35,12 +35,12 @@ namespace boost::corosio { base reference selects the backend's `assign_socket` through normal virtual dispatch. - @par Preconditions - Both sockets must be in the closed state. + An already-open socket is rejected with + `errc::already_connected`; both sockets are left untouched. @par Exception Safety - Nothrow. On failure both sockets remain closed and any underlying - resources are released. + Nothrow. On failure closed sockets remain closed and any + underlying resources are released. @param a Receives the accepted/first endpoint of the pair. @param b Receives the connected/second endpoint of the pair. @@ -57,8 +57,8 @@ connect_pair(local_stream_socket& a, local_stream_socket& b) noexcept; POSIX only. Uses `socketpair(AF_UNIX, SOCK_DGRAM)` and adopts the descriptors via `assign()`. - @par Preconditions - Both sockets must be in the closed state. + An already-open socket is rejected with + `errc::already_connected`; both sockets are left untouched. @par Exception Safety Nothrow. diff --git a/include/boost/corosio/local_datagram_socket.hpp b/include/boost/corosio/local_datagram_socket.hpp index 1dd13ce62..fc13bfaa6 100644 --- a/include/boost/corosio/local_datagram_socket.hpp +++ b/include/boost/corosio/local_datagram_socket.hpp @@ -572,7 +572,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object If the socket needs to be opened and the open fails, the awaitable completes immediately with that error. */ - auto connect(corosio::local_endpoint ep) + [[nodiscard]] auto connect(corosio::local_endpoint ep) { connect_awaitable aw(*this, ep); if (!is_open()) @@ -617,7 +617,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto send_to( + [[nodiscard]] auto send_to( Buffers const& buf, corosio::local_endpoint dest, corosio::message_flags flags) @@ -630,7 +630,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object /// @overload template - auto send_to(Buffers const& buf, corosio::local_endpoint dest) + [[nodiscard]] auto send_to(Buffers const& buf, corosio::local_endpoint dest) { return send_to(buf, dest, corosio::message_flags::none); } @@ -657,7 +657,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto recv_from( + [[nodiscard]] auto recv_from( Buffers const& buf, corosio::local_endpoint& source, corosio::message_flags flags) @@ -670,7 +670,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object /// @overload template - auto recv_from(Buffers const& buf, corosio::local_endpoint& source) + [[nodiscard]] auto recv_from(Buffers const& buf, corosio::local_endpoint& source) { return recv_from(buf, source, corosio::message_flags::none); } @@ -691,7 +691,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto send(Buffers const& buf, corosio::message_flags flags) + [[nodiscard]] auto send(Buffers const& buf, corosio::message_flags flags) { send_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) @@ -701,7 +701,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object /// @overload template - auto send(Buffers const& buf) + [[nodiscard]] auto send(Buffers const& buf) { return send(buf, corosio::message_flags::none); } @@ -722,7 +722,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto recv(Buffers const& buf, corosio::message_flags flags) + [[nodiscard]] auto recv(Buffers const& buf, corosio::message_flags flags) { recv_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) @@ -732,7 +732,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object /// @overload template - auto recv(Buffers const& buf) + [[nodiscard]] auto recv(Buffers const& buf) { return recv(buf, corosio::message_flags::none); } @@ -743,7 +743,7 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object errc::operation_canceled. Check ec == cond::canceled for portable comparison. */ - void cancel(); + void cancel() noexcept; /** Get the native socket handle. @@ -894,7 +894,8 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object } private: - std::error_code open_for_family(int family, int type, int protocol) noexcept; + [[nodiscard]] std::error_code + open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/local_stream_acceptor.hpp b/include/boost/corosio/local_stream_acceptor.hpp index 0fbbc2a2b..223bb7e58 100644 --- a/include/boost/corosio/local_stream_acceptor.hpp +++ b/include/boost/corosio/local_stream_acceptor.hpp @@ -193,6 +193,22 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object */ explicit local_stream_acceptor(capy::execution_context& ctx); + /** Convenience constructor: open + bind + listen. + + Creates a fully-bound listening acceptor in a single + expression. + + @param ctx The execution context that will own this acceptor. + @param ep The local endpoint to bind to. + @param backlog The maximum pending connection queue length. + + @throws std::system_error on open, bind, or listen failure. + */ + local_stream_acceptor( + capy::execution_context& ctx, + corosio::local_endpoint ep, + int backlog = 128); + /** Construct an acceptor from an executor. The acceptor is associated with the executor's context. @@ -210,6 +226,22 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object { } + /** Convenience constructor from an executor. + + @param ex The executor whose context will own the acceptor. + @param ep The local endpoint to bind to. + @param backlog The maximum pending connection queue length. + + @throws std::system_error on open, bind, or listen failure. + */ + template + requires capy::Executor + local_stream_acceptor( + Ex const& ex, corosio::local_endpoint ep, int backlog = 128) + : local_stream_acceptor(ex.context(), std::move(ep), backlog) + { + } + /** Move constructor. Transfers ownership of the acceptor resources. @@ -322,7 +354,7 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object A closed acceptor reports `errc::bad_file_descriptor`. */ - auto accept(local_stream_socket& peer) + [[nodiscard]] auto accept(local_stream_socket& peer) { accept_awaitable aw(*this, peer); if (!is_open()) @@ -374,7 +406,7 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object A closed acceptor reports `errc::bad_file_descriptor`. */ - auto accept() + [[nodiscard]] auto accept() { move_accept_awaitable aw(*this); if (!is_open()) @@ -388,7 +420,7 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @c capy::cond::canceled. Safe to call when no operations are pending (no-op). */ - void cancel(); + void cancel() noexcept; /** Release ownership of the native socket handle. diff --git a/include/boost/corosio/local_stream_socket.hpp b/include/boost/corosio/local_stream_socket.hpp index 65cc82508..7edcd0d9b 100644 --- a/include/boost/corosio/local_stream_socket.hpp +++ b/include/boost/corosio/local_stream_socket.hpp @@ -339,7 +339,7 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream If the socket needs to be opened and the open fails, the awaitable completes immediately with that error. */ - auto connect(corosio::local_endpoint ep) + [[nodiscard]] auto connect(corosio::local_endpoint ep) { connect_awaitable aw(*this, ep); if (!is_open()) @@ -371,7 +371,7 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream All outstanding operations complete with `errc::operation_canceled`. Check `ec == cond::canceled` for portable comparison. */ - void cancel(); + void cancel() noexcept; /** Get the native socket handle. @@ -533,7 +533,8 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream private: friend class local_stream_acceptor; - std::error_code open_for_family(int family, int type, int protocol) noexcept; + [[nodiscard]] std::error_code + open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/native/detail/epoll/epoll_traits.hpp b/include/boost/corosio/native/detail/epoll/epoll_traits.hpp index 8230bf7ab..e8a88eac8 100644 --- a/include/boost/corosio/native/detail/epoll/epoll_traits.hpp +++ b/include/boost/corosio/native/detail/epoll/epoll_traits.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -120,7 +121,7 @@ struct epoll_traits if (family == AF_INET6) { int one = 1; - (void)::setsockopt( + std::ignore = ::setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); } return {}; @@ -134,7 +135,7 @@ struct epoll_traits if (family == AF_INET6) { int val = 0; - (void)::setsockopt( + std::ignore = ::setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); } return {}; diff --git a/include/boost/corosio/native/detail/iocp/win_signal.hpp b/include/boost/corosio/native/detail/iocp/win_signal.hpp index e7038eeec..91b9a12d5 100644 --- a/include/boost/corosio/native/detail/iocp/win_signal.hpp +++ b/include/boost/corosio/native/detail/iocp/win_signal.hpp @@ -104,7 +104,7 @@ class win_signal final std::error_code add(int signal_number, signal_set::flags_t flags) override; std::error_code remove(int signal_number) override; std::error_code clear() override; - void cancel() override; + void cancel() noexcept override; }; } // namespace boost::corosio::detail diff --git a/include/boost/corosio/native/detail/iocp/win_signals.hpp b/include/boost/corosio/native/detail/iocp/win_signals.hpp index 88a5ea5e7..adfb32bba 100644 --- a/include/boost/corosio/native/detail/iocp/win_signals.hpp +++ b/include/boost/corosio/native/detail/iocp/win_signals.hpp @@ -371,7 +371,7 @@ win_signal::clear() } inline void -win_signal::cancel() +win_signal::cancel() noexcept { svc_.cancel_wait(*this); } diff --git a/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp b/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp index 0b7678a01..80c52f86d 100644 --- a/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp +++ b/include/boost/corosio/native/detail/iocp/win_tcp_acceptor_service.hpp @@ -1729,10 +1729,9 @@ win_tcp_acceptor::get_internal() const noexcept // win_tcp_acceptor_service inline win_tcp_acceptor_service::win_tcp_acceptor_service( - capy::execution_context& ctx, win_tcp_service& svc) + [[maybe_unused]] capy::execution_context& ctx, win_tcp_service& svc) : svc_(svc) { - (void)ctx; } inline io_object::implementation* diff --git a/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp b/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp index 0cf8b6dc0..190f3baa9 100644 --- a/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp +++ b/include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -193,7 +194,7 @@ struct kqueue_traits if (family == AF_INET6) { int v6only = 1; - (void)::setsockopt( + std::ignore = ::setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)); } @@ -212,7 +213,7 @@ struct kqueue_traits if (family == AF_INET6) { int val = 0; - (void)::setsockopt( + std::ignore = ::setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); } return {}; diff --git a/include/boost/corosio/native/detail/posix/posix_signal.hpp b/include/boost/corosio/native/detail/posix/posix_signal.hpp index 8024938ff..2eb88a12b 100644 --- a/include/boost/corosio/native/detail/posix/posix_signal.hpp +++ b/include/boost/corosio/native/detail/posix/posix_signal.hpp @@ -95,7 +95,7 @@ class posix_signal final std::error_code add(int signal_number, signal_set::flags_t flags) override; std::error_code remove(int signal_number) override; std::error_code clear() override; - void cancel() override; + void cancel() noexcept override; }; } // namespace detail diff --git a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp index a4dbd6c3b..d03afd606 100644 --- a/include/boost/corosio/native/detail/posix/posix_signal_service.hpp +++ b/include/boost/corosio/native/detail/posix/posix_signal_service.hpp @@ -465,7 +465,7 @@ posix_signal::clear() } inline void -posix_signal::cancel() +posix_signal::cancel() noexcept { svc_.cancel_wait(*this); } diff --git a/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp b/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp index 755eb0424..92b5fd919 100644 --- a/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp +++ b/include/boost/corosio/native/detail/reactor/reactor_scheduler.hpp @@ -952,8 +952,7 @@ reactor_scheduler::do_one( lock.unlock(); } - work_cleanup on_exit{this, &lock, ctx}; - (void)on_exit; + [[maybe_unused]] work_cleanup on_exit{this, &lock, ctx}; if (ready_is_continuation(e)) ready_as_cont(e)->h.resume(); diff --git a/include/boost/corosio/native/detail/select/select_traits.hpp b/include/boost/corosio/native/detail/select/select_traits.hpp index 1eded3024..33fca49af 100644 --- a/include/boost/corosio/native/detail/select/select_traits.hpp +++ b/include/boost/corosio/native/detail/select/select_traits.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include @@ -220,7 +221,7 @@ struct select_traits if (family == AF_INET6) { int one = 1; - (void)::setsockopt( + std::ignore = ::setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); } @@ -235,7 +236,7 @@ struct select_traits if (family == AF_INET6) { int val = 0; - (void)::setsockopt( + std::ignore = ::setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); } diff --git a/include/boost/corosio/native/native_local_datagram_socket.hpp b/include/boost/corosio/native/native_local_datagram_socket.hpp index dffd0bfae..19e0da52f 100644 --- a/include/boost/corosio/native/native_local_datagram_socket.hpp +++ b/include/boost/corosio/native/native_local_datagram_socket.hpp @@ -387,7 +387,7 @@ class native_local_datagram_socket : public local_datagram_socket dispatch. Otherwise identical to @ref local_datagram_socket::send_to. */ template - auto send_to( + [[nodiscard]] auto send_to( CB const& buffers, corosio::local_endpoint dest, corosio::message_flags flags) @@ -400,7 +400,7 @@ class native_local_datagram_socket : public local_datagram_socket /// @overload template - auto send_to(CB const& buffers, corosio::local_endpoint dest) + [[nodiscard]] auto send_to(CB const& buffers, corosio::local_endpoint dest) { return send_to(buffers, dest, corosio::message_flags::none); } @@ -411,7 +411,7 @@ class native_local_datagram_socket : public local_datagram_socket dispatch. Otherwise identical to @ref local_datagram_socket::recv_from. */ template - auto recv_from( + [[nodiscard]] auto recv_from( MB const& buffers, corosio::local_endpoint& source, corosio::message_flags flags) @@ -424,7 +424,7 @@ class native_local_datagram_socket : public local_datagram_socket /// @overload template - auto recv_from(MB const& buffers, corosio::local_endpoint& source) + [[nodiscard]] auto recv_from(MB const& buffers, corosio::local_endpoint& source) { return recv_from(buffers, source, corosio::message_flags::none); } @@ -436,7 +436,7 @@ class native_local_datagram_socket : public local_datagram_socket If the socket is not already open, it is opened automatically. */ - auto connect(corosio::local_endpoint ep) + [[nodiscard]] auto connect(corosio::local_endpoint ep) { native_connect_awaitable aw(*this, ep); if (!is_open()) @@ -450,7 +450,7 @@ class native_local_datagram_socket : public local_datagram_socket dispatch. Otherwise identical to @ref local_datagram_socket::send. */ template - auto send(CB const& buffers, corosio::message_flags flags) + [[nodiscard]] auto send(CB const& buffers, corosio::message_flags flags) { native_send_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) @@ -460,7 +460,7 @@ class native_local_datagram_socket : public local_datagram_socket /// @overload template - auto send(CB const& buffers) + [[nodiscard]] auto send(CB const& buffers) { return send(buffers, corosio::message_flags::none); } @@ -471,7 +471,7 @@ class native_local_datagram_socket : public local_datagram_socket dispatch. Otherwise identical to @ref local_datagram_socket::recv. */ template - auto recv(MB const& buffers, corosio::message_flags flags) + [[nodiscard]] auto recv(MB const& buffers, corosio::message_flags flags) { native_recv_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) @@ -481,7 +481,7 @@ class native_local_datagram_socket : public local_datagram_socket /// @overload template - auto recv(MB const& buffers) + [[nodiscard]] auto recv(MB const& buffers) { return recv(buffers, corosio::message_flags::none); } diff --git a/include/boost/corosio/native/native_local_stream_acceptor.hpp b/include/boost/corosio/native/native_local_stream_acceptor.hpp index e69fed3c1..e94dc5f7d 100644 --- a/include/boost/corosio/native/native_local_stream_acceptor.hpp +++ b/include/boost/corosio/native/native_local_stream_acceptor.hpp @@ -249,7 +249,7 @@ class native_local_stream_acceptor : public local_stream_acceptor Both this acceptor and @p peer must outlive the returned awaitable. */ - auto accept(local_stream_socket& peer) + [[nodiscard]] auto accept(local_stream_socket& peer) { native_accept_awaitable aw(*this, peer); if (!is_open()) @@ -271,7 +271,7 @@ class native_local_stream_acceptor : public local_stream_acceptor This acceptor must outlive the returned awaitable. */ - auto accept() + [[nodiscard]] auto accept() { // The awaitable builds the peer from context(), which a // moved-from acceptor no longer has. diff --git a/include/boost/corosio/native/native_local_stream_socket.hpp b/include/boost/corosio/native/native_local_stream_socket.hpp index 5022d850c..85d86f36f 100644 --- a/include/boost/corosio/native/native_local_stream_socket.hpp +++ b/include/boost/corosio/native/native_local_stream_socket.hpp @@ -283,7 +283,7 @@ class native_local_stream_socket : public local_stream_socket @return An awaitable yielding `(error_code, std::size_t)`. */ template - auto read_some(MB const& buffers) + [[nodiscard]] auto read_some(MB const& buffers) { return native_read_awaitable(*this, buffers); } @@ -298,7 +298,7 @@ class native_local_stream_socket : public local_stream_socket @return An awaitable yielding `(error_code, std::size_t)`. */ template - auto write_some(CB const& buffers) + [[nodiscard]] auto write_some(CB const& buffers) { return native_write_awaitable(*this, buffers); } @@ -317,7 +317,7 @@ class native_local_stream_socket : public local_stream_socket If the socket needs to be opened and the open fails, the awaitable completes immediately with that error. */ - auto connect(corosio::local_endpoint ep) + [[nodiscard]] auto connect(corosio::local_endpoint ep) { native_connect_awaitable aw(*this, ep); if (!is_open()) diff --git a/include/boost/corosio/native/native_random_access_file.hpp b/include/boost/corosio/native/native_random_access_file.hpp index 5172a9213..191dd4e66 100644 --- a/include/boost/corosio/native/native_random_access_file.hpp +++ b/include/boost/corosio/native/native_random_access_file.hpp @@ -214,7 +214,7 @@ class native_random_access_file : public random_access_file dispatch. Otherwise identical to @ref random_access_file::read_some_at. */ template - auto read_some_at(std::uint64_t offset, MB const& buffers) + [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers) { return native_read_at_awaitable(*this, offset, buffers); } @@ -225,7 +225,7 @@ class native_random_access_file : public random_access_file dispatch. Otherwise identical to @ref random_access_file::write_some_at. */ template - auto write_some_at(std::uint64_t offset, CB const& buffers) + [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers) { return native_write_at_awaitable(*this, offset, buffers); } diff --git a/include/boost/corosio/native/native_resolver.hpp b/include/boost/corosio/native/native_resolver.hpp index f58b53a1e..cd5e71fc6 100644 --- a/include/boost/corosio/native/native_resolver.hpp +++ b/include/boost/corosio/native/native_resolver.hpp @@ -196,7 +196,7 @@ class native_resolver : public resolver @note `resolver_results` is an alias for `std::vector`; copying it deep-copies every entry. See @ref resolver::resolve. */ - auto resolve(std::string_view host, std::string_view service) + [[nodiscard]] auto resolve(std::string_view host, std::string_view service) { return native_resolve_awaitable( *this, host, service, resolve_flags::none); @@ -212,7 +212,7 @@ class native_resolver : public resolver @return An awaitable yielding `io_result`. */ - auto resolve( + [[nodiscard]] auto resolve( std::string_view host, std::string_view service, resolve_flags flags) { return native_resolve_awaitable(*this, host, service, flags); @@ -231,7 +231,7 @@ class native_resolver : public resolver @return An awaitable yielding `io_result`. */ - auto resolve(endpoint const& ep) + [[nodiscard]] auto resolve(endpoint const& ep) { return native_reverse_awaitable(*this, ep, reverse_flags::none); } @@ -246,7 +246,7 @@ class native_resolver : public resolver @return An awaitable yielding `io_result`. */ - auto resolve(endpoint const& ep, reverse_flags flags) + [[nodiscard]] auto resolve(endpoint const& ep, reverse_flags flags) { return native_reverse_awaitable(*this, ep, flags); } diff --git a/include/boost/corosio/native/native_signal_set.hpp b/include/boost/corosio/native/native_signal_set.hpp index b4b09d6c7..789c164d6 100644 --- a/include/boost/corosio/native/native_signal_set.hpp +++ b/include/boost/corosio/native/native_signal_set.hpp @@ -147,7 +147,7 @@ class native_signal_set : public signal_set This signal set must outlive the returned awaitable. */ - auto wait() + [[nodiscard]] auto wait() { return native_wait_awaitable(*this); } diff --git a/include/boost/corosio/native/native_stream_file.hpp b/include/boost/corosio/native/native_stream_file.hpp index b456e9e77..9713ad36c 100644 --- a/include/boost/corosio/native/native_stream_file.hpp +++ b/include/boost/corosio/native/native_stream_file.hpp @@ -200,7 +200,7 @@ class native_stream_file : public stream_file dispatch. Otherwise identical to @ref io_stream::read_some. */ template - auto read_some(MB const& buffers) + [[nodiscard]] auto read_some(MB const& buffers) { return native_read_awaitable(*this, buffers); } @@ -211,7 +211,7 @@ class native_stream_file : public stream_file dispatch. Otherwise identical to @ref io_stream::write_some. */ template - auto write_some(CB const& buffers) + [[nodiscard]] auto write_some(CB const& buffers) { return native_write_awaitable(*this, buffers); } diff --git a/include/boost/corosio/native/native_tcp_acceptor.hpp b/include/boost/corosio/native/native_tcp_acceptor.hpp index a04b4dede..2b3e48d0f 100644 --- a/include/boost/corosio/native/native_tcp_acceptor.hpp +++ b/include/boost/corosio/native/native_tcp_acceptor.hpp @@ -245,7 +245,7 @@ class native_tcp_acceptor : public tcp_acceptor Both this acceptor and @p peer must outlive the returned awaitable. */ - auto accept(tcp_socket& peer) + [[nodiscard]] auto accept(tcp_socket& peer) { native_accept_awaitable aw(*this, peer); if (!is_open()) @@ -264,7 +264,7 @@ class native_tcp_acceptor : public tcp_acceptor This acceptor must outlive the returned awaitable. */ - auto accept() + [[nodiscard]] auto accept() { // The awaitable builds the peer from context(), which a // moved-from acceptor no longer has. diff --git a/include/boost/corosio/native/native_tcp_socket.hpp b/include/boost/corosio/native/native_tcp_socket.hpp index 84f55deb7..ceaf01d99 100644 --- a/include/boost/corosio/native/native_tcp_socket.hpp +++ b/include/boost/corosio/native/native_tcp_socket.hpp @@ -296,7 +296,7 @@ class native_tcp_socket : public tcp_socket completes. */ template - auto read_some(MB const& buffers) + [[nodiscard]] auto read_some(MB const& buffers) { return native_read_awaitable(*this, buffers); } @@ -315,7 +315,7 @@ class native_tcp_socket : public tcp_socket completes. */ template - auto write_some(CB const& buffers) + [[nodiscard]] auto write_some(CB const& buffers) { return native_write_awaitable(*this, buffers); } @@ -335,7 +335,7 @@ class native_tcp_socket : public tcp_socket This socket must outlive the returned awaitable. */ - auto connect(endpoint ep) + [[nodiscard]] auto connect(endpoint ep) { native_connect_awaitable aw(*this, ep); if (!is_open()) diff --git a/include/boost/corosio/native/native_udp_socket.hpp b/include/boost/corosio/native/native_udp_socket.hpp index 2bda3cfc4..f66002fcc 100644 --- a/include/boost/corosio/native/native_udp_socket.hpp +++ b/include/boost/corosio/native/native_udp_socket.hpp @@ -383,7 +383,7 @@ class native_udp_socket : public udp_socket @return An awaitable yielding `(error_code, std::size_t)`. */ template - auto send_to( + [[nodiscard]] auto send_to( CB const& buffers, endpoint dest, corosio::message_flags flags) @@ -396,7 +396,7 @@ class native_udp_socket : public udp_socket /// @overload template - auto send_to(CB const& buffers, endpoint dest) + [[nodiscard]] auto send_to(CB const& buffers, endpoint dest) { return send_to(buffers, dest, corosio::message_flags::none); } @@ -414,7 +414,7 @@ class native_udp_socket : public udp_socket @return An awaitable yielding `(error_code, std::size_t)`. */ template - auto recv_from( + [[nodiscard]] auto recv_from( MB const& buffers, endpoint& source, corosio::message_flags flags) @@ -427,7 +427,7 @@ class native_udp_socket : public udp_socket /// @overload template - auto recv_from(MB const& buffers, endpoint& source) + [[nodiscard]] auto recv_from(MB const& buffers, endpoint& source) { return recv_from(buffers, source, corosio::message_flags::none); } @@ -447,7 +447,7 @@ class native_udp_socket : public udp_socket If the socket needs to be opened and the open fails, the awaitable completes immediately with that error. */ - auto connect(endpoint ep) + [[nodiscard]] auto connect(endpoint ep) { native_connect_awaitable aw(*this, ep); if (!is_open()) @@ -468,7 +468,7 @@ class native_udp_socket : public udp_socket A closed socket reports `errc::bad_file_descriptor`. */ template - auto send(CB const& buffers, corosio::message_flags flags) + [[nodiscard]] auto send(CB const& buffers, corosio::message_flags flags) { native_send_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) @@ -478,7 +478,7 @@ class native_udp_socket : public udp_socket /// @overload template - auto send(CB const& buffers) + [[nodiscard]] auto send(CB const& buffers) { return send(buffers, corosio::message_flags::none); } @@ -496,7 +496,7 @@ class native_udp_socket : public udp_socket A closed socket reports `errc::bad_file_descriptor`. */ template - auto recv(MB const& buffers, corosio::message_flags flags) + [[nodiscard]] auto recv(MB const& buffers, corosio::message_flags flags) { native_recv_awaitable aw(*this, buffers, static_cast(flags)); if (!is_open()) @@ -506,7 +506,7 @@ class native_udp_socket : public udp_socket /// @overload template - auto recv(MB const& buffers) + [[nodiscard]] auto recv(MB const& buffers) { return recv(buffers, corosio::message_flags::none); } diff --git a/include/boost/corosio/openssl_stream.hpp b/include/boost/corosio/openssl_stream.hpp index bcf632e60..52331bbaf 100644 --- a/include/boost/corosio/openssl_stream.hpp +++ b/include/boost/corosio/openssl_stream.hpp @@ -155,7 +155,7 @@ class BOOST_COROSIO_DECL openssl_stream final : public tls_stream @return An awaitable yielding `(error_code)`. */ - capy::io_task<> handshake(tls_role role) override; + [[nodiscard]] capy::io_task<> handshake(tls_role role) override; /** Asynchronously shut down the TLS session. @@ -177,7 +177,7 @@ class BOOST_COROSIO_DECL openssl_stream final : public tls_stream @return An awaitable yielding `(error_code)`. */ - capy::io_task<> shutdown() override; + [[nodiscard]] capy::io_task<> shutdown() override; /** Reset TLS session state for reuse. diff --git a/include/boost/corosio/random_access_file.hpp b/include/boost/corosio/random_access_file.hpp index 300e61395..b9e542603 100644 --- a/include/boost/corosio/random_access_file.hpp +++ b/include/boost/corosio/random_access_file.hpp @@ -311,7 +311,7 @@ class BOOST_COROSIO_DECL random_access_file : public io_object A closed file reports `errc::bad_file_descriptor`. */ template - auto read_some_at(std::uint64_t offset, MB const& buffers) + [[nodiscard]] auto read_some_at(std::uint64_t offset, MB const& buffers) { read_some_at_awaitable aw(*this, offset, buffers); if (!is_open()) @@ -329,7 +329,7 @@ class BOOST_COROSIO_DECL random_access_file : public io_object A closed file reports `errc::bad_file_descriptor`. */ template - auto write_some_at(std::uint64_t offset, CB const& buffers) + [[nodiscard]] auto write_some_at(std::uint64_t offset, CB const& buffers) { write_some_at_awaitable aw(*this, offset, buffers); if (!is_open()) @@ -338,7 +338,7 @@ class BOOST_COROSIO_DECL random_access_file : public io_object } /** Cancel pending asynchronous operations. */ - void cancel(); + void cancel() noexcept; /** Get the native file descriptor or handle. */ native_handle_type native_handle() const noexcept; diff --git a/include/boost/corosio/resolver.hpp b/include/boost/corosio/resolver.hpp index 1895b3cc0..33c63eb6a 100644 --- a/include/boost/corosio/resolver.hpp +++ b/include/boost/corosio/resolver.hpp @@ -364,7 +364,7 @@ class BOOST_COROSIO_DECL resolver : public io_object auto [ec, results] = co_await r.resolve("www.example.com", "https"); @endcode */ - auto resolve(std::string_view host, std::string_view service) + [[nodiscard]] auto resolve(std::string_view host, std::string_view service) { return resolve_awaitable(*this, host, service, resolve_flags::none); } @@ -383,7 +383,7 @@ class BOOST_COROSIO_DECL resolver : public io_object @return An awaitable that completes with `io_result`. */ - auto resolve( + [[nodiscard]] auto resolve( std::string_view host, std::string_view service, resolve_flags flags) { return resolve_awaitable(*this, host, service, flags); @@ -409,7 +409,7 @@ class BOOST_COROSIO_DECL resolver : public io_object std::cout << result.host_name() << ":" << result.service_name(); @endcode */ - auto resolve(endpoint const& ep) + [[nodiscard]] auto resolve(endpoint const& ep) { return reverse_resolve_awaitable(*this, ep, reverse_flags::none); } @@ -428,7 +428,7 @@ class BOOST_COROSIO_DECL resolver : public io_object @return An awaitable that completes with `io_result`. */ - auto resolve(endpoint const& ep, reverse_flags flags) + [[nodiscard]] auto resolve(endpoint const& ep, reverse_flags flags) { return reverse_resolve_awaitable(*this, ep, flags); } @@ -438,7 +438,7 @@ class BOOST_COROSIO_DECL resolver : public io_object All outstanding operations complete with `errc::operation_canceled`. Check `ec == cond::canceled` for portable comparison. */ - void cancel(); + void cancel() noexcept; public: /** Backend interface for DNS resolution operations. diff --git a/include/boost/corosio/signal_set.hpp b/include/boost/corosio/signal_set.hpp index 858c1f149..1eee9eaee 100644 --- a/include/boost/corosio/signal_set.hpp +++ b/include/boost/corosio/signal_set.hpp @@ -343,7 +343,7 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set explicit signal_set(handle h) noexcept : io_signal_set(std::move(h)) {} private: - void do_cancel() override; + void do_cancel() noexcept override; implementation& get() const noexcept { diff --git a/include/boost/corosio/stream_file.hpp b/include/boost/corosio/stream_file.hpp index ae1e34e80..dcc3a4a67 100644 --- a/include/boost/corosio/stream_file.hpp +++ b/include/boost/corosio/stream_file.hpp @@ -196,7 +196,7 @@ class BOOST_COROSIO_DECL stream_file : public io_stream All outstanding operations complete with `errc::operation_canceled`. */ - void cancel(); + void cancel() noexcept; /** Get the native file descriptor or handle. diff --git a/include/boost/corosio/tcp_acceptor.hpp b/include/boost/corosio/tcp_acceptor.hpp index ae2ad26cc..9ae2592cf 100644 --- a/include/boost/corosio/tcp_acceptor.hpp +++ b/include/boost/corosio/tcp_acceptor.hpp @@ -399,7 +399,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @see accept() */ - auto accept(tcp_socket& peer) + [[nodiscard]] auto accept(tcp_socket& peer) { accept_awaitable aw(*this, peer); if (!is_open()) @@ -443,7 +443,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @see accept(tcp_socket&) */ - auto accept() + [[nodiscard]] auto accept() { accept_value_awaitable aw(*this); if (!is_open()) @@ -487,7 +487,7 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object All outstanding operations complete with `errc::operation_canceled`. Check `ec == cond::canceled` for portable comparison. */ - void cancel(); + void cancel() noexcept; /** Get the native socket handle. diff --git a/include/boost/corosio/tcp_socket.hpp b/include/boost/corosio/tcp_socket.hpp index 5a9cded2c..c2809b8f1 100644 --- a/include/boost/corosio/tcp_socket.hpp +++ b/include/boost/corosio/tcp_socket.hpp @@ -391,7 +391,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream if (ec) { ... } @endcode */ - auto connect(endpoint ep) + [[nodiscard]] auto connect(endpoint ep) { connect_awaitable aw(*this, ep); if (!is_open()) @@ -433,7 +433,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream All outstanding operations complete with `errc::operation_canceled`. Check `ec == cond::canceled` for portable comparison. */ - void cancel(); + void cancel() noexcept; /** Get the native socket handle. @@ -642,7 +642,8 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream friend class tcp_acceptor; /// Open the socket for the given protocol triple. - std::error_code open_for_family(int family, int type, int protocol) noexcept; + [[nodiscard]] std::error_code + open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/test/mocket.hpp b/include/boost/corosio/test/mocket.hpp index 7db8a9ce0..b69f7bb48 100644 --- a/include/boost/corosio/test/mocket.hpp +++ b/include/boost/corosio/test/mocket.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include namespace boost::corosio::test { @@ -188,36 +189,37 @@ class basic_mocket expect_.append(s); } - /** Close the mocket and verify test expectations. + /** Check that every test expectation was consumed. - Closes the underlying socket and verifies that both the - `expect()` and `provide()` buffers are empty. If either - buffer contains unconsumed data, returns `test_failure` - and calls `fuse::fail()`. + Verifies that both the `expect()` and `provide()` buffers are + empty. An unmet expectation also trips the fuse, so even a + discarded result still fails the test. - @return An error code indicating success or failure. - Returns `error::test_failure` if buffers are not empty. + @return `error::test_failure` if either buffer holds + unconsumed data; empty otherwise. */ - std::error_code close() + [[nodiscard]] std::error_code verify() noexcept { - if (!sock_.is_open()) + if (expect_.empty() && provide_.empty()) return {}; + fuse_.fail(); + return capy::error::test_failure; + } - if (!expect_.empty()) - { - fuse_.fail(); - sock_.close(); - return capy::error::test_failure; - } - if (!provide_.empty()) - { - fuse_.fail(); - sock_.close(); - return capy::error::test_failure; - } + /** Close the mocket. + + Idempotent, like every `close()` in the library. Unconsumed + `expect()`/`provide()` data trips the fuse on the way out; use + @ref verify to inspect the outcome as a code. + */ + void close() noexcept + { + if (!sock_.is_open()) + return; + // Discarded on purpose: the fuse reports unmet expectations. + std::ignore = verify(); sock_.close(); - return {}; } /** Cancel pending I/O operations. @@ -225,7 +227,7 @@ class basic_mocket Cancels any pending asynchronous operations on the underlying socket. Outstanding operations complete with `cond::canceled`. */ - void cancel() + void cancel() noexcept { sock_.cancel(); } @@ -250,7 +252,7 @@ class basic_mocket @return An awaitable yielding `(error_code, std::size_t)`. */ template - auto read_some(MutableBufferSequence const& buffers) + [[nodiscard]] auto read_some(MutableBufferSequence const& buffers) { return read_some_awaitable(*this, buffers); } @@ -266,7 +268,7 @@ class basic_mocket @return An awaitable yielding `(error_code, std::size_t)`. */ template - auto write_some(ConstBufferSequence const& buffers) + [[nodiscard]] auto write_some(ConstBufferSequence const& buffers) { return write_some_awaitable(*this, buffers); } diff --git a/include/boost/corosio/test/temp_path.hpp b/include/boost/corosio/test/temp_path.hpp index 8603072e4..a75ac074f 100644 --- a/include/boost/corosio/test/temp_path.hpp +++ b/include/boost/corosio/test/temp_path.hpp @@ -76,7 +76,8 @@ class temp_socket_dir } } throw std::runtime_error( - "temp_socket_dir: could not create temp directory"); + "temp_socket_dir: could not create temp directory: " + + ec.message()); } ~temp_socket_dir() noexcept diff --git a/include/boost/corosio/tls_context.hpp b/include/boost/corosio/tls_context.hpp index 51927b3d9..81d30ef93 100644 --- a/include/boost/corosio/tls_context.hpp +++ b/include/boost/corosio/tls_context.hpp @@ -792,12 +792,10 @@ class BOOST_COROSIO_DECL tls_context @tparam Callback A callable with signature `bool( bool preverified, verify_context& ctx )`. - @param callback The verification callback. - - @return Success. The callback is recorded here and applied during the - handshake. On a WolfSSL build that cannot honor it, the handshake - fails with `std::errc::function_not_supported` (see Backend - Support). + @param callback The verification callback. Recorded here and + applied during the handshake; on a WolfSSL build that + cannot honor it, the handshake fails with + `std::errc::function_not_supported` (see Backend Support). @par Example @code @@ -818,7 +816,7 @@ class BOOST_COROSIO_DECL tls_context @see set_verify_mode */ template - [[nodiscard]] std::error_code set_verify_callback(Callback callback); + void set_verify_callback(Callback callback); /** Set a callback for Server Name Indication (SNI). @@ -998,11 +996,10 @@ tls_context::set_password_callback(Callback callback) } template -std::error_code +void tls_context::set_verify_callback(Callback callback) { set_verify_callback_impl(std::move(callback)); - return {}; } } // namespace boost::corosio diff --git a/include/boost/corosio/tls_stream.hpp b/include/boost/corosio/tls_stream.hpp index bb1ece740..24f63b1bd 100644 --- a/include/boost/corosio/tls_stream.hpp +++ b/include/boost/corosio/tls_stream.hpp @@ -94,7 +94,7 @@ class BOOST_COROSIO_DECL tls_stream @return An awaitable yielding `(error_code,std::size_t)`. */ template - auto read_some(Buffers const& buffers) + [[nodiscard]] auto read_some(Buffers const& buffers) { return do_read_some(buffers); } @@ -119,7 +119,7 @@ class BOOST_COROSIO_DECL tls_stream @return An awaitable yielding `(error_code,std::size_t)`. */ template - auto write_some(Buffers const& buffers) + [[nodiscard]] auto write_some(Buffers const& buffers) { return do_write_some(buffers); } @@ -144,7 +144,7 @@ class BOOST_COROSIO_DECL tls_stream @return An awaitable yielding `(error_code)`. */ - virtual capy::io_task<> handshake(tls_role role) = 0; + [[nodiscard]] virtual capy::io_task<> handshake(tls_role role) = 0; /** Asynchronously perform a graceful TLS shutdown. @@ -165,7 +165,7 @@ class BOOST_COROSIO_DECL tls_stream @return An awaitable yielding `(error_code)`. */ - virtual capy::io_task<> shutdown() = 0; + [[nodiscard]] virtual capy::io_task<> shutdown() = 0; /** Reset TLS session state for reuse. diff --git a/include/boost/corosio/udp_socket.hpp b/include/boost/corosio/udp_socket.hpp index 77d2ea2f3..b56ccdcad 100644 --- a/include/boost/corosio/udp_socket.hpp +++ b/include/boost/corosio/udp_socket.hpp @@ -534,7 +534,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object `errc::operation_canceled`. Check `ec == cond::canceled` for portable comparison. */ - void cancel(); + void cancel() noexcept; /** Get the native socket handle. @@ -649,7 +649,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto send_to( + [[nodiscard]] auto send_to( Buffers const& buf, endpoint dest, corosio::message_flags flags) @@ -662,7 +662,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object /// @overload template - auto send_to(Buffers const& buf, endpoint dest) + [[nodiscard]] auto send_to(Buffers const& buf, endpoint dest) { return send_to(buf, dest, corosio::message_flags::none); } @@ -680,7 +680,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto recv_from( + [[nodiscard]] auto recv_from( Buffers const& buf, endpoint& source, corosio::message_flags flags) @@ -693,7 +693,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object /// @overload template - auto recv_from(Buffers const& buf, endpoint& source) + [[nodiscard]] auto recv_from(Buffers const& buf, endpoint& source) { return recv_from(buf, source, corosio::message_flags::none); } @@ -710,7 +710,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object If the socket needs to be opened and the open fails, the awaitable completes immediately with that error. */ - auto connect(endpoint ep) + [[nodiscard]] auto connect(endpoint ep) { connect_awaitable aw(*this, ep); if (!is_open()) @@ -750,7 +750,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto send(Buffers const& buf, corosio::message_flags flags) + [[nodiscard]] auto send(Buffers const& buf, corosio::message_flags flags) { send_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) @@ -760,7 +760,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object /// @overload template - auto send(Buffers const& buf) + [[nodiscard]] auto send(Buffers const& buf) { return send(buf, corosio::message_flags::none); } @@ -776,7 +776,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object A closed socket reports `errc::bad_file_descriptor`. */ template - auto recv(Buffers const& buf, corosio::message_flags flags) + [[nodiscard]] auto recv(Buffers const& buf, corosio::message_flags flags) { recv_awaitable aw(*this, buf, static_cast(flags)); if (!is_open()) @@ -786,7 +786,7 @@ class BOOST_COROSIO_DECL udp_socket : public io_object /// @overload template - auto recv(Buffers const& buf) + [[nodiscard]] auto recv(Buffers const& buf) { return recv(buf, corosio::message_flags::none); } @@ -808,7 +808,8 @@ class BOOST_COROSIO_DECL udp_socket : public io_object private: /// Open the socket for the given protocol triple. - std::error_code open_for_family(int family, int type, int protocol) noexcept; + [[nodiscard]] std::error_code + open_for_family(int family, int type, int protocol) noexcept; inline implementation& get() const noexcept { diff --git a/include/boost/corosio/wolfssl_stream.hpp b/include/boost/corosio/wolfssl_stream.hpp index 2a1f8154d..b198e47ec 100644 --- a/include/boost/corosio/wolfssl_stream.hpp +++ b/include/boost/corosio/wolfssl_stream.hpp @@ -155,7 +155,7 @@ class BOOST_COROSIO_DECL wolfssl_stream final : public tls_stream @return An awaitable yielding `(error_code)`. */ - capy::io_task<> handshake(tls_role role) override; + [[nodiscard]] capy::io_task<> handshake(tls_role role) override; /** Asynchronously shut down the TLS session. @@ -177,7 +177,7 @@ class BOOST_COROSIO_DECL wolfssl_stream final : public tls_stream @return An awaitable yielding `(error_code)`. */ - capy::io_task<> shutdown() override; + [[nodiscard]] capy::io_task<> shutdown() override; /** Reset TLS session state for reuse. diff --git a/perf/bench/asio/callback/fan_out_bench.cpp b/perf/bench/asio/callback/fan_out_bench.cpp index 35a86df86..b3ba64d3e 100644 --- a/perf/bench/asio/callback/fan_out_bench.cpp +++ b/perf/bench/asio/callback/fan_out_bench.cpp @@ -106,8 +106,8 @@ struct sub_request_op : std::enable_shared_from_this auto self = shared_from_this(); asio::async_read( client, asio::buffer(recv_buf, 64), - [self](boost::system::error_code ec, std::size_t) { - (void)ec; + [self]([[maybe_unused]] boost::system::error_code ec, + std::size_t) { self->finish(); }); } diff --git a/perf/bench/asio/coroutine/fan_out_bench.cpp b/perf/bench/asio/coroutine/fan_out_bench.cpp index 7e932f743..7c07eebbf 100644 --- a/perf/bench/asio/coroutine/fan_out_bench.cpp +++ b/perf/bench/asio/coroutine/fan_out_bench.cpp @@ -135,9 +135,8 @@ bench_fork_join(bench::state& state) // async_wait registering. while (remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; } } @@ -213,8 +212,7 @@ bench_nested(bench::state& state) // registering. while (subs_remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); } groups_notifier.arrive(); @@ -241,9 +239,8 @@ bench_nested(bench::state& state) while (groups_remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; } } @@ -326,9 +323,8 @@ bench_concurrent_parents(bench::state& state) // async_wait registering. while (remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; } } @@ -406,9 +402,8 @@ bench_fork_join_lockless(bench::state& state) // async_wait registering. while (remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; } } @@ -483,8 +478,7 @@ bench_nested_lockless(bench::state& state) // registering. while (subs_remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); } groups_notifier.arrive(); @@ -511,9 +505,8 @@ bench_nested_lockless(bench::state& state) while (groups_remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; } } @@ -595,9 +588,8 @@ bench_concurrent_parents_lockless(bench::state& state) // async_wait registering. while (remaining.load(std::memory_order_acquire) > 0) { - auto [ec] = + [[maybe_unused]] auto [ec] = co_await t.async_wait(asio::as_tuple(asio::deferred)); - (void)ec; } } diff --git a/perf/bench/corosio/accept_churn_bench.cpp b/perf/bench/corosio/accept_churn_bench.cpp index 17724216f..904ae2ff9 100644 --- a/perf/bench/corosio/accept_churn_bench.cpp +++ b/perf/bench/corosio/accept_churn_bench.cpp @@ -82,8 +82,7 @@ bench_sequential_churn(bench::state& state) capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { - auto [ec] = co_await c.connect(ep); - (void)ec; + [[maybe_unused]] auto [ec] = co_await c.connect(ep); }(client, ep)); auto [aec] = co_await acc.accept(server); @@ -165,8 +164,7 @@ bench_sequential_churn_lockless(bench::state& state) capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { - auto [ec] = co_await c.connect(ep); - (void)ec; + [[maybe_unused]] auto [ec] = co_await c.connect(ep); }(client, ep)); auto [aec] = co_await acc.accept(server); @@ -257,8 +255,7 @@ bench_concurrent_churn(bench::state& state) capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { - auto [ec] = co_await c.connect(ep); - (void)ec; + [[maybe_unused]] auto [ec] = co_await c.connect(ep); }(client, ep)); auto [aec] = co_await acc.accept(server); @@ -345,12 +342,11 @@ bench_burst_churn(bench::state& state) for (int i = 0; i < burst_size; ++i) { clients.emplace_back(ioc); - (void)clients.back().open(); + std::ignore = clients.back().open(); configure_churn_socket(clients.back()); capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { - auto [ec] = co_await c.connect(ep); - (void)ec; + [[maybe_unused]] auto [ec] = co_await c.connect(ep); }(clients.back(), ep)); } @@ -431,12 +427,11 @@ bench_burst_churn_lockless(bench::state& state) for (int i = 0; i < burst_size; ++i) { clients.emplace_back(ioc); - (void)clients.back().open(); + std::ignore = clients.back().open(); configure_churn_socket(clients.back()); capy::run_async(ioc.get_executor())( [](socket_type& c, corosio::endpoint ep) -> capy::task<> { - auto [ec] = co_await c.connect(ep); - (void)ec; + [[maybe_unused]] auto [ec] = co_await c.connect(ep); }(clients.back(), ep)); } diff --git a/perf/bench/corosio/fan_out_bench.cpp b/perf/bench/corosio/fan_out_bench.cpp index b13443c8a..5a383a5f2 100644 --- a/perf/bench/corosio/fan_out_bench.cpp +++ b/perf/bench/corosio/fan_out_bench.cpp @@ -87,10 +87,8 @@ sub_request( co_return; } - auto [rec, rn] = + [[maybe_unused]] auto [rec, rn] = co_await capy::read(client, capy::mutable_buffer(recv_buf, 64)); - (void)rec; - (void)rn; latch.arrive(); } @@ -134,8 +132,7 @@ bench_fork_join(bench::state& state) capy::run_async(ioc.get_executor())( sub_request(clients[i], latch)); - auto [ec] = co_await latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await latch.done.wait(); } for (auto& c : clients) @@ -201,8 +198,7 @@ bench_nested(bench::state& state) capy::run_async(ioc.get_executor())( sub_request(clients[base_idx + i], subs_latch)); - auto [ec] = co_await subs_latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await subs_latch.done.wait(); groups_latch.arrive(); }; @@ -217,8 +213,7 @@ bench_nested(bench::state& state) capy::run_async(ioc.get_executor())(group_task( g * subs_per_group, subs_per_group, groups_latch)); - auto [ec] = co_await groups_latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await groups_latch.done.wait(); } for (auto& c : clients) @@ -291,8 +286,7 @@ bench_concurrent_parents(bench::state& state) capy::run_async(ioc.get_executor())( sub_request(clients[base + i], latch)); - auto [ec] = co_await latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await latch.done.wait(); } if (parents_done.fetch_add(1, std::memory_order_acq_rel) == @@ -363,8 +357,7 @@ bench_fork_join_lockless(bench::state& state) capy::run_async(ioc.get_executor())( sub_request(clients[i], latch)); - auto [ec] = co_await latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await latch.done.wait(); } for (auto& c : clients) @@ -431,8 +424,7 @@ bench_nested_lockless(bench::state& state) capy::run_async(ioc.get_executor())( sub_request(clients[base_idx + i], subs_latch)); - auto [ec] = co_await subs_latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await subs_latch.done.wait(); groups_latch.arrive(); }; @@ -447,8 +439,7 @@ bench_nested_lockless(bench::state& state) capy::run_async(ioc.get_executor())(group_task( g * subs_per_group, subs_per_group, groups_latch)); - auto [ec] = co_await groups_latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await groups_latch.done.wait(); } for (auto& c : clients) @@ -522,8 +513,7 @@ bench_concurrent_parents_lockless(bench::state& state) capy::run_async(ioc.get_executor())( sub_request(clients[base + i], latch)); - auto [ec] = co_await latch.done.wait(); - (void)ec; + [[maybe_unused]] auto [ec] = co_await latch.done.wait(); } if (parents_done.fetch_add(1, std::memory_order_acq_rel) == diff --git a/src/corosio/src/endpoint.cpp b/src/corosio/src/endpoint.cpp index 2acef0645..5d615fffe 100644 --- a/src/corosio/src/endpoint.cpp +++ b/src/corosio/src/endpoint.cpp @@ -65,8 +65,8 @@ parse_port(std::string_view s, std::uint16_t& port) noexcept } // namespace -std::error_code -parse_endpoint(std::string_view s, endpoint& ep) noexcept +static std::error_code +parse_endpoint_impl(std::string_view s, endpoint& ep) noexcept { if (s.empty()) return std::make_error_code(std::errc::invalid_argument); @@ -77,8 +77,7 @@ parse_endpoint(std::string_view s, endpoint& ep) noexcept { case endpoint_format::ipv4_no_port: { - ipv4_address addr; - auto ec = parse_ipv4_address(s, addr); + auto [ec, addr] = make_ipv4_address(s); if (ec) return ec; ep = endpoint(addr, 0); @@ -95,8 +94,7 @@ parse_endpoint(std::string_view s, endpoint& ep) noexcept auto addr_str = s.substr(0, colon_pos); auto port_str = s.substr(colon_pos + 1); - ipv4_address addr; - auto ec = parse_ipv4_address(addr_str, addr); + auto [ec, addr] = make_ipv4_address(addr_str); if (ec) return ec; @@ -110,8 +108,7 @@ parse_endpoint(std::string_view s, endpoint& ep) noexcept case endpoint_format::ipv6_no_port: { - ipv6_address addr; - auto ec = parse_ipv6_address(s, addr); + auto [ec, addr] = make_ipv6_address(s); if (ec) return ec; ep = endpoint(addr, 0); @@ -130,8 +127,7 @@ parse_endpoint(std::string_view s, endpoint& ep) noexcept auto addr_str = s.substr(1, close_bracket - 1); - ipv6_address addr; - auto ec = parse_ipv6_address(addr_str, addr); + auto [ec, addr] = make_ipv6_address(addr_str); if (ec) return ec; @@ -156,4 +152,13 @@ parse_endpoint(std::string_view s, endpoint& ep) noexcept } } +capy::io_result +make_endpoint(std::string_view s) noexcept +{ + endpoint ep; + if (auto ec = parse_endpoint_impl(s, ep)) + return {ec, endpoint{}}; + return {std::error_code{}, ep}; +} + } // namespace boost::corosio diff --git a/src/corosio/src/host_name.cpp b/src/corosio/src/host_name.cpp index 15bd0533b..41f82e201 100644 --- a/src/corosio/src/host_name.cpp +++ b/src/corosio/src/host_name.cpp @@ -25,30 +25,25 @@ namespace boost::corosio { #if BOOST_COROSIO_POSIX -std::string +capy::io_result host_name() { // 256 exceeds POSIX's _POSIX_HOST_NAME_MAX floor of 255 and // every mainstream OS's actual cap (Linux 64, macOS/BSD 255). char buf[256]; if (::gethostname(buf, sizeof(buf)) != 0) - { - throw std::system_error( - std::error_code(errno, std::generic_category()), "gethostname"); - } + return {std::error_code(errno, std::system_category()), {}}; // POSIX does not guarantee NUL termination on truncation. if (std::memchr(buf, '\0', sizeof(buf)) == nullptr) - throw std::system_error( - make_error_code(std::errc::value_too_large), - "gethostname: hostname truncated"); + return {make_error_code(std::errc::value_too_large), {}}; - return std::string(buf); + return {std::error_code{}, std::string(buf)}; } #elif BOOST_COROSIO_HAS_IOCP -std::string +capy::io_result host_name() { // Size query: returns ERROR_MORE_DATA and writes the required @@ -59,53 +54,45 @@ host_name() DWORD err = ::GetLastError(); if (ok) { - throw std::system_error( - make_error_code(std::errc::protocol_error), - "GetComputerNameExW (size query) unexpectedly succeeded"); + // Can't-happen guard: a zero-length size query succeeding + // would leave `size` meaningless. + return {make_error_code(std::errc::protocol_error), {}}; } if (err != ERROR_MORE_DATA) - { - throw std::system_error( + return { std::error_code(static_cast(err), std::system_category()), - "GetComputerNameExW (size query)"); - } + {}}; // On success, GetComputerNameExW rewrites `size` to the count // without the NUL, so resize(size) below trims to the hostname. std::wstring wide(size, L'\0'); if (!::GetComputerNameExW( ComputerNameDnsHostname, wide.data(), &size)) - { - throw std::system_error( + return { std::error_code( static_cast(::GetLastError()), std::system_category()), - "GetComputerNameExW"); - } + {}}; wide.resize(size); int needed = ::WideCharToMultiByte( CP_UTF8, 0, wide.data(), static_cast(wide.size()), nullptr, 0, nullptr, nullptr); if (needed <= 0) - { - throw std::system_error( + return { std::error_code( static_cast(::GetLastError()), std::system_category()), - "WideCharToMultiByte (size query)"); - } + {}}; std::string out(static_cast(needed), '\0'); int written = ::WideCharToMultiByte( CP_UTF8, 0, wide.data(), static_cast(wide.size()), out.data(), needed, nullptr, nullptr); if (written != needed) - { - throw std::system_error( + return { std::error_code( static_cast(::GetLastError()), std::system_category()), - "WideCharToMultiByte"); - } - return out; + {}}; + return {std::error_code{}, std::move(out)}; } #endif diff --git a/src/corosio/src/io_context.cpp b/src/corosio/src/io_context.cpp index eff780408..7957cb8ac 100644 --- a/src/corosio/src/io_context.cpp +++ b/src/corosio/src/io_context.cpp @@ -151,8 +151,8 @@ namespace { // Pre-create services that must exist before construct() runs. void pre_create_services( - capy::execution_context& ctx, - io_context_options const& opts) + [[maybe_unused]] capy::execution_context& ctx, + [[maybe_unused]] io_context_options const& opts) { #if BOOST_COROSIO_POSIX if (opts.thread_pool_size < 1) @@ -166,8 +166,6 @@ pre_create_services( ctx.make_service(opts.thread_pool_size); #endif - (void)ctx; - (void)opts; } // Map the locking tier to the scheduler's threading facilities. one_thread is @@ -188,9 +186,9 @@ make_threading_config(io_context_options const& opts) // runs post everything for cross-thread work-stealing. void apply_scheduler_options( - detail::scheduler& sched, - io_context_options const& opts, - unsigned concurrency_hint) + [[maybe_unused]] detail::scheduler& sched, + [[maybe_unused]] io_context_options const& opts, + [[maybe_unused]] unsigned concurrency_hint) { sched.configure_threading(make_threading_config(opts)); @@ -238,9 +236,6 @@ apply_scheduler_options( } #endif - (void)sched; - (void)opts; - (void)concurrency_hint; } detail::scheduler& diff --git a/src/corosio/src/ipv4_address.cpp b/src/corosio/src/ipv4_address.cpp index aa4b96916..2980eff0b 100644 --- a/src/corosio/src/ipv4_address.cpp +++ b/src/corosio/src/ipv4_address.cpp @@ -9,6 +9,8 @@ #include +#include + #include #include @@ -26,9 +28,10 @@ ipv4_address::ipv4_address(bytes_type const& bytes) noexcept ipv4_address::ipv4_address(std::string_view s) { - auto ec = parse_ipv4_address(s, *this); + auto [ec, addr] = make_ipv4_address(s); if (ec) - throw std::invalid_argument("invalid IPv4 address"); + detail::throw_system_error(ec, "invalid IPv4 address"); + *this = addr; } auto @@ -172,8 +175,8 @@ parse_dec_octet(char const*& it, char const* end, unsigned char& octet) noexcept } // namespace -std::error_code -parse_ipv4_address(std::string_view s, ipv4_address& addr) noexcept +static std::error_code +parse_ipv4_impl(std::string_view s, ipv4_address& addr) noexcept { auto it = s.data(); auto const end = it + s.size(); @@ -205,4 +208,13 @@ parse_ipv4_address(std::string_view s, ipv4_address& addr) noexcept return {}; } +capy::io_result +make_ipv4_address(std::string_view s) noexcept +{ + ipv4_address addr; + if (auto ec = parse_ipv4_impl(s, addr)) + return {ec, ipv4_address{}}; + return {std::error_code{}, addr}; +} + } // namespace boost::corosio diff --git a/src/corosio/src/ipv6_address.cpp b/src/corosio/src/ipv6_address.cpp index cc87c86b1..498abb46b 100644 --- a/src/corosio/src/ipv6_address.cpp +++ b/src/corosio/src/ipv6_address.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include #include @@ -30,9 +32,10 @@ ipv6_address::ipv6_address(ipv4_address const& addr) noexcept ipv6_address::ipv6_address(std::string_view s) { - auto ec = parse_ipv6_address(s, *this); + auto [ec, addr] = make_ipv6_address(s); if (ec) - throw std::invalid_argument("invalid IPv6 address"); + detail::throw_system_error(ec, "invalid IPv6 address"); + *this = addr; } std::string @@ -281,8 +284,8 @@ maybe_octet(unsigned char const* p) noexcept } // namespace -std::error_code -parse_ipv6_address(std::string_view s, ipv6_address& addr) noexcept +static std::error_code +parse_ipv6_impl(std::string_view s, ipv6_address& addr) noexcept { auto it = s.data(); auto const end = it + s.size(); @@ -361,11 +364,10 @@ parse_ipv6_address(std::string_view s, ipv6_address& addr) noexcept } // rewind the h16 and parse it as IPv4 it = prev; - ipv4_address v4; - auto ec = parse_ipv4_address( - std::string_view(it, static_cast(end - it)), v4); - if (ec) - return ec; + auto [v4ec, v4] = make_ipv4_address( + std::string_view(it, static_cast(end - it))); + if (v4ec) + return v4ec; // Must consume exactly the IPv4 address portion // Re-parse to find where it ends auto v4_it = it; @@ -373,12 +375,10 @@ parse_ipv6_address(std::string_view s, ipv6_address& addr) noexcept (*v4_it == '.' || (*v4_it >= '0' && *v4_it <= '9'))) ++v4_it; // Verify it parsed correctly by re-parsing the exact substring - ipv4_address v4_check; - ec = parse_ipv4_address( - std::string_view(it, static_cast(v4_it - it)), - v4_check); - if (ec) - return ec; + auto [ckec, v4_check] = make_ipv4_address( + std::string_view(it, static_cast(v4_it - it))); + if (ckec) + return ckec; it = v4_it; auto const b4 = v4_check.to_bytes(); bytes[2 * (7 - n) + 0] = b4[0]; @@ -450,4 +450,13 @@ parse_ipv6_address(std::string_view s, ipv6_address& addr) noexcept return {}; } +capy::io_result +make_ipv6_address(std::string_view s) noexcept +{ + ipv6_address addr; + if (auto ec = parse_ipv6_impl(s, addr)) + return {ec, ipv6_address{}}; + return {std::error_code{}, addr}; +} + } // namespace boost::corosio diff --git a/src/corosio/src/local_connect_pair.cpp b/src/corosio/src/local_connect_pair.cpp index a04327474..1a7a631d3 100644 --- a/src/corosio/src/local_connect_pair.cpp +++ b/src/corosio/src/local_connect_pair.cpp @@ -267,13 +267,7 @@ std::error_code connect_pair(local_stream_socket& a, local_stream_socket& b) noexcept { if (a.is_open() || b.is_open()) - return detail::make_err( -#if BOOST_COROSIO_POSIX - EISCONN -#else - WSAEISCONN -#endif - ); + return std::make_error_code(std::errc::already_connected); #if BOOST_COROSIO_POSIX int a_fd = -1, b_fd = -1; @@ -296,7 +290,7 @@ std::error_code connect_pair(local_datagram_socket& a, local_datagram_socket& b) noexcept { if (a.is_open() || b.is_open()) - return detail::make_err(EISCONN); + return std::make_error_code(std::errc::already_connected); int a_fd = -1, b_fd = -1; if (auto ec = make_pair_fds(SOCK_DGRAM, a_fd, b_fd)) diff --git a/src/corosio/src/local_datagram_socket.cpp b/src/corosio/src/local_datagram_socket.cpp index fa7b774b3..80d0e12fc 100644 --- a/src/corosio/src/local_datagram_socket.cpp +++ b/src/corosio/src/local_datagram_socket.cpp @@ -67,7 +67,7 @@ local_datagram_socket::bind(corosio::local_endpoint ep) noexcept } void -local_datagram_socket::cancel() +local_datagram_socket::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/local_stream_acceptor.cpp b/src/corosio/src/local_stream_acceptor.cpp index c19eda34e..1f575888c 100644 --- a/src/corosio/src/local_stream_acceptor.cpp +++ b/src/corosio/src/local_stream_acceptor.cpp @@ -39,6 +39,18 @@ local_stream_acceptor::local_stream_acceptor(capy::execution_context& ctx) { } +local_stream_acceptor::local_stream_acceptor( + capy::execution_context& ctx, corosio::local_endpoint ep, int backlog) + : local_stream_acceptor(ctx) +{ + if (auto ec = open()) + detail::throw_system_error(ec, "local_stream_acceptor"); + if (auto ec = bind(ep)) + detail::throw_system_error(ec, "local_stream_acceptor"); + if (auto ec = listen(backlog)) + detail::throw_system_error(ec, "local_stream_acceptor"); +} + std::error_code local_stream_acceptor::open(local_stream proto) noexcept { @@ -135,7 +147,7 @@ local_stream_acceptor::release() } void -local_stream_acceptor::cancel() +local_stream_acceptor::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/local_stream_socket.cpp b/src/corosio/src/local_stream_socket.cpp index 3d4d70cde..8bee574a7 100644 --- a/src/corosio/src/local_stream_socket.cpp +++ b/src/corosio/src/local_stream_socket.cpp @@ -60,7 +60,7 @@ local_stream_socket::close() noexcept } void -local_stream_socket::cancel() +local_stream_socket::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/random_access_file.cpp b/src/corosio/src/random_access_file.cpp index c2f93b811..9f3760387 100644 --- a/src/corosio/src/random_access_file.cpp +++ b/src/corosio/src/random_access_file.cpp @@ -52,7 +52,7 @@ random_access_file::close() noexcept } void -random_access_file::cancel() +random_access_file::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/resolver.cpp b/src/corosio/src/resolver.cpp index 4df53ad69..f4a736375 100644 --- a/src/corosio/src/resolver.cpp +++ b/src/corosio/src/resolver.cpp @@ -53,7 +53,7 @@ resolver::resolver(capy::execution_context& ctx) } void -resolver::cancel() +resolver::cancel() noexcept { if (h_) get().cancel(); diff --git a/src/corosio/src/signal_set.cpp b/src/corosio/src/signal_set.cpp index 6c98242c2..ed61af7d9 100644 --- a/src/corosio/src/signal_set.cpp +++ b/src/corosio/src/signal_set.cpp @@ -77,7 +77,7 @@ signal_set::operator=(signal_set&& other) noexcept } void -signal_set::do_cancel() +signal_set::do_cancel() noexcept { get().cancel(); } diff --git a/src/corosio/src/stream_file.cpp b/src/corosio/src/stream_file.cpp index af1f22625..ead93ffd6 100644 --- a/src/corosio/src/stream_file.cpp +++ b/src/corosio/src/stream_file.cpp @@ -52,7 +52,7 @@ stream_file::close() noexcept } void -stream_file::cancel() +stream_file::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/tcp_acceptor.cpp b/src/corosio/src/tcp_acceptor.cpp index e88f7dc52..fdf60e598 100644 --- a/src/corosio/src/tcp_acceptor.cpp +++ b/src/corosio/src/tcp_acceptor.cpp @@ -164,7 +164,7 @@ tcp_acceptor::close() noexcept } void -tcp_acceptor::cancel() +tcp_acceptor::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/tcp_socket.cpp b/src/corosio/src/tcp_socket.cpp index c80438ffa..dfd00f4c5 100644 --- a/src/corosio/src/tcp_socket.cpp +++ b/src/corosio/src/tcp_socket.cpp @@ -112,7 +112,7 @@ tcp_socket::close() noexcept } void -tcp_socket::cancel() +tcp_socket::cancel() noexcept { if (!is_open()) return; diff --git a/src/corosio/src/tls/detail/engine_driver.hpp b/src/corosio/src/tls/detail/engine_driver.hpp index c6b359262..b54141fe9 100644 --- a/src/corosio/src/tls/detail/engine_driver.hpp +++ b/src/corosio/src/tls/detail/engine_driver.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -251,7 +252,7 @@ class engine_driver // nothing is pending. capy::task best_effort_flush() { - (void)co_await flush_output(); + std::ignore = co_await flush_output(); } // gen is the caller's read_gen_ snapshot from its engine call; a diff --git a/src/corosio/src/tls/detail/engine_types.hpp b/src/corosio/src/tls/detail/engine_types.hpp index b2b9e6699..34b677a03 100644 --- a/src/corosio/src/tls/detail/engine_types.hpp +++ b/src/corosio/src/tls/detail/engine_types.hpp @@ -81,9 +81,8 @@ enum class engine_op inline bool is_ip_literal(std::string const& s) noexcept { - ipv4_address v4; - ipv6_address v6; - return !parse_ipv4_address(s, v4) || !parse_ipv6_address(s, v6); + return !std::get<0>(make_ipv4_address(s)) || + !std::get<0>(make_ipv6_address(s)); } /** Map a transport error observed while filling engine input. diff --git a/src/corosio/src/udp_socket.cpp b/src/corosio/src/udp_socket.cpp index 893e7becd..0a4f6ba7a 100644 --- a/src/corosio/src/udp_socket.cpp +++ b/src/corosio/src/udp_socket.cpp @@ -89,7 +89,7 @@ udp_socket::shutdown(shutdown_type what) noexcept } void -udp_socket::cancel() +udp_socket::cancel() noexcept { if (!is_open()) return; diff --git a/src/wolfssl/src/detail/engine.cpp b/src/wolfssl/src/detail/engine.cpp index c85a70b8d..b1940d2f8 100644 --- a/src/wolfssl/src/detail/engine.cpp +++ b/src/wolfssl/src/detail/engine.cpp @@ -899,7 +899,7 @@ engine::reset() } void -engine::capture_alpn(std::string& out) const +engine::capture_alpn([[maybe_unused]] std::string& out) const { #if defined(HAVE_ALPN) char* name = nullptr; @@ -907,8 +907,6 @@ engine::capture_alpn(std::string& out) const if (wolfSSL_ALPN_GetProtocol(ssl_, &name, &sz) == WOLFSSL_SUCCESS && name && sz) out.assign(name, sz); -#else - (void)out; #endif } diff --git a/test/doc/snippets/3d_tls_context.cpp b/test/doc/snippets/3d_tls_context.cpp index 141630b8f..e0c0ff7f6 100644 --- a/test/doc/snippets/3d_tls_context.cpp +++ b/test/doc/snippets/3d_tls_context.cpp @@ -344,7 +344,7 @@ void verify_callback(tls_context& ctx) { // tag::verify_callback[] - if (auto ec = ctx.set_verify_callback( + ctx.set_verify_callback( []( bool preverified, corosio::verify_context& verify_ctx ) -> bool { if( !preverified ) @@ -353,8 +353,7 @@ verify_callback(tls_context& ctx) auto der = verify_ctx.certificate(); // DER of the current cert return der.size() == expected_pin.size() && std::equal( der.begin(), der.end(), expected_pin.begin() ); - })) - return; + }); // end::verify_callback[] } diff --git a/test/doc/snippets/4f_endpoints.cpp b/test/doc/snippets/4f_endpoints.cpp index 243216e55..bb0337560 100644 --- a/test/doc/snippets/4f_endpoints.cpp +++ b/test/doc/snippets/4f_endpoints.cpp @@ -274,35 +274,31 @@ struct endpoints_test void testParseAddresses() { - // tag::parse_addresses[] + // tag::make_addresses[] // IPv4 - corosio::ipv4_address addr; - if (auto ec = corosio::parse_ipv4_address("192.168.1.1", addr); !ec) - { - corosio::endpoint ep(addr, 8080); - } + auto [ec, addr] = corosio::make_ipv4_address("192.168.1.1"); + if (ec) + return; + corosio::endpoint ep(addr, 8080); // IPv6 - corosio::ipv6_address addr6; - if (auto ec = corosio::parse_ipv6_address("2001:db8::1", addr6); !ec) - { - corosio::endpoint ep(addr6, 8080); - } - // end::parse_addresses[] - BOOST_TEST(addr.to_string() == "192.168.1.1"); - BOOST_TEST(addr6.to_string() == "2001:db8::1"); + auto [ec6, addr6] = corosio::make_ipv6_address("2001:db8::1"); + if (ec6) + return; + corosio::endpoint ep6(addr6, 8080); + // end::make_addresses[] + BOOST_TEST(ep.v4_address().to_string() == "192.168.1.1"); + BOOST_TEST(ep6.v6_address().to_string() == "2001:db8::1"); } void testParseEndpoint() { - // tag::parse_endpoint[] - corosio::endpoint ep; - if (auto ec = corosio::parse_endpoint("192.168.1.1:8080", ep); !ec) - { - // Use ep... - } - // end::parse_endpoint[] + // tag::make_endpoint[] + auto [ec, ep] = corosio::make_endpoint("192.168.1.1:8080"); + if (ec) + return; + // end::make_endpoint[] BOOST_TEST(ep.is_v4()); BOOST_TEST(ep.v4_address().to_string() == "192.168.1.1"); BOOST_TEST(ep.port() == 8080); diff --git a/test/doc/snippets/5a_mocket.cpp b/test/doc/snippets/5a_mocket.cpp index 0cb1de771..ae4b9a97b 100644 --- a/test/doc/snippets/5a_mocket.cpp +++ b/test/doc/snippets/5a_mocket.cpp @@ -73,7 +73,8 @@ struct mocket_page_test // "Both are open and immediately usable." BOOST_TEST(m.is_open()); BOOST_TEST(peer.is_open()); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -97,7 +98,8 @@ struct mocket_page_test ioc.restart(); // A clean close proves the staged bytes were fully consumed. - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -121,7 +123,8 @@ struct mocket_page_test ioc.restart(); // A clean close proves the expected bytes were all written. - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -169,7 +172,8 @@ struct mocket_page_test ioc.run(); ioc.restart(); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -183,7 +187,8 @@ struct mocket_page_test m.provide("unread"); // tag::close_check[] - auto ec = m.close(); + auto ec = m.verify(); + m.close(); if (ec == capy::error::test_failure) { // Either provide() data was never read, @@ -210,7 +215,8 @@ struct mocket_page_test static_assert(std::is_same_v); BOOST_TEST(m.is_open()); BOOST_TEST(peer.is_open()); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -225,7 +231,8 @@ struct mocket_page_test // Pass `under` into a TLS stream, a custom framing layer, etc. // end::socket_access[] BOOST_TEST(under.is_open()); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); // `under` is the mocket's own socket, so it closed with it. BOOST_TEST(!under.is_open()); peer.close(); diff --git a/test/doc/snippets/5c_patterns.cpp b/test/doc/snippets/5c_patterns.cpp index c3e0c6141..232a25719 100644 --- a/test/doc/snippets/5c_patterns.cpp +++ b/test/doc/snippets/5c_patterns.cpp @@ -84,7 +84,8 @@ struct patterns_page_test capy::run_async(ioc.get_executor())(task(m)); ioc.run(); - auto ec = m.close(); // !ec means everything was written + auto ec = m.verify(); // !ec means everything was written + m.close(); // end::request_format[] BOOST_TEST(!ec); peer.close(); @@ -113,7 +114,8 @@ struct patterns_page_test ioc.restart(); // A clean close proves the consumer read the whole response. - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -141,7 +143,8 @@ struct patterns_page_test ioc.restart(); // A clean close proves the loop consumed all 8 staged bytes. - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -157,7 +160,8 @@ struct patterns_page_test // e.g., openssl_stream tls(&under, tls_ctx); // end::layering[] BOOST_TEST(under.is_open()); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -209,7 +213,8 @@ struct patterns_page_test m.expect("never written"); // tag::close_verification[] - auto ec = m.close(); + auto ec = m.verify(); + m.close(); // ec == capy::error::test_failure means leftover provide() data was // never read, or expect() data was never written. Either way, the test // would have passed silently without this check. diff --git a/test/unit/connect.cpp b/test/unit/connect.cpp index 8a4077789..30d680868 100644 --- a/test/unit/connect.cpp +++ b/test/unit/connect.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include "context.hpp" @@ -117,7 +118,7 @@ struct connect_test }; auto accept_task = [&]() -> capy::task<> { - (void)co_await acc.accept(peer); + std::ignore = co_await acc.accept(peer); }; capy::run_async(ioc.get_executor())(accept_task()); @@ -156,7 +157,7 @@ struct connect_test }; auto accept_task = [&]() -> capy::task<> { - (void)co_await acc.accept(peer); + std::ignore = co_await acc.accept(peer); }; capy::run_async(ioc.get_executor())(accept_task()); @@ -225,7 +226,7 @@ struct connect_test }; auto accept_task = [&]() -> capy::task<> { - (void)co_await acc.accept(peer); + std::ignore = co_await acc.accept(peer); }; capy::run_async(ioc.get_executor())(accept_task()); @@ -264,7 +265,7 @@ struct connect_test // Must also cancel the acceptor since nothing will ever connect. auto cancel_task = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); acc.cancel(); }; @@ -311,7 +312,7 @@ struct connect_test }; auto accept_task = [&]() -> capy::task<> { - (void)co_await acc.accept(peer); + std::ignore = co_await acc.accept(peer); }; capy::run_async(ioc.get_executor())(accept_task()); @@ -349,7 +350,7 @@ struct connect_test }; auto accept_task = [&]() -> capy::task<> { - (void)co_await acc.accept(peer); + std::ignore = co_await acc.accept(peer); }; capy::run_async(ioc.get_executor())(accept_task()); @@ -407,14 +408,13 @@ struct connect_test bool connect_done = false; auto connect_task = [&]() -> capy::task<> { - auto [ec, ep] = co_await corosio::connect(client, endpoints); + [[maybe_unused]] auto [ec, ep] = co_await corosio::connect(client, endpoints); connect_ec = ec; connect_done = true; - (void)ep; }; auto cancel_task = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); client.cancel(); }; diff --git a/test/unit/datagram_paths.cpp b/test/unit/datagram_paths.cpp index 065e6f60a..496901c9d 100644 --- a/test/unit/datagram_paths.cpp +++ b/test/unit/datagram_paths.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #if BOOST_COROSIO_POSIX #include @@ -97,10 +98,9 @@ struct datagram_paths_test recv_n = n; }; auto sender = [&]() -> capy::task<> { - auto [ec, n] = co_await s2.send( + [[maybe_unused]] auto [ec, n] = co_await s2.send( capy::const_buffer(payload.data(), payload.size())); send_ec = ec; - (void)n; }; capy::run_async(ex)(receiver()); @@ -140,11 +140,10 @@ struct datagram_paths_test recv_n = n; }; auto sender = [&]() -> capy::task<> { - auto [ec, n] = co_await send_sock.send_to( + [[maybe_unused]] auto [ec, n] = co_await send_sock.send_to( capy::const_buffer(payload.data(), payload.size()), recv_sock.local_endpoint()); send_ec = ec; - (void)n; }; capy::run_async(ex)(receiver()); @@ -179,7 +178,7 @@ struct datagram_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -213,7 +212,7 @@ struct datagram_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -248,10 +247,9 @@ struct datagram_paths_test recv_n = n; }; auto sender = [&]() -> capy::task<> { - auto [ec, n] = co_await s2.send( + [[maybe_unused]] auto [ec, n] = co_await s2.send( capy::const_buffer(payload.data(), payload.size())); send_ec = ec; - (void)n; }; capy::run_async(ex)(receiver()); @@ -292,11 +290,10 @@ struct datagram_paths_test recv_n = n; }; auto sender = [&]() -> capy::task<> { - auto [ec, n] = co_await s2.send_to( + [[maybe_unused]] auto [ec, n] = co_await s2.send_to( capy::const_buffer(payload.data(), payload.size()), local_endpoint(tmp1.path())); send_ec = ec; - (void)n; }; capy::run_async(ex)(receiver()); @@ -349,7 +346,7 @@ struct datagram_paths_test }; auto reader = [&]() -> capy::task<> { // Let the writer fill the kernel queue and park first. - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); char buf[2048]; while (!writer_done || received < sent) @@ -430,7 +427,7 @@ struct datagram_paths_test writer_done = true; }; auto reader = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); char buf[2048]; local_endpoint source; @@ -482,14 +479,13 @@ struct datagram_paths_test char buf[64]; auto receiver = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.recv( + [[maybe_unused]] auto [ec, n] = co_await s1.recv( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; recv_ec = ec; recv_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -521,14 +517,13 @@ struct datagram_paths_test local_endpoint source; auto receiver = [&]() -> capy::task<> { - auto [ec, n] = co_await sock.recv_from( + [[maybe_unused]] auto [ec, n] = co_await sock.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); - (void)n; recv_ec = ec; recv_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -567,9 +562,8 @@ struct datagram_paths_test // 64 KiB into ~4 KiB of queue: parks long before the limit. for (int i = 0; i < 64; ++i) { - auto [ec, n] = co_await s1.send( + [[maybe_unused]] auto [ec, n] = co_await s1.send( capy::const_buffer(dgram, sizeof(dgram))); - (void)n; if (ec) { send_ec = ec; @@ -580,7 +574,7 @@ struct datagram_paths_test send_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -631,9 +625,8 @@ struct datagram_paths_test auto writer = [&]() -> capy::task<> { for (int i = 0; i < 64; ++i) { - auto [ec, n] = co_await s1.send_to( + [[maybe_unused]] auto [ec, n] = co_await s1.send_to( capy::const_buffer(dgram, sizeof(dgram)), dest); - (void)n; if (ec) { send_ec = ec; @@ -644,7 +637,7 @@ struct datagram_paths_test send_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -682,7 +675,7 @@ struct datagram_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -713,7 +706,7 @@ struct datagram_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; diff --git a/test/unit/delay.cpp b/test/unit/delay.cpp index 7a8343667..240415864 100644 --- a/test/unit/delay.cpp +++ b/test/unit/delay.cpp @@ -339,9 +339,8 @@ struct delay_test auto t = [](std::chrono::steady_clock::duration& out) -> capy::task<> { auto start = std::chrono::steady_clock::now(); - auto [ec] = co_await delay(std::chrono::milliseconds(50)); + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::milliseconds(50)); out = std::chrono::steady_clock::now() - start; - (void)ec; }; capy::run_async(ioc.get_executor())(t(elapsed)); @@ -409,8 +408,7 @@ struct delay_test ~guard() { ++c_; } }; guard g{counter}; - auto [ec] = co_await delay(std::chrono::hours(1)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::hours(1)); }; capy::run_async(ioc.get_executor())(task(destroyed)); @@ -439,8 +437,7 @@ struct delay_test ~guard() { ++c_; } }; guard g{counter}; - auto [ec] = co_await delay(std::chrono::hours(ms)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::hours(ms)); }; capy::run_async(ex)(task(1, destroyed)); @@ -471,8 +468,7 @@ struct delay_test ~guard() { ++c_; } }; guard g{counter}; - auto [ec] = co_await delay(std::chrono::hours(1)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::hours(1)); }; capy::run_async(ioc.get_executor(), src.get_token())( @@ -578,8 +574,7 @@ struct delay_test std::vector order; auto d = [](int ms, int id, std::vector& out) -> capy::task<> { - auto [ec] = co_await delay(std::chrono::milliseconds(ms)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::milliseconds(ms)); out.push_back(id); }; @@ -611,8 +606,7 @@ struct delay_test auto waiter = [](bool& started_out) -> capy::task<> { started_out = true; - auto [ec] = co_await delay(std::chrono::hours(1)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::hours(1)); }; auto stopper = [](io_context& ctx) -> capy::task<> { ctx.stop(); @@ -646,15 +640,13 @@ struct delay_test auto delay_frame = [](int& counter) -> capy::task<> { struct guard { int& c_; ~guard() { ++c_; } }; guard g{counter}; - auto [ec] = co_await delay(std::chrono::hours(1)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::hours(1)); }; auto timeout_frame = [](int& counter) -> capy::task<> { struct guard { int& c_; ~guard() { ++c_; } }; guard g{counter}; - auto [ec] = co_await timeout( + [[maybe_unused]] auto [ec] = co_await timeout( delay(std::chrono::hours(1)), std::chrono::hours(1)); - (void)ec; }; capy::run_async(ex)(delay_frame(destroyed)); @@ -685,8 +677,8 @@ struct delay_test auto task = [](std::atomic& done, int ms) -> capy::task<> { - auto [ec] = co_await delay(std::chrono::milliseconds(ms)); - (void)ec; // success or canceled — both acceptable + [[maybe_unused]] auto [ec] = co_await delay(std::chrono::milliseconds(ms)); + // success or canceled — both acceptable done.fetch_add(1, std::memory_order_relaxed); }; @@ -869,8 +861,7 @@ struct delay_test }; guard g{counter}; auto tp = test_clock::now() + std::chrono::hours(1); - auto [ec] = co_await delay(tp); - (void)ec; + [[maybe_unused]] auto [ec] = co_await delay(tp); }; capy::run_async(ioc.get_executor())(task(destroyed)); @@ -912,8 +903,8 @@ struct delay_test auto task = [](std::atomic& done) -> capy::task<> { auto tp = test_clock::now() + std::chrono::milliseconds(50); - auto [ec] = co_await delay(tp); - (void)ec; // success or canceled — both acceptable + [[maybe_unused]] auto [ec] = co_await delay(tp); + // success or canceled — both acceptable done.fetch_add(1, std::memory_order_relaxed); }; diff --git a/test/unit/endpoint.cpp b/test/unit/endpoint.cpp index e18882c53..529af6d8f 100644 --- a/test/unit/endpoint.cpp +++ b/test/unit/endpoint.cpp @@ -108,6 +108,19 @@ struct endpoint_parse_test void testConstructFromStringThrows() { + // The constructor throws the code make_endpoint returns. + std::error_code caught; + try + { + endpoint("not an endpoint"); + BOOST_TEST_FAIL(); + } + catch (std::system_error const& e) + { + caught = e.code(); + } + BOOST_TEST(caught == std::errc::invalid_argument); + // Empty string BOOST_TEST_THROWS(endpoint(""), std::system_error); @@ -134,6 +147,10 @@ struct endpoint_parse_test void testDetectFormat() { + // Empty input classifies as ipv4_no_port; the parse arm + // rejects it. + BOOST_TEST( + detect_endpoint_format("") == endpoint_format::ipv4_no_port); BOOST_TEST( detect_endpoint_format("192.168.1.1") == endpoint_format::ipv4_no_port); @@ -154,8 +171,7 @@ struct endpoint_parse_test void testParseIPv4NoPort() { - endpoint ep; - auto ec = parse_endpoint("192.168.1.1", ep); + auto [ec, ep] = make_endpoint("192.168.1.1"); BOOST_TEST(!ec); BOOST_TEST(ep.is_v4()); BOOST_TEST_EQ(ep.port(), 0); @@ -164,69 +180,66 @@ struct endpoint_parse_test void testParseIPv4WithPort() { - endpoint ep; - auto ec = parse_endpoint("192.168.1.1:8080", ep); + auto [ec, ep] = make_endpoint("192.168.1.1:8080"); BOOST_TEST(!ec); BOOST_TEST(ep.is_v4()); BOOST_TEST_EQ(ep.port(), 8080); BOOST_TEST_EQ(ep.v4_address().to_string(), "192.168.1.1"); // Edge cases - ec = parse_endpoint("127.0.0.1:0", ep); - BOOST_TEST(!ec); - BOOST_TEST_EQ(ep.port(), 0); + auto [ec0, ep0] = make_endpoint("127.0.0.1:0"); + BOOST_TEST(!ec0); + BOOST_TEST_EQ(ep0.port(), 0); - ec = parse_endpoint("127.0.0.1:65535", ep); - BOOST_TEST(!ec); - BOOST_TEST_EQ(ep.port(), 65535); + auto [ec1, ep1] = make_endpoint("127.0.0.1:65535"); + BOOST_TEST(!ec1); + BOOST_TEST_EQ(ep1.port(), 65535); } void testParseIPv6NoPort() { - endpoint ep; - auto ec = parse_endpoint("::1", ep); + auto [ec, ep] = make_endpoint("::1"); BOOST_TEST(!ec); BOOST_TEST(ep.is_v6()); BOOST_TEST_EQ(ep.port(), 0); BOOST_TEST(ep.v6_address().is_loopback()); - ec = parse_endpoint("2001:db8::1", ep); - BOOST_TEST(!ec); - BOOST_TEST(ep.is_v6()); - BOOST_TEST_EQ(ep.port(), 0); + auto [ec1, ep1] = make_endpoint("2001:db8::1"); + BOOST_TEST(!ec1); + BOOST_TEST(ep1.is_v6()); + BOOST_TEST_EQ(ep1.port(), 0); } void testParseIPv6Bracketed() { - endpoint ep; - // Bracketed without port - auto ec = parse_endpoint("[::1]", ep); + auto [ec, ep] = make_endpoint("[::1]"); BOOST_TEST(!ec); BOOST_TEST(ep.is_v6()); BOOST_TEST_EQ(ep.port(), 0); BOOST_TEST(ep.v6_address().is_loopback()); // Bracketed with port - ec = parse_endpoint("[::1]:8080", ep); - BOOST_TEST(!ec); - BOOST_TEST(ep.is_v6()); - BOOST_TEST_EQ(ep.port(), 8080); - BOOST_TEST(ep.v6_address().is_loopback()); + auto [ec1, ep1] = make_endpoint("[::1]:8080"); + BOOST_TEST(!ec1); + BOOST_TEST(ep1.is_v6()); + BOOST_TEST_EQ(ep1.port(), 8080); + BOOST_TEST(ep1.v6_address().is_loopback()); // Full address with port - ec = parse_endpoint("[2001:db8::1]:443", ep); - BOOST_TEST(!ec); - BOOST_TEST(ep.is_v6()); - BOOST_TEST_EQ(ep.port(), 443); + auto [ec2, ep2] = make_endpoint("[2001:db8::1]:443"); + BOOST_TEST(!ec2); + BOOST_TEST(ep2.is_v6()); + BOOST_TEST_EQ(ep2.port(), 443); } void testParseInvalid() { auto check_invalid = [](std::string_view s) { - endpoint ep; - auto ec = parse_endpoint(s, ep); - BOOST_TEST(bool(ec)); + auto [ec, ep] = make_endpoint(s); + BOOST_TEST(ec == std::errc::invalid_argument); + // The failure payload is the documented default value. + BOOST_TEST(ep == endpoint()); }; // Empty @@ -237,6 +250,13 @@ struct endpoint_parse_test check_invalid("1.2.3"); check_invalid("1.2.3.4.5"); + // Invalid IPv4 with a valid port (the with-port parse arm) + check_invalid("256.0.0.1:80"); + + // Invalid unbracketed IPv6 (two-plus colons routes to the + // ipv6_no_port arm) + check_invalid("1:2:zz"); + // Invalid port check_invalid("1.2.3.4:"); check_invalid("1.2.3.4:abc"); diff --git a/test/unit/error_conditions.cpp b/test/unit/error_conditions.cpp index 1b20ada02..10045cb87 100644 --- a/test/unit/error_conditions.cpp +++ b/test/unit/error_conditions.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -69,11 +70,10 @@ struct error_conditions_test auto reader = [&](tcp_socket& b) -> capy::task<> { char buf[32] = {}; - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); read_ec = ec; read_done = true; - (void)n; }; auto closer = [](tcp_socket& a) -> capy::task<> { a.close(); // graceful FIN @@ -106,11 +106,10 @@ struct error_conditions_test // exact IOCP scenario that yields ERROR_NETNAME_DELETED. auto reader = [&](tcp_socket& b) -> capy::task<> { char buf[32] = {}; - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); read_ec = ec; read_done = true; - (void)n; }; auto closer = [](tcp_socket& a) -> capy::task<> { a.close(); // RST via SO_LINGER{on,0} @@ -145,7 +144,7 @@ struct error_conditions_test b.close(); // peer dies (RST) // Let the RST propagate before writing. - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); // Keep writing until the failure surfaces. The budget (256 x 64 KiB // = 16 MiB) is far beyond any platform's send buffer + in-flight @@ -155,10 +154,9 @@ struct error_conditions_test std::array buf{}; for (int i = 0; i < 256; ++i) { - auto [ec, n] = co_await a.write_some( + [[maybe_unused]] auto [ec, n] = co_await a.write_some( capy::const_buffer(buf.data(), buf.size())); write_ec = ec; - (void)n; if (ec) { write_failed = true; diff --git a/test/unit/host_name.cpp b/test/unit/host_name.cpp index c996b0b9e..317722a1f 100644 --- a/test/unit/host_name.cpp +++ b/test/unit/host_name.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "test_suite.hpp" @@ -21,15 +22,18 @@ struct host_name_test // Every configured machine has a hostname. void testReturnsNonEmpty() { - std::string h = host_name(); + auto [ec, h] = host_name(); + BOOST_TEST(!ec); BOOST_TEST(!h.empty()); } // Catches buffer or string-lifetime bugs across calls. void testStable() { - std::string a = host_name(); - std::string b = host_name(); + auto [ec1, a] = host_name(); + auto [ec2, b] = host_name(); + BOOST_TEST(!ec1); + BOOST_TEST(!ec2); BOOST_TEST_EQ(a, b); } @@ -37,7 +41,8 @@ struct host_name_test // from a miscounted buffer. void testReasonableLength() { - std::string h = host_name(); + auto [ec, h] = host_name(); + BOOST_TEST(!ec); BOOST_TEST(h.size() > 0); BOOST_TEST(h.size() <= 255); } @@ -47,7 +52,8 @@ struct host_name_test // corosio's WSAStartup is lazy (inside io_context). void testNoIoContextNeeded() { - std::string h = host_name(); + auto [ec, h] = host_name(); + BOOST_TEST(!ec); BOOST_TEST(!h.empty()); } @@ -56,7 +62,8 @@ struct host_name_test // accept any printable ASCII byte or high-bit byte. void testCharsetSanity() { - std::string h = host_name(); + auto [ec, h] = host_name(); + BOOST_TEST(!ec); for (unsigned char c : h) { bool printable_ascii = (c >= 0x20 && c <= 0x7E); diff --git a/test/unit/io_context.cpp b/test/unit/io_context.cpp index 2c2cf350a..349cee1b3 100644 --- a/test/unit/io_context.cpp +++ b/test/unit/io_context.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -308,9 +309,7 @@ inline capy::task when_all_set_event_main(bool& finished) { capy::async_event evt; - auto [ec, a, b] = co_await capy::when_all(evt.wait(), set_event_task(evt)); - (void)a; - (void)b; + [[maybe_unused]] auto [ec, a, b] = co_await capy::when_all(evt.wait(), set_event_task(evt)); BOOST_TEST(!ec); finished = true; } @@ -947,7 +946,7 @@ struct io_context_test std::thread runner([&]() { // 5s ceiling is a safety net only; we release the guard // below as soon as work is drained. - (void)ioc.run_for(std::chrono::seconds(5)); + std::ignore = ioc.run_for(std::chrono::seconds(5)); }); for (int i = 0; i < 8; ++i) diff --git a/test/unit/ipv4_address.cpp b/test/unit/ipv4_address.cpp index 786370e77..78eba1bc9 100644 --- a/test/unit/ipv4_address.cpp +++ b/test/unit/ipv4_address.cpp @@ -11,6 +11,7 @@ #include #include +#include #include "test_suite.hpp" @@ -47,11 +48,20 @@ struct ipv4_address_test BOOST_TEST_EQ(a.to_string(), "10.0.0.1"); } - // Invalid string throws + // Invalid string throws system_error carrying the parse code { - BOOST_TEST_THROWS(ipv4_address("invalid"), std::invalid_argument); - BOOST_TEST_THROWS(ipv4_address("256.0.0.1"), std::invalid_argument); - BOOST_TEST_THROWS(ipv4_address("1.2.3"), std::invalid_argument); + BOOST_TEST_THROWS(ipv4_address("invalid"), std::system_error); + BOOST_TEST_THROWS(ipv4_address("256.0.0.1"), std::system_error); + BOOST_TEST_THROWS(ipv4_address("1.2.3"), std::system_error); + try + { + ipv4_address("invalid"); + BOOST_TEST_FAIL(); + } + catch (std::system_error const& e) + { + BOOST_TEST(e.code() == std::errc::invalid_argument); + } } } @@ -59,8 +69,7 @@ struct ipv4_address_test { // Valid addresses auto check_valid = [](std::string_view s, std::uint32_t expected) { - ipv4_address addr; - auto ec = parse_ipv4_address(s, addr); + auto [ec, addr] = make_ipv4_address(s); BOOST_TEST(!ec); BOOST_TEST_EQ(addr.to_uint(), expected); }; @@ -73,9 +82,10 @@ struct ipv4_address_test // Invalid addresses auto check_invalid = [](std::string_view s) { - ipv4_address addr; - auto ec = parse_ipv4_address(s, addr); + auto [ec, addr] = make_ipv4_address(s); BOOST_TEST(ec == std::errc::invalid_argument); + // The failure payload is the documented default value. + BOOST_TEST(addr == ipv4_address()); }; check_invalid(""); diff --git a/test/unit/ipv6_address.cpp b/test/unit/ipv6_address.cpp index 00b5ee307..00507c9e9 100644 --- a/test/unit/ipv6_address.cpp +++ b/test/unit/ipv6_address.cpp @@ -12,6 +12,8 @@ #include #include +#include +#include #include "test_suite.hpp" @@ -49,10 +51,19 @@ struct ipv6_address_test BOOST_TEST(a.is_loopback()); } - // Invalid string throws + // Invalid string throws system_error carrying the parse code { - BOOST_TEST_THROWS(ipv6_address("invalid"), std::invalid_argument); - BOOST_TEST_THROWS(ipv6_address(":::1"), std::invalid_argument); + BOOST_TEST_THROWS(ipv6_address("invalid"), std::system_error); + BOOST_TEST_THROWS(ipv6_address(":::1"), std::system_error); + try + { + ipv6_address("invalid"); + BOOST_TEST_FAIL(); + } + catch (std::system_error const& e) + { + BOOST_TEST(e.code() == std::errc::invalid_argument); + } } } @@ -60,8 +71,7 @@ struct ipv6_address_test { // Valid addresses auto check_valid = [](std::string_view s) { - ipv6_address addr; - auto ec = parse_ipv6_address(s, addr); + auto [ec, addr] = make_ipv6_address(s); if (ec) { BOOST_TEST_FAIL(); @@ -97,9 +107,10 @@ struct ipv6_address_test // Invalid addresses auto check_invalid = [](std::string_view s) { - ipv6_address addr; - auto ec = parse_ipv6_address(s, addr); + auto [ec, addr] = make_ipv6_address(s); BOOST_TEST(ec == std::errc::invalid_argument); + // The failure payload is the documented default value. + BOOST_TEST(addr == ipv6_address()); }; check_invalid(""); @@ -171,51 +182,57 @@ struct ipv6_address_test void testParseEndsWithDoubleColon() { // "1::" — '::' at the end requires the "ends in ::" hex break path. - ipv6_address addr; - auto ec = parse_ipv6_address("1::", addr); + auto [ec, addr] = make_ipv6_address("1::"); BOOST_TEST(!ec); BOOST_TEST_EQ(addr.to_string(), "1::"); } void testParseInvalidIPv4Suffix() { - ipv6_address addr; + auto rejects = [](std::string_view s) { + return bool(std::get<0>(make_ipv6_address(s))); + }; // "::1.2.3" — IPv4 portion incomplete. - BOOST_TEST(parse_ipv6_address("::1.2.3", addr)); + BOOST_TEST(rejects("::1.2.3")); // "::g.0.0.0" — non-numeric hex. - BOOST_TEST(parse_ipv6_address("::g.0.0.0", addr)); + BOOST_TEST(rejects("::g.0.0.0")); // "1:2:3:4:5:6.7.8.9" — IPv4 with no '::' but not enough h16 groups. - BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6.7.8.9", addr)); + BOOST_TEST(rejects("1:2:3:4:5:6.7.8.9")); // The embedded-IPv4 validator parses each octet as an h16 and // rejects values dotted decimal can never produce. - BOOST_TEST(parse_ipv6_address("::1.2.3.400", addr)); - BOOST_TEST(parse_ipv6_address("::1.2.3.2a", addr)); - BOOST_TEST(parse_ipv6_address("::1.2.3.a1", addr)); + BOOST_TEST(rejects("::1.2.3.400")); + BOOST_TEST(rejects("::1.2.3.2a")); + BOOST_TEST(rejects("::1.2.3.a1")); + BOOST_TEST(rejects("::1.2.3.1a1")); + BOOST_TEST(rejects("1:zz::")); } void testParseMoreEdges() { - ipv6_address addr; + auto rejects = [](std::string_view s) { + return bool(std::get<0>(make_ipv6_address(s))); + }; // Uppercase hex digits. - BOOST_TEST(!parse_ipv6_address("ABCD::EF01", addr)); - BOOST_TEST_EQ(addr.to_string(), "abcd::ef01"); + auto [uec, upper] = make_ipv6_address("ABCD::EF01"); + BOOST_TEST(!uec); + BOOST_TEST_EQ(upper.to_string(), "abcd::ef01"); // Full-form embedded IPv4 with no '::'. - BOOST_TEST(!parse_ipv6_address("1:2:3:4:5:6:1.2.3.4", addr)); + BOOST_TEST(!rejects("1:2:3:4:5:6:1.2.3.4")); // Input ending right after a colon. - BOOST_TEST(parse_ipv6_address("1:", addr)); - BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6:7:", addr)); + BOOST_TEST(rejects("1:")); + BOOST_TEST(rejects("1:2:3:4:5:6:7:")); // Non-hex garbage after '::'. - BOOST_TEST(parse_ipv6_address("1::zz", addr)); + BOOST_TEST(rejects("1::zz")); // '::' with all eight groups already present. - BOOST_TEST(parse_ipv6_address("1:2:3:4:5:6:7:8::", addr)); + BOOST_TEST(rejects("1:2:3:4:5:6:7:8::")); // '::' compressing exactly zero remaining groups at the end. - BOOST_TEST(!parse_ipv6_address("1:2:3:4:5:6:7::", addr)); + BOOST_TEST(!rejects("1:2:3:4:5:6:7::")); } void testPredicates() diff --git a/test/unit/local_connect_pair.cpp b/test/unit/local_connect_pair.cpp index 8fa2aabbf..4b8e4e485 100644 --- a/test/unit/local_connect_pair.cpp +++ b/test/unit/local_connect_pair.cpp @@ -85,7 +85,7 @@ struct local_connect_pair_test // a is open; connect_pair must refuse and leave both sockets // in their original state (a open, b closed). auto ec = connect_pair(a, b); - BOOST_TEST(static_cast(ec)); + BOOST_TEST(ec == std::errc::already_connected); BOOST_TEST(a.is_open()); BOOST_TEST(!b.is_open()); } @@ -97,7 +97,7 @@ struct local_connect_pair_test local_datagram_socket a(ioc), b(ioc); BOOST_TEST(!b.open()); auto ec = connect_pair(a, b); - BOOST_TEST(static_cast(ec)); + BOOST_TEST(ec == std::errc::already_connected); BOOST_TEST(!a.is_open()); BOOST_TEST(b.is_open()); } diff --git a/test/unit/local_datagram_socket.cpp b/test/unit/local_datagram_socket.cpp index c24517ef7..f05a09a39 100644 --- a/test/unit/local_datagram_socket.cpp +++ b/test/unit/local_datagram_socket.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -616,7 +617,7 @@ struct local_datagram_socket_test std::error_code caught; try { - (void)sock.release(); + std::ignore = sock.release(); } catch (std::system_error const& e) { @@ -633,7 +634,7 @@ struct local_datagram_socket_test std::error_code caught; try { - (void)sock.available(); + std::ignore = sock.available(); } catch (std::system_error const& e) { @@ -656,7 +657,7 @@ struct local_datagram_socket_test capy::run_async(ex)( [](local_datagram_socket& s, char const* m, std::size_t n, bool& d) -> capy::task<> { - (void)co_await s.send(capy::const_buffer(m, n)); + std::ignore = co_await s.send(capy::const_buffer(m, n)); d = true; }(s1, msg, std::strlen(msg), done)); @@ -685,9 +686,8 @@ struct local_datagram_socket_test std::error_code recv_ec; auto reader = [&]() -> capy::task<> { char buf[8]; - auto [ec, n] = co_await d1.recv( + [[maybe_unused]] auto [ec, n] = co_await d1.recv( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; recv_ec = ec; recv_done = true; }; @@ -755,7 +755,6 @@ struct local_datagram_socket_test local_datagram_socket s1(ioc), s2(ioc); if (auto ec = connect_pair(s1, s2)) throw std::system_error(ec, "connect_pair"); - (void)s2; BOOST_TEST(s1.is_open()); int fd = s1.release(); @@ -782,7 +781,7 @@ struct local_datagram_socket_test BOOST_TEST(set_ec == std::errc::bad_file_descriptor); try { - (void)closed.get_option(); + std::ignore = closed.get_option(); } catch (std::system_error const& e) { @@ -808,7 +807,7 @@ struct local_datagram_socket_test bool get_threw = false; try { - (void)sock.get_option(); + std::ignore = sock.get_option(); } catch (std::system_error const& e) { @@ -854,10 +853,9 @@ struct local_datagram_socket_test void testCancelPendingRecv() { io_context ioc(Backend); - local_datagram_socket s1(ioc), s2(ioc); + [[maybe_unused]] local_datagram_socket s1(ioc), s2(ioc); if (auto ec = connect_pair(s1, s2)) throw std::system_error(ec, "connect_pair"); - (void)s1; auto ex = ioc.get_executor(); std::error_code recv_ec; @@ -867,15 +865,14 @@ struct local_datagram_socket_test [](local_datagram_socket& s, std::error_code& ec_out, bool& done) -> capy::task<> { char buf[8]; - auto [ec, n] = co_await s.recv( + [[maybe_unused]] auto [ec, n] = co_await s.recv( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; ec_out = ec; done = true; }(s2, recv_ec, recv_done)); auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s2.cancel(); }; capy::run_async(ex)(canceller()); diff --git a/test/unit/local_stream_socket.cpp b/test/unit/local_stream_socket.cpp index 6c6373caa..4288ce9dc 100644 --- a/test/unit/local_stream_socket.cpp +++ b/test/unit/local_stream_socket.cpp @@ -46,6 +46,7 @@ #include #include #include +#include #include #include "context.hpp" @@ -92,6 +93,37 @@ struct local_stream_socket_test BOOST_TEST_EQ(s1.is_open(), false); } + void testAcceptorConvenienceConstructor() + { + io_context ioc(Backend); + test::temp_socket_dir tmp; + local_endpoint ep(tmp.path()); + + local_stream_acceptor acc(ioc, ep); + BOOST_TEST(acc.is_open()); + + // Opening an already-open acceptor is a no-op reporting success. + BOOST_TEST(!acc.open()); + BOOST_TEST(acc.is_open()); + BOOST_TEST_EQ(acc.local_endpoint().path(), tmp.path()); + + // A second acceptor on the same path surfaces the bind + // conflict by throwing system_error. + std::error_code caught; + try + { + local_stream_acceptor dup(ioc, ep); + BOOST_TEST_FAIL(); + } + catch (std::system_error const& e) + { + caught = e.code(); + } + BOOST_TEST(caught == std::errc::address_in_use); + + acc.close(); + } + void testConnectAccept() { io_context ioc(Backend); @@ -402,29 +434,27 @@ struct local_stream_socket_test capy::run_async(ex)( [](local_stream_acceptor& a, local_stream_socket& s) -> capy::task<> { - (void)co_await a.accept(s); + std::ignore = co_await a.accept(s); }(acc, server)); capy::run_async(ex)( [](local_stream_socket& s, local_endpoint ep) -> capy::task<> { - (void)co_await s.connect(ep); + std::ignore = co_await s.connect(ep); }(client, local_endpoint(path))); ioc.run(); ioc.restart(); // Endpoint accessors hit the backend - auto cl = client.local_endpoint(); + [[maybe_unused]] auto cl = client.local_endpoint(); auto cr = client.remote_endpoint(); auto sl = server.local_endpoint(); - auto sr = server.remote_endpoint(); + [[maybe_unused]] auto sr = server.remote_endpoint(); // server local should match the listening path BOOST_TEST_EQ(sl.path(), path); // client remote should match the listening path BOOST_TEST_EQ(cr.path(), path); // touch the others so the lines exec - (void)cl; - (void)sr; } void testShutdown() @@ -462,9 +492,8 @@ struct local_stream_socket_test std::error_code read_ec; auto reader = [&]() -> capy::task<> { char buf[4]; - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; read_done = true; }; @@ -521,9 +550,8 @@ struct local_stream_socket_test bool got = false; auto writer = [&]() -> capy::task<> { char const out[] = "ok"; - auto [wec, wn] = co_await s2.write_some( + [[maybe_unused]] auto [wec, wn] = co_await s2.write_some( capy::const_buffer(out, 2)); - (void)wn; BOOST_TEST(!wec); }; auto reader = [&]() -> capy::task<> { @@ -603,7 +631,7 @@ struct local_stream_socket_test std::error_code caught; try { - (void)sock.release(); + std::ignore = sock.release(); } catch (std::system_error const& e) { @@ -620,7 +648,7 @@ struct local_stream_socket_test std::error_code caught; try { - (void)sock.available(); + std::ignore = sock.available(); } catch (std::system_error const& e) { @@ -655,7 +683,7 @@ struct local_stream_socket_test // of failing it, retract it so the test reports the miss // instead of hanging the suite. auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!done) client.cancel(); }; @@ -695,7 +723,7 @@ struct local_stream_socket_test // Schedule a cancel after a brief delay auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); acc.cancel(); }; capy::run_async(ex)(canceller()); @@ -733,7 +761,7 @@ struct local_stream_socket_test accept_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -765,7 +793,7 @@ struct local_stream_socket_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s1.cancel(); }; @@ -792,14 +820,13 @@ struct local_stream_socket_test char buf[16]; auto reader = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; read_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s1.cancel(); }; @@ -827,14 +854,13 @@ struct local_stream_socket_test char buf[16]; auto reader = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; read_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -932,7 +958,7 @@ struct local_stream_socket_test try { acc.set_option(socket_option::reuse_address(true)); - (void)acc.get_option(); + std::ignore = acc.get_option(); } catch (std::system_error const&) { @@ -1008,7 +1034,7 @@ struct local_stream_socket_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); acc.cancel(); }; @@ -1097,7 +1123,7 @@ struct local_stream_socket_test // failing it, retract it so the test reports the miss // instead of hanging the suite. auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!accept_done) acc.cancel(); }; @@ -1130,13 +1156,13 @@ struct local_stream_socket_test local_stream_socket server(ioc); auto acceptor_task = [&]() -> capy::task<> { - (void)co_await acc.accept(server); + std::ignore = co_await acc.accept(server); }; capy::run_async(ex)(acceptor_task()); // Run the coroutine to its parked suspension point only, then // fall off the end of the scope with the accept outstanding. - (void)ioc.run_one(); + std::ignore = ioc.run_one(); BOOST_TEST_PASS(); } @@ -1153,12 +1179,12 @@ struct local_stream_socket_test char buf[16]; auto reader = [&]() -> capy::task<> { - (void)co_await s1.read_some( + std::ignore = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); }; capy::run_async(ex)(reader()); - (void)ioc.run_one(); + std::ignore = ioc.run_one(); BOOST_TEST_PASS(); } @@ -1218,7 +1244,7 @@ struct local_stream_socket_test bool sock_get_threw = false; try { - (void)s1.get_option(); + std::ignore = s1.get_option(); } catch (std::system_error const& e) { @@ -1243,7 +1269,7 @@ struct local_stream_socket_test bool get_threw = false; try { - (void)acc.get_option(); + std::ignore = acc.get_option(); } catch (std::system_error const& e) { @@ -1299,7 +1325,7 @@ struct local_stream_socket_test bool caught = false; try { - (void)acc.release(); + std::ignore = acc.release(); } catch (std::system_error const&) { @@ -1376,10 +1402,9 @@ struct local_stream_socket_test auto [cec] = co_await client.connect(local_endpoint(path)); BOOST_TEST(!cec); char const out[] = "ping"; - auto [wec, wn] = + [[maybe_unused]] auto [wec, wn] = co_await client.write_some(capy::const_buffer(out, 4)); BOOST_TEST(!wec); - (void)wn; }; auto ex = ioc.get_executor(); @@ -1503,10 +1528,9 @@ struct local_stream_socket_test auto [cec] = co_await client.connect(local_endpoint(path)); BOOST_TEST(!cec); char const out[] = "ping"; - auto [wec, wn] = + [[maybe_unused]] auto [wec, wn] = co_await client.write_some(capy::const_buffer(out, 4)); BOOST_TEST(!wec); - (void)wn; }; capy::run_async(ex)(server()); @@ -1536,8 +1560,7 @@ struct local_stream_socket_test bool caught = false; try { - local_endpoint ep(too_long); - (void)ep; + [[maybe_unused]] local_endpoint ep(too_long); } catch (std::system_error const&) { @@ -1600,6 +1623,7 @@ struct local_stream_socket_test testMove(); testMoveAssign(); testCancelOnClosedSocket(); + testAcceptorConvenienceConstructor(); testNativeHandleClosed(); testEndpointsClosed(); testConnectAccept(); @@ -1686,7 +1710,7 @@ struct local_stream_socket_test capy::run_async(ex)( [](local_stream_socket& s, char const* data, std::size_t len, bool& d) -> capy::task<> { - (void)co_await capy::write(s, capy::const_buffer(data, len)); + std::ignore = co_await capy::write(s, capy::const_buffer(data, len)); d = true; }(s1, msg, std::strlen(msg), done)); @@ -1746,9 +1770,8 @@ struct local_stream_socket_test #endif auto reader = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; read_done = true; }; diff --git a/test/unit/native/native_io_context.cpp b/test/unit/native/native_io_context.cpp index 99c8f269a..91f873cc0 100644 --- a/test/unit/native/native_io_context.cpp +++ b/test/unit/native/native_io_context.cpp @@ -36,8 +36,7 @@ struct native_io_context_test void testIoContextPolymorphicSlice() { native_io_context ctx; - io_context& base = ctx; - (void)base; + [[maybe_unused]] io_context& base = ctx; BOOST_TEST_PASS(); } diff --git a/test/unit/native/native_local_datagram_socket.cpp b/test/unit/native/native_local_datagram_socket.cpp index 74a7cdf89..6a07aa708 100644 --- a/test/unit/native/native_local_datagram_socket.cpp +++ b/test/unit/native/native_local_datagram_socket.cpp @@ -270,11 +270,9 @@ struct native_local_datagram_socket_test }; auto sender = [&]() -> capy::task<> { char dg[1] = {'X'}; - auto [ec, n] = co_await send.send_to( + [[maybe_unused]] auto [ec, n] = co_await send.send_to( capy::const_buffer(dg, sizeof(dg)), local_endpoint(rx_path)); - (void)ec; - (void)n; }; capy::run_async(ex)(waiter()); diff --git a/test/unit/native/native_local_stream_socket.cpp b/test/unit/native/native_local_stream_socket.cpp index 0e883dcfe..08ea33cc9 100644 --- a/test/unit/native/native_local_stream_socket.cpp +++ b/test/unit/native/native_local_stream_socket.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -237,7 +238,7 @@ struct native_local_stream_socket_test BOOST_TEST_EQ(ec, std::error_code{}); char const msg[] = "virtual"; - (void)co_await c.write_some( + std::ignore = co_await c.write_some( capy::const_buffer(msg, sizeof(msg) - 1)); }; @@ -271,12 +272,10 @@ struct native_local_stream_socket_test bool wait_done = false; auto rendezvous = [&]() -> capy::task<> { - auto [ec] = co_await acc.accept(server); - (void)ec; + [[maybe_unused]] auto [ec] = co_await acc.accept(server); }; auto connect_task = [&]() -> capy::task<> { - auto [ec] = co_await client.connect(local_endpoint(path)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await client.connect(local_endpoint(path)); }; capy::run_async(ex)(rendezvous()); capy::run_async(ex)(connect_task()); @@ -322,8 +321,7 @@ struct native_local_stream_socket_test wait_done = true; }; auto connect_task = [&]() -> capy::task<> { - auto [ec] = co_await client.connect(local_endpoint(path)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await client.connect(local_endpoint(path)); }; capy::run_async(ex)(waiter()); capy::run_async(ex)(connect_task()); @@ -342,7 +340,7 @@ struct native_local_stream_socket_test bool threw = false; try { - (void)a.accept(); + std::ignore = a.accept(); } catch (std::logic_error const&) { diff --git a/test/unit/native/native_resolver.cpp b/test/unit/native/native_resolver.cpp index 37982569c..86a2ad660 100644 --- a/test/unit/native/native_resolver.cpp +++ b/test/unit/native/native_resolver.cpp @@ -83,8 +83,7 @@ struct native_resolver_test io_context ctx(Backend); native_resolver nr(ctx); - resolver& base = nr; - (void)base; + [[maybe_unused]] resolver& base = nr; BOOST_TEST_PASS(); } diff --git a/test/unit/native/native_signal_set.cpp b/test/unit/native/native_signal_set.cpp index 06cf33b28..734e9a9e9 100644 --- a/test/unit/native/native_signal_set.cpp +++ b/test/unit/native/native_signal_set.cpp @@ -47,11 +47,9 @@ struct native_signal_set_test io_context ctx(Backend); native_signal_set nss(ctx, SIGINT); - signal_set& base = nss; - (void)base; + [[maybe_unused]] signal_set& base = nss; - io_signal_set& io_base = nss; - (void)io_base; + [[maybe_unused]] io_signal_set& io_base = nss; BOOST_TEST_PASS(); } diff --git a/test/unit/native/native_tcp_acceptor.cpp b/test/unit/native/native_tcp_acceptor.cpp index cefe9eccf..72d2adc03 100644 --- a/test/unit/native/native_tcp_acceptor.cpp +++ b/test/unit/native/native_tcp_acceptor.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -112,9 +113,8 @@ struct native_tcp_acceptor_test wait_done = true; }; auto connector = [&]() -> capy::task<> { - auto [ec] = co_await client.connect( + [[maybe_unused]] auto [ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); - (void)ec; }; capy::run_async(ex)(waiter()); @@ -156,9 +156,8 @@ struct native_tcp_acceptor_test accept_done = true; }; auto connector = [&]() -> capy::task<> { - auto [ec] = co_await client.connect( + [[maybe_unused]] auto [ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); - (void)ec; }; capy::run_async(ex)(acceptor()); @@ -218,7 +217,7 @@ struct native_tcp_acceptor_test bool threw = false; try { - (void)a.accept(); + std::ignore = a.accept(); } catch (std::logic_error const&) { diff --git a/test/unit/native/native_tcp_socket.cpp b/test/unit/native/native_tcp_socket.cpp index e17ad2ea1..0a21ff2fd 100644 --- a/test/unit/native/native_tcp_socket.cpp +++ b/test/unit/native/native_tcp_socket.cpp @@ -88,14 +88,11 @@ struct native_tcp_socket_test tcp_socket& base = ns; BOOST_TEST(base.is_open()); - io_stream& stream_base = ns; - (void)stream_base; + [[maybe_unused]] io_stream& stream_base = ns; - io_read_stream& read_base = ns; - (void)read_base; + [[maybe_unused]] io_read_stream& read_base = ns; - io_write_stream& write_base = ns; - (void)write_base; + [[maybe_unused]] io_write_stream& write_base = ns; BOOST_TEST_PASS(); } diff --git a/test/unit/native/native_udp_socket.cpp b/test/unit/native/native_udp_socket.cpp index 87437db8d..2b912b635 100644 --- a/test/unit/native/native_udp_socket.cpp +++ b/test/unit/native/native_udp_socket.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -168,11 +169,11 @@ struct native_udp_socket_test }; capy::run_async(ioc.get_executor())(nested()); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); sock.cancel(); // Let the cancellation settle before checking the result. - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(recv_done); BOOST_TEST(recv_ec == capy::cond::canceled); @@ -206,11 +207,11 @@ struct native_udp_socket_test }; capy::run_async(ioc.get_executor())(nested()); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); sock.close(); // Let the close settle before checking the result. - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(recv_done); BOOST_TEST(recv_ec == capy::cond::canceled); @@ -363,11 +364,9 @@ struct native_udp_socket_test }; auto sender = [&]() -> capy::task<> { char dg[1] = {'X'}; - auto [ec, n] = co_await send.send_to( + [[maybe_unused]] auto [ec, n] = co_await send.send_to( capy::const_buffer(dg, sizeof(dg)), endpoint(ipv4_address::loopback(), port)); - (void)ec; - (void)n; }; capy::run_async(ex)(waiter()); diff --git a/test/unit/openssl_stream.cpp b/test/unit/openssl_stream.cpp index 109cdf6cc..b06d6c9ce 100644 --- a/test/unit/openssl_stream.cpp +++ b/test/unit/openssl_stream.cpp @@ -81,13 +81,11 @@ struct openssl_stream_test openssl_stream stream(&sock, ctx); // Non-const overload via mutable stream. - capy::any_stream& mutable_next = stream.next_layer(); - (void)mutable_next; + [[maybe_unused]] capy::any_stream& mutable_next = stream.next_layer(); // Const overload via reference to const. openssl_stream const& cref = stream; - capy::any_stream const& const_next = cref.next_layer(); - (void)const_next; + [[maybe_unused]] capy::any_stream const& const_next = cref.next_layer(); BOOST_TEST(&mutable_next == &const_next); } diff --git a/test/unit/precancel.cpp b/test/unit/precancel.cpp index 54d70063a..bd4ffab84 100644 --- a/test/unit/precancel.cpp +++ b/test/unit/precancel.cpp @@ -67,16 +67,14 @@ struct precancel_test int done = 0; auto reader = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; ++done; }; auto writer = [&]() -> capy::task<> { - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await s1.write_some(capy::const_buffer("x", 1)); - (void)n; write_ec = ec; ++done; }; @@ -178,16 +176,14 @@ struct precancel_test int done = 0; auto send_to_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.send_to( + [[maybe_unused]] auto [ec, n] = co_await s1.send_to( capy::const_buffer("x", 1), peer_ep); - (void)n; send_to_ec = ec; ++done; }; auto recv_from_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.recv_from( + [[maybe_unused]] auto [ec, n] = co_await s1.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); - (void)n; recv_from_ec = ec; ++done; }; @@ -232,15 +228,13 @@ struct precancel_test std::error_code send_ec, recv_ec; auto send_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.send(capy::const_buffer("x", 1)); - (void)n; + [[maybe_unused]] auto [ec, n] = co_await s1.send(capy::const_buffer("x", 1)); send_ec = ec; ++done; }; auto recv_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.recv( + [[maybe_unused]] auto [ec, n] = co_await s1.recv( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; recv_ec = ec; ++done; }; @@ -272,16 +266,14 @@ struct precancel_test int done = 0; auto reader = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; ++done; }; auto writer = [&]() -> capy::task<> { - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await s1.write_some(capy::const_buffer("x", 1)); - (void)n; write_ec = ec; ++done; }; @@ -362,15 +354,13 @@ struct precancel_test int done = 0; auto send_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.send(capy::const_buffer("x", 1)); - (void)n; + [[maybe_unused]] auto [ec, n] = co_await s1.send(capy::const_buffer("x", 1)); send_ec = ec; ++done; }; auto recv_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.recv( + [[maybe_unused]] auto [ec, n] = co_await s1.recv( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; recv_ec = ec; ++done; }; @@ -414,16 +404,14 @@ struct precancel_test int done = 0; auto send_to_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.send_to( + [[maybe_unused]] auto [ec, n] = co_await s1.send_to( capy::const_buffer("x", 1), local_endpoint(tmp2.path())); - (void)n; send_to_ec = ec; ++done; }; auto recv_from_task = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.recv_from( + [[maybe_unused]] auto [ec, n] = co_await s1.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); - (void)n; recv_from_ec = ec; ++done; }; diff --git a/test/unit/random_access_file.cpp b/test/unit/random_access_file.cpp index 5952baa85..0f3daabd6 100644 --- a/test/unit/random_access_file.cpp +++ b/test/unit/random_access_file.cpp @@ -954,10 +954,8 @@ struct random_access_file_test auto reader = [](random_access_file* f, std::uint64_t off, std::atomic* c) -> capy::task<> { char buf[1024]; - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await f->read_some_at(off, capy::mutable_buffer(buf, 1024)); - (void)ec; - (void)n; c->fetch_add(1); }; diff --git a/test/unit/reactor_paths.cpp b/test/unit/reactor_paths.cpp index 0efd13632..1bccabfdd 100644 --- a/test/unit/reactor_paths.cpp +++ b/test/unit/reactor_paths.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #if BOOST_COROSIO_POSIX @@ -94,11 +95,9 @@ struct reactor_paths_test }; auto peer_writer = [&]() -> capy::task<> { // Brief delay so the read side parks first. - (void)co_await corosio::delay(std::chrono::milliseconds(10)); - auto [ec, n] = co_await s2.write_some( + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); + [[maybe_unused]] auto [ec, n] = co_await s2.write_some( capy::const_buffer(payload.data(), payload.size())); - (void)ec; - (void)n; }; capy::run_async(ex)(reader()); @@ -142,7 +141,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(500)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(500)); sock.cancel(); }; @@ -203,11 +202,11 @@ struct reactor_paths_test wait_done = true; }; auto closer = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s2.close(); // Bound the wait: cancel s1 after another delay if the peer // close did not surface as an error condition. - (void)co_await corosio::delay(std::chrono::milliseconds(200)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(200)); s1.cancel(); }; @@ -238,7 +237,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s1.cancel(); }; @@ -278,11 +277,9 @@ struct reactor_paths_test read_n = n; }; auto writer = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); - auto [ec, n] = co_await s2.write_some( + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); + [[maybe_unused]] auto [ec, n] = co_await s2.write_some( capy::const_buffer(payload.data(), payload.size())); - (void)ec; - (void)n; }; capy::run_async(ex)(reader()); @@ -471,7 +468,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); acc.cancel(); }; @@ -532,7 +529,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); sock.cancel(); }; @@ -765,7 +762,7 @@ struct reactor_paths_test wait_done = true; }; auto closer = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); sock.close(); }; @@ -815,14 +812,12 @@ struct reactor_paths_test capy::run_async(ex)( [](tcp_acceptor& a, tcp_socket& p, bool& done) -> capy::task<> { - auto [ec] = co_await a.accept(p); - (void)ec; + [[maybe_unused]] auto [ec] = co_await a.accept(p); done = true; }(accs[i], peers[i], accept_done[i])); capy::run_async(ex)( [](tcp_socket& c, endpoint ep, bool& done) -> capy::task<> { - auto [ec] = co_await c.connect(ep); - (void)ec; + [[maybe_unused]] auto [ec] = co_await c.connect(ep); done = true; }(clients[i], endpoint(ipv4_address::loopback(), ports[i]), connect_done[i])); @@ -857,7 +852,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -887,7 +882,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -936,14 +931,13 @@ struct reactor_paths_test char buf[64]; auto receiver = [&]() -> capy::task<> { - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await s1.recv(capy::mutable_buffer(buf, sizeof(buf))); - (void)n; recv_ec = ec; recv_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -973,14 +967,13 @@ struct reactor_paths_test char buf[64]; auto receiver = [&]() -> capy::task<> { - auto [ec, n] = co_await sock.recv_from( + [[maybe_unused]] auto [ec, n] = co_await sock.recv_from( capy::mutable_buffer(buf, sizeof(buf)), src); - (void)n; recv_ec = ec; recv_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -1015,7 +1008,7 @@ struct reactor_paths_test accept_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -1106,7 +1099,7 @@ struct reactor_paths_test // assertions below accept — only a false success is a failure. int filler = ::socket(AF_INET, SOCK_STREAM, 0); BOOST_TEST(filler >= 0); - (void)::connect( + std::ignore = ::connect( filler, reinterpret_cast(&addr), sizeof(addr)); auto port = ntohs(addr.sin_port); @@ -1126,16 +1119,14 @@ struct reactor_paths_test // is processed only after the driver has parked. auto canceller = [&]() -> capy::task<> { char c[2]; - auto [ec, n] = co_await t1.read_some( + [[maybe_unused]] auto [ec, n] = co_await t1.read_some( capy::mutable_buffer(c, sizeof(c))); - (void)ec; - (void)n; // Drain the ready queue before cancelling: a falsely // completed connect is already posted at this point, and // cancelling first would mark the op cancelled and mask // the wrong ec at delivery. Let it deliver, then cancel // the (correctly) parked op. - (void)co_await corosio::delay(std::chrono::milliseconds(1)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(1)); cancel_sent = true; sock.cancel(); }; @@ -1143,11 +1134,9 @@ struct reactor_paths_test // Yield through one reactor cycle with no op parked so the // fresh socket's spurious writable event is dispatched and // latched before the connect begins. - (void)co_await corosio::delay(std::chrono::milliseconds(1)); - auto [sec, sn] = co_await t2.write_some( + std::ignore = co_await corosio::delay(std::chrono::milliseconds(1)); + [[maybe_unused]] auto [sec, sn] = co_await t2.write_some( capy::const_buffer("go", 2)); - (void)sec; - (void)sn; auto [ec] = co_await sock.connect( endpoint(ipv4_address::loopback(), port)); conn_ec = ec; @@ -1198,7 +1187,7 @@ struct reactor_paths_test { int soerr = 0; socklen_t sslen = sizeof(soerr); - (void)::getsockopt( + std::ignore = ::getsockopt( sock.native_handle(), SOL_SOCKET, SO_ERROR, &soerr, &sslen); false_success = soerr == 0; @@ -1232,9 +1221,8 @@ struct reactor_paths_test auto waiter = [&]() -> capy::task<> { char c; - auto [rec, rn] = co_await s1.read_some( + [[maybe_unused]] auto [rec, rn] = co_await s1.read_some( capy::mutable_buffer(&c, 1)); - (void)rn; read_ec = rec; // Reporting the reset consumed SO_ERROR. Linux keeps // POLLHUP visible on the dead socket; on a platform that @@ -1291,9 +1279,8 @@ struct reactor_paths_test std::size_t recv_n = 42; auto task = [&]() -> capy::task<> { - auto [sec, sn] = co_await ssock.send_to( + [[maybe_unused]] auto [sec, sn] = co_await ssock.send_to( capy::const_buffer(nullptr, 0), rsock.local_endpoint()); - (void)sn; // Some platforms reject zero-length datagram sends (same // variation as testUdpSendToEmpty); nothing is queued // then, so there is no readiness to wait for. @@ -1335,8 +1322,7 @@ struct reactor_paths_test bool threw = false; try { - io_context ioc(Backend, opts); - (void)ioc; + [[maybe_unused]] io_context ioc(Backend, opts); } catch (std::out_of_range const&) { @@ -1397,7 +1383,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s1.cancel(); }; @@ -1463,11 +1449,9 @@ struct reactor_paths_test read_n = n; }; auto writer = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); - auto [ec, n] = co_await s2.write_some( + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); + [[maybe_unused]] auto [ec, n] = co_await s2.write_some( capy::const_buffer(payload.data(), payload.size())); - (void)ec; - (void)n; }; capy::run_async(ex)(reader()); @@ -1506,10 +1490,8 @@ struct reactor_paths_test }; auto reader = [&]() -> capy::task<> { char buf[64]; - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await s2.read_some(capy::mutable_buffer(buf, sizeof(buf))); - (void)ec; - (void)n; }; capy::run_async(ex)(writer()); @@ -1538,7 +1520,7 @@ struct reactor_paths_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); s1.cancel(); }; @@ -1681,14 +1663,13 @@ struct reactor_paths_test char buf[16]; auto reader = [&]() -> capy::task<> { - auto [ec, n] = co_await s1.read_some( + [[maybe_unused]] auto [ec, n] = co_await s1.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; read_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -1793,19 +1774,19 @@ struct reactor_paths_test endpoint source; auto tcp_reader = [&]() -> capy::task<> { - (void)co_await t1.read_some( + std::ignore = co_await t1.read_some( capy::mutable_buffer(buf, sizeof(buf))); }; auto udp_reader = [&]() -> capy::task<> { - (void)co_await u1.recv_from( + std::ignore = co_await u1.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); }; auto ls_reader = [&]() -> capy::task<> { - (void)co_await ls1.read_some( + std::ignore = co_await ls1.read_some( capy::mutable_buffer(buf, sizeof(buf))); }; auto ld_reader = [&]() -> capy::task<> { - (void)co_await ld1.recv( + std::ignore = co_await ld1.recv( capy::mutable_buffer(buf, sizeof(buf))); }; @@ -1816,7 +1797,7 @@ struct reactor_paths_test // Run each coroutine to its parked suspension point only. for (int i = 0; i < 4; ++i) - (void)ioc.run_one(); + std::ignore = ioc.run_one(); // Sockets and io_context destruct here with the ops parked. BOOST_TEST_PASS(); diff --git a/test/unit/resolver.cpp b/test/unit/resolver.cpp index cc847d832..b27c6cf4f 100644 --- a/test/unit/resolver.cpp +++ b/test/unit/resolver.cpp @@ -340,10 +340,9 @@ struct resolver_test auto task = [](resolver& r_ref, std::error_code& ec_out, bool& done) -> capy::task<> { - auto [ec, res] = co_await r_ref.resolve("localhost", "80"); + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve("localhost", "80"); ec_out = ec; done = true; - (void)res; }; capy::run_async(ioc.get_executor())(task(r, result_ec, completed)); ioc.run(); @@ -372,10 +371,9 @@ struct resolver_test auto task = [](resolver& r_ref, std::error_code& ec_out, bool& done) -> capy::task<> { endpoint ep(ipv4_address({127, 0, 0, 1}), 80); - auto [ec, res] = co_await r_ref.resolve(ep); + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve(ep); ec_out = ec; done = true; - (void)res; }; capy::run_async(ioc.get_executor())(task(r, result_ec, completed)); ioc.run(); @@ -405,10 +403,9 @@ struct resolver_test auto task = [](resolver& r_ref, std::error_code& ec_out, bool& done) -> capy::task<> { - auto [ec, res] = co_await r_ref.resolve("localhost", "80"); + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve("localhost", "80"); ec_out = ec; done = true; - (void)res; }; capy::run_async(ioc.get_executor())(task(r, result_ec, completed)); ioc.run(); @@ -429,12 +426,11 @@ struct resolver_test auto task = [](resolver& r_ref, std::error_code& ec_out, bool& done) -> capy::task<> { - auto [ec, res] = co_await r_ref.resolve( + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve( "127.0.0.1", "not-a-real-service", resolve_flags::numeric_host | resolve_flags::numeric_service); ec_out = ec; done = true; - (void)res; }; capy::run_async(ioc.get_executor())(task(r, result_ec, completed)); ioc.run(); @@ -455,9 +451,7 @@ struct resolver_test auto flags = resolve_flags::passive | resolve_flags::address_configured | resolve_flags::v4_mapped | resolve_flags::all_matching; - auto [ec, res] = co_await r_ref.resolve("127.0.0.1", "80", flags); - (void)ec; - (void)res; + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve("127.0.0.1", "80", flags); }; capy::run_async(ioc.get_executor())(task(r)); ioc.run(); @@ -548,10 +542,9 @@ struct resolver_test auto task = [](resolver& r_ref, std::error_code& ec_out, bool& done) -> capy::task<> { - auto [ec, res] = co_await r_ref.resolve("localhost", "80"); + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve("localhost", "80"); ec_out = ec; done = true; - (void)res; }; capy::run_async(ioc.get_executor(), stop_src.get_token())( task(r, result_ec, completed)); @@ -579,10 +572,9 @@ struct resolver_test auto task = [](resolver& r_ref, std::error_code& ec_out, bool& done) -> capy::task<> { endpoint ep(ipv4_address({127, 0, 0, 1}), 80); - auto [ec, res] = co_await r_ref.resolve(ep); + [[maybe_unused]] auto [ec, res] = co_await r_ref.resolve(ep); ec_out = ec; done = true; - (void)res; }; capy::run_async(ioc.get_executor(), stop_src.get_token())( task(r, result_ec, completed)); @@ -755,11 +747,8 @@ struct resolver_test // Test range-based for std::size_t count = 0; - for (auto const& entry : results) - { - (void)entry; + for ([[maybe_unused]] auto const& entry : results) ++count; - } BOOST_TEST_EQ(count, results.size()); // Test cbegin/cend diff --git a/test/unit/signal_set.cpp b/test/unit/signal_set.cpp index 7396ebc70..41f841721 100644 --- a/test/unit/signal_set.cpp +++ b/test/unit/signal_set.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -223,7 +224,7 @@ struct signal_set_test // Raise signal after a short delay auto raise_task = []() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); std::raise(SIGINT); }; capy::run_async(ioc.get_executor())(raise_task()); @@ -244,16 +245,15 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, int& sig_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); sig_out = signum; done_out = true; - (void)ec; }; capy::run_async(ioc.get_executor())( wait_task(s, received_signal, completed)); auto raise_task = []() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); std::raise(SIGTERM); }; capy::run_async(ioc.get_executor())(raise_task()); @@ -275,15 +275,14 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, std::error_code& ec_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); ec_out = ec; done_out = true; - (void)signum; }; capy::run_async(ioc.get_executor())(wait_task(s, result_ec, completed)); auto cancel_task = [](signal_set& s_ref) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); s_ref.cancel(); }; capy::run_async(ioc.get_executor())(cancel_task(s)); @@ -303,10 +302,9 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, std::error_code& ec_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); ec_out = ec; done_out = true; - (void)signum; }; capy::run_async(ioc.get_executor())( wait_task(s, result_ec, completed)); @@ -353,10 +351,9 @@ struct signal_set_test std::error_code result_ec; auto wait_task = [&]() -> capy::task<> { - auto [ec, signum] = co_await s.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s.wait(); result_ec = ec; completed = true; - (void)signum; }; capy::run_async(ioc.get_executor(), src.get_token())(wait_task()); @@ -370,13 +367,11 @@ struct signal_set_test // Construct a signal_set that owns a signal registration, then let // the io_context shutdown drain the impl_list (covers shutdown // loop deleting registrations). - int destroyed = 0; - (void)destroyed; + [[maybe_unused]] int destroyed = 0; { io_context ioc(Backend); - signal_set s(ioc, SIGINT, SIGTERM); - (void)s; + [[maybe_unused]] signal_set s(ioc, SIGINT, SIGTERM); // No run() — drop directly into io_context destruction so the // service's shutdown path walks impl_list_ and frees both // signal_registration nodes. @@ -399,10 +394,9 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, int& sig_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); sig_out = signum; done_out = true; - (void)ec; }; capy::run_async(ioc.get_executor())( wait_task(s1, s1_signal, s1_completed)); @@ -410,7 +404,7 @@ struct signal_set_test wait_task(s2, s2_signal, s2_completed)); auto raise_task = []() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); std::raise(SIGINT); }; capy::run_async(ioc.get_executor())(raise_task()); @@ -432,17 +426,16 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, int& sig_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); sig_out = signum; done_out = true; - (void)ec; }; capy::run_async(ioc.get_executor())( wait_task(s, received_signal, completed)); // Raise SIGTERM (not SIGINT) auto raise_task = []() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); std::raise(SIGTERM); }; capy::run_async(ioc.get_executor())(raise_task()); @@ -467,10 +460,9 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, int& sig_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); sig_out = signum; done_out = true; - (void)ec; }; capy::run_async(ioc.get_executor())( wait_task(s, received_signal, completed)); @@ -491,7 +483,7 @@ struct signal_set_test auto task = [](signal_set& s_ref, int& count_out) -> capy::task<> { // First wait - (void)co_await corosio::delay(std::chrono::milliseconds(5)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(5)); std::raise(SIGINT); auto [ec1, sig1] = co_await s_ref.wait(); @@ -500,7 +492,7 @@ struct signal_set_test ++count_out; // Second wait - (void)co_await corosio::delay(std::chrono::milliseconds(5)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(5)); std::raise(SIGINT); auto [ec2, sig2] = co_await s_ref.wait(); @@ -569,7 +561,7 @@ struct signal_set_test // Raise after a delay while waiting: exercises the live-waiter // path where the drain posts a completion. - (void)co_await delay(std::chrono::milliseconds(1)); + std::ignore = co_await delay(std::chrono::milliseconds(1)); std::raise(SIGINT); auto [ec2, sig2] = co_await s_ref.wait(); BOOST_TEST(!ec2); @@ -593,7 +585,7 @@ struct signal_set_test bool result_ok = false; auto task = [](signal_set& s_ref, bool& ok_out) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(5)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(5)); std::raise(SIGINT); auto result = co_await s_ref.wait(); @@ -622,7 +614,7 @@ struct signal_set_test capy::run_async(ioc.get_executor())(wait_task(s, result_ok, result_ec)); auto cancel_task = [](signal_set& s_ref) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); s_ref.cancel(); }; capy::run_async(ioc.get_executor())(cancel_task(s)); @@ -642,7 +634,7 @@ struct signal_set_test auto task = [](signal_set& s_ref, std::error_code& ec_out, int& sig_out) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(5)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(5)); std::raise(SIGINT); auto [ec, signum] = co_await s_ref.wait(); @@ -814,16 +806,15 @@ struct signal_set_test auto wait_task = [](signal_set& s_ref, int& sig_out, bool& done_out) -> capy::task<> { - auto [ec, signum] = co_await s_ref.wait(); + [[maybe_unused]] auto [ec, signum] = co_await s_ref.wait(); sig_out = signum; done_out = true; - (void)ec; }; capy::run_async(ioc.get_executor())( wait_task(s, received_signal, completed)); auto raise_task = []() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); std::raise(SIGINT); }; capy::run_async(ioc.get_executor())(raise_task()); diff --git a/test/unit/socket_option.cpp b/test/unit/socket_option.cpp index 4b028fe52..96309d17f 100644 --- a/test/unit/socket_option.cpp +++ b/test/unit/socket_option.cpp @@ -26,6 +26,7 @@ #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -169,7 +170,7 @@ struct socket_option_test std::error_code get_caught; try { - (void)sock.get_option(); + std::ignore = sock.get_option(); } catch (std::system_error const& e) { diff --git a/test/unit/socket_stress.cpp b/test/unit/socket_stress.cpp index d38adf811..b6b3bed83 100644 --- a/test/unit/socket_stress.cpp +++ b/test/unit/socket_stress.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -178,17 +179,15 @@ struct stop_token_stress_test else if (i % 3 == 1) { // Brief delay then cancel - (void)co_await corosio::delay( + std::ignore = co_await corosio::delay( std::chrono::microseconds(1)); stop_src.request_stop(); } else { // Write data so read completes normally, then cancel (race!) - auto [ec, n] = co_await s1.write_some( + [[maybe_unused]] auto [ec, n] = co_await s1.write_some( capy::const_buffer("x", 1)); - (void)ec; - (void)n; stop_src.request_stop(); } @@ -197,7 +196,7 @@ struct stop_token_stress_test { if (read_done.load(std::memory_order_acquire)) break; - (void)co_await corosio::delay( + std::ignore = co_await corosio::delay( std::chrono::milliseconds(10)); } @@ -211,7 +210,7 @@ struct stop_token_stress_test BOOST_TEST( read_done.load(std::memory_order_acquire)); stop_src.request_stop(); - (void)co_await corosio::delay( + std::ignore = co_await corosio::delay( std::chrono::milliseconds(100)); } @@ -233,7 +232,7 @@ struct stop_token_stress_test // Timer to stop the test auto stopper = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::seconds(duration)); + std::ignore = co_await corosio::delay(std::chrono::seconds(duration)); stop_flag.store(true, std::memory_order_relaxed); }; @@ -323,7 +322,7 @@ struct sync_completion_stress_test // Timer to stop the test auto stopper = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::seconds(duration)); + std::ignore = co_await corosio::delay(std::chrono::seconds(duration)); stop_flag.store(true, std::memory_order_relaxed); }; @@ -404,7 +403,7 @@ struct cancel_close_stress_test case 0: { // Yield to let the posted read_coro start - (void)co_await corosio::delay( + std::ignore = co_await corosio::delay( std::chrono::microseconds(1)); // Cancel via tcp_socket.cancel() s2.cancel(); @@ -414,10 +413,8 @@ struct cancel_close_stress_test case 1: // Write data to complete the read normally { - auto [ec, n] = co_await s1.write_some( + [[maybe_unused]] auto [ec, n] = co_await s1.write_some( capy::const_buffer("data", 4)); - (void)ec; - (void)n; } ++writes; break; @@ -425,10 +422,8 @@ struct cancel_close_stress_test // Cancel then immediately write (race) s2.cancel(); { - auto [ec, n] = co_await s1.write_some( + [[maybe_unused]] auto [ec, n] = co_await s1.write_some( capy::const_buffer("data", 4)); - (void)ec; - (void)n; } ++cancel_writes; break; @@ -439,7 +434,7 @@ struct cancel_close_stress_test { if (read_done.load(std::memory_order_acquire)) break; - (void)co_await corosio::delay( + std::ignore = co_await corosio::delay( std::chrono::milliseconds(10)); } @@ -454,7 +449,7 @@ struct cancel_close_stress_test read_done.load(std::memory_order_acquire)); // Force cancel s2.cancel(); - (void)co_await corosio::delay( + std::ignore = co_await corosio::delay( std::chrono::milliseconds(100)); } @@ -472,7 +467,7 @@ struct cancel_close_stress_test // Timer to stop the test auto stopper = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::seconds(duration)); + std::ignore = co_await corosio::delay(std::chrono::seconds(duration)); stop_flag.store(true, std::memory_order_relaxed); }; @@ -576,7 +571,7 @@ struct concurrent_ops_stress_test // Timer to stop the test auto stopper = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::seconds(duration)); + std::ignore = co_await corosio::delay(std::chrono::seconds(duration)); stop_flag.store(true, std::memory_order_relaxed); // Close all sockets to unblock pending operations @@ -660,13 +655,12 @@ struct accept_stress_test { tcp_socket client(ioc); BOOST_TEST(!client.open()); - auto [ec] = co_await client.connect( + [[maybe_unused]] auto [ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); - (void)ec; client.close(); // Small delay to avoid overwhelming the accept queue - (void)co_await corosio::delay(std::chrono::microseconds(100)); + std::ignore = co_await corosio::delay(std::chrono::microseconds(100)); } }; @@ -675,7 +669,7 @@ struct accept_stress_test // Timer to stop the test auto stopper = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::seconds(duration)); + std::ignore = co_await corosio::delay(std::chrono::seconds(duration)); stop_flag.store(true, std::memory_order_relaxed); acc.close(); }; diff --git a/test/unit/tcp_acceptor.cpp b/test/unit/tcp_acceptor.cpp index 32b877393..eedbcb915 100644 --- a/test/unit/tcp_acceptor.cpp +++ b/test/unit/tcp_acceptor.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #ifndef _WIN32 // For the SO_REUSEPORT guard around testReusePort. The corosio public @@ -209,7 +210,7 @@ struct tcp_acceptor_test tcp_acceptor closed(ioc); BOOST_TEST_THROWS(closed.set_option(socket_option::reuse_address(true)), std::system_error); - BOOST_TEST_THROWS((void)closed.get_option(), std::system_error); + BOOST_TEST_THROWS(std::ignore = closed.get_option(), std::system_error); } void testMoveConstruct() @@ -287,11 +288,11 @@ struct tcp_acceptor_test capy::run_async(ioc.get_executor())(nested_coro()); // Wait then cancel - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); acc.cancel(); // Wait for accept to complete - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(accept_done); BOOST_TEST(accept_ec == capy::cond::canceled); @@ -327,11 +328,11 @@ struct tcp_acceptor_test capy::run_async(ex)( [](tcp_acceptor& a, tcp_socket& s) -> capy::task<> { - (void)co_await a.accept(s); + std::ignore = co_await a.accept(s); }(acc, server)); capy::run_async(ex)( [](tcp_socket& s, endpoint e) -> capy::task<> { - (void)co_await s.connect(e); + std::ignore = co_await s.connect(e); }(client, ep)); ioc.run(); BOOST_TEST(server.is_open()); @@ -341,12 +342,12 @@ struct tcp_acceptor_test char buf[16]; auto reader = [&]() -> capy::task<> { - (void)co_await server.read_some( + std::ignore = co_await server.read_some( capy::mutable_buffer(buf, sizeof(buf))); }; capy::run_async(ex)(reader()); - (void)ioc.run_one(); + std::ignore = ioc.run_one(); BOOST_TEST_PASS(); } @@ -386,10 +387,10 @@ struct tcp_acceptor_test capy::run_async(ioc.get_executor())(nested_coro()); // Wait then close the acceptor - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); acc.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(accept_done); BOOST_TEST(accept_ec == capy::cond::canceled); @@ -619,7 +620,7 @@ struct tcp_acceptor_test // Cancel lingering accept after connect completes auto cancel_task = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(200)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(200)); acc.cancel(); }; capy::run_async(ex)(cancel_task()); @@ -900,7 +901,7 @@ struct tcp_acceptor_test bool get_threw = false; try { - (void)acc.get_option(); + std::ignore = acc.get_option(); } catch (std::system_error const& e) { @@ -1004,7 +1005,7 @@ struct tcp_acceptor_test accept_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); ss.request_stop(); }; @@ -1099,7 +1100,7 @@ struct tcp_acceptor_test // failing it, retract it so the test reports the miss // instead of hanging the suite. auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!accept_done) acc.cancel(); }; @@ -1131,13 +1132,13 @@ struct tcp_acceptor_test tcp_socket peer(ioc); auto acceptor_task = [&]() -> capy::task<> { - (void)co_await acc.accept(peer); + std::ignore = co_await acc.accept(peer); }; capy::run_async(ex)(acceptor_task()); // Run the coroutine to its parked suspension point only, then // fall off the end of the scope with the accept outstanding. - (void)ioc.run_one(); + std::ignore = ioc.run_one(); BOOST_TEST_PASS(); } @@ -1180,10 +1181,9 @@ struct tcp_acceptor_test : endpoint(ipv4_address::loopback(), port)); BOOST_TEST(!cec); char const out[] = "ping"; - auto [wec, wn] = + [[maybe_unused]] auto [wec, wn] = co_await client.write_some(capy::const_buffer(out, 4)); BOOST_TEST(!wec); - (void)wn; }; auto ex = ioc.get_executor(); @@ -1236,7 +1236,7 @@ struct tcp_acceptor_test auto client = make_native_socket(AF_INET, SOCK_STREAM); BOOST_TEST(client != invalid_native_socket); BOOST_TEST(native_connect_loopback(client, port, false)); - (void)ioc.poll(); + std::ignore = ioc.poll(); ioc.restart(); std::error_code wait_ec; @@ -1252,7 +1252,7 @@ struct tcp_acceptor_test // parks forever; retract the wait so the miss is reported // instead of hanging the suite. auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!wait_done) { watchdog_fired = true; @@ -1303,7 +1303,7 @@ struct tcp_acceptor_test // Pump once with nothing parked so the registration-time // readiness edge has already been dispatched and dropped. - (void)ioc.poll(); + std::ignore = ioc.poll(); ioc.restart(); std::error_code wait_ec; @@ -1316,7 +1316,7 @@ struct tcp_acceptor_test wait_done = true; }; auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!wait_done) { watchdog_fired = true; @@ -1373,7 +1373,7 @@ struct tcp_acceptor_test // Watchdog: a backend that parks the meaningless wait would // hang the suite; retract it so the miss is reported. auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!wait_done) { watchdog_fired = true; @@ -1407,7 +1407,7 @@ struct tcp_acceptor_test // Pump once so the listen-time accept arming is live in the // kernel before the descriptor is swapped underneath it. - (void)ioc.poll(); + std::ignore = ioc.poll(); ioc.restart(); std::uint16_t port = 0; @@ -1473,7 +1473,7 @@ struct tcp_acceptor_test // Pump once so the listen-time accept arming is live in the // kernel before release() retires it. - (void)ioc.poll(); + std::ignore = ioc.poll(); ioc.restart(); auto released = acc.release(); @@ -1512,7 +1512,7 @@ struct tcp_acceptor_test auto port = acc.local_endpoint().port(); // Pump once so the first arming is live before the re-listen. - (void)ioc.poll(); + std::ignore = ioc.poll(); ioc.restart(); ec = acc.listen(256); @@ -1535,7 +1535,7 @@ struct tcp_acceptor_test // accept forever; retract it so the theft is reported instead // of hanging the suite. auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!accept_done) { watchdog_fired = true; @@ -1574,7 +1574,7 @@ struct tcp_acceptor_test auto stale = make_native_socket(AF_INET, SOCK_STREAM); BOOST_TEST(stale != invalid_native_socket); BOOST_TEST(native_connect_loopback(stale, port_a, false)); - (void)ioc.poll(); + std::ignore = ioc.poll(); ioc.restart(); auto released = acc.release(); @@ -1608,7 +1608,7 @@ struct tcp_acceptor_test accepted_port = peer.local_endpoint().port(); }; auto watchdog = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(250)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(250)); if (!accept_done) { watchdog_fired = true; @@ -1698,7 +1698,7 @@ struct tcp_acceptor_test std::error_code caught; try { - (void)acc.release(); + std::ignore = acc.release(); } catch (std::system_error const& e) { diff --git a/test/unit/tcp_server.cpp b/test/unit/tcp_server.cpp index f9c27d6ad..e4ed40c7f 100644 --- a/test/unit/tcp_server.cpp +++ b/test/unit/tcp_server.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "context.hpp" #include "test_suite.hpp" @@ -45,7 +46,7 @@ class test_worker : public tcp_server::worker_base char buf[64]; auto [ec, n] = co_await sock->read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)co_await sock->write_some(capy::const_buffer(buf, n)); + std::ignore = co_await sock->write_some(capy::const_buffer(buf, n)); sock->close(); }(&sock_)); } @@ -93,7 +94,7 @@ struct tcp_server_test auto client_task = [](test_server* srv, std::atomic* client_done) -> capy::task<> { // Brief delay to ensure server accept loop is running - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); // Request stop - server should exit accept loop srv->stop(); @@ -179,7 +180,7 @@ struct tcp_server_test srv.start(); // Second call should be no-op auto task = [](test_server* srv) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); srv->stop(); }(&srv); @@ -198,7 +199,7 @@ struct tcp_server_test srv.start(); auto task = [](test_server* srv) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(10)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(10)); // Calling stop() twice should be safe srv->stop(); @@ -261,7 +262,7 @@ struct tcp_server_test }(&ioc, port, &connections_handled); auto stop_task1 = [](test_server* srv) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); srv->stop(); }(&srv); @@ -299,7 +300,7 @@ struct tcp_server_test }(&ioc, port, &connections_handled); auto stop_task2 = [](test_server* srv) -> capy::task<> { - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); srv->stop(); }(&srv); @@ -534,11 +535,10 @@ struct tcp_server_test { tcp_socket client(*ioc); BOOST_TEST(!client.open()); - auto [cec] = co_await client.connect( + [[maybe_unused]] auto [cec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); - (void)cec; client.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); } srv->stop(); }(&ioc, port, &srv); @@ -571,16 +571,13 @@ struct tcp_server_test { launch( ctx_.get_executor(), - [](io_context* ctx, + []([[maybe_unused]] io_context* ctx, corosio::tcp_socket* s) -> capy::task<> { // Block on read until the client disconnects. char buf[64]; - auto [ec, n] = co_await s->read_some( + [[maybe_unused]] auto [ec, n] = co_await s->read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)ec; - (void)n; s->close(); - (void)ctx; }(&ctx_, &sock_)); } }; @@ -627,16 +624,16 @@ struct tcp_server_test if (!e3) connected->fetch_add(1); // Give the server time to register the connections. - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); // Disconnect middle first, then tail, then head: // exercises remove from each list position. c2.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); c3.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); c1.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(20)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(20)); srv->stop(); }(&ioc, port, &connected, &srv); @@ -719,13 +716,12 @@ struct tcp_server_test one_worker_server* srv) -> capy::task<> { tcp_socket client(*ioc); BOOST_TEST(!client.open()); - auto [cec] = co_await client.connect( + [[maybe_unused]] auto [cec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); - (void)cec; client.close(); // Give server time to handle the connection, then stop. - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); srv->stop(); }(&ioc, port, &srv); diff --git a/test/unit/tcp_socket.cpp b/test/unit/tcp_socket.cpp index cd8bc377f..6735c8b89 100644 --- a/test/unit/tcp_socket.cpp +++ b/test/unit/tcp_socket.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #if BOOST_COROSIO_POSIX #include // getpid() @@ -281,7 +282,7 @@ struct tcp_socket_test bool get_threw = false; try { - (void)sock.get_option(); + std::ignore = sock.get_option(); } catch (std::system_error const& e) { @@ -478,21 +479,21 @@ struct tcp_socket_test char buf[32] = {}; // First exchange - (void)co_await a.write_some(capy::const_buffer("one", 3)); + std::ignore = co_await a.write_some(capy::const_buffer("one", 3)); auto [ec1, n1] = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); BOOST_TEST(!ec1); BOOST_TEST_EQ(std::string_view(buf, n1), "one"); // Second exchange - (void)co_await a.write_some(capy::const_buffer("two", 3)); + std::ignore = co_await a.write_some(capy::const_buffer("two", 3)); auto [ec2, n2] = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); BOOST_TEST(!ec2); BOOST_TEST_EQ(std::string_view(buf, n2), "two"); // Third exchange - (void)co_await a.write_some(capy::const_buffer("three", 5)); + std::ignore = co_await a.write_some(capy::const_buffer("three", 5)); auto [ec3, n3] = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); BOOST_TEST(!ec3); @@ -537,8 +538,8 @@ struct tcp_socket_test BOOST_TEST_EQ(std::string_view(buf, n4), "from_b"); // Interleaved: write a, write b, read b, read a - (void)co_await a.write_some(capy::const_buffer("msg_a", 5)); - (void)co_await b.write_some(capy::const_buffer("msg_b", 5)); + std::ignore = co_await a.write_some(capy::const_buffer("msg_a", 5)); + std::ignore = co_await b.write_some(capy::const_buffer("msg_b", 5)); auto [ec5, n5] = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); @@ -574,7 +575,7 @@ struct tcp_socket_test BOOST_TEST_EQ(n1, 0u); // Send actual data so read can complete - (void)co_await a.write_some(capy::const_buffer("x", 1)); + std::ignore = co_await a.write_some(capy::const_buffer("x", 1)); // Read with empty buffer should return 0 auto [ec2, n2] = @@ -584,7 +585,7 @@ struct tcp_socket_test // Drain the actual data char buf[8]; - (void)co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); + std::ignore = co_await b.read_some(capy::mutable_buffer(buf, sizeof(buf))); }; capy::run_async(ioc.get_executor())(task(s1, s2)); @@ -683,7 +684,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Write data then close - (void)co_await a.write_some(capy::const_buffer("final", 5)); + std::ignore = co_await a.write_some(capy::const_buffer("final", 5)); a.close(); // Read the data @@ -717,7 +718,7 @@ struct tcp_socket_test b.close(); // Give OS time to process the close - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); // Writing to closed peer should eventually fail. // We need to write enough data to fill the tcp_socket buffer and @@ -768,11 +769,11 @@ struct tcp_socket_test capy::run_async(ioc.get_executor())(nested_coro()); // Wait for the read to be underway then cancel it - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); b.cancel(); // Wait for read to complete - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(read_done); BOOST_TEST(read_ec == capy::cond::canceled); @@ -807,10 +808,10 @@ struct tcp_socket_test capy::run_async(ioc.get_executor())(nested_coro()); // Wait then close the tcp_socket - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); b.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(read_done); // Close should cancel pending operations @@ -841,7 +842,7 @@ struct tcp_socket_test // Reader task - signals ready then blocks waiting for data auto reader_task = [&]() -> capy::task<> { // Signal we're about to start the blocking read - (void)co_await s2.write_some(capy::const_buffer("R", 1)); + std::ignore = co_await s2.write_some(capy::const_buffer("R", 1)); // Now block waiting for data that will never come char buf[32]; @@ -855,7 +856,7 @@ struct tcp_socket_test auto canceller_task = [&]() -> capy::task<> { // Wait for reader's "ready" signal char buf[1]; - (void)co_await s1.read_some(capy::mutable_buffer(buf, 1)); + std::ignore = co_await s1.read_some(capy::mutable_buffer(buf, 1)); // Reader is now blocked on read - request stop stop_src.request_stop(); @@ -905,7 +906,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Write exactly 100 bytes std::string send_data(100, 'X'); - (void)co_await capy::write( + std::ignore = co_await capy::write( a, capy::const_buffer(send_data.data(), send_data.size())); // Read exactly 100 bytes using corosio::read @@ -959,7 +960,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { std::string send_data = "Hello, this is a test message!"; - (void)co_await capy::write(a, capy::make_buffer(send_data)); + std::ignore = co_await capy::write(a, capy::make_buffer(send_data)); char buf[64] = {}; auto [ec, n] = co_await capy::read( @@ -984,7 +985,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Send 50 bytes but try to read 100 std::string send_data(50, 'Z'); - (void)co_await capy::write( + std::ignore = co_await capy::write( a, capy::const_buffer(send_data.data(), send_data.size())); a.close(); @@ -1014,7 +1015,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Write data then shutdown send // (unqualified: using enum avoids GCC 11 ICE in tsubst_copy) - (void)co_await a.write_some(capy::const_buffer("hello", 5)); + std::ignore = co_await a.write_some(capy::const_buffer("hello", 5)); BOOST_TEST(!a.shutdown(shutdown_send)); // Read the data @@ -1047,7 +1048,7 @@ struct tcp_socket_test BOOST_TEST(!b.shutdown(shutdown_receive)); // b can still send - (void)co_await b.write_some(capy::const_buffer("from_b", 6)); + std::ignore = co_await b.write_some(capy::const_buffer("from_b", 6)); char buf[32] = {}; auto [ec, n] = @@ -1084,7 +1085,7 @@ struct tcp_socket_test auto task = [](tcp_socket& a, tcp_socket& b) -> capy::task<> { // Write data then shutdown both - (void)co_await a.write_some(capy::const_buffer("goodbye", 7)); + std::ignore = co_await a.write_some(capy::const_buffer("goodbye", 7)); BOOST_TEST(!a.shutdown(shutdown_both)); // Peer should receive the data @@ -2307,9 +2308,8 @@ struct tcp_socket_test char buf[16]; auto reader = [&]() -> capy::task<> { - auto [rec, rn] = + [[maybe_unused]] auto [rec, rn] = co_await s1.read_some(capy::mutable_buffer(buf, sizeof(buf))); - (void)rn; read_ec = rec; read_done = true; }; @@ -2318,10 +2318,9 @@ struct tcp_socket_test auto [aec, peer] = co_await acc.accept(); BOOST_TEST(!aec); char const out[] = "ping"; - auto [wec, wn] = co_await s1.write_some( + [[maybe_unused]] auto [wec, wn] = co_await s1.write_some( capy::const_buffer(out, 4)); BOOST_TEST(!wec); - (void)wn; char in[8]; auto [rec, rn] = co_await peer.read_some(capy::mutable_buffer(in, sizeof(in))); @@ -2361,9 +2360,8 @@ struct tcp_socket_test auto released = invalid_native_socket; auto reader = [&]() -> capy::task<> { - auto [rec, rn] = + [[maybe_unused]] auto [rec, rn] = co_await s1.read_some(capy::mutable_buffer(buf, sizeof(buf))); - (void)rn; read_ec = rec; read_done = true; }; @@ -2408,7 +2406,7 @@ struct tcp_socket_test std::error_code caught; try { - (void)sock.release(); + std::ignore = sock.release(); } catch (std::system_error const& e) { @@ -2454,10 +2452,9 @@ struct tcp_socket_test auto [aec, peer] = co_await acc.accept(); BOOST_TEST(!aec); char const out[] = "v6"; - auto [wec, wn] = co_await adopted.write_some( + [[maybe_unused]] auto [wec, wn] = co_await adopted.write_some( capy::const_buffer(out, 2)); BOOST_TEST(!wec); - (void)wn; char in[8]; auto [rec, rn] = co_await peer.read_some(capy::mutable_buffer(in, sizeof(in))); diff --git a/test/unit/test/mocket.cpp b/test/unit/test/mocket.cpp index 1b7ab05c3..fea79ed90 100644 --- a/test/unit/test/mocket.cpp +++ b/test/unit/test/mocket.cpp @@ -65,7 +65,8 @@ struct mocket_test ioc.restart(); // All staged data should be consumed - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -79,9 +80,10 @@ struct mocket_test // Set expectation that won't be fulfilled m.expect("never_written"); - // Close should fail because expect_ is not empty - auto ec = m.close(); + // Verification fails because expect_ is not empty + auto ec = m.verify(); BOOST_TEST(ec == capy::error::test_failure); + m.close(); peer.close(); } @@ -96,9 +98,10 @@ struct mocket_test // Stage data that won't be consumed m.provide("never_read"); - // Close should fail because provide_ is not empty - auto ec = m.close(); + // Verification fails because provide_ is not empty + auto ec = m.verify(); BOOST_TEST(ec == capy::error::test_failure); + m.close(); peer.close(); } @@ -141,7 +144,8 @@ struct mocket_test ioc.run(); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } @@ -192,7 +196,8 @@ struct native_mocket_test ioc.run(); ioc.restart(); - BOOST_TEST(!m.close()); + BOOST_TEST(!m.verify()); + m.close(); peer.close(); } diff --git a/test/unit/test_utils.hpp b/test/unit/test_utils.hpp index 889849cbc..f72a058f8 100644 --- a/test/unit/test_utils.hpp +++ b/test/unit/test_utils.hpp @@ -27,6 +27,7 @@ #include "test_suite.hpp" #include +#include #include #include #include @@ -95,10 +96,10 @@ make_native_socket(int family, int type) @param h The descriptor to configure. */ inline void -make_native_adoptable(native_handle_type h) +make_native_adoptable([[maybe_unused]] native_handle_type h) { #if BOOST_COROSIO_HAS_IOCP - (void)h; // WSA_FLAG_OVERLAPPED is set at creation + // WSA_FLAG_OVERLAPPED is set at creation. #else int fd = static_cast(h); int flags = ::fcntl(fd, F_GETFL); @@ -2348,7 +2349,7 @@ run_stop_token_handshake_test( auto server_task = [&s2, &stop_src]() -> capy::task<> { // Wait for client to send ClientHello (proves client started handshake) char buf[1]; - (void)co_await s2.read_some(capy::mutable_buffer(buf, 1)); + std::ignore = co_await s2.read_some(capy::mutable_buffer(buf, 1)); // Client is now blocked waiting for ServerHello - cancel it stop_src.request_stop(); }; @@ -2571,13 +2572,10 @@ run_shutdown_cancel_test( auto server_drain_then_cancel = [&server, &stop_src, &s1, mode]() -> capy::task<> { char buf[64]; - auto [ec, n] = + [[maybe_unused]] auto [ec, n] = co_await server.read_some(capy::mutable_buffer(buf, sizeof(buf))); - (void)ec; - (void)n; - auto [dec] = co_await corosio::delay( + [[maybe_unused]] auto [dec] = co_await corosio::delay( std::chrono::milliseconds(20 * failsafe_scale)); - (void)dec; if (mode == shutdown_cancel_mode::socket_cancel) s1.cancel(); else @@ -2694,7 +2692,7 @@ run_stop_token_write_test( auto server_cancel = [&s2, &stop_src]() -> capy::task<> { // Wait for client to send some data (proves client started writing) char buf[1]; - (void)co_await s2.read_some(capy::mutable_buffer(buf, 1)); + std::ignore = co_await s2.read_some(capy::mutable_buffer(buf, 1)); // Client is now writing - cancel it stop_src.request_stop(); }; @@ -2777,7 +2775,7 @@ run_socket_cancel_test( auto server_task = [&s1, &s2]() -> capy::task<> { // Wait for client to send ClientHello (proves client started handshake) char buf[1]; - (void)co_await s2.read_some(capy::mutable_buffer(buf, 1)); + std::ignore = co_await s2.read_some(capy::mutable_buffer(buf, 1)); // Client is now blocked waiting for ServerHello - cancel its socket s1.cancel(); }; diff --git a/test/unit/timeout.cpp b/test/unit/timeout.cpp index dca52acaf..8d9ee2e83 100644 --- a/test/unit/timeout.cpp +++ b/test/unit/timeout.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -205,7 +206,7 @@ struct timeout_test auto t = [](bool& out) -> capy::task<> { try { - (void) co_await timeout( + std::ignore = co_await timeout( throwing_awaitable{}, std::chrono::seconds(10)); } catch(std::runtime_error const&) @@ -429,10 +430,9 @@ struct timeout_test ~guard() { ++c_; } }; guard g{counter}; - auto [ec] = co_await timeout( + [[maybe_unused]] auto [ec] = co_await timeout( delay(std::chrono::hours(1)), std::chrono::hours(1)); - (void)ec; }; capy::run_async(ioc.get_executor())(task(destroyed)); @@ -450,22 +450,21 @@ struct timeout_test // example). io_context ioc(Backend); auto ex = ioc.get_executor(); - auto [s1, s2] = test::make_socket_pair(ioc); + // s2 is held open but silent. + [[maybe_unused]] auto [s1, s2] = test::make_socket_pair(ioc); bool timed_out = false; std::array buf{}; auto t = [&]() -> capy::task<> { - auto [ec, n] = co_await timeout( + [[maybe_unused]] auto [ec, n] = co_await timeout( s1.read_some(capy::mutable_buffer(buf.data(), buf.size())), std::chrono::milliseconds(50)); - (void)n; timed_out = (ec == capy::cond::timeout); }; capy::run_async(ex)(t()); ioc.run(); BOOST_TEST(timed_out); - (void)s2; // held open but silent } void testTypeErasedInnerFullProtocol() @@ -504,24 +503,23 @@ struct timeout_test // path. The pointer form exercises non-owning reference mode. io_context ioc(Backend); auto ex = ioc.get_executor(); - auto [s1, s2] = test::make_socket_pair(ioc); + // s2 is held open but silent. + [[maybe_unused]] auto [s1, s2] = test::make_socket_pair(ioc); bool timed_out = false; std::array buf{}; auto t = [&]() -> capy::task<> { capy::any_read_stream stream(&s1); - auto [ec, n] = co_await timeout( + [[maybe_unused]] auto [ec, n] = co_await timeout( stream.read_some( capy::mutable_buffer(buf.data(), buf.size())), std::chrono::milliseconds(50)); - (void)n; timed_out = (ec == capy::cond::timeout); }; capy::run_async(ex)(t()); ioc.run(); BOOST_TEST(timed_out); - (void)s2; // held open but silent } void testNestedTimeoutOverTypeErased() diff --git a/test/unit/tls_stream_stress.cpp b/test/unit/tls_stream_stress.cpp index 7449ddea9..1a744fe48 100644 --- a/test/unit/tls_stream_stress.cpp +++ b/test/unit/tls_stream_stress.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -278,7 +279,7 @@ struct tls_concurrent_io_stress_impl // Stopper: wait for duration then close all sockets auto stopper = [&]() -> capy::task<> { - (void)co_await corosio::delay(std::chrono::seconds(duration)); + std::ignore = co_await corosio::delay(std::chrono::seconds(duration)); stop_flag.store(true, std::memory_order_relaxed); sa1.close(); @@ -355,7 +356,7 @@ struct tls_cancel_handshake_stress_impl // Server: wait for ClientHello then trigger cancellation auto server_task = [&s2, &stop_src]() -> capy::task<> { char buf[1]; - (void)co_await s2.read_some(capy::mutable_buffer(buf, 1)); + std::ignore = co_await s2.read_some(capy::mutable_buffer(buf, 1)); stop_src.request_stop(); }; diff --git a/test/unit/tls_stream_tests.hpp b/test/unit/tls_stream_tests.hpp index c5daf1e4c..4d9ae78c9 100644 --- a/test/unit/tls_stream_tests.hpp +++ b/test/unit/tls_stream_tests.hpp @@ -80,8 +80,8 @@ testHandshakeFuse(StreamFactory make_stream) client_ec = ec; if (ec) { - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } }; @@ -90,8 +90,8 @@ testHandshakeFuse(StreamFactory make_stream) server_ec = ec; if (ec) { - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } }; @@ -103,8 +103,8 @@ testHandshakeFuse(StreamFactory make_stream) if (!client_ec && !server_ec) clean_seen = true; - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); co_return; }); BOOST_TEST(clean_seen); @@ -141,8 +141,8 @@ testReadWriteFuse(StreamFactory make_stream) // instead of waiting on bytes that will never come. The // data check runs only on the injection-free pass. auto bail = [&]() { - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); }; auto client_task = [&]() -> capy::task<> { @@ -184,8 +184,8 @@ testReadWriteFuse(StreamFactory make_stream) capy::run_async(ioc.get_executor())(server_task()); ioc.run(); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); co_return; }); } @@ -230,15 +230,15 @@ testShutdownFuse(StreamFactory make_stream) // peer parked on a close_notify that never comes, so the // unconditional close is what guarantees no side hangs. auto bail = [&]() { - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); }; auto client_task = [&]() -> capy::task<> { auto [ec] = co_await client.handshake(tls_role::client); client_hs_ec = ec; if (!ec) - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); bail(); }; @@ -248,7 +248,7 @@ testShutdownFuse(StreamFactory make_stream) if (!ec) { char buf[32]; - (void)co_await server.read_some( + std::ignore = co_await server.read_some( capy::mutable_buffer(buf, sizeof(buf))); } bail(); @@ -261,7 +261,7 @@ testShutdownFuse(StreamFactory make_stream) if (!client_hs_ec && !server_hs_ec) clean_seen = true; - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); co_return; }); BOOST_TEST(clean_seen); @@ -618,8 +618,8 @@ run_hostname_round( client_ec = ec; if (ec) { - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } }; auto hs_server = [&]() -> capy::task<> { @@ -640,13 +640,13 @@ run_hostname_round( return; auto sd_client = [&]() -> capy::task<> { - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); }; auto sd_server = [&]() -> capy::task<> { char drain[32]; - (void)co_await server.read_some( + std::ignore = co_await server.read_some( capy::mutable_buffer(drain, sizeof(drain))); - (void)co_await server.shutdown(); + std::ignore = co_await server.shutdown(); }; capy::run_async(ioc.get_executor())(sd_client()); capy::run_async(ioc.get_executor())(sd_server()); @@ -697,8 +697,8 @@ testHostnamePersistence(StreamFactory make_stream) BOOST_TEST_EQ(sni_count, 2u); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } /** A new hostname set after reset() takes effect on the next @@ -746,9 +746,9 @@ testHostnameRedirect(StreamFactory make_stream) } if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** set_hostname("") after reset() disables SNI and verification: @@ -788,8 +788,8 @@ testHostnameClear(StreamFactory make_stream) // Only round 1 sent SNI BOOST_TEST_EQ(sni_count, 1u); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } /** A hostname set after a failed handshake attempt takes effect on @@ -871,8 +871,8 @@ testHostnameRetryAfterFailure(StreamFactory make_stream) if (seen.size() == 1u) BOOST_TEST_EQ(seen[0], "www.example.com"); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } /** An IP-literal hostname is matched against the certificate's @@ -919,11 +919,11 @@ testHostnameIpLiteral(StreamFactory make_stream, bool ip_supported) auto hs_client = [&]() -> capy::task<> { auto [ec] = co_await client.handshake(tls_role::client); client_ec = ec; - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); }; auto hs_server = [&]() -> capy::task<> { - (void)co_await server.handshake(tls_role::server); + std::ignore = co_await server.handshake(tls_role::server); }; capy::run_async(ioc.get_executor())(hs_client()); capy::run_async(ioc.get_executor())(hs_server()); @@ -951,9 +951,9 @@ testHostnameIpLiteral(StreamFactory make_stream, bool ip_supported) ioc, client, server, m1, m2, false); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** A freshly constructed stream reports no negotiated ALPN protocol. @@ -970,8 +970,8 @@ testAlpnAccessorEmpty(StreamFactory make_stream) auto ctx = make_client_context(); auto stream = make_stream(m1, ctx); BOOST_TEST(stream.alpn_protocol().empty()); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } /** Test CRL-based revocation. @@ -1382,8 +1382,8 @@ testAlpn(StreamFactory make_stream, bool alpn_supported) sec == std::errc::function_not_supported); } - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } /** ALPN with no common protocol fails the handshake (RFC 7301 §3.2). @@ -1446,10 +1446,10 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) { io_context ioc; auto client_ctx = make_client_context(); - require_ok(client_ctx.set_verify_callback( + client_ctx.set_verify_callback( [](bool preverified, verify_context&) -> bool { return preverified; - })); + }); auto server_ctx = make_server_context(); std::error_code client_ec; run_tls_test_fail( @@ -1464,13 +1464,12 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) // non-null (which would let the retry bypass the check). { io_context ioc; - auto [m1, m2] = corosio::test::make_mocket_pair(ioc); - (void)m2; + [[maybe_unused]] auto [m1, m2] = corosio::test::make_mocket_pair(ioc); auto client_ctx = make_client_context(); - require_ok(client_ctx.set_verify_callback( + client_ctx.set_verify_callback( [](bool preverified, verify_context&) -> bool { return preverified; - })); + }); auto client = make_stream(m1, client_ctx); std::error_code ec1; @@ -1488,7 +1487,7 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) BOOST_TEST(ec1 == std::errc::function_not_supported); BOOST_TEST(ec2 == std::errc::function_not_supported); - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); } return; } @@ -1502,7 +1501,7 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) bool saw_unverified = false; auto client_ctx = make_wrong_ca_context(); - require_ok(client_ctx.set_verify_callback( + client_ctx.set_verify_callback( [&saw_unverified](bool preverified, verify_context& vc) -> bool { if (!preverified) { @@ -1518,7 +1517,7 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) BOOST_TEST(der[0] == 0x30); } return true; - })); + }); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); @@ -1529,8 +1528,8 @@ testVerifyCallback(StreamFactory make_stream, bool callback_supported = true) { io_context ioc; auto client_ctx = make_wrong_ca_context(); - require_ok(client_ctx.set_verify_callback( - [](bool, verify_context&) -> bool { return false; })); + client_ctx.set_verify_callback( + [](bool, verify_context&) -> bool { return false; }); auto server_ctx = make_server_context(); run_tls_test_fail( @@ -1559,14 +1558,14 @@ testVerifyCallbackOnSuccess(StreamFactory make_stream) bool saw_cert = false; auto client_ctx = make_client_context(); - require_ok(client_ctx.set_verify_callback( + client_ctx.set_verify_callback( [&](bool preverified, verify_context& vc) -> bool { invoked = true; if (preverified && !vc.certificate().empty() && vc.certificate()[0] == 0x30) saw_cert = true; return preverified; - })); + }); auto server_ctx = make_server_context(); run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); @@ -1578,8 +1577,8 @@ testVerifyCallbackOnSuccess(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - require_ok(client_ctx.set_verify_callback( - [](bool, verify_context&) -> bool { return false; })); + client_ctx.set_verify_callback( + [](bool, verify_context&) -> bool { return false; }); auto server_ctx = make_server_context(); run_tls_test_fail( @@ -1592,13 +1591,13 @@ testVerifyCallbackOnSuccess(StreamFactory make_stream) { io_context ioc; auto client_ctx = make_client_context(); - require_ok(client_ctx.set_verify_callback( + client_ctx.set_verify_callback( [](bool preverified, verify_context& vc) -> bool { if (!preverified) return false; auto der = vc.certificate(); return der.size() == 1 && der[0] == 0xFF; // never matches - })); + }); auto server_ctx = make_server_context(); run_tls_test_fail( @@ -1707,9 +1706,8 @@ testAbruptClose(StreamFactory make_stream) std::error_code read_ec; auto reader = [&]() -> capy::task<> { char buf[16]; - auto [ec, n] = co_await client.read_some( + [[maybe_unused]] auto [ec, n] = co_await client.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n; read_ec = ec; read_done = true; }; @@ -1724,7 +1722,7 @@ testAbruptClose(StreamFactory make_stream) // normalized, not reported as a transport error. bool shutdown_done = false; auto closer = [&]() -> capy::task<> { - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); shutdown_done = true; }; capy::run_async(ioc.get_executor())(closer()); @@ -1786,8 +1784,8 @@ testEncryptedKey(StreamFactory make_stream, bool expect_success = true) if (!ec) { failsafe_hit = true; - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } }; capy::run_async(ioc.get_executor())(client_hs()); @@ -1837,13 +1835,13 @@ testInvalidContextHandshake(StreamFactory make_stream) client_ec = ec; client_done = true; // Unblock the server if it is still waiting on the transport. - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); }; auto server_hs = [&]() -> capy::task<> { auto [ec] = co_await server.handshake(tls_role::server); server_ec = ec; server_done = true; - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); }; capy::run_async(ioc.get_executor())(client_hs()); capy::run_async(ioc.get_executor())(server_hs()); @@ -1957,14 +1955,14 @@ testReset(StreamFactory make_stream, std::array const& modes) // Shutdown both sides concurrently auto sd_client = [&]() -> capy::task<> { - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); }; auto sd_server = [&]() -> capy::task<> { // Read until close_notify, then send ours char drain[32]; - (void)co_await server.read_some( + std::ignore = co_await server.read_some( capy::mutable_buffer(drain, sizeof(drain))); - (void)co_await server.shutdown(); + std::ignore = co_await server.shutdown(); }; capy::run_async(ioc.get_executor())(sd_client()); @@ -1983,8 +1981,8 @@ testReset(StreamFactory make_stream, std::array const& modes) // Round 2 do_round("hello2"); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } } @@ -2049,13 +2047,13 @@ testResetViaHandshake( ioc.restart(); auto sd_client = [&]() -> capy::task<> { - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); }; auto sd_server = [&]() -> capy::task<> { char drain[32]; - (void)co_await server.read_some( + std::ignore = co_await server.read_some( capy::mutable_buffer(drain, sizeof(drain))); - (void)co_await server.shutdown(); + std::ignore = co_await server.shutdown(); }; capy::run_async(ioc.get_executor())(sd_client()); @@ -2072,8 +2070,8 @@ testResetViaHandshake( // Round 2 do_round("round2"); - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); } } @@ -2109,8 +2107,8 @@ testResetFuse(StreamFactory make_stream) auto server = make_stream(m2, server_ctx); auto bail = [&]() { - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); }; // Round 1 @@ -2200,8 +2198,8 @@ testResetFuse(StreamFactory make_stream) clean_seen = true; } - m1.close(); // NOLINT(bugprone-unused-return-value) - m2.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); + m2.close(); }); BOOST_TEST(clean_seen); } @@ -2300,7 +2298,7 @@ testFullDuplex(StreamFactory make_stream) client_done = true; // Tear down the transport so the server's reader completes. - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); }; auto failsafe_task = [&]() -> capy::task<> { @@ -2310,9 +2308,9 @@ testFullDuplex(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; @@ -2327,9 +2325,9 @@ testFullDuplex(StreamFactory make_stream) BOOST_TEST(server_done); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** Sustained transfer in both directions at once: claim interleaving, @@ -2424,9 +2422,9 @@ testFullDuplexBulk(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; @@ -2441,9 +2439,9 @@ testFullDuplexBulk(StreamFactory make_stream) BOOST_TEST(server_ok); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** Exact TLS record-boundary payloads: one transfer of exactly 16384 @@ -2534,9 +2532,9 @@ testRecordBoundaryTransfer(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; @@ -2549,9 +2547,9 @@ testRecordBoundaryTransfer(StreamFactory make_stream) BOOST_TEST(done); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** shutdown() while a read is parked on the transport: the reader @@ -2627,9 +2625,9 @@ testShutdownOverRead(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; @@ -2645,9 +2643,9 @@ testShutdownOverRead(StreamFactory make_stream) BOOST_TEST(peer_sd_ok); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** Stream wrapper whose transport completions can be held at @@ -2784,9 +2782,9 @@ testShutdownSimultaneousClose(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); // A gate-wait parks on an in-process event, not transport // I/O; closing the mockets alone would leave it stuck, so // the failure would hang instead of surfacing. @@ -2807,9 +2805,9 @@ testShutdownSimultaneousClose(StreamFactory make_stream) BOOST_TEST(peer_sd_ok); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** Stream wrapper that injects an error alongside a real transport @@ -2912,9 +2910,8 @@ testPartialReadWithError(StreamFactory make_stream) pes.inject_ec_ = std::make_error_code(std::errc::connection_reset); char buf[512]; - auto [ec1, n1] = co_await client.read_some( + [[maybe_unused]] auto [ec1, n1] = co_await client.read_some( capy::mutable_buffer(buf, sizeof(buf))); - (void)n1; first_read_errored = (ec1 == std::errc::connection_reset); pes.armed_ = false; @@ -2933,9 +2930,9 @@ testPartialReadWithError(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; @@ -2951,9 +2948,9 @@ testPartialReadWithError(StreamFactory make_stream) BOOST_TEST(second_read_ok); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** Cancelling a parked reader releases its transport claim: a second @@ -3043,10 +3040,8 @@ testCancelParkedReader(StreamFactory make_stream) client_got_all = (got == reply); // Reply received; now cancel the parked server reader. reader_stop.request_stop(); - auto [ec, n] = co_await capy::write( + [[maybe_unused]] auto [ec, n] = co_await capy::write( client, capy::const_buffer(probe, probe_size)); - (void)ec; - (void)n; }; auto failsafe_task = [&]() -> capy::task<> { @@ -3056,9 +3051,9 @@ testCancelParkedReader(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); reader_done.set(); // unstick a parked wait on this event too // A leaked rd_cm_ claim would park the second read forever // on its own lock acquisition, past the point closing the @@ -3084,9 +3079,9 @@ testCancelParkedReader(StreamFactory make_stream) BOOST_TEST(second_read_ok); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** The documented multithreaded pattern: all operations on one stream @@ -3180,7 +3175,7 @@ testFullDuplexMtStrand(StreamFactory make_stream) } client_got_size = got.size(); client_done = true; - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); }; auto failsafe_task = [&]() -> capy::task<> { @@ -3195,12 +3190,12 @@ testFullDuplexMtStrand(StreamFactory make_stream) // them from the session tasks non-concurrent. auto close_m1 = [&]() -> capy::task<> { if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); co_return; }; auto close_m2 = [&]() -> capy::task<> { if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); co_return; }; capy::run_async(client_strand)(close_m1()); @@ -3227,9 +3222,9 @@ testFullDuplexMtStrand(StreamFactory make_stream) BOOST_TEST(server_done); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } /** Test that a trailing flush failure is deferred to the next operation. @@ -3312,9 +3307,9 @@ testDeferredFlushError(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; capy::run_async(ioc.get_executor())(write2()); @@ -3327,9 +3322,9 @@ testDeferredFlushError(StreamFactory make_stream) BOOST_TEST_EQ(n2, 0u); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } // Read side: the same stash surfaces when the next operation after @@ -3397,9 +3392,9 @@ testDeferredFlushError(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; capy::run_async(ioc.get_executor())(read1()); @@ -3434,9 +3429,9 @@ testDeferredFlushError(StreamFactory make_stream) { failsafe_hit2 = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; capy::run_async(ioc.get_executor())(read2()); @@ -3453,9 +3448,9 @@ testDeferredFlushError(StreamFactory make_stream) BOOST_TEST_EQ(n3, 0u); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } // shutdown() surfaces a stash left by an earlier deferred flush. @@ -3518,9 +3513,9 @@ testDeferredFlushError(StreamFactory make_stream) { failsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; capy::run_async(ioc.get_executor())(shutdown_task()); @@ -3541,9 +3536,9 @@ testDeferredFlushError(StreamFactory make_stream) BOOST_TEST(sec != capy::cond::stream_truncated); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } } @@ -3620,9 +3615,9 @@ testTlsLifecycleEdges(StreamFactory make_stream) BOOST_TEST(!c_sd2); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } // (b) read_some after a completed shutdown. @@ -3650,10 +3645,10 @@ testTlsLifecycleEdges(StreamFactory make_stream) ioc.restart(); auto client_sd = [&]() -> capy::task<> { - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); }; auto server_sd = [&]() -> capy::task<> { - (void)co_await server.shutdown(); + std::ignore = co_await server.shutdown(); }; capy::run_async(ioc.get_executor())(client_sd()); capy::run_async(ioc.get_executor())(server_sd()); @@ -3680,9 +3675,9 @@ testTlsLifecycleEdges(StreamFactory make_stream) BOOST_TEST_EQ(rd_n, 0u); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } // write_some after a completed shutdown, on its own transport so its @@ -3711,10 +3706,10 @@ testTlsLifecycleEdges(StreamFactory make_stream) ioc.restart(); auto client_sd = [&]() -> capy::task<> { - (void)co_await client.shutdown(); + std::ignore = co_await client.shutdown(); }; auto server_sd = [&]() -> capy::task<> { - (void)co_await server.shutdown(); + std::ignore = co_await server.shutdown(); }; capy::run_async(ioc.get_executor())(client_sd()); capy::run_async(ioc.get_executor())(server_sd()); @@ -3740,9 +3735,9 @@ testTlsLifecycleEdges(StreamFactory make_stream) { wfailsafe_hit = true; if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; capy::run_async(ioc.get_executor())(write_op()); @@ -3765,9 +3760,9 @@ testTlsLifecycleEdges(StreamFactory make_stream) } if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } // (c) zero-length buffer sequences: clean { {}, 0 } completion, @@ -3816,9 +3811,9 @@ testTlsLifecycleEdges(StreamFactory make_stream) BOOST_TEST_EQ(rn, 0u); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } } @@ -3883,7 +3878,7 @@ testShutdownTruncation(StreamFactory make_stream) { failsafe_hit = true; if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } }; @@ -3897,9 +3892,9 @@ testShutdownTruncation(StreamFactory make_stream) BOOST_TEST(shutdown_ec == capy::cond::stream_truncated); if (m1.is_open()) - m1.close(); // NOLINT(bugprone-unused-return-value) + m1.close(); if (m2.is_open()) - m2.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); } } // namespace boost::corosio::test diff --git a/test/unit/udp_socket.cpp b/test/unit/udp_socket.cpp index 4ed0f2fe6..a725ed7e9 100644 --- a/test/unit/udp_socket.cpp +++ b/test/unit/udp_socket.cpp @@ -256,7 +256,7 @@ struct udp_socket_test std::error_code caught; try { - (void)sock.get_option(); + std::ignore = sock.get_option(); } catch (std::system_error const& e) { @@ -577,7 +577,7 @@ struct udp_socket_test bool get_threw = false; try { - (void)sock.get_option(); + std::ignore = sock.get_option(); } catch (std::system_error const& e) { @@ -628,10 +628,10 @@ struct udp_socket_test }; capy::run_async(ioc.get_executor())(nested()); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); sock.cancel(); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(recv_done); BOOST_TEST(recv_ec == capy::cond::canceled); @@ -665,10 +665,10 @@ struct udp_socket_test }; capy::run_async(ioc.get_executor())(nested()); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); sock.close(); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(recv_done); BOOST_TEST(recv_ec == capy::cond::canceled); @@ -705,7 +705,7 @@ struct udp_socket_test // Reader task: signal ready, then block on recv_from auto reader_task = [&]() -> capy::task<> { char const msg[] = "R"; - (void)co_await reader.send_to( + std::ignore = co_await reader.send_to( capy::const_buffer(msg, 1), signal_ep); char buf[64]; @@ -720,7 +720,7 @@ struct udp_socket_test auto canceller_task = [&]() -> capy::task<> { char buf[1]; endpoint source; - (void)co_await signal_sock.recv_from( + std::ignore = co_await signal_sock.recv_from( capy::mutable_buffer(buf, 1), source); stop_src.request_stop(); @@ -809,7 +809,7 @@ struct udp_socket_test BOOST_TEST_EQ(ec3, std::error_code{}); // Wait for recv to complete - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(recv_done); BOOST_TEST_EQ(recv_ec, std::error_code{}); @@ -1080,10 +1080,10 @@ struct udp_socket_test }; capy::run_async(ioc.get_executor())(nested()); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); a.cancel(); - (void)co_await corosio::delay(std::chrono::milliseconds(50)); + std::ignore = co_await corosio::delay(std::chrono::milliseconds(50)); BOOST_TEST(recv_done); BOOST_TEST(recv_ec == capy::cond::canceled); @@ -1346,10 +1346,9 @@ struct udp_socket_test // Reverse direction through the adopted socket. char const reply[] = "back"; - auto [ec3, n3] = co_await peer.send_to( + [[maybe_unused]] auto [ec3, n3] = co_await peer.send_to( capy::const_buffer(reply, sizeof(reply)), source); BOOST_TEST(!ec3); - (void)n3; char buf2[64] = {}; endpoint from; @@ -1484,9 +1483,8 @@ struct udp_socket_test endpoint source; auto receiver = [&]() -> capy::task<> { - auto [rec, rn] = co_await sock.recv_from( + [[maybe_unused]] auto [rec, rn] = co_await sock.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); - (void)rn; recv_ec = rec; recv_done = true; }; @@ -1546,9 +1544,8 @@ struct udp_socket_test auto released = invalid_native_socket; auto receiver = [&]() -> capy::task<> { - auto [rec, rn] = co_await sock.recv_from( + [[maybe_unused]] auto [rec, rn] = co_await sock.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); - (void)rn; recv_ec = rec; recv_done = true; }; @@ -1595,7 +1592,7 @@ struct udp_socket_test bool caught = false; try { - (void)sock.release(); + std::ignore = sock.release(); } catch (std::system_error const&) { diff --git a/test/unit/wait.cpp b/test/unit/wait.cpp index f589f949c..554209642 100644 --- a/test/unit/wait.cpp +++ b/test/unit/wait.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #if BOOST_COROSIO_POSIX // Raw descriptor access for the write-backpressure tests. @@ -255,10 +256,8 @@ struct wait_test bytes_read = n; }; auto writer = [&]() -> capy::task<> { - auto [ec, n] = co_await s2.write_some( + [[maybe_unused]] auto [ec, n] = co_await s2.write_some( capy::const_buffer(payload.data(), payload.size())); - (void)ec; - (void)n; }; capy::run_async(ex)(reader()); @@ -408,7 +407,7 @@ struct wait_test std::stop_source ss; ss.request_stop(); - std::error_code first_ec; + [[maybe_unused]] std::error_code first_ec; bool first_done = false; auto first = [&]() -> capy::task<> { auto [ec] = co_await s1.wait(wait_type::write); @@ -423,7 +422,6 @@ struct wait_test // success is immaterial here; the subject is what its cancel // left behind. BOOST_TEST(first_done); - (void)first_ec; // Second wait: full buffer, fresh token. It must park and // complete on the drain — not absorb the first wait's cancel. @@ -495,11 +493,9 @@ struct wait_test }; auto sender = [&]() -> capy::task<> { char dg[1] = { 'X' }; - auto [ec, n] = co_await send.send_to( + [[maybe_unused]] auto [ec, n] = co_await send.send_to( capy::const_buffer(dg, sizeof(dg)), endpoint(ipv4_address::loopback(), port)); - (void)ec; - (void)n; }; capy::run_async(ex)(waiter()); @@ -541,9 +537,8 @@ struct wait_test accept_ec = ec2; }; auto connector = [&]() -> capy::task<> { - auto [ec] = co_await client.connect( + [[maybe_unused]] auto [ec] = co_await client.connect( endpoint(ipv4_address::loopback(), port)); - (void)ec; }; capy::run_async(ex)(waiter()); @@ -576,12 +571,10 @@ struct wait_test BOOST_TEST(!client.open()); auto accept_task = [&]() -> capy::task<> { - auto [ec] = co_await acc.accept(server); - (void)ec; + [[maybe_unused]] auto [ec] = co_await acc.accept(server); }; auto connect_task = [&]() -> capy::task<> { - auto [ec] = co_await client.connect(local_endpoint(path)); - (void)ec; + [[maybe_unused]] auto [ec] = co_await client.connect(local_endpoint(path)); }; capy::run_async(ex)(accept_task()); capy::run_async(ex)(connect_task()); @@ -598,10 +591,8 @@ struct wait_test wait_done = true; }; auto writer = [&]() -> capy::task<> { - auto [ec, n] = co_await client.write_some( + [[maybe_unused]] auto [ec, n] = co_await client.write_some( capy::const_buffer(payload.data(), payload.size())); - (void)ec; - (void)n; }; capy::run_async(ex)(waiter()); @@ -628,7 +619,7 @@ struct wait_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await delay(std::chrono::milliseconds(20)); + std::ignore = co_await delay(std::chrono::milliseconds(20)); s1.cancel(); }; @@ -663,7 +654,7 @@ struct wait_test wait_done = true; }; auto canceller = [&]() -> capy::task<> { - (void)co_await delay(std::chrono::milliseconds(20)); + std::ignore = co_await delay(std::chrono::milliseconds(20)); sock.cancel(); }; @@ -702,15 +693,12 @@ struct wait_test wait_done = true; if (ec2) co_return; - auto [ec3, n3] = co_await s1.read_some( + [[maybe_unused]] auto [ec3, n3] = co_await s1.read_some( capy::mutable_buffer(rest.data(), rest.size())); - (void)ec3; rest_n = n3; }; auto writer = [&]() -> capy::task<> { - auto [ec, n] = co_await s2.write_some(capy::const_buffer("xy", 2)); - (void)ec; - (void)n; + [[maybe_unused]] auto [ec, n] = co_await s2.write_some(capy::const_buffer("xy", 2)); }; capy::run_async(ex)(reader()); @@ -750,15 +738,12 @@ struct wait_test }; auto driver = [&]() -> capy::task<> { // Latch a readiness edge on s1 with no read op parked... - auto [wec, wn] = co_await s2.write_some( + [[maybe_unused]] auto [wec, wn] = co_await s2.write_some( capy::const_buffer("xy", 2)); - (void)wec; - (void)wn; // ...drain it, typically on the speculative success path... std::array buf{}; - auto [rec, rn] = co_await s1.read_some( + [[maybe_unused]] auto [rec, rn] = co_await s1.read_some( capy::mutable_buffer(buf.data(), buf.size())); - (void)rec; drained_n = rn; // ...then park the wait before the release signal exists. // Spawning here queues the wait initiation ahead of every @@ -766,17 +751,13 @@ struct wait_test // ordered after the park on FIFO schedulers and completion // ports alike. capy::run_async(ex)(waiter()); - auto [sec, sn] = co_await t2.write_some( + [[maybe_unused]] auto [sec, sn] = co_await t2.write_some( capy::const_buffer("go", 2)); - (void)sec; - (void)sn; }; auto canceller = [&]() -> capy::task<> { char c[2]; - auto [ec, n] = co_await t1.read_some( + [[maybe_unused]] auto [ec, n] = co_await t1.read_some( capy::mutable_buffer(c, sizeof(c))); - (void)ec; - (void)n; cancel_sent = true; s1.cancel(); }; @@ -815,9 +796,8 @@ struct wait_test auto receiver = [&]() -> capy::task<> { char dg[4]; endpoint source; - auto [ec, n] = co_await recv.recv_from( + [[maybe_unused]] auto [ec, n] = co_await recv.recv_from( capy::mutable_buffer(dg, sizeof(dg)), source); - (void)ec; first_n = n; auto [wec] = co_await recv.wait(wait_type::read); wait_ec = wec; @@ -827,14 +807,10 @@ struct wait_test char a[1] = { 'a' }; char b[1] = { 'b' }; endpoint dst(ipv4_address::loopback(), port); - auto [e1, n1] = co_await send.send_to( + [[maybe_unused]] auto [e1, n1] = co_await send.send_to( capy::const_buffer(a, sizeof(a)), dst); - (void)e1; - (void)n1; - auto [e2, n2] = co_await send.send_to( + [[maybe_unused]] auto [e2, n2] = co_await send.send_to( capy::const_buffer(b, sizeof(b)), dst); - (void)e2; - (void)n2; }; capy::run_async(ex)(receiver()); @@ -877,17 +853,14 @@ struct wait_test auto driver = [&]() -> capy::task<> { // Latch a readiness edge on rsock with no recv parked... char dg[1] = { 'x' }; - auto [wec, wn] = co_await ssock.send_to( + [[maybe_unused]] auto [wec, wn] = co_await ssock.send_to( capy::const_buffer(dg, sizeof(dg)), rsock.local_endpoint()); - (void)wec; - (void)wn; // ...drain it, typically on the speculative success path... char buf[4]; endpoint source; - auto [rec, rn] = co_await rsock.recv_from( + [[maybe_unused]] auto [rec, rn] = co_await rsock.recv_from( capy::mutable_buffer(buf, sizeof(buf)), source); - (void)rec; drained_n = rn; // ...then park the wait before the release signal exists. // Spawning here queues the wait initiation ahead of every @@ -895,17 +868,13 @@ struct wait_test // ordered after the park on FIFO schedulers and completion // ports alike. capy::run_async(ex)(waiter()); - auto [sec, sn] = co_await t2.write_some( + [[maybe_unused]] auto [sec, sn] = co_await t2.write_some( capy::const_buffer("go", 2)); - (void)sec; - (void)sn; }; auto canceller = [&]() -> capy::task<> { char c[2]; - auto [ec, n] = co_await t1.read_some( + [[maybe_unused]] auto [ec, n] = co_await t1.read_some( capy::mutable_buffer(c, sizeof(c))); - (void)ec; - (void)n; cancel_sent = true; rsock.cancel(); }; diff --git a/test/unit/wolfssl_stream.cpp b/test/unit/wolfssl_stream.cpp index 806dfacca..0135c21f8 100644 --- a/test/unit/wolfssl_stream.cpp +++ b/test/unit/wolfssl_stream.cpp @@ -81,12 +81,10 @@ struct wolfssl_stream_test tcp_socket sock(ioc); wolfssl_stream stream(&sock, ctx); - capy::any_stream& mutable_next = stream.next_layer(); - (void)mutable_next; + [[maybe_unused]] capy::any_stream& mutable_next = stream.next_layer(); wolfssl_stream const& cref = stream; - capy::any_stream const& const_next = cref.next_layer(); - (void)const_next; + [[maybe_unused]] capy::any_stream const& const_next = cref.next_layer(); BOOST_TEST(&mutable_next == &const_next); } From ce7e7683da8e389e7daee0c9d598774d77bc94e7 Mon Sep 17 00:00:00 2001 From: Steve Gerbino Date: Fri, 21 Aug 2026 18:41:11 +0200 Subject: [PATCH 5/5] docs: align the javadocs and the rulebook with the error contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code was more correct than its documentation; this closes the gap the error-handling audit measured: - tls_context @return clauses promised immediate validation the implementation defers to the first handshake's native context build; every setter now states the deferred model, and the file-loading setters distinguish file-read errors (returned now) from decode errors (surfacing at handshake). - release() documents its throwing channel on tcp_acceptor, local_stream_acceptor, stream_file, and random_access_file; wait() docs replace the "must be open" precondition with the deterministic closed-object completion the backends now guarantee; the type-erased read_some/write_some and native send_to/recv_from docs state it too. - reuse_port names the right channel, the io_context option constructors document their invalid_argument, the tcp_acceptor convenience constructor enumerates all throwing steps, native accept() documents its moved-from logic_error, and the TLS streams stop calling a moved-from object "valid but unspecified". - Every @code example models checked usage: no discarded [[nodiscard]] results, no comment-only if bodies, no open() before an auto-opening connect(). The rulebook states the doctrine in the present tense, and every former special case is now derived: rule 1's classification gains its third outcome — not-actionable reports nothing (void noexcept), with everything actionable available through an earlier return-channel operation (sync_* for durability, shutdown for orderly teardown) and a reported close() error rejected as an attractive nuisance whose invited retry is a double-close hazard under EINTR; rule 2 gains the constructor derivation (no return channel exists, so only misuse behind a public pre-check, wrapped codes of an abbreviated piecewise path, or root setup); and Composite Operations replaces Special Cases — a multi-step operation reports its first failure through its own single channel, deriving connect()'s auto-open reporting, the convenience constructors, the free corosio::connect, and the TLS deferred-configuration model. Rule 7 fixes std::errc as the only user vocabulary via the make_err normalization boundary. Rule 6 legislates the discard split: std::ignore for deliberate discards, [[maybe_unused]] for unused names, never a void cast. The guide pages join the contract: the error-handling page opens with the one-channel model and the deterministic corosio code table, and its io_result prose matches the type (a std::tuple alias — the members it described never existed); the http-client tutorial and UDP guide drop must-open-before-connect claims; the mocket pages teach the verify()-then-close() idiom; the acceptor page names the portable canceled condition. The error-handling guide gains an Avoiding Exceptions section mapping every throwing convenience to its exception-free spelling (or public pre-check), the throwing constructors cross-reference their alternatives, and the acceptor guide's throw enumeration matches the implementation. --- doc/error-handling-rulebook.md | 220 ++++++++++++++++++ .../pages/3.tutorials/3b.http-client.adoc | 2 +- .../ROOT/pages/4.guide/4a.tcp-networking.adoc | 7 + .../ROOT/pages/4.guide/4e.tcp-acceptor.adoc | 20 +- .../ROOT/pages/4.guide/4j.resolver.adoc | 6 +- .../ROOT/pages/4.guide/4m.error-handling.adoc | 130 ++++++++++- doc/modules/ROOT/pages/4.guide/4q.udp.adoc | 7 +- .../ROOT/pages/5.testing/5a.mocket.adoc | 19 +- .../ROOT/pages/5.testing/5b.socket-pair.adoc | 8 +- .../ROOT/pages/5.testing/5c.patterns.adoc | 15 +- include/boost/corosio/endpoint.hpp | 2 + include/boost/corosio/io/io_read_stream.hpp | 2 + include/boost/corosio/io/io_write_stream.hpp | 2 + include/boost/corosio/io_context.hpp | 6 + .../boost/corosio/local_datagram_socket.hpp | 19 +- include/boost/corosio/local_stream.hpp | 3 +- .../boost/corosio/local_stream_acceptor.hpp | 18 +- include/boost/corosio/local_stream_socket.hpp | 6 +- .../native/native_local_datagram_socket.hpp | 6 +- .../native/native_local_stream_acceptor.hpp | 2 + .../native/native_local_stream_socket.hpp | 3 +- .../native/native_random_access_file.hpp | 3 +- .../corosio/native/native_signal_set.hpp | 3 + .../corosio/native/native_socket_option.hpp | 3 +- .../corosio/native/native_stream_file.hpp | 3 +- .../corosio/native/native_tcp_acceptor.hpp | 2 + .../corosio/native/native_tcp_socket.hpp | 3 +- .../corosio/native/native_udp_socket.hpp | 10 +- include/boost/corosio/openssl_stream.hpp | 12 +- include/boost/corosio/random_access_file.hpp | 3 + include/boost/corosio/signal_set.hpp | 23 +- include/boost/corosio/socket_option.hpp | 17 +- include/boost/corosio/stream_file.hpp | 16 +- include/boost/corosio/tcp.hpp | 9 +- include/boost/corosio/tcp_acceptor.hpp | 39 +++- include/boost/corosio/tcp_server.hpp | 6 +- include/boost/corosio/tcp_socket.hpp | 16 +- include/boost/corosio/timeout.hpp | 5 +- include/boost/corosio/tls_context.hpp | 141 +++++++---- include/boost/corosio/udp.hpp | 6 +- include/boost/corosio/udp_socket.hpp | 29 ++- include/boost/corosio/wolfssl_stream.hpp | 12 +- .../snippets/4b_concurrent_programming.cpp | 7 +- test/doc/snippets/4d_sockets.cpp | 7 +- test/doc/snippets/4e_tcp_acceptor.cpp | 9 +- test/doc/snippets/4g_composed_operations.cpp | 7 +- test/doc/snippets/4i_signals.cpp | 39 ++-- test/doc/snippets/4l_tls.cpp | 9 +- test/doc/snippets/4n_buffers.cpp | 18 +- test/doc/snippets/4o_file_io.cpp | 5 +- test/doc/snippets/4p_unix_sockets.cpp | 15 +- test/doc/snippets/4q_udp.cpp | 9 +- test/doc/snippets/5b_socket_pair.cpp | 4 +- test/doc/snippets/5c_patterns.cpp | 8 +- 54 files changed, 759 insertions(+), 242 deletions(-) create mode 100644 doc/error-handling-rulebook.md diff --git a/doc/error-handling-rulebook.md b/doc/error-handling-rulebook.md new file mode 100644 index 000000000..a3a44faec --- /dev/null +++ b/doc/error-handling-rulebook.md @@ -0,0 +1,220 @@ +# Corosio Error-Handling Rulebook + +How corosio reports, classifies, tests, and documents errors. + +## 1. One Channel + +Every fallible operation reports through exactly one channel, chosen +by classifying its failures: + +- **Expected runtime conditions** — anything the environment or a + peer can cause at runtime: missing file, address in use, descriptor + exhaustion, disk full, peer disconnect race, unparseable input, + foreign descriptors unfit for adoption — report through the + **return value**. +- **Misuse** — violations of a documented precondition the caller + could have checked: calling on a closed object, invoking twice, + breaking a stated invariant — **throw**. +- **Not actionable** — nothing a caller can act on remains — report + nothing: **`void noexcept`**. The effect is guaranteed: `close()` + releases the descriptor even when the OS objects, `cancel()`'s + request always lands. Everything actionable was available through + an earlier return-channel operation — durability through + `sync_data`/`sync_all`, orderly teardown through `shutdown` — and + cancellation's outcome is inherently asynchronous, surfacing + through each canceled completion. Reporting a `close()` error + would be an attractive nuisance: the retry it invites is a + double-close hazard, because POSIX leaves descriptor release under + `EINTR` unspecified. +- Never both channels for one operation. Never `std::error_code&` + out-params. Never a throwing/non-throwing overload pair. + +## 2. Return Shapes + +| Situation | Shape | +|---|---| +| Sync, no payload | `[[nodiscard]] std::error_code f(...) noexcept` | +| Sync, with payload | `[[nodiscard]] capy::io_result f(...) noexcept` | +| Async | the awaitable completes with `io_result<...>` — initiators never throw | + +`io_result` is `std::tuple`, so results +destructure; the sync and async surfaces share one idiom: + +```cpp +if (auto ec = sock.open()) // no payload + co_return; +auto [ec, pos] = f.seek(0, file_base::seek_end); // payload +auto [ec2, ep] = make_endpoint("10.0.0.1:80"); // factory +``` + +The payload always rides in the return — the `make_*` factories +return `io_result` with a default-constructed payload on failure. +Every awaitable-returning initiator is `[[nodiscard]]`: a discarded +awaitable is a silent no-op that also drops any pre-set completion. + +Constructors have no return channel to choose, so rule 1 admits only +three constructor failures: misuse, guarded by a public pre-check +(`local_endpoint`'s `max_path_length`, `io_context`'s +`thread_pool_size`); the wrapped codes of a code-returning path the +constructor abbreviates (`tcp_acceptor(ctx, ep)` over +`open`+`bind`+`listen`, `endpoint(str)` over `make_endpoint`, thrown +as `std::system_error` carrying the code the piecewise spelling +returns); and root setup for which no code-returning spelling can +exist (`io_context` backend creation, allocation). + +## 3. The Classification Test + +Ask: **can the caller reliably prevent the failure by checking state +they own, race-free, before the call?** + +- **No** → normal error. File existence (TOCTOU), a port's + availability, a peer's connection state, or whether a string parses + cannot be pre-checked. +- **Yes** → precondition, misuse, throw. The canonical example: + `local_endpoint(path)` throws on an over-long path because the + limit is the public `max_path_length` constant — one integer + comparison, no grammar, no race. The pre-check *is* the + non-throwing API. + +Corollary: an operation with no realistic failure on a valid open +object (`size()`, `release()`, `available()`) stays on the throwing +channel — an `io_result` return would tax every correct call site to +carry a bit that is always "you have a bug". + +## 4. The Closed-Object Condition + +One condition, one code — `errc::bad_file_descriptor` — delivered +through whichever channel the operation already uses: + +- Error-returning sync methods (`bind`, `listen`, `shutdown`, + `assign`, file `resize`/`sync_*`/`seek`) **return** it. +- Throwing-channel methods (`size`, `release`, `available`, + `set_option`, `get_option`) **throw** + `std::system_error(errc::bad_file_descriptor)`. Not `logic_error`: + closed-ness is legitimately observable in async teardown (another + coroutine closes to cancel), the platform spells it as a system + condition (`EBADF`), and one exception type keeps the surface + coherent. +- Async initiators **pre-fail the awaitable**: set `aw.ec_` and + return it; `await_ready` treats a pre-set `ec_` as + ready-with-result, so the operation completes immediately without + dispatching. `connect()`'s auto-open reports an open failure the + same way. The backends enforce the same contract at their operation + entries — a closed object reaching a backend read/write/wait + completes with `bad_file_descriptor` before touching the kernel — + so the code is deterministic on every path, including the + devirtualized native facades. + +Genuine logic errors unrelated to object state stay `logic_error`: +service not installed, launcher invoked twice, zero-sized thread pool. + +## 5. Composite Operations + +An operation that performs several fallible steps reports the first +failure through its own single channel — sub-steps never add a +second channel: + +- `connect()` opens the socket automatically when needed; an open + failure surfaces through the connect completion, so callers need no + explicit `open()` before `connect`. +- The convenience constructors wrap `open`+`bind`+`listen` and throw + the code the failing step returned. +- The free `corosio::connect` walks a candidate range and reports + through one completion — `no_such_device_or_address` when no + candidate is viable. +- `tls_context` setters record configuration that is applied when a + handshake first configures the engine; application failures surface + through that handshake's completion. + +## 6. Attributes and Spelling + +- Every error-returning function is `[[nodiscard]]` and, where the + body permits, `noexcept`. A silently dropped + `use_certificate_chain_file` failure is a security bug; the + attribute makes it a warning. +- **`[[nodiscard]]` goes BEFORE `BOOST_COROSIO_DECL`.** The macro + expands to a visibility/dllexport attribute in shared builds; + placed after it, `[[nodiscard]]` binds to the return *type* — a + hard error that static local builds never see. Gate new free + functions with a `-DBOOST_COROSIO_DYN_LINK -DBOOST_COROSIO_SOURCE` + syntax check. +- Deliberate discards use `std::ignore = expr;`, never `(void)` + casts. Reserve them for calls whose outcome is asserted downstream + (hostile-input tests, best-effort bench teardown). +- Unused names — parameters kept for signature clarity, structured + bindings partially consumed, `#if`-gated uses — are declared + `[[maybe_unused]]`, never silenced with a void cast. + +## 7. Code Values and Portability + +- **The user vocabulary is `std::errc`** — corosio defines no error + namespace of its own. +- Codes corosio generates itself are deterministic contracts: + `invalid_argument` (parsers, signal_set, negative seek), + `bad_file_descriptor` (closed objects, descriptor validation), + `wrong_protocol_type` / `address_family_not_supported` (adoption + rejection), `value_too_large` (off_t guard, truncated hostname), + `filename_too_long`, + `already_connected` (`connect_pair` on an open socket), + `no_such_device_or_address` (`corosio::connect` with no viable + candidate). +- Portable comparison comes from **normalizing at the boundary**: the + Windows `make_err` maps the contracted WSA/Win32 codes to + generic-category `errc` values (`WSAEOPNOTSUPP`, `WSAENOTSOCK`, + `WSAEAFNOSUPPORT`, `WSAEPROTOTYPE`, `WSAEADDRINUSE`, + `WSAEADDRNOTAVAIL`, `ERROR_NEGATIVE_SEEK`; `iocp_make_err` adds the + async condition set and `WSAEBADF`/`ERROR_INVALID_HANDLE`). On + POSIX, raw errno satisfies `errc` comparison with one exception: + `make_err` normalizes `ENOTSUP` so platforms where it differs from + `EOPNOTSUPP` still compare equal to + `errc::operation_not_supported`. +- Kernel codes outside the contracted set go out as raw + `system_category` values — normalization is for contracts, not a + laundering of every error. Some rejections stay platform- or even + runner-dependent (WinSock's handling of IPv6-level options on + AF_INET sockets; Darwin's `getsockopt(TCP_NODELAY)` on AF_UNIX); + Windows reports `not_a_socket` where POSIX validation reports + `EBADF` for garbage (non-sentinel) handles. +- Conditions the standard cannot spell come from capy: + `capy::cond::eof`, `capy::cond::canceled` (a stop token, not + `errc::operation_canceled`), `capy::cond::timeout` (our deadline, + not a kernel `ETIMEDOUT`). + +## 8. Testing + +- **Lock deterministic codes by equality**: everything corosio + generates, plus kernel codes with reliable mappings on the platform + under test. `BOOST_TEST(ec == std::errc::bad_file_descriptor)`. +- **Platform-variant codes assert only that a genuine error arrived** + (option failures, fsync-on-pipe, resize-read-only), with a comment + naming the variance. +- **Platform-variant *premises* get gated, not weakened**: if the + operation legitimately succeeds somewhere (IOCP dual-stack + acceptors accepting `v6_only`, Darwin's permissive `getsockopt`), + gate the whole assertion with `#if` and a comment — don't leave an + assertion that intermittently passes. +- Contracted codes compare by `errc` equality on every platform — + raw-value pinning is reserved for kernel codes outside the contract + list. +- Closed-object behavior is tested on **every channel**: returned + codes, thrown `system_error`s, and awaited completions + (`co_await` + `ioc.run()` + a `done` flag so the test cannot pass + vacuously). +- Error paths count as coverage targets: exercise the reachable ones + (wrong-direction I/O, huge offsets, bind conflicts, pipe sync); + document the rest as fault-injection-only. Run the gcc coverage + build — it doubles as a second-compiler gate. + +## 9. Documentation + +- Javadocs state the channel: `@return The error code, empty on + success.` for returns; `@throws std::system_error + \`errc::bad_file_descriptor\` if ...` for throws. Never a stale + `@throws` on a `noexcept` function. +- Examples and doc snippets model checked usage — `if (auto ec = ...)` + early-return — and never discard a `[[nodiscard]]` result, even + where a pragma would let it compile. Fragments before `connect` + simply omit `open()`. +- Example `if` bodies are real statements (`co_return;`), never a + comment alone — a comment-only body silently swallows the next + statement. diff --git a/doc/modules/ROOT/pages/3.tutorials/3b.http-client.adoc b/doc/modules/ROOT/pages/3.tutorials/3b.http-client.adoc index f8790c587..32b086576 100644 --- a/doc/modules/ROOT/pages/3.tutorials/3b.http-client.adoc +++ b/doc/modules/ROOT/pages/3.tutorials/3b.http-client.adoc @@ -71,7 +71,7 @@ Key points: include::example$client/http_client.cpp[tag=run_client] ---- -The socket must be opened before connecting. We pass the socket as an +`connect()` opens the socket automatically. We pass the socket as an `io_stream&` to `do_request`, so the same function works with any plain socket. TLS streams have a different type and need their own overload, as shown below. diff --git a/doc/modules/ROOT/pages/4.guide/4a.tcp-networking.adoc b/doc/modules/ROOT/pages/4.guide/4a.tcp-networking.adoc index c703d8584..00964cd3f 100644 --- a/doc/modules/ROOT/pages/4.guide/4a.tcp-networking.adoc +++ b/doc/modules/ROOT/pages/4.guide/4a.tcp-networking.adoc @@ -684,6 +684,13 @@ When a socket closes, its address may remain in TIME_WAIT state for minutes. This is essential for servers that restart—without it, the restart fails with "address already in use" until TIME_WAIT expires. +The flag means something different on Windows: there it grants a socket the +right to bind over an address another socket actively holds, silently +splitting incoming connections between the two. Windows servers that want +the POSIX contract set `SO_EXCLUSIVEADDRUSE` instead — the `tcp_acceptor` +convenience constructor does this automatically, so a second listener fails +with `errc::address_in_use` on every platform. + === SO_REUSEPORT `SO_REUSEPORT` (available on some operating systems) allows multiple sockets diff --git a/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc b/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc index fb306c9cb..365459803 100644 --- a/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc +++ b/doc/modules/ROOT/pages/4.guide/4e.tcp-acceptor.adoc @@ -59,9 +59,15 @@ which opens, sets `SO_REUSEADDR`, binds, and listens in a single step: include::example$snippets/4e_tcp_acceptor.cpp[tag=convenience_ctor,indent=0] ---- -This throws `std::system_error` if binding or listening fails. Unlike the -standalone `open()`, the convenience constructor enables `SO_REUSEADDR` before -binding, so the listening port can be reused immediately after a restart. +This throws `std::system_error` on open, `set_option()`, bind, or listen +failure — exactly the codes the piecewise path (open, set option, bind, +listen) reports. Unlike the standalone `open()`, the convenience +constructor configures address reuse before binding, so the listening +port can be reused immediately after a restart. The option is +platform-specific: `SO_REUSEADDR` on POSIX, and `SO_EXCLUSIVEADDRUSE` on +Windows, where `SO_REUSEADDR` would instead let other sockets bind over +the port. Either way the observable contract is the same — a second +listener on an occupied endpoint throws `errc::address_in_use`. === bind() and listen() @@ -142,7 +148,7 @@ Common accept errors: |=== | Error | Meaning -| `operation_canceled` +| `capy::cond::canceled` | Cancelled via `cancel()` or stop token | Resource errors @@ -169,7 +175,8 @@ Cancel pending accept operations: include::example$snippets/4e_tcp_acceptor.cpp[tag=cancel,indent=0] ---- -All outstanding `accept()` operations complete with `operation_canceled`. +All outstanding `accept()` operations complete with an error matching +`capy::cond::canceled`. === Stop Token Cancellation @@ -192,7 +199,8 @@ Release tcp_acceptor resources: include::example$snippets/4e_tcp_acceptor.cpp[tag=close,indent=0] ---- -Pending accept operations complete with `operation_canceled`. +Pending accept operations complete with an error matching +`capy::cond::canceled`. === is_open() diff --git a/doc/modules/ROOT/pages/4.guide/4j.resolver.adoc b/doc/modules/ROOT/pages/4.guide/4j.resolver.adoc index f8ac79132..7babf3341 100644 --- a/doc/modules/ROOT/pages/4.guide/4j.resolver.adoc +++ b/doc/modules/ROOT/pages/4.guide/4j.resolver.adoc @@ -254,9 +254,9 @@ I/O context. [IMPORTANT] ==== -Because resolution runs on a thread pool, it requires a multi-threaded -`io_context`. When the `io_context` is constructed single-threaded (a -`concurrency_hint` of 1), `resolve()` completes with +Because resolution runs on a thread pool, it relies on scheduler +locking, which is on by default. Under the fully lockless tier +(`locking_mode::unsafe`), `resolve()` completes with `std::errc::operation_not_supported` and never performs a lookup. ==== diff --git a/doc/modules/ROOT/pages/4.guide/4m.error-handling.adoc b/doc/modules/ROOT/pages/4.guide/4m.error-handling.adoc index 71b2739ae..fa5e2af86 100644 --- a/doc/modules/ROOT/pages/4.guide/4m.error-handling.adoc +++ b/doc/modules/ROOT/pages/4.guide/4m.error-handling.adoc @@ -12,6 +12,79 @@ Corosio reports I/O errors through the `io_result` type, which carries an error code alongside any values produced by the operation. +== One Channel + +Every fallible operation reports through exactly one channel: + +* *Expected runtime conditions return the error.* Synchronous + operations return `std::error_code` (or `io_result` when a + payload rides along — `seek()`, the `make_*` factories), and + asynchronous operations complete with `io_result<...>`. Every + error-returning function is `[[nodiscard]]`. +* *Misuse of a documented precondition throws* `std::system_error` — + for example `release()`, `set_option()`, or `get_option()` on a + closed object. +* *Operations whose outcome nobody can act on report nothing.* + `close()` and `cancel()` are `void noexcept`; durability reporting + belongs to `sync_data()`/`sync_all()`, and cancellation reports + through each canceled completion. + +Using a closed object is deterministic on every backend and every +channel: the operation returns, throws, or completes with +`errc::bad_file_descriptor`. + +== Avoiding Exceptions + +Every throwing convenience is sugar over an exception-free spelling, +or is guarded by a public pre-check — code that performs the check or +uses the piecewise path never sees the throw: + +[cols="1,1"] +|=== +| Throwing convenience | Exception-free spelling + +| `tcp_acceptor(ctx, ep, backlog)` + + `local_stream_acceptor(ctx, ep, backlog)` +| default-construct, then `open()` + `bind()` + `listen()` + ( `tcp_acceptor` also configures address reuse via `set_option()` + between open and bind: `SO_REUSEADDR` on POSIX, + `SO_EXCLUSIVEADDRUSE` on Windows ) — the constructor throws exactly + the codes this path reports + +| `endpoint("host:port")` +| `make_endpoint(s)` + +| `ipv4_address(s)` / `ipv6_address(s)` +| `make_ipv4_address(s)` / `make_ipv6_address(s)` + +| `signal_set(ctx, sig, sigs...)` +| `signal_set(ctx)`, then `add()` per signal + +| `local_endpoint(path)` +| check `path.size() <= local_endpoint::max_path_length` first — the + public constant is the entire precondition + +| `io_context(opts, ...)` throwing `std::invalid_argument` +| ensure `opts.thread_pool_size >= 1` + +| `release()`, `size()`, `available()`, `set_option()`, + `get_option()` on a closed object +| check `is_open()` first — closed-ness is the documented precondition +|=== + +What cannot be spelled exception-free: root construction and the run +loop. `io_context` itself throws if backend setup fails (there is no +code-returning way to construct it), any constructor can throw +`std::bad_alloc`, and `run()`/`stop()` throw `std::system_error` if +the OS demultiplexer itself fails — a process-fatal condition with no +per-operation channel to carry it. One environmental caveat on the +last table row: `set_option()`/`get_option()` also throw when the +platform rejects the option itself (an unsupported option on that +protocol or OS), so an open check removes the closed-object throw but +not that environmental arm — probe an option once at startup if it +must not throw later. +Startup construction failing by exception is the intended shape. + [NOTE] ==== Code snippets assume: @@ -23,14 +96,12 @@ include::example$snippets/4m_error_handling.cpp[tag=assume] == The io_result Type -Every I/O operation returns an `io_result<...>` with two public members: - -* `ec` — the error code (always present) -* `values` — a tuple of any additional values produced by the operation - -`io_result` also models the tuple protocol, so it can be destructured with -structured bindings. It has no `value()` member and no conversion to `bool`; -check for errors by testing `ec`. +`io_result` is an alias for `std::tuple`: +the error code always comes first, followed by any values the operation +produced. Because it is a `std::tuple`, results interoperate with the +whole tuple API — structured bindings, `std::tie`, `std::get`, +`std::apply`. There is no `value()` member and no conversion to `bool`; +check for errors by testing the error code. [source,cpp] ---- @@ -48,17 +119,19 @@ include::example$snippets/4m_error_handling.cpp[tag=structured_bindings,indent=0 This pattern gives you full control over error handling. -== Accessing Members Directly +== Accessing Elements Directly -You can also bind the whole result and read its members: +You can also bind the whole result and read its elements through the +tuple API: [source,cpp] ---- include::example$snippets/4m_error_handling.cpp[tag=direct_members,indent=0] ---- -The payload lives in `result.values`; for single-value results, prefer -structured bindings, which name the value for you. +The error code is `std::get<0>(result)` and payload elements follow. +For single-value results, prefer structured bindings, which name the +value for you. == Throwing on Error @@ -125,6 +198,39 @@ include::example$snippets/4m_error_handling.cpp[tag=throw_style,indent=0] | No route to host |=== +=== Deterministic Corosio Codes + +Codes corosio generates itself are contracts, portable across every +platform and backend: + +[cols="1,2"] +|=== +| Error | Meaning + +| `bad_file_descriptor` +| Operation on a closed object, or adopting an invalid descriptor + +| `invalid_argument` +| Unparseable input to the `make_*` factories, invalid signal_set + flags, a negative seek + +| `already_connected` +| `connect_pair()` on an already-open socket + +| `no_such_device_or_address` +| `corosio::connect()` with no viable candidate + +| `wrong_protocol_type` / `address_family_not_supported` +| Adopting a foreign descriptor of the wrong type or family + +| `value_too_large` +| A file offset beyond what the platform can represent, or a + truncated hostname from `host_name()` + +| `filename_too_long` +| A `local_endpoint` path over `max_path_length` +|=== + === Cancellation Cancellation does not map deterministically to a single category or diff --git a/doc/modules/ROOT/pages/4.guide/4q.udp.adoc b/doc/modules/ROOT/pages/4.guide/4q.udp.adoc index 0d89e8a49..bd21d1aa8 100644 --- a/doc/modules/ROOT/pages/4.guide/4q.udp.adoc +++ b/doc/modules/ROOT/pages/4.guide/4q.udp.adoc @@ -43,8 +43,9 @@ open — see xref:#connected-mode[Connected Mode] below. == Opening and Binding -A socket must be open before any I/O. To receive datagrams it must also be -bound to a local endpoint: +Receiving datagrams requires an open socket bound to a local endpoint +(`connect()` opens the socket automatically; the other initiators +complete with `errc::bad_file_descriptor` on a closed socket): [source,cpp] ---- @@ -224,7 +225,7 @@ Related options: == Cancellation `cancel()` aborts every operation in flight on the socket. They complete -with `errc::operation_canceled`: +with an error matching `capy::cond::canceled`: [source,cpp] ---- diff --git a/doc/modules/ROOT/pages/5.testing/5a.mocket.adoc b/doc/modules/ROOT/pages/5.testing/5a.mocket.adoc index 938cf76fa..3c40d601e 100644 --- a/doc/modules/ROOT/pages/5.testing/5a.mocket.adoc +++ b/doc/modules/ROOT/pages/5.testing/5a.mocket.adoc @@ -98,22 +98,21 @@ the constructor. The default is `std::size_t(-1)` (unlimited). == Closing and Verification -`close()` shuts the underlying socket and verifies that both staging -buffers are empty: +`verify()` checks that both staging buffers were fully consumed and +returns `error::test_failure` if either holds leftover data; `close()` +shuts the underlying socket, running the same check through the fuse +on the way out: [source,cpp] ---- include::example$snippets/5a_mocket.cpp[tag=close_check,indent=0] ---- -Always call `close()` at the end of a test that uses `provide` / `expect` -and assert that the result is empty. This is what catches "the test -passed because the code under test did nothing." - -The leftover-data check only runs on the *first* `close()` of a -still-open mocket. `close()` on an already-closed mocket returns success -without inspecting the staging buffers, so the verifying `close()` must be -the first one. +Always call `verify()` at the end of a test that uses `provide` / +`expect` and assert that the result is empty. This is what catches +"the test passed because the code under test did nothing." An unmet +expectation also trips the fuse, so even an unchecked `close()` still +fails the test. == Templated over Socket diff --git a/doc/modules/ROOT/pages/5.testing/5b.socket-pair.adoc b/doc/modules/ROOT/pages/5.testing/5b.socket-pair.adoc index f0a5ecc67..6c176a6b2 100644 --- a/doc/modules/ROOT/pages/5.testing/5b.socket-pair.adoc +++ b/doc/modules/ROOT/pages/5.testing/5b.socket-pair.adoc @@ -39,10 +39,10 @@ The function: Both sockets come back `is_open()` and ready to use. -If bind, listen, accept, or connect fails, `make_socket_pair` throws -`std::runtime_error` naming the failing step; the underlying -`error_code::message()` appears in the exception text for bind and -listen failures, and on `stderr` for accept and connect failures. +If open, bind, listen, accept, or connect fails, `make_socket_pair` +throws `std::runtime_error` naming the failing step; the underlying +`error_code::message()` appears in the exception text for open, bind, +and listen failures, and on `stderr` for accept and connect failures. == Round Trip diff --git a/doc/modules/ROOT/pages/5.testing/5c.patterns.adoc b/doc/modules/ROOT/pages/5.testing/5c.patterns.adoc index 9cc987c44..2da7d6941 100644 --- a/doc/modules/ROOT/pages/5.testing/5c.patterns.adoc +++ b/doc/modules/ROOT/pages/5.testing/5c.patterns.adoc @@ -25,7 +25,7 @@ include::example$snippets/5c_patterns.cpp[tag=assume] == Verifying Request Format When a function under test must emit an exact byte sequence, stage it -with `expect()` and assert on `close()` at the end: +with `expect()` and assert on `verify()` at the end: [source,cpp] ---- @@ -86,7 +86,7 @@ include::example$snippets/5c_patterns.cpp[tag=end_to_end,indent=0] == Deterministic Close-Verification -Always call `close()` on the mocket at the end of a test that uses +Always call `verify()` on the mocket at the end of a test that uses `provide` / `expect`, and assert the result is empty: [source,cpp] @@ -95,13 +95,10 @@ include::example$snippets/5c_patterns.cpp[tag=close_verification,indent=0] ---- This is the line that catches "the test passed because the code under -test silently did nothing." Treat it as a test-suite convention. - -The leftover-data check only runs on the *first* `close()` of a -still-open mocket. If the mocket is already closed, `close()` returns -success without inspecting the staging buffers, so leftover `provide()` / -`expect()` data goes undetected. Make the verifying `close()` the first -one. +test silently did nothing." Treat it as a test-suite convention. An +unmet expectation also trips the fuse, so a plain `close()` without +the explicit `verify()` still fails the test — the assertion just +makes the failure local and readable. == See Also diff --git a/include/boost/corosio/endpoint.hpp b/include/boost/corosio/endpoint.hpp index be8bf3b6b..e73ad0bca 100644 --- a/include/boost/corosio/endpoint.hpp +++ b/include/boost/corosio/endpoint.hpp @@ -142,6 +142,8 @@ class endpoint @param s The string to parse. @throws std::system_error on parse failure. + + @see make_endpoint for the non-throwing form. */ explicit endpoint(std::string_view s); diff --git a/include/boost/corosio/io/io_read_stream.hpp b/include/boost/corosio/io/io_read_stream.hpp index d1bd4a0e7..40d0d402d 100644 --- a/include/boost/corosio/io/io_read_stream.hpp +++ b/include/boost/corosio/io/io_read_stream.hpp @@ -101,6 +101,8 @@ class BOOST_COROSIO_DECL io_read_stream : virtual public io_object referenced by @p buffers must remain valid until the operation completes. + A closed stream completes with `errc::bad_file_descriptor`. + @param buffers The buffer sequence to read data into. @return An awaitable yielding `(error_code, std::size_t)`. diff --git a/include/boost/corosio/io/io_write_stream.hpp b/include/boost/corosio/io/io_write_stream.hpp index cc09f2724..362d04abb 100644 --- a/include/boost/corosio/io/io_write_stream.hpp +++ b/include/boost/corosio/io/io_write_stream.hpp @@ -101,6 +101,8 @@ class BOOST_COROSIO_DECL io_write_stream : virtual public io_object referenced by @p buffers must remain valid until the operation completes. + A closed stream completes with `errc::bad_file_descriptor`. + @param buffers The buffer sequence containing data to write. @return An awaitable yielding `(error_code, std::size_t)`. diff --git a/include/boost/corosio/io_context.hpp b/include/boost/corosio/io_context.hpp index 62fdd44ae..201ff7833 100644 --- a/include/boost/corosio/io_context.hpp +++ b/include/boost/corosio/io_context.hpp @@ -277,6 +277,9 @@ class BOOST_COROSIO_DECL io_context : public capy::execution_context service behavior. @param concurrency_hint Hint for the number of threads that will call `run()`. + + @throws std::invalid_argument If `opts.thread_pool_size` is + less than 1 (POSIX). */ explicit io_context( io_context_options const& opts, @@ -311,6 +314,9 @@ class BOOST_COROSIO_DECL io_context : public capy::execution_context service behavior. @param concurrency_hint Hint for the number of threads that will call `run()`. + + @throws std::invalid_argument If `opts.thread_pool_size` is + less than 1 (POSIX). */ template requires requires { Backend::construct; } diff --git a/include/boost/corosio/local_datagram_socket.hpp b/include/boost/corosio/local_datagram_socket.hpp index fc13bfaa6..e2d002873 100644 --- a/include/boost/corosio/local_datagram_socket.hpp +++ b/include/boost/corosio/local_datagram_socket.hpp @@ -83,17 +83,25 @@ namespace boost::corosio { @code // Connectionless local_datagram_socket sender(ioc); - sender.open(); - sender.bind(local_endpoint("/tmp/sender.sock")); + if (auto ec = sender.open()) + co_return; + if (auto ec = sender.bind(local_endpoint("/tmp/sender.sock"))) + co_return; auto [ec, n] = co_await sender.send_to( capy::const_buffer("hello", 5), local_endpoint("/tmp/receiver.sock")); + if (ec) + co_return; // Connected local_datagram_socket sock(ioc); - co_await sock.connect(local_endpoint("/tmp/peer.sock")); + auto [cec] = co_await sock.connect(local_endpoint("/tmp/peer.sock")); + if (cec) + co_return; auto [ec2, n2] = co_await sock.send( capy::const_buffer("hi", 2)); + if (ec2) + co_return; @endcode */ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @@ -590,9 +598,10 @@ class BOOST_COROSIO_DECL local_datagram_socket : public io_object @return An awaitable that completes with `io_result<>`. + A closed socket completes with `errc::bad_file_descriptor`. + @par Preconditions - The socket must be open. This socket must outlive the - returned awaitable. + This socket must outlive the returned awaitable. */ [[nodiscard]] auto wait(wait_type w) { diff --git a/include/boost/corosio/local_stream.hpp b/include/boost/corosio/local_stream.hpp index 5e5b46633..edc23703b 100644 --- a/include/boost/corosio/local_stream.hpp +++ b/include/boost/corosio/local_stream.hpp @@ -31,7 +31,8 @@ class local_stream_acceptor; @par Example @code local_stream_socket sock(ctx); - sock.open(local_stream{}); + if (auto ec = sock.open(local_stream{})) + return; @endcode @see native_local_stream, local_stream_socket, local_stream_acceptor diff --git a/include/boost/corosio/local_stream_acceptor.hpp b/include/boost/corosio/local_stream_acceptor.hpp index 223bb7e58..f43466d40 100644 --- a/include/boost/corosio/local_stream_acceptor.hpp +++ b/include/boost/corosio/local_stream_acceptor.hpp @@ -69,12 +69,12 @@ enum class bind_option io_context ioc; local_stream_acceptor acc(ioc); if (auto ec = acc.open()) - return ec; + co_return ec; if (auto ec = acc.bind(local_endpoint("/tmp/my.sock"), bind_option::unlink_existing)) - return ec; + co_return ec; if (auto ec = acc.listen()) - return ec; + co_return ec; auto [aec, peer] = co_await acc.accept(); @endcode */ @@ -196,7 +196,8 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object /** Convenience constructor: open + bind + listen. Creates a fully-bound listening acceptor in a single - expression. + expression, throwing the codes the piecewise `open()` + + `bind()` + `listen()` path returns. @param ctx The execution context that will own this acceptor. @param ep The local endpoint to bind to. @@ -380,8 +381,10 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return An awaitable that completes with `io_result<>`. + A closed acceptor completes with `errc::bad_file_descriptor`. + @par Preconditions - The acceptor must be listening. + This acceptor must outlive the returned awaitable. */ [[nodiscard]] auto wait(wait_type w) { @@ -405,6 +408,8 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object io_result. A closed acceptor reports `errc::bad_file_descriptor`. + On failure the returned socket is default-constructed and + may only be destroyed or assigned. */ [[nodiscard]] auto accept() { @@ -430,7 +435,8 @@ class BOOST_COROSIO_DECL local_stream_acceptor : public io_object @return The native handle. - A closed acceptor reports `errc::bad_file_descriptor`. + @throws std::system_error `errc::bad_file_descriptor` if the + acceptor is not open. @post is_open() == false */ diff --git a/include/boost/corosio/local_stream_socket.hpp b/include/boost/corosio/local_stream_socket.hpp index 7edcd0d9b..8e5eb64cf 100644 --- a/include/boost/corosio/local_stream_socket.hpp +++ b/include/boost/corosio/local_stream_socket.hpp @@ -64,7 +64,6 @@ namespace boost::corosio { @code io_context ioc; local_stream_socket s(ioc); - s.open(); auto [ec] = co_await s.connect(local_endpoint("/tmp/my.sock")); if (ec) @@ -357,9 +356,10 @@ class BOOST_COROSIO_DECL local_stream_socket : public io_stream @return An awaitable that completes with `io_result<>`. + A closed socket completes with `errc::bad_file_descriptor`. + @par Preconditions - The socket must be open. This socket must outlive the - returned awaitable. + This socket must outlive the returned awaitable. */ [[nodiscard]] auto wait(wait_type w) { diff --git a/include/boost/corosio/native/native_local_datagram_socket.hpp b/include/boost/corosio/native/native_local_datagram_socket.hpp index 19e0da52f..9ff10ba8d 100644 --- a/include/boost/corosio/native/native_local_datagram_socket.hpp +++ b/include/boost/corosio/native/native_local_datagram_socket.hpp @@ -66,8 +66,10 @@ namespace boost::corosio { native_io_context ctx; native_local_datagram_socket s(ctx); - s.open(); - s.bind(local_endpoint("/tmp/recv.sock")); + if (auto ec = s.open()) + co_return; + if (auto ec = s.bind(local_endpoint("/tmp/recv.sock"))) + co_return; char buf[1024]; local_endpoint sender; auto [ec, n] = co_await s.recv_from( diff --git a/include/boost/corosio/native/native_local_stream_acceptor.hpp b/include/boost/corosio/native/native_local_stream_acceptor.hpp index e94dc5f7d..c0181c5c8 100644 --- a/include/boost/corosio/native/native_local_stream_acceptor.hpp +++ b/include/boost/corosio/native/native_local_stream_acceptor.hpp @@ -269,6 +269,8 @@ class native_local_stream_acceptor : public local_stream_acceptor A closed acceptor reports `errc::bad_file_descriptor`. + @throws std::logic_error If the acceptor has been moved from. + This acceptor must outlive the returned awaitable. */ [[nodiscard]] auto accept() diff --git a/include/boost/corosio/native/native_local_stream_socket.hpp b/include/boost/corosio/native/native_local_stream_socket.hpp index 85d86f36f..0b2b11615 100644 --- a/include/boost/corosio/native/native_local_stream_socket.hpp +++ b/include/boost/corosio/native/native_local_stream_socket.hpp @@ -65,8 +65,9 @@ namespace boost::corosio { native_io_context ctx; native_local_stream_socket s(ctx); - s.open(); auto [ec] = co_await s.connect(local_endpoint("/tmp/my.sock")); + if (ec) + co_return; @endcode @see local_stream_socket, epoll_t, iocp_t diff --git a/include/boost/corosio/native/native_random_access_file.hpp b/include/boost/corosio/native/native_random_access_file.hpp index 191dd4e66..43599c811 100644 --- a/include/boost/corosio/native/native_random_access_file.hpp +++ b/include/boost/corosio/native/native_random_access_file.hpp @@ -64,7 +64,8 @@ namespace boost::corosio { native_io_context ctx; native_random_access_file f(ctx); - f.open("data.bin", file_base::read_only); + if (auto ec = f.open("data.bin", file_base::read_only)) + co_return; char buf[4096]; auto [ec, n] = co_await f.read_some_at( 0, capy::mutable_buffer(buf, sizeof(buf))); diff --git a/include/boost/corosio/native/native_signal_set.hpp b/include/boost/corosio/native/native_signal_set.hpp index 789c164d6..23143fb4f 100644 --- a/include/boost/corosio/native/native_signal_set.hpp +++ b/include/boost/corosio/native/native_signal_set.hpp @@ -106,6 +106,9 @@ class native_signal_set : public signal_set @param signals Additional signal numbers to add. @throws std::system_error on failure. + + @see add for the non-throwing form: construct with the + context alone, then `add()` each signal. */ template... Signals> native_signal_set( diff --git a/include/boost/corosio/native/native_socket_option.hpp b/include/boost/corosio/native/native_socket_option.hpp index 020e4f674..258061d88 100644 --- a/include/boost/corosio/native/native_socket_option.hpp +++ b/include/boost/corosio/native/native_socket_option.hpp @@ -68,8 +68,7 @@ namespace boost::corosio::native_socket_option { @code sock.set_option( native_socket_option::no_delay( true ) ); auto nd = sock.get_option(); - if ( nd.value() ) - // Nagle's algorithm is disabled + bool disabled = nd.value(); // true: Nagle's algorithm is off @endcode @tparam Level The protocol level (e.g. `SOL_SOCKET`, `IPPROTO_TCP`). diff --git a/include/boost/corosio/native/native_stream_file.hpp b/include/boost/corosio/native/native_stream_file.hpp index 9713ad36c..5192e4b03 100644 --- a/include/boost/corosio/native/native_stream_file.hpp +++ b/include/boost/corosio/native/native_stream_file.hpp @@ -64,7 +64,8 @@ namespace boost::corosio { native_io_context ctx; native_stream_file f(ctx); - f.open("data.bin", file_base::read_only); + if (auto ec = f.open("data.bin", file_base::read_only)) + co_return; char buf[4096]; auto [ec, n] = co_await f.read_some( capy::mutable_buffer(buf, sizeof(buf))); diff --git a/include/boost/corosio/native/native_tcp_acceptor.hpp b/include/boost/corosio/native/native_tcp_acceptor.hpp index 2b3e48d0f..03ab1aba4 100644 --- a/include/boost/corosio/native/native_tcp_acceptor.hpp +++ b/include/boost/corosio/native/native_tcp_acceptor.hpp @@ -262,6 +262,8 @@ class native_tcp_acceptor : public tcp_acceptor A closed acceptor reports `errc::bad_file_descriptor`. + @throws std::logic_error If the acceptor has been moved from. + This acceptor must outlive the returned awaitable. */ [[nodiscard]] auto accept() diff --git a/include/boost/corosio/native/native_tcp_socket.hpp b/include/boost/corosio/native/native_tcp_socket.hpp index ceaf01d99..f33402b1e 100644 --- a/include/boost/corosio/native/native_tcp_socket.hpp +++ b/include/boost/corosio/native/native_tcp_socket.hpp @@ -64,8 +64,9 @@ namespace boost::corosio { native_io_context ctx; native_tcp_socket s(ctx); - s.open(); auto [ec] = co_await s.connect(ep); + if (ec) + co_return; auto [ec2, n] = co_await s.read_some(buf); @endcode diff --git a/include/boost/corosio/native/native_udp_socket.hpp b/include/boost/corosio/native/native_udp_socket.hpp index f66002fcc..78f792570 100644 --- a/include/boost/corosio/native/native_udp_socket.hpp +++ b/include/boost/corosio/native/native_udp_socket.hpp @@ -65,8 +65,10 @@ namespace boost::corosio { native_io_context ctx; native_udp_socket s(ctx); - s.open(); - s.bind(endpoint(ipv4_address::any(), 9000)); + if (auto ec = s.open()) + co_return; + if (auto ec = s.bind(endpoint(ipv4_address::any(), 9000))) + co_return; char buf[1024]; endpoint sender; auto [ec, n] = co_await s.recv_from( @@ -381,6 +383,8 @@ class native_udp_socket : public udp_socket @param flags Message flags. @return An awaitable yielding `(error_code, std::size_t)`. + + A closed socket reports `errc::bad_file_descriptor`. */ template [[nodiscard]] auto send_to( @@ -412,6 +416,8 @@ class native_udp_socket : public udp_socket @param flags Message flags (e.g. message_flags::peek). @return An awaitable yielding `(error_code, std::size_t)`. + + A closed socket reports `errc::bad_file_descriptor`. */ template [[nodiscard]] auto recv_from( diff --git a/include/boost/corosio/openssl_stream.hpp b/include/boost/corosio/openssl_stream.hpp index 52331bbaf..aac583994 100644 --- a/include/boost/corosio/openssl_stream.hpp +++ b/include/boost/corosio/openssl_stream.hpp @@ -60,12 +60,16 @@ namespace boost::corosio { ctx.set_verify_mode(tls_verify_mode::peer); corosio::tcp_socket sock(ioc); - co_await sock.connect(endpoint); + auto [ec] = co_await sock.connect(endpoint); + if (ec) + co_return; // Reference mode - sock must outlive tls corosio::openssl_stream tls(&sock, ctx); tls.set_hostname("example.com"); - auto [ec] = co_await tls.handshake(tls_role::client); + auto [hec] = co_await tls.handshake(tls_role::client); + if (hec) + co_return; // Or owning mode - tls owns the socket corosio::openssl_stream tls2(std::move(sock), ctx); @@ -128,14 +132,14 @@ class BOOST_COROSIO_DECL openssl_stream final : public tls_stream /** Move construct from another OpenSSL stream. @param other The source stream. After the move, - @p other is in a valid but unspecified state. + @p other may only be destroyed or assigned to. */ openssl_stream(openssl_stream&& other) noexcept; /** Move assign from another OpenSSL stream. @param other The source stream. After the move, - @p other is in a valid but unspecified state. + @p other may only be destroyed or assigned to. @return `*this`. */ diff --git a/include/boost/corosio/random_access_file.hpp b/include/boost/corosio/random_access_file.hpp index b9e542603..59a596f5e 100644 --- a/include/boost/corosio/random_access_file.hpp +++ b/include/boost/corosio/random_access_file.hpp @@ -388,6 +388,9 @@ class BOOST_COROSIO_DECL random_access_file : public io_object responsible for closing the returned handle. @return The native file descriptor or handle. + + @throws std::system_error `errc::bad_file_descriptor` if the + file is not open. */ native_handle_type release(); diff --git a/include/boost/corosio/signal_set.hpp b/include/boost/corosio/signal_set.hpp index 1eee9eaee..97b6584fa 100644 --- a/include/boost/corosio/signal_set.hpp +++ b/include/boost/corosio/signal_set.hpp @@ -76,13 +76,9 @@ namespace boost::corosio { signal_set signals(ctx, SIGINT, SIGTERM); auto [ec, signum] = co_await signals.wait(); if (ec == capy::cond::canceled) - { - // Operation was cancelled via stop_token or cancel() - } - else if (!ec) - { + co_return; + if (!ec) std::cout << "Received signal " << signum << std::endl; - } @endcode */ class BOOST_COROSIO_DECL signal_set : public io_signal_set @@ -212,6 +208,9 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set @param signals Additional signal numbers to add. @throws std::system_error Thrown on failure. + + @see add for the non-throwing form: construct with the + context alone, then `add()` each signal. */ template... Signals> signal_set(capy::execution_context& ctx, int signal, Signals... signals) @@ -247,6 +246,9 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set @param signals Additional signal numbers to add. @throws std::system_error Thrown on failure. + + @see add for the non-throwing form: construct with the + executor alone, then `add()` each signal. */ template... Signals> requires capy::Executor @@ -295,10 +297,17 @@ class BOOST_COROSIO_DECL signal_set : public io_signal_set signal_set) and the flags differ, an error is returned unless one of them has the `dont_care` flag. + The first signal registration on an execution context + installs the process signal-delivery pipe; if that + installation fails the error is returned, and the next + call retries it. + @param signal_number The signal to be added to the set. @param flags The flags to apply when registering the signal. On POSIX systems, these map to sigaction() flags. - On Windows, flags are accepted but ignored. + On Windows, only `none` and `dont_care` are supported; + other flags cause `errc::operation_not_supported` to + be returned. @return Success, or an error if the signal could not be added. Returns `errc::invalid_argument` if the signal is already diff --git a/include/boost/corosio/socket_option.hpp b/include/boost/corosio/socket_option.hpp index 98cb3bc40..26df552a9 100644 --- a/include/boost/corosio/socket_option.hpp +++ b/include/boost/corosio/socket_option.hpp @@ -303,8 +303,7 @@ class BOOST_COROSIO_DECL byte_integer_option @code sock.set_option( socket_option::no_delay( true ) ); auto nd = sock.get_option(); - if ( nd.value() ) - // Nagle's algorithm is disabled + bool disabled = nd.value(); // true: Nagle's algorithm is off @endcode */ class BOOST_COROSIO_DECL no_delay : public boolean_option @@ -393,7 +392,8 @@ class BOOST_COROSIO_DECL reuse_address : public boolean_option @par Example @code udp_socket sock( ioc ); - sock.open(); + if ( auto ec = sock.open() ) + return; sock.set_option( socket_option::broadcast( true ) ); @endcode */ @@ -413,14 +413,17 @@ class BOOST_COROSIO_DECL broadcast : public boolean_option /** Allow multiple sockets to bind to the same port (SO_REUSEPORT). Not available on all platforms. On unsupported platforms, - `set_option` will return an error. + `set_option` throws `std::system_error`. @par Example @code - acc.open( tcp::v6() ); + if ( auto ec = acc.open( tcp::v6() ) ) + return; acc.set_option( socket_option::reuse_port( true ) ); - acc.bind( endpoint( ipv6_address::any(), 8080 ) ); - acc.listen(); + if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) + return; + if ( auto ec = acc.listen() ) + return; @endcode */ class BOOST_COROSIO_DECL reuse_port : public boolean_option diff --git a/include/boost/corosio/stream_file.hpp b/include/boost/corosio/stream_file.hpp index dcc3a4a67..f85ae5d71 100644 --- a/include/boost/corosio/stream_file.hpp +++ b/include/boost/corosio/stream_file.hpp @@ -54,10 +54,15 @@ namespace boost::corosio { co_return; // report the error char buf[4096]; - auto [ec, n] = co_await f.read_some( - capy::mutable_buffer(buf, sizeof(buf))); - if (ec == capy::cond::eof) - // end of file + for (;;) + { + auto [ec, n] = co_await f.read_some( + capy::mutable_buffer(buf, sizeof(buf))); + if (ec == capy::cond::eof) + break; + if (ec) + co_return; + } @endcode */ class BOOST_COROSIO_DECL stream_file : public io_stream @@ -250,6 +255,9 @@ class BOOST_COROSIO_DECL stream_file : public io_stream responsible for closing the returned handle. @return The native file descriptor or handle. + + @throws std::system_error `errc::bad_file_descriptor` if the + file is not open. */ native_handle_type release(); diff --git a/include/boost/corosio/tcp.hpp b/include/boost/corosio/tcp.hpp index c165208c7..cde0bfa9b 100644 --- a/include/boost/corosio/tcp.hpp +++ b/include/boost/corosio/tcp.hpp @@ -32,10 +32,13 @@ class tcp_acceptor; @par Example @code tcp_acceptor acc( ioc ); - acc.open( tcp::v6() ); // IPv6 socket + if ( auto ec = acc.open( tcp::v6() ) ) // IPv6 socket + return; acc.set_option( socket_option::reuse_address( true ) ); - acc.bind( endpoint( ipv6_address::any(), 8080 ) ); - acc.listen(); + if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) + return; + if ( auto ec = acc.listen() ) + return; @endcode @see native_tcp, tcp_socket, tcp_acceptor diff --git a/include/boost/corosio/tcp_acceptor.hpp b/include/boost/corosio/tcp_acceptor.hpp index 9ae2592cf..b17058c4e 100644 --- a/include/boost/corosio/tcp_acceptor.hpp +++ b/include/boost/corosio/tcp_acceptor.hpp @@ -56,7 +56,7 @@ namespace boost::corosio { @par Example @code - // Convenience constructor: open + SO_REUSEADDR + bind + listen + // Convenience constructor: open + configure + bind + listen io_context ioc; tcp_acceptor acc( ioc, endpoint( 8080 ) ); @@ -197,16 +197,27 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object */ explicit tcp_acceptor(capy::execution_context& ctx); - /** Convenience constructor: open + SO_REUSEADDR + bind + listen. + /** Convenience constructor: open + configure + bind + listen. Creates a fully-bound listening acceptor in a single - expression. The address family is deduced from @p ep. + expression, throwing the codes the piecewise `open()` + + `set_option()` + `bind()` + `listen()` path reports. The + address family is deduced from @p ep. + + Before binding, the constructor configures address reuse so + a server can rebind its port immediately after a restart: + `SO_REUSEADDR` on POSIX, `SO_EXCLUSIVEADDRUSE` on Windows + ( where `SO_REUSEADDR` instead grants other sockets + bind-over rights ). A second listener on an occupied + endpoint therefore throws `errc::address_in_use` on every + platform. @param ctx The execution context that will own this acceptor. @param ep The local endpoint to bind to. @param backlog The maximum pending connection queue length. - @throws std::system_error on bind or listen failure. + @throws std::system_error on open, configuration, bind, or + listen failure. */ tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128); @@ -229,7 +240,8 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @param ep The local endpoint to bind to. @param backlog The maximum pending connection queue length. - @throws std::system_error on bind or listen failure. + @throws std::system_error on open, configuration, bind, or + listen failure. */ template requires capy::Executor @@ -392,9 +404,9 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @code tcp_socket peer(ioc); auto [ec] = co_await acc.accept(peer); - if (!ec) { - // Use peer socket - } + if (ec) + co_return; + auto [wec, n] = co_await peer.write_some(buffer); @endcode @see accept() @@ -429,6 +441,8 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object Check `ec == cond::canceled` for portable comparison. A closed acceptor completes with `errc::bad_file_descriptor`. + On failure the returned socket is default-constructed and + may only be destroyed or assigned. @par Preconditions This acceptor must outlive the returned awaitable. @@ -436,9 +450,9 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @par Example @code auto [ec, peer] = co_await acc.accept(); - if (!ec) { - // peer is a connected socket - } + if (ec) + co_return; + auto [wec, n] = co_await peer.write_some(buffer); @endcode @see accept(tcp_socket&) @@ -543,7 +557,8 @@ class BOOST_COROSIO_DECL tcp_acceptor : public io_object @return The native handle. - A closed acceptor reports `errc::bad_file_descriptor`. + @throws std::system_error `errc::bad_file_descriptor` if the + acceptor is not open. @post is_open() == false */ diff --git a/include/boost/corosio/tcp_server.hpp b/include/boost/corosio/tcp_server.hpp index 620c02ace..05dd8d9fe 100644 --- a/include/boost/corosio/tcp_server.hpp +++ b/include/boost/corosio/tcp_server.hpp @@ -70,7 +70,8 @@ namespace boost::corosio { io_context ioc; tcp_server srv(ioc, ioc.get_executor()); srv.set_workers(make_workers(ioc, 100)); - srv.bind(endpoint{address_v4::any(), 8080}); + if (auto ec = srv.bind(endpoint{ipv4_address::any(), 8080})) + return; srv.start(); ioc.run(); // Blocks until all work completes @endcode @@ -592,7 +593,8 @@ class BOOST_COROSIO_DECL tcp_server @code tcp_server srv(ctx, ctx.get_executor()); srv.set_workers(make_workers(ctx, 100)); - srv.bind(endpoint{...}); + if (auto ec = srv.bind(endpoint{...})) + return; srv.start(); @endcode */ diff --git a/include/boost/corosio/tcp_socket.hpp b/include/boost/corosio/tcp_socket.hpp index c2809b8f1..1117dc067 100644 --- a/include/boost/corosio/tcp_socket.hpp +++ b/include/boost/corosio/tcp_socket.hpp @@ -63,7 +63,6 @@ namespace boost::corosio { @code io_context ioc; tcp_socket s(ioc); - s.open(); // Using structured bindings auto [ec] = co_await s.connect( @@ -388,7 +387,8 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @code // Socket opened automatically with correct address family: auto [ec] = co_await s.connect(endpoint); - if (ec) { ... } + if (ec) + co_return; @endcode */ [[nodiscard]] auto connect(endpoint ep) @@ -419,9 +419,10 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream stream; a subsequent `read_some` (for read waits) returns the available data. + A closed socket completes with `errc::bad_file_descriptor`. + @par Preconditions - The socket must be open. This socket must outlive the - returned awaitable. + This socket must outlive the returned awaitable. */ [[nodiscard]] auto wait(wait_type w) { @@ -522,9 +523,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @code auto [ec, n] = co_await sock.read_some(buffer); if (ec == capy::cond::eof) - { - // Peer closed their send direction - } + co_return; // Peer closed their send direction @endcode Failures such as a peer that already disconnected are @@ -574,8 +573,7 @@ class BOOST_COROSIO_DECL tcp_socket : public io_stream @par Example @code auto nd = sock.get_option(); - if ( nd.value() ) - // Nagle's algorithm is disabled + bool disabled = nd.value(); // true: Nagle's algorithm is off @endcode @return The current option value. diff --git a/include/boost/corosio/timeout.hpp b/include/boost/corosio/timeout.hpp index 94f273145..c8274a225 100644 --- a/include/boost/corosio/timeout.hpp +++ b/include/boost/corosio/timeout.hpp @@ -48,9 +48,8 @@ namespace boost::corosio { @par Example @code auto [ec, n] = co_await timeout(sock.read_some(buf), 50ms); - if (ec == capy::cond::timeout) { - // handle timeout - } + if (ec == capy::cond::timeout) + co_return; @endcode @param a The awaitable to race against the deadline. diff --git a/include/boost/corosio/tls_context.hpp b/include/boost/corosio/tls_context.hpp index 81d30ef93..ae64ca204 100644 --- a/include/boost/corosio/tls_context.hpp +++ b/include/boost/corosio/tls_context.hpp @@ -227,13 +227,16 @@ tls_context_data const& get_tls_context_data(tls_context const&) noexcept; @code // Create a client context with system trust anchors corosio::tls_context ctx; - ctx.set_default_verify_paths(); - ctx.set_verify_mode( corosio::tls_verify_mode::peer ); + if (auto ec = ctx.set_default_verify_paths()) + co_return; + if (auto ec = ctx.set_verify_mode( corosio::tls_verify_mode::peer )) + co_return; // Use with a TLS stream corosio::openssl_stream secure( &sock, ctx ); secure.set_hostname( "example.com" ); - co_await secure.handshake( corosio::tls_role::client ); + if (auto [ec] = co_await secure.handshake( corosio::tls_role::client ); ec) + co_return; @endcode @see tls_role @@ -331,8 +334,9 @@ class BOOST_COROSIO_DECL tls_context @param format The encoding format of the certificate data. - @return Success, or an error if the certificate could not be parsed - or is invalid. + @return Success. The certificate is recorded and decoded when the + native context is first built; a malformed certificate surfaces + as a handshake failure. @see use_certificate_file @see use_private_key @@ -350,12 +354,15 @@ class BOOST_COROSIO_DECL tls_context @param format The encoding format of the file. - @return Success, or an error if the file could not be read or the - certificate is invalid. + @return Success, or an error if the file could not be read. The + certificate is decoded when the native context is first built; + a malformed certificate surfaces as a handshake failure. @par Example @code - ctx.use_certificate_file( "server.crt", tls_file_format::pem ); + if (auto ec = ctx.use_certificate_file( + "server.crt", tls_file_format::pem )) + return; @endcode @see use_certificate @@ -373,7 +380,9 @@ class BOOST_COROSIO_DECL tls_context @param chain The certificate chain data in PEM format (concatenated certificates). - @return Success, or an error if the chain could not be parsed. + @return Success. The chain is recorded and decoded when the native + context is first built; a malformed chain surfaces as a + handshake failure. @see use_certificate_chain_file */ @@ -387,12 +396,14 @@ class BOOST_COROSIO_DECL tls_context @param filename Path to the certificate chain file. - @return Success, or an error if the file could not be read or parsed. + @return Success, or an error if the file could not be read. The + chain is decoded when the native context is first built; a + malformed chain surfaces as a handshake failure. @par Example @code - // Load certificate chain (cert + intermediates) - ctx.use_certificate_chain_file( "fullchain.pem" ); + if (auto ec = ctx.use_certificate_chain_file( "fullchain.pem" )) + return; @endcode @see use_certificate_chain @@ -412,9 +423,10 @@ class BOOST_COROSIO_DECL tls_context @param format The encoding format of the key data. - @return Success, or an error if the key could not be parsed, - is encrypted without a password callback, or doesn't match - the certificate. + @return Success. The key is recorded and decoded when the native + context is first built; a malformed key, a missing password + callback for an encrypted key, or a certificate mismatch + surfaces as a handshake failure. @see use_private_key_file @see set_password_callback @@ -435,12 +447,16 @@ class BOOST_COROSIO_DECL tls_context @param format The encoding format of the file. - @return Success, or an error if the file could not be read, - the key is invalid, or it doesn't match the certificate. + @return Success, or an error if the file could not be read. The + key is decoded when the native context is first built; a + malformed key or a certificate mismatch surfaces as a + handshake failure. @par Example @code - ctx.use_private_key_file( "server.key", tls_file_format::pem ); + if (auto ec = ctx.use_private_key_file( + "server.key", tls_file_format::pem )) + return; @endcode @see use_private_key @@ -493,7 +509,8 @@ class BOOST_COROSIO_DECL tls_context @par Example @code - ctx.use_pkcs12_file( "credentials.pfx", "secret" ); + if (auto ec = ctx.use_pkcs12_file( "credentials.pfx", "secret" )) + return; @endcode @see use_pkcs12 @@ -513,7 +530,9 @@ class BOOST_COROSIO_DECL tls_context @param ca The CA certificate data in PEM format. - @return Success, or an error if the certificate could not be parsed. + @return Success. The certificate is recorded and decoded when the + native context is first built; a malformed certificate + surfaces as a handshake failure. @see load_verify_file @see set_default_verify_paths @@ -527,12 +546,15 @@ class BOOST_COROSIO_DECL tls_context @param filename Path to a PEM file containing CA certificates. - @return Success, or an error if the file could not be read or parsed. + @return Success, or an error if the file could not be read. The + certificates are decoded when the native context is first + built; malformed certificates surface as a handshake failure. @par Example @code - // Load a custom CA bundle - ctx.load_verify_file( "/etc/ssl/certs/ca-certificates.crt" ); + if (auto ec = ctx.load_verify_file( + "/etc/ssl/certs/ca-certificates.crt" )) + return; @endcode @see add_certificate_authority @@ -560,7 +582,8 @@ class BOOST_COROSIO_DECL tls_context @par Example @code - ctx.add_verify_path( "/etc/ssl/certs" ); + if (auto ec = ctx.add_verify_path( "/etc/ssl/certs" )) + return; @endcode @see load_verify_file @@ -594,8 +617,10 @@ class BOOST_COROSIO_DECL tls_context @par Example @code // Trust the same CAs as the system - ctx.set_default_verify_paths(); - ctx.set_verify_mode( tls_verify_mode::peer ); + if (auto ec = ctx.set_default_verify_paths()) + return; + if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) + return; @endcode @see load_verify_file @@ -615,13 +640,14 @@ class BOOST_COROSIO_DECL tls_context @param v The minimum protocol version to accept. - @return Success, or an error if the version is not supported - by the backend. + @return Success. The version is recorded and applied when the + native context is first built. @par Example @code // Require TLS 1.3 minimum - ctx.set_min_protocol_version( tls_version::tls_1_3 ); + if (auto ec = ctx.set_min_protocol_version( tls_version::tls_1_3 )) + return; @endcode @see set_max_protocol_version @@ -635,8 +661,8 @@ class BOOST_COROSIO_DECL tls_context @param v The maximum protocol version to accept. - @return Success, or an error if the version is not supported - by the backend. + @return Success. The version is recorded and applied when the + native context is first built. @note On WolfSSL the ceiling is applied by selecting a version-specific method (no native set-max API exists); an @@ -655,12 +681,15 @@ class BOOST_COROSIO_DECL tls_context @param ciphers The cipher suite specification string. - @return Success, or an error if the cipher string is invalid. + @return Success. The string is recorded and applied when the + native context is first built; an invalid cipher string + surfaces as a handshake failure. @par Example @code // TLS 1.2 cipher suites (OpenSSL format) - ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" ); + if (auto ec = ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" )) + return; @endcode @note This configures cipher suites for TLS 1.2 and below. For @@ -676,12 +705,15 @@ class BOOST_COROSIO_DECL tls_context @param ciphers The TLS 1.3 cipher suite list. - @return Success, or an error if the cipher string is invalid. + @return Success. The string is recorded and applied when the + native context is first built; an invalid cipher string + surfaces as a handshake failure. @par Example @code - ctx.set_ciphersuites_tls13( - "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" ); + if (auto ec = ctx.set_ciphersuites_tls13( + "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" )) + return; @endcode @note On the WolfSSL backend, TLS 1.2 and TLS 1.3 suites share a @@ -714,7 +746,8 @@ class BOOST_COROSIO_DECL tls_context @par Example @code // Prefer HTTP/2, fall back to HTTP/1.1 - ctx.set_alpn( { "h2", "http/1.1" } ); + if (auto ec = ctx.set_alpn( { "h2", "http/1.1" } )) + return; @endcode */ [[nodiscard]] std::error_code set_alpn(std::initializer_list protocols); @@ -730,15 +763,15 @@ class BOOST_COROSIO_DECL tls_context @param mode The verification mode to use. - @return Success, or an error if the mode could not be set. + @return Success. The mode is recorded and applied when the native + context is first built. @par Example @code - // Verify peer certificate (typical for clients) - ctx.set_verify_mode( tls_verify_mode::peer ); - - // Require client certificate (server-side mTLS) - ctx.set_verify_mode( tls_verify_mode::require_peer ); + // Verify peer certificate (typical for clients; servers doing + // mTLS use tls_verify_mode::require_peer instead) + if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) + return; @endcode @see tls_verify_mode @@ -753,7 +786,8 @@ class BOOST_COROSIO_DECL tls_context @param depth Maximum number of intermediate certificates allowed. - @return Success, or an error if the depth is invalid. + @return Success. The depth is recorded and applied when the native + context is first built. */ [[nodiscard]] std::error_code set_verify_depth(int depth); @@ -799,7 +833,8 @@ class BOOST_COROSIO_DECL tls_context @par Example @code - ctx.set_verify_mode( tls_verify_mode::peer ); + if (auto ec = ctx.set_verify_mode( tls_verify_mode::peer )) + return; ctx.set_verify_callback( []( bool preverified, verify_context& ctx ) -> bool { @@ -875,7 +910,9 @@ class BOOST_COROSIO_DECL tls_context @param crl The CRL data in DER or PEM format. - @return Success, or an error if the CRL could not be parsed. + @return Success. The CRL is recorded and decoded when the native + context is first built; a malformed CRL surfaces as a + handshake failure. @note CRLs are consulted only when a revocation policy is set via @ref set_revocation_policy. On WolfSSL, CRL checking requires a @@ -895,8 +932,9 @@ class BOOST_COROSIO_DECL tls_context @param filename Path to a CRL file (DER or PEM format). - @return Success, or an error if the file could not be read - or the CRL is invalid. + @return Success, or an error if the file could not be read. The + CRL is decoded when the native context is first built; a + malformed CRL surfaces as a handshake failure. @note CRLs are consulted only when a revocation policy is set via @ref set_revocation_policy (WolfSSL requires a `HAVE_CRL` @@ -904,7 +942,8 @@ class BOOST_COROSIO_DECL tls_context @par Example @code - ctx.add_crl_file( "issuer.crl" ); + if (auto ec = ctx.add_crl_file( "issuer.crl" )) + return; @endcode @see add_crl @@ -969,7 +1008,9 @@ class BOOST_COROSIO_DECL tls_context }); // Now load encrypted key - ctx.use_private_key_file( "encrypted.key", tls_file_format::pem ); + if (auto ec = ctx.use_private_key_file( + "encrypted.key", tls_file_format::pem )) + return; @endcode @see tls_password_purpose diff --git a/include/boost/corosio/udp.hpp b/include/boost/corosio/udp.hpp index 44ae75f84..7157fcfc5 100644 --- a/include/boost/corosio/udp.hpp +++ b/include/boost/corosio/udp.hpp @@ -31,8 +31,10 @@ class udp_socket; @par Example @code udp_socket sock( ioc ); - sock.open( udp::v4() ); - sock.bind( endpoint( ipv4_address::any(), 9000 ) ); + if ( auto ec = sock.open( udp::v4() ) ) + return; + if ( auto ec = sock.bind( endpoint( ipv4_address::any(), 9000 ) ) ) + return; @endcode @see native_udp, udp_socket diff --git a/include/boost/corosio/udp_socket.hpp b/include/boost/corosio/udp_socket.hpp index b56ccdcad..63324e475 100644 --- a/include/boost/corosio/udp_socket.hpp +++ b/include/boost/corosio/udp_socket.hpp @@ -67,24 +67,32 @@ namespace boost::corosio { // Connectionless mode io_context ioc; udp_socket sock( ioc ); - sock.open( udp::v4() ); - sock.bind( endpoint( ipv4_address::any(), 9000 ) ); + if ( auto ec = sock.open( udp::v4() ) ) + co_return; + if ( auto ec = sock.bind( endpoint( ipv4_address::any(), 9000 ) ) ) + co_return; char buf[1024]; endpoint sender; auto [ec, n] = co_await sock.recv_from( capy::mutable_buffer( buf, sizeof( buf ) ), sender ); - if ( !ec ) - co_await sock.send_to( - capy::const_buffer( buf, n ), sender ); + if ( ec ) + co_return; + auto [sec, sn] = co_await sock.send_to( + capy::const_buffer( buf, n ), sender ); + if ( sec ) + co_return; // Connected mode udp_socket csock( ioc ); auto [cec] = co_await csock.connect( endpoint( ipv4_address::loopback(), 9000 ) ); - if ( !cec ) - co_await csock.send( - capy::const_buffer( buf, n ) ); + if ( cec ) + co_return; + auto [wec, wn] = co_await csock.send( + capy::const_buffer( buf, n ) ); + if ( wec ) + co_return; @endcode */ class BOOST_COROSIO_DECL udp_socket : public io_object @@ -730,9 +738,10 @@ class BOOST_COROSIO_DECL udp_socket : public io_object @return An awaitable that completes with `io_result<>`. + A closed socket completes with `errc::bad_file_descriptor`. + @par Preconditions - The socket must be open. This socket must outlive the - returned awaitable. + This socket must outlive the returned awaitable. */ [[nodiscard]] auto wait(wait_type w) { diff --git a/include/boost/corosio/wolfssl_stream.hpp b/include/boost/corosio/wolfssl_stream.hpp index b198e47ec..fbbfe8fce 100644 --- a/include/boost/corosio/wolfssl_stream.hpp +++ b/include/boost/corosio/wolfssl_stream.hpp @@ -60,12 +60,16 @@ namespace boost::corosio { ctx.set_verify_mode(tls_verify_mode::peer); corosio::tcp_socket sock(ioc); - co_await sock.connect(endpoint); + auto [ec] = co_await sock.connect(endpoint); + if (ec) + co_return; // Reference mode - sock must outlive tls corosio::wolfssl_stream tls(&sock, ctx); tls.set_hostname("example.com"); - auto [ec] = co_await tls.handshake(tls_role::client); + auto [hec] = co_await tls.handshake(tls_role::client); + if (hec) + co_return; // Or owning mode - tls owns the socket corosio::wolfssl_stream tls2(std::move(sock), ctx); @@ -128,14 +132,14 @@ class BOOST_COROSIO_DECL wolfssl_stream final : public tls_stream /** Move construct from another WolfSSL stream. @param other The source stream. After the move, - @p other is in a valid but unspecified state. + @p other may only be destroyed or assigned to. */ wolfssl_stream(wolfssl_stream&& other) noexcept; /** Move assign from another WolfSSL stream. @param other The source stream. After the move, - @p other is in a valid but unspecified state. + @p other may only be destroyed or assigned to. @return `*this`. */ diff --git a/test/doc/snippets/4b_concurrent_programming.cpp b/test/doc/snippets/4b_concurrent_programming.cpp index 52966ff3a..a208534c0 100644 --- a/test/doc/snippets/4b_concurrent_programming.cpp +++ b/test/doc/snippets/4b_concurrent_programming.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -129,7 +130,7 @@ capy::task session(corosio::tcp_socket sock) auto [ec, n] = co_await sock.read_some(buf); // No other code in this coroutine runs until above completes - co_await sock.write_some(response); + std::tie(ec, n) = co_await sock.write_some(response); // Still sequential } // end::strand_session[] @@ -218,7 +219,7 @@ capy::task bad() // RIGHT: suspend with an async delay capy::task good() { - co_await corosio::delay(1s); + std::ignore = co_await corosio::delay(1s); } // end::blocking[] @@ -268,7 +269,7 @@ cross_executor( // Dangerous: socket created on ctx1, used from ex2 corosio::tcp_socket sock(ctx1); capy::run_async(ex2)([&sock, ep]() -> capy::task { - co_await sock.connect(ep); // Wrong executor! + std::ignore = co_await sock.connect(ep); // Wrong executor! }()); // end::cross_executor[] } diff --git a/test/doc/snippets/4d_sockets.cpp b/test/doc/snippets/4d_sockets.cpp index be67188f7..e40405f02 100644 --- a/test/doc/snippets/4d_sockets.cpp +++ b/test/doc/snippets/4d_sockets.cpp @@ -68,6 +68,7 @@ namespace capy = boost::capy; #include #include #include +#include #include #include @@ -306,7 +307,7 @@ capy::const_buffer some_buffer("hi", 2); // tag::io_stream_poly[] capy::task send_data(corosio::io_stream& stream) { - co_await capy::write(stream, some_buffer); + std::ignore = co_await capy::write(stream, some_buffer); } // end::io_stream_poly[] @@ -337,14 +338,14 @@ buffer_sequences_fragment(corosio::tcp_socket& s) // tag::buffer_sequences[] // Single buffer capy::mutable_buffer buf(data, size); - co_await s.read_some(buf); + auto [ec, n] = co_await s.read_some(buf); // Multiple buffers (scatter/gather I/O) std::array bufs = { capy::mutable_buffer(header, header_size), capy::mutable_buffer(body, body_size) }; - co_await s.read_some(bufs); + std::tie(ec, n) = co_await s.read_some(bufs); // end::buffer_sequences[] } diff --git a/test/doc/snippets/4e_tcp_acceptor.cpp b/test/doc/snippets/4e_tcp_acceptor.cpp index e6325095e..ba31730b6 100644 --- a/test/doc/snippets/4e_tcp_acceptor.cpp +++ b/test/doc/snippets/4e_tcp_acceptor.cpp @@ -47,6 +47,7 @@ namespace capy = boost::capy; #include #include +#include #include #include @@ -190,10 +191,10 @@ stop_token_accept( // tag::accept_stop_token[] // Inside a cancellable task: auto [ec] = co_await acc.accept(peer); - if (ec == std::errc::operation_canceled) + if (ec == capy::cond::canceled) std::cout << "Accept cancelled\n"; // end::accept_stop_token[] - canceled = ec == std::errc::operation_canceled; + canceled = ec == capy::cond::canceled; } void @@ -257,7 +258,7 @@ capy::task accept_loop( if (ec) { - if (ec == std::errc::operation_canceled) + if (ec == capy::cond::canceled) break; // Shutdown requested std::cerr << "Accept error: " << ec.message() << "\n"; @@ -383,7 +384,7 @@ struct tcp_acceptor_test co_return; }(acc)); ioc.run(); - BOOST_TEST(accept_ec == std::errc::operation_canceled); + BOOST_TEST(accept_ec == capy::cond::canceled); } void diff --git a/test/doc/snippets/4g_composed_operations.cpp b/test/doc/snippets/4g_composed_operations.cpp index 073663fd3..e3d9b2e24 100644 --- a/test/doc/snippets/4g_composed_operations.cpp +++ b/test/doc/snippets/4g_composed_operations.cpp @@ -60,6 +60,7 @@ namespace capy = boost::capy; #include #include #include +#include #include "test_suite.hpp" @@ -222,11 +223,11 @@ capy::task<> cancellation_frag( std::array bufs = { capy::mutable_buffer(header, 16), capy::mutable_buffer(body, 1024)}; - co_await capy::read(stream, bufs); + std::ignore = co_await capy::read(stream, bufs); // Less efficient: may require more system calls - co_await capy::read(stream, buf1); - co_await capy::read(stream, buf2); + std::ignore = co_await capy::read(stream, buf1); + std::ignore = co_await capy::read(stream, buf2); // end::multiple_buffers[] } diff --git a/test/doc/snippets/4i_signals.cpp b/test/doc/snippets/4i_signals.cpp index e52ac4b6f..60d2c6abd 100644 --- a/test/doc/snippets/4i_signals.cpp +++ b/test/doc/snippets/4i_signals.cpp @@ -54,6 +54,7 @@ namespace capy = boost::capy; #include #include #include +#include #include #include "test_suite.hpp" @@ -191,7 +192,8 @@ capy::task child_reaper(corosio::io_context& ioc) // Only notify on child termination, not stop/continue // Prevent zombie processes automatically - signals.add(SIGCHLD, flags::no_child_stop | flags::no_child_wait); + if (signals.add(SIGCHLD, flags::no_child_stop | flags::no_child_wait)) + co_return; for (;;) { @@ -257,9 +259,11 @@ struct signals_test corosio::io_context ioc; // tag::construct_empty[] corosio::signal_set signals(ioc); - signals.add(SIGINT); - signals.add(SIGTERM); + std::error_code ec = signals.add(SIGINT); + if (! ec) + ec = signals.add(SIGTERM); // end::construct_empty[] + BOOST_TEST(!ec); } #if BOOST_COROSIO_POSIX @@ -285,7 +289,8 @@ struct signals_test corosio::io_context ioc; corosio::signal_set signals(ioc); // tag::add_signal[] - signals.add(SIGUSR1); + if (auto ec = signals.add(SIGUSR1)) + std::cout << "add failed: " << ec.message() << "\n"; // end::add_signal[] } @@ -298,11 +303,14 @@ struct signals_test using flags = corosio::signal_set; // Restart interrupted system calls automatically - signals.add(SIGCHLD, flags::restart); + std::error_code ec = signals.add(SIGHUP, flags::restart); // Multiple flags can be combined - signals.add(SIGCHLD, flags::restart | flags::no_child_stop); + if (! ec) + ec = signals.add( + SIGCHLD, flags::restart | flags::no_child_stop); // end::add_flags[] + BOOST_TEST(!ec); } void @@ -314,13 +322,15 @@ struct signals_test corosio::signal_set s1(ioc); corosio::signal_set s2(ioc); - s1.add(SIGINT, flags::restart); // OK - first registration - s2.add(SIGINT, flags::restart); // OK - same flags - s2.add(SIGINT, flags::no_defer); // Error! - different flags + std::error_code ec; + ec = s1.add(SIGINT, flags::restart); // OK - first registration + ec = s2.add(SIGINT, flags::restart); // OK - same flags + ec = s2.add(SIGINT, flags::no_defer); // invalid_argument - different flags // Use dont_care to accept existing flags - s2.add(SIGINT, flags::dont_care); // OK - accepts existing flags + ec = s2.add(SIGINT, flags::dont_care); // OK - accepts existing flags // end::flag_compat[] + BOOST_TEST(!ec); BOOST_TEST( s2.add(SIGINT, flags::no_defer) == std::errc::invalid_argument); } @@ -332,10 +342,10 @@ struct signals_test corosio::io_context ioc; corosio::signal_set signals(ioc, SIGINT); // tag::remove_signal[] - signals.remove(SIGINT); - - // remove() returns an error_code instead of throwing std::error_code ec = signals.remove(SIGINT); + + // Removing a signal that's not in the set is not an error + ec = signals.remove(SIGINT); // end::remove_signal[] BOOST_TEST(!ec); } @@ -346,9 +356,6 @@ struct signals_test corosio::io_context ioc; corosio::signal_set signals(ioc, SIGINT, SIGTERM); // tag::clear_signals[] - signals.clear(); - - // clear() returns an error_code instead of throwing std::error_code ec = signals.clear(); // end::clear_signals[] BOOST_TEST(!ec); diff --git a/test/doc/snippets/4l_tls.cpp b/test/doc/snippets/4l_tls.cpp index e4561b585..203a7a50c 100644 --- a/test/doc/snippets/4l_tls.cpp +++ b/test/doc/snippets/4l_tls.cpp @@ -62,6 +62,7 @@ using namespace boost::corosio; #include #include #include +#include #include #include "test_suite.hpp" @@ -258,7 +259,7 @@ capy::task send_request(corosio::io_stream& stream) throw std::system_error(ec); char response[4096]; - co_await capy::read( + auto [ec, n] = co_await capy::read( stream, capy::mutable_buffer(response, sizeof(response))); } @@ -270,7 +271,7 @@ capy::task send_request(corosio::tls_stream& stream) throw std::system_error(ec); char response[4096]; - co_await capy::read( + auto [ec, n] = co_await capy::read( stream, capy::mutable_buffer(response, sizeof(response))); } // end::stream_overloads[] @@ -363,7 +364,7 @@ capy::task https_get( std::cout << response << "\n"; // Graceful shutdown - co_await secure.shutdown(); + std::ignore = co_await secure.shutdown(); } // end::https_get[] @@ -414,7 +415,7 @@ capy::task handle_tls_client( capy::mutable_buffer(buf, sizeof(buf))); // Graceful shutdown - co_await secure.shutdown(); + std::ignore = co_await secure.shutdown(); } // end::tls_server[] #endif diff --git a/test/doc/snippets/4n_buffers.cpp b/test/doc/snippets/4n_buffers.cpp index 37c2eb7a9..1d01d89ec 100644 --- a/test/doc/snippets/4n_buffers.cpp +++ b/test/doc/snippets/4n_buffers.cpp @@ -67,6 +67,7 @@ namespace capy = boost::capy; #include #include #include +#include #include #include "test_suite.hpp" @@ -95,7 +96,7 @@ single_buffer_read( { // tag::single_buffer[] capy::mutable_buffer buf(data, size); - co_await sock.read_some(buf); // Works directly + auto [ec, n] = co_await sock.read_some(buf); // Works directly // end::single_buffer[] } @@ -111,7 +112,7 @@ multi_buffer_read( capy::mutable_buffer(header, header_size), capy::mutable_buffer(body, body_size) }; - co_await sock.read_some(bufs); + auto [ec, n] = co_await sock.read_some(bufs); // end::multi_buffer[] } @@ -127,7 +128,7 @@ multi_buffer_write( std::vector send_bufs; send_bufs.push_back(capy::const_buffer(header.data(), header.size())); send_bufs.push_back(capy::const_buffer(body.data(), body.size())); - co_await sock.write_some(send_bufs); + auto [ec, n] = co_await sock.write_some(send_bufs); // end::multi_buffer[] } @@ -138,11 +139,14 @@ slice_writes( { // tag::buffer_slice[] // Send only the first 16 bytes of the sequence - co_await capy::write(sock, capy::buffer_slice(bufs, 0, 16)); + auto [ec, n] = co_await capy::write( + sock, capy::buffer_slice(bufs, 0, 16)); + if (ec) + co_return; // Everything after the first 16 bytes, as a value auto rest = capy::buffer_slice(bufs, 16); - co_await capy::write(sock, rest); + std::tie(ec, n) = co_await capy::write(sock, rest); // end::buffer_slice[] sock.shutdown(corosio::shutdown_send); } @@ -185,14 +189,14 @@ capy::task bad_example(corosio::tcp_socket& sock) buf = capy::const_buffer(temp.data(), temp.size()); } // temp destroyed here! - co_await sock.write_some(buf); // Undefined behavior + std::ignore = co_await sock.write_some(buf); // Undefined behavior } // CORRECT: keep storage alive capy::task good_example(corosio::tcp_socket& sock) { std::string msg = "Hello"; - co_await sock.write_some( + std::ignore = co_await sock.write_some( capy::const_buffer(msg.data(), msg.size())); } // end::lifetime[] diff --git a/test/doc/snippets/4o_file_io.cpp b/test/doc/snippets/4o_file_io.cpp index 1df9d4b0c..9680b9124 100644 --- a/test/doc/snippets/4o_file_io.cpp +++ b/test/doc/snippets/4o_file_io.cpp @@ -82,9 +82,12 @@ stream_read( capy::mutable_buffer(buf, sizeof(buf))); if (ec == capy::cond::eof) + { // reached end of file - // end::stream_read[] eof_seen = true; + co_return; + } + // end::stream_read[] bytes_read = n; } diff --git a/test/doc/snippets/4p_unix_sockets.cpp b/test/doc/snippets/4p_unix_sockets.cpp index ed82ce0d4..266c22360 100644 --- a/test/doc/snippets/4p_unix_sockets.cpp +++ b/test/doc/snippets/4p_unix_sockets.cpp @@ -141,7 +141,9 @@ stream_pair( throw std::system_error(ec, "connect_pair"); // Data written to s1 can be read from s2, and vice versa. - co_await s1.write_some(capy::const_buffer("ping", 4)); + if (auto [ec, n] = co_await s1.write_some( + capy::const_buffer("ping", 4)); ec) + co_return; char buf[16]; auto [ec, n] = co_await s2.read_some( @@ -167,9 +169,10 @@ datagram_connectionless( co_return; // Send to a specific peer - co_await s.send_to( - capy::const_buffer("hello", 5), - corosio::local_endpoint("/tmp/peer.sock")); + if (auto [ec, n] = co_await s.send_to( + capy::const_buffer("hello", 5), + corosio::local_endpoint("/tmp/peer.sock")); ec) + co_return; // Receive from any sender corosio::local_endpoint sender; @@ -190,7 +193,9 @@ datagram_pair( if (auto ec = corosio::connect_pair(s1, s2)) throw std::system_error(ec, "connect_pair"); - co_await s1.send(capy::const_buffer("msg", 3)); + if (auto [ec, n] = co_await s1.send( + capy::const_buffer("msg", 3)); ec) + co_return; auto [ec, n] = co_await s2.recv( capy::mutable_buffer(buf, sizeof(buf))); diff --git a/test/doc/snippets/4q_udp.cpp b/test/doc/snippets/4q_udp.cpp index f03843e3f..e4e056f76 100644 --- a/test/doc/snippets/4q_udp.cpp +++ b/test/doc/snippets/4q_udp.cpp @@ -139,8 +139,9 @@ capy::task<> echo(corosio::io_context& ioc) capy::mutable_buffer(buf, sizeof(buf)), sender); if (rec) co_return; - co_await sock.send_to( - capy::const_buffer(buf, n), sender); + if (auto [sec, sn] = co_await sock.send_to( + capy::const_buffer(buf, n), sender); sec) + co_return; } } // end::echo[] @@ -156,7 +157,9 @@ connected_mode(corosio::io_context& ioc) corosio::endpoint(corosio::ipv4_address::loopback(), 9000)); if (cec) co_return; - co_await sock.send(capy::const_buffer("ping", 4)); + if (auto [sec, sn] = co_await sock.send( + capy::const_buffer("ping", 4)); sec) + co_return; char buf[64]; auto [rec, n] = co_await sock.recv( diff --git a/test/doc/snippets/5b_socket_pair.cpp b/test/doc/snippets/5b_socket_pair.cpp index 60d9f47ba..c34a6ac9b 100644 --- a/test/doc/snippets/5b_socket_pair.cpp +++ b/test/doc/snippets/5b_socket_pair.cpp @@ -71,7 +71,9 @@ struct socket_pair_page_test auto task = [](corosio::tcp_socket& a, corosio::tcp_socket& b) -> capy::task<> { - co_await a.write_some(capy::const_buffer("ping", 4)); + if (auto [wec, wn] = co_await a.write_some( + capy::const_buffer("ping", 4)); wec) + co_return; char buf[8] = {}; auto [ec, n] = co_await b.read_some(capy::make_buffer(buf)); diff --git a/test/doc/snippets/5c_patterns.cpp b/test/doc/snippets/5c_patterns.cpp index 232a25719..724bdbdd6 100644 --- a/test/doc/snippets/5c_patterns.cpp +++ b/test/doc/snippets/5c_patterns.cpp @@ -44,6 +44,7 @@ namespace capy = boost::capy; #include #include +#include #include "test_suite.hpp" @@ -54,7 +55,8 @@ capy::task<> my_http_get(corosio::test::mocket& m, std::string_view target) { std::string req = "GET " + std::string(target) + " HTTP/1.1\r\n\r\n"; - co_await m.write_some(capy::const_buffer(req.data(), req.size())); + std::ignore = co_await m.write_some( + capy::const_buffer(req.data(), req.size())); } capy::task @@ -189,7 +191,9 @@ struct patterns_page_test // A reverse round trip with assertions proves data really flows. auto verify = [](corosio::tcp_socket& a, corosio::tcp_socket& b) -> capy::task<> { - co_await b.write_some(capy::const_buffer("reply", 5)); + auto [wec, wn] = co_await b.write_some( + capy::const_buffer("reply", 5)); + BOOST_TEST(!wec); char buf[16] = {}; auto [ec, n] = co_await a.read_some(capy::make_buffer(buf));