socket::shutdown with tests and documentation#40
Conversation
📝 WalkthroughWalkthroughAdds socket shutdown support: a public Changes
Sequence DiagramsequenceDiagram
rect rgba(240,248,255,0.5)
participant Client
end
rect rgba(224,255,255,0.5)
participant Socket as socket::shutdown()
participant Impl as socket_impl
participant Backend as epoll_socket_impl / win_socket_impl
end
rect rgba(255,250,240,0.5)
participant OS as Kernel / WinSock
end
Client->>Socket: shutdown(shutdown_type)
activate Socket
Socket->>Impl: get().shutdown(type)
activate Impl
Impl->>Backend: shutdown(type) override
activate Backend
Backend->>Backend: map type -> SHUT_/SD_ constant
Backend->>OS: call ::shutdown(fd, how)
activate OS
OS-->>Backend: success or errno/WSA error
deactivate OS
Backend-->>Impl: return system::error_code
deactivate Backend
Impl-->>Socket: return system::error_code
deactivate Impl
Socket-->>Client: returns (void) — error communicated via error_code
deactivate Socket
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
|
An automated preview of the documentation is available at https://40.corosio.prtest3.cppalliance.org/index.html If more commits are pushed to the pull request, the docs will rebuild at the same URL. 2026-01-21 00:37:54 UTC |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Fix all issues with AI agents
In `@include/boost/corosio/socket.hpp`:
- Around line 305-313: The docs in the socket comment refer to capy::error::eof
but the example and API use the portable condition name; update the wording to
refer to capy::cond::eof and adjust the example accordingly (the read_some()
call belongs to the portable condition API matching io_stream::read_some).
Locate the comment around the read_some example in socket.hpp and replace any
occurrences of capy::error::eof with capy::cond::eof so the text and code
example are consistent (keep the example checking if (ec == capy::cond::eof)).
In `@src/corosio/src/detail/epoll/sockets.hpp`:
- Around line 140-151: The switch in shutdown(socket::shutdown_type what)
noexcept override leaves local variable how uninitialized for invalid enum
values; update the switch in this function (and mirror the same change in the
IOCP variant) to add a default case that returns an error (e.g., return
make_err(EINVAL)) instead of falling through, ensuring ::shutdown(fd_, how) is
only called with a valid how value.
In `@src/corosio/src/detail/iocp/sockets.hpp`:
- Around line 228-239: The switch in the shutdown(socket::shutdown_type) method
can leave 'how' uninitialized for out-of-range shutdown_type values; add a
default case in the switch inside the shutdown method(s) (the IOCP
implementation in src/corosio/src/detail/iocp/sockets.hpp and the epoll
implementation counterpart) that handles invalid values by returning an error
instead of falling through — e.g. return make_err(WSAEINVAL) on Windows (IOCP)
or return make_err(EINVAL) / appropriate errno conversion on POSIX (epoll);
reference the shutdown method, shutdown_type, internal_->native_handle(), and
the existing WSAGetLastError/SD_* usage so the guard is implemented in the same
function and platform-specific file.
In `@test/unit/socket.cpp`:
- Around line 703-706: The EOF assertion in the test after shutdown_send is too
strict: locate the assertion using b.read_some that binds auto [ec2, n2] and
replace the strict check (BOOST_TEST(ec2 == capy::cond::eof)) with a condition
that accepts either capy::cond::eof or zero bytes (e.g., BOOST_TEST((ec2 ==
capy::cond::eof) || (n2 == 0))) so it matches the robust pattern used in
testReadAfterPeerClose.
| // Next read should get EOF | ||
| auto [ec2, n2] = co_await b.read_some( | ||
| capy::mutable_buffer(buf, sizeof(buf))); | ||
| BOOST_TEST(ec2 == capy::cond::eof); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Get context around the failing assertion
sed -n '690,720p' test/unit/socket.cppRepository: cppalliance/corosio
Length of output: 1007
🏁 Script executed:
#!/bin/bash
# Find testReadAfterPeerClose test
rg -n "testReadAfterPeerClose" test/unit/socket.cpp -A 20 -B 2Repository: cppalliance/corosio
Length of output: 1518
🏁 Script executed:
#!/bin/bash
# Search for EOF/eof handling patterns in read operations
rg -n "cond::eof|error::eof" --type=cpp | head -30Repository: cppalliance/corosio
Length of output: 2573
🏁 Script executed:
#!/bin/bash
# Check how EOF is handled in different backends
rg -n "ec.*=.*eof|n.*==.*0" --type=cpp src/ | head -20Repository: cppalliance/corosio
Length of output: 2196
🏁 Script executed:
#!/bin/bash
# Get the assertion in testReadAfterPeerClose
sed -n '420,430p' test/unit/socket.cppRepository: cppalliance/corosio
Length of output: 432
🏁 Script executed:
#!/bin/bash
# Find definitions of cond::eof and error::eof
rg -n "enum.*eof|error::eof|cond::eof" --type=cpp -B 2 -A 2 src/ include/ | head -40Repository: cppalliance/corosio
Length of output: 2677
🏁 Script executed:
#!/bin/bash
# Check how cond::eof and error::eof relate to each other
rg -n "cond::eof|error::eof" --type=hpp include/ -B 1 -A 1 | head -50Repository: cppalliance/corosio
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Find cond::eof and error::eof definitions
rg -n "cond|error" include/boost/corosio/ --type=cpp -B 1 -A 1 | grep -E "eof|namespace|enum|struct|class" | head -60Repository: cppalliance/corosio
Length of output: 1212
🏁 Script executed:
#!/bin/bash
# Look for condition codes or error codes definitions
fd -e h -e hpp include/ | xargs rg "cond::|error::" -lRepository: cppalliance/corosio
Length of output: 1707
🏁 Script executed:
#!/bin/bash
# Search for any file mentioning cond::eof or difference between condition and error
rg -n "cond::eof" . --type=cpp -B 3 -A 3Repository: cppalliance/corosio
Length of output: 1916
🏁 Script executed:
#!/bin/bash
# Check socket.hpp documentation for shutdown behavior
sed -n '300,320p' include/boost/corosio/socket.hppRepository: cppalliance/corosio
Length of output: 766
🏁 Script executed:
#!/bin/bash
# Find where cond::eof is defined
rg -n "namespace.*cond|class.*eof|cond\s*=" include/ src/ --type=cppRepository: cppalliance/corosio
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Check error codes and condition codes definitions
rg -n "error_code|condition_code" include/boost/corosio/ -A 3 | head -50Repository: cppalliance/corosio
Length of output: 3247
🏁 Script executed:
#!/bin/bash
# Look for the definitions of cond and error namespaces/categories
find include/boost/corosio -name "*.hpp" -o -name "*.h" | xargs grep -l "cond::" | head -5Repository: cppalliance/corosio
Length of output: 233
🏁 Script executed:
#!/bin/bash
# Get the full context of io_stream.hpp to understand cond::eof better
cat include/boost/corosio/io_stream.hpp | head -100Repository: cppalliance/corosio
Length of output: 3730
🏁 Script executed:
#!/bin/bash
# Search for cond namespace and how it relates to error codes
find include/boost/corosio -type f | xargs rg "cond" | head -30Repository: cppalliance/corosio
Length of output: 2570
🏁 Script executed:
#!/bin/bash
# Look for any condition code category or definition
rg -n "condition|cond" include/boost/capy --type=cpp | head -50Repository: cppalliance/corosio
Length of output: 121
🏁 Script executed:
#!/bin/bash
# Check if there's any sys condition or condition code category defined
rg -n "struct.*cond|enum.*cond|make_error_code.*cond" . --type=cpp -B 2 -A 2 | head -50Repository: cppalliance/corosio
Length of output: 521
🏁 Script executed:
#!/bin/bash
# Look for how cond::eof is being used/compared in the codebase
rg "cond::eof" . --type=cpp -B 5 -A 5Repository: cppalliance/corosio
Length of output: 2521
🏁 Script executed:
#!/bin/bash
# Look for all EOF assertions in tests to see the pattern
rg "cond::eof|error::eof|n.*==.*0" test/unit/socket.cpp -B 2 -A 1Repository: cppalliance/corosio
Length of output: 756
🏁 Script executed:
#!/bin/bash
# Check if capy::cond is defined in this repo or external
find . -name "*.hpp" -o -name "*.h" | xargs grep -l "namespace capy" | head -10Repository: cppalliance/corosio
Length of output: 102
🏁 Script executed:
#!/bin/bash
# Look for how shutdown_send and close differ
rg -n "shutdown_send|shutdown_receive" include/boost/corosio/socket.hpp -B 5 -A 5Repository: cppalliance/corosio
Length of output: 1275
🏁 Script executed:
#!/bin/bash
# Check if there's any code path that returns {ec={}, n=0} vs cond::eof
rg -n "ec_out.*=.*NULL|bytes_transferred.*==.*0" src/corosio/src/detail/ --type=cpp -B 3 -A 3 | head -50Repository: cppalliance/corosio
Length of output: 1275
🏁 Script executed:
#!/bin/bash
# Check if there are any tests that explicitly verify both error::eof and cond::eof equivalence
rg "error::eof|cond::eof" test/unit/ --type=cpp -B 3 -A 3 | grep -A 3 -B 3 "BOOST_TEST"Repository: cppalliance/corosio
Length of output: 1364
🏁 Script executed:
#!/bin/bash
# Look at the complete testShutdownSend function to understand if there's any special handling
sed -n '688,715p' test/unit/socket.cppRepository: cppalliance/corosio
Length of output: 951
🏁 Script executed:
#!/bin/bash
# Verify if other shutdown tests use the same assertion pattern
rg -n "testShutdown" test/unit/socket.cpp -A 30 | grep -E "testShutdown|BOOST_TEST.*eof|BOOST_TEST.*n.*=="Repository: cppalliance/corosio
Length of output: 317
🏁 Script executed:
#!/bin/bash
# Search for where capy::cond and capy::error are defined or aliased
find . -type f \( -name "*.hpp" -o -name "*.h" -o -name "*.cpp" \) -exec grep -l "cond\|error" {} \; | grep -E "capy|corosio" | head -20Repository: cppalliance/corosio
Length of output: 839
🏁 Script executed:
#!/bin/bash
# Look for any mapping or equivalence between cond::eof and error::eof
rg "cond|error" . --type=cpp -B 2 -A 2 | grep -E "using|typedef|==" | head -30Repository: cppalliance/corosio
Length of output: 2294
🏁 Script executed:
#!/bin/bash
# Check testShutdownReceive and testShutdownOnClosedSocket for their EOF assertions
sed -n '716,770p' test/unit/socket.cpp | grep -E "BOOST_TEST|eof|ec"Repository: cppalliance/corosio
Length of output: 595
🏁 Script executed:
#!/bin/bash
# Look for the actual implementation of read_some to see what it returns
rg -n "read_some.*{|co_return.*ec" src/corosio/src/detail/ --type=cpp -A 3 | head -50Repository: cppalliance/corosio
Length of output: 183
🏁 Script executed:
#!/bin/bash
# Look for socket implementation details about read_some
rg -n "read_some" src/corosio/src/detail/ --type=cpp -A 5 | head -60Repository: cppalliance/corosio
Length of output: 2656
🏁 Script executed:
#!/bin/bash
# Check what capy is - is it an external library?
find . -name "*.hpp" -o -name "*.h" | xargs grep "namespace capy" | head -5Repository: cppalliance/corosio
Length of output: 119
🏁 Script executed:
#!/bin/bash
# Get full testShutdownReceive to see EOF assertions
sed -n '716,760p' test/unit/socket.cppRepository: cppalliance/corosio
Length of output: 1238
🏁 Script executed:
#!/bin/bash
# Look for any documentation about cond vs error in capy library
rg -n "portable|condition.*test|cond::" include/boost/corosio/ --type=cpp -B 2 -A 2 | head -80Repository: cppalliance/corosio
Length of output: 5617
🏁 Script executed:
#!/bin/bash
# Search for capy library - is it external or in this repo?
find . -name "*.hpp" -o -name "*.h" | xargs grep -l "namespace capy" | xargs head -20Repository: cppalliance/corosio
Length of output: 636
🏁 Script executed:
#!/bin/bash
# Look for error code category definitions to understand cond vs error equivalence
rg -n "error_category|condition_check|operator==" include/boost/corosio/ --type=cpp -B 2 -A 2 | head -60Repository: cppalliance/corosio
Length of output: 1507
🏁 Script executed:
#!/bin/bash
# Check if there's any special handling that converts error::eof to cond::eof
rg -n "error::eof.*cond|cond::eof.*error" . --type=cpp -B 2 -A 2Repository: cppalliance/corosio
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Look for any test utilities that might explain the difference
cat test/unit/socket.cpp | head -100Repository: cppalliance/corosio
Length of output: 2397
🏁 Script executed:
#!/bin/bash
# Get the first 50 lines of test/unit/socket.cpp to see all imports
head -50 test/unit/socket.cppRepository: cppalliance/corosio
Length of output: 1340
🏁 Script executed:
#!/bin/bash
# Now understand the relationship - search for how capy::cond::eof and capy::error::eof relate
# Check if cond provides portable comparison operators
rg -n "cond::eof|error::eof" . --type=cpp | wc -lRepository: cppalliance/corosio
Length of output: 65
🏁 Script executed:
#!/bin/bash
# Let me compare all three EOF test cases side by side
echo "=== testReadAfterPeerClose ===" && sed -n '403,430p' test/unit/socket.cpp
echo -e "\n=== testReadPartialEOF ===" && rg -A 20 "testReadPartialEOF" test/unit/socket.cpp | head -30
echo -e "\n=== testShutdownSend ===" && sed -n '685,710p' test/unit/socket.cppRepository: cppalliance/corosio
Length of output: 2853
🏁 Script executed:
#!/bin/bash
# Count how EOF is checked across all shutdown/close tests
echo "=== EOF handling patterns ===" && \
rg "testRead.*Close|testShutdown" test/unit/socket.cpp -A 25 | grep -E "testRead|testShutdown|BOOST_TEST.*ec.*eof|BOOST_TEST.*n.*==" | head -20Repository: cppalliance/corosio
Length of output: 502
🏁 Script executed:
#!/bin/bash
# Verify what the composed read() operation does with EOF
sed -n '630,680p' test/unit/socket.cpp | grep -E "testRead|BOOST_TEST.*eof"Repository: cppalliance/corosio
Length of output: 183
EOF assertion should accept zero bytes for robustness.
The test testReadAfterPeerClose (line 427) already accepts either error or zero bytes (ec2 || n2 == 0), but this test uses a strict check. After shutdown_send, both scenarios represent peer-initiated close and should handle EOF consistently. Consider accepting either condition:
- BOOST_TEST(ec2 == capy::cond::eof);
+ BOOST_TEST(ec2 == capy::cond::eof || n2 == 0);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Next read should get EOF | |
| auto [ec2, n2] = co_await b.read_some( | |
| capy::mutable_buffer(buf, sizeof(buf))); | |
| BOOST_TEST(ec2 == capy::cond::eof); | |
| // Next read should get EOF | |
| auto [ec2, n2] = co_await b.read_some( | |
| capy::mutable_buffer(buf, sizeof(buf))); | |
| BOOST_TEST(ec2 == capy::cond::eof || n2 == 0); |
🤖 Prompt for AI Agents
In `@test/unit/socket.cpp` around lines 703 - 706, The EOF assertion in the test
after shutdown_send is too strict: locate the assertion using b.read_some that
binds auto [ec2, n2] and replace the strict check (BOOST_TEST(ec2 ==
capy::cond::eof)) with a condition that accepts either capy::cond::eof or zero
bytes (e.g., BOOST_TEST((ec2 == capy::cond::eof) || (n2 == 0))) so it matches
the robust pattern used in testReadAfterPeerClose.
There was a problem hiding this comment.
No it should not accept zero bytes, that would be a postcondition violation. The contract says eof.
There was a problem hiding this comment.
@vinniefalco, thank you for the clarification. You're absolutely right—I was incorrect. The contract for shutdown_send requires that an explicit EOF error code (capy::cond::eof) be returned, not just zero bytes. The test is correct as written.
The difference between shutdown() and close() is important here: shutdown() has a stricter postcondition that guarantees an EOF error code, while close() may signal EOF either through an error code or zero bytes (which is why testReadAfterPeerClose uses the more permissive ec2 || n2 == 0 pattern).
✏️ Learnings added
Learnt from: vinniefalco
Repo: cppalliance/corosio PR: 40
File: test/unit/socket.cpp:703-706
Timestamp: 2026-01-21T00:31:19.012Z
Learning: In boost::corosio, `socket::shutdown(shutdown_send)` has a contract that requires subsequent read operations to return an explicit EOF error code (`capy::cond::eof`), not just zero bytes. This is a stricter postcondition than `socket::close()`, which may signal EOF either through an error code or by returning zero bytes.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @vinniefalco. * #40 (comment) The following files were modified: * `src/corosio/src/detail/epoll/sockets.hpp` * `src/corosio/src/detail/iocp/sockets.hpp` * `src/corosio/src/socket.cpp` * `test/unit/socket.cpp`
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@include/boost/corosio/socket.hpp`:
- Around line 284-323: Update the shutdown() documentation (the comment above
void shutdown(shutdown_type what)) to explicitly state that calling shutdown on
an already-closed socket is a no-op and any such call is silently ignored (no
exception or error is produced), referencing the existing tests that verify this
behavior; keep the existing examples
(shutdown_send/shutdown_receive/shutdown_both) and portable eof guidance
(read_some and capy::cond::eof) but add one short sentence clarifying the
closed-socket silent-ignore semantics so callers are not surprised.
| /** Disable sends or receives on the socket. | ||
|
|
||
| TCP connections are full-duplex: each direction (send and receive) | ||
| operates independently. This function allows you to close one or | ||
| both directions without destroying the socket. | ||
|
|
||
| @li @ref shutdown_send sends a TCP FIN packet to the peer, | ||
| signaling that you have no more data to send. You can still | ||
| receive data until the peer also closes their send direction. | ||
| This is the most common use case, typically called before | ||
| close() to ensure graceful connection termination. | ||
|
|
||
| @li @ref shutdown_receive disables reading on the socket. This | ||
| does NOT send anything to the peer - they are not informed | ||
| and may continue sending data. Subsequent reads will fail | ||
| or return end-of-file. Incoming data may be discarded or | ||
| buffered depending on the operating system. | ||
|
|
||
| @li @ref shutdown_both combines both effects: sends a FIN and | ||
| disables reading. | ||
|
|
||
| When the peer shuts down their send direction (sends a FIN), | ||
| subsequent read operations will complete with `capy::cond::eof`. | ||
| Use the portable condition test rather than comparing error | ||
| codes directly: | ||
|
|
||
| @code | ||
| auto [ec, n] = co_await sock.read_some(buffer); | ||
| if (ec == capy::cond::eof) | ||
| { | ||
| // Peer closed their send direction | ||
| } | ||
| @endcode | ||
|
|
||
| Any error from the underlying system call is silently discarded | ||
| because it is unlikely to be helpful. | ||
|
|
||
| @param what Determines what operations will no longer be allowed. | ||
| */ | ||
| void shutdown(shutdown_type what); |
There was a problem hiding this comment.
Document shutdown() behavior when the socket is closed.
The public API silently ignores shutdown on a closed socket (see tests). That should be stated to avoid surprises.
✏️ Proposed doc tweak
/** Disable sends or receives on the socket.
TCP connections are full-duplex: each direction (send and receive)
operates independently. This function allows you to close one or
both directions without destroying the socket.
+
+ If the socket is not open, this function has no effect.🤖 Prompt for AI Agents
In `@include/boost/corosio/socket.hpp` around lines 284 - 323, Update the
shutdown() documentation (the comment above void shutdown(shutdown_type what))
to explicitly state that calling shutdown on an already-closed socket is a no-op
and any such call is silently ignored (no exception or error is produced),
referencing the existing tests that verify this behavior; keep the existing
examples (shutdown_send/shutdown_receive/shutdown_both) and portable eof
guidance (read_some and capy::cond::eof) but add one short sentence clarifying
the closed-socket silent-ignore semantics so callers are not surprised.
Summary by CodeRabbit
New Features
Documentation
Tests
✏️ Tip: You can customize this high-level summary in your review settings.