Repository navigation
Conversation
UrmaEndpoint::OnNewDataFromTcp returned without calling MoreReadEvents() in the server UNINIT branch (after starting the handshake bthread), the client UNINIT branch and the handshake branch. The socket's _nevent then never dropped back to 0, so Socket::OnInputEvent never started another read handler for the socket. Once the handshake finished, the FIN of a client that closed its connection or exited was never read: the server socket was never failed, the TCP connection stayed in CLOSE_WAIT and the URMA resources of the connection were kept until the server exited. Let those branches fall through to MoreReadEvents() as RdmaEndpoint does, and start the loop at PROGRESS_INIT. Only the FALLBACK_TCP and ESTABLISHED branches still return, because their handlers release the events themselves. Events seen during the handshake are now released without reading the fd, so TryReadOnTcp() takes the read side through _nevent the way Socket::OnInputEvent does, like RdmaEndpoint::TryReadOnTcp() did before the RDMA server handshake moved into InputMessenger. It also reads in ESTABLISHED, and both handshakes call it once ESTABLISHED, so a FIN that arrived after the last handshake read is not lost. Taking _nevent also keeps the fallback's OnNewMessages() from running next to OnNewDataFromTcp(). Add brpc_urma_connection_unittest: over the URMA mock, short connections are closed by the client after each call and the server must fail its socket. It fails without this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
For internal review before this goes to apache/brpc. No upstream issue has been filed yet; the issue draft's root-cause section is included below.
What problem does this PR solve?
Issue Number: none yet (to be filed on apache/brpc)
Problem Summary:
With
socket_mode = SOCKET_MODE_URMA, a server never notices that a client closed its connection or exited, whether normally or withkill -9, once the URMA handshake has started. The server-sideSocketis never failed or recycled, so for every client connection that ever existed the server keeps:CLOSE_WAIT;UrmaEndpointand its URMA resources (jetty, JFC, JFR, imported peer segment)./sockets/<id>still shows the socket as healthy: nobroken Socketmark,ninprocess=1(no EOF seen) and URMAstate=ESTABLISHED.Root cause:
UrmaEndpoint::OnNewDataFromTcp(urma_endpoint.cpp#L1208-L1267) returns early in three branches: serverUNINIT(after starting theUrmaServerHandshakebthread), clientUNINIT, andstate < ESTABLISHED(after waking_read_butex). None of them reachesMoreReadEvents()at the bottom of the loop, so the socket's_neventnever goes back to 0.Socket::OnInputEventstarts a read handler only when_neventgoes from 0 to 1. On the server, the first TCP event of an accepted connection always finds the endpoint inUNINIT, so no read handler runs on that socket again. The handshake still completes, because the handshake bthread polls the fd itself every 50 ms. After it, the client's FIN is never read,TryReadOnTcpDuringUrmaEstnever callsSetEOF(), and the socket is never failed.RdmaEndpoint::OnNewDataFromTcpAtClient(rdma_endpoint.cpp#L251-L279), and the RDMA server path before apache#3350, which URMA follows, fall through toMoreReadEvents()in these branches.What is changed and the side effects?
Changed:
OnNewDataFromTcp: the serverUNINIT, clientUNINITandstate < ESTABLISHEDbranches fall through toMoreReadEvents(), as inRdmaEndpoint::OnNewDataFromTcpAtClient. The loop starts atSocket::PROGRESS_INITinstead of 0, which cost one extra iteration. OnlyFALLBACK_TCP,ESTABLISHEDand the!IsUrmaAvailable()fallback still return, because their handlers release the events themselves.TryReadOnTcp(): events that arrive during the handshake are now released without reading the fd. So when the handshake bthread finishes, it takes the read side through_nevent.fetch_add(1)the waySocket::OnInputEventdoes, and reads only if it went from 0 to 1. This is whatRdmaEndpoint::TryReadOnTcp()did before Support RDMA handshake protocol apache/brpc#3350. IfOnNewDataFromTcpis still running, the increment makes it loop once more and see the final state. This also keeps the fallback'sOnNewMessages()from running at the same time as one started by an event. Before, only the stuck_neventprevented that.TryReadOnTcp()also reads inESTABLISHED, and both handshakes call it right after they reachESTABLISHED. Without this, a FIN that arrived between the last handshake read and the state change would be released unread and never read again.test/brpc_urma_connection_unittest.cpp: over the URMA mock, a short-connection channel makes three calls. Each time the client closes the connection, the server must fail its socket within 5 seconds. While the call is in progress, the test also checks that the connection is URMA (state=ESTABLISHED), so a TCP fallback cannot make it pass. It fails without this change. It is a separate binary because the end-to-end setup needs the real global URMA init (over the mock), whilebrpc_urma_unittestskips it inmain().Side effects:
Performance effects: none on the data path. There is one extra
read()on the TCP fd per connection, right after the handshake. During the handshake, TCP events now wake the handshake bthread instead of waiting for its 50 ms poll.Breaking backward compatibility: none. The wire format is unchanged.
Testing status
src/brpc/urma/urma_endpoint.cpp,test/brpc_urma_connection_unittest.cppandtest/brpc_urma_unittest.cpppass-fsyntax-onlywith clang++ and g++-14 against the UMDK headers, with no warnings.cmake -DWITH_URMA=ON -DURMA_USE_MOCK=ON -DBUILD_UNIT_TESTS=ON, thenbrpc_urma_connection_unittestandbrpc_urma_unittest.Check List:
🤖 Generated with Claude Code