Copilot commented on code in PR #3621:
URL: https://github.com/apache/brpc/pull/3621#discussion_r4348739323


##########
test/brpc_http_rpc_protocol_unittest.cpp:
##########
@@ -2319,6 +2320,43 @@ TEST_F(HttpTest, h2_ping_ack_respects_socket_write_cap) {
     ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, pr.error());
 }
 
+TEST_F(HttpTest, socket_overcrowded_state_tracks_pending_writes) {
+    int fds[2];
+    ASSERT_EQ(0, pipe(fds));
+    butil::fd_guard read_fd(fds[0]);
+    ASSERT_EQ(0, butil::make_non_blocking(read_fd));
+    brpc::SocketOptions options;
+    options.fd = fds[1];
+    brpc::SocketId id;
+    ASSERT_EQ(0, brpc::Socket::Create(options, &id));
+    brpc::SocketUniquePtr sock;
+    ASSERT_EQ(0, brpc::Socket::Address(id, &sock));
+
+    GFLAGS_NAMESPACE::FlagSaver flag_saver;
+    brpc::FLAGS_socket_max_unwritten_bytes = 64;
+
+    butil::IOBuf data;
+    data.append(std::string(1024 * 1024, 'x'));

Review Comment:
   The test allocates/appends a 1MB `std::string` to trigger overcrowding with 
a 64-byte limit. This is more memory/CPU than needed for the boundary behavior 
under test and can slow CI. Consider reducing the payload to the smallest size 
that reliably drives `_unwritten_bytes` past the threshold (e.g., a few 
pipe-bufferfuls / a smaller fixed size).



##########
test/brpc_http_rpc_protocol_unittest.cpp:
##########
@@ -2319,6 +2320,43 @@ TEST_F(HttpTest, h2_ping_ack_respects_socket_write_cap) {
     ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, pr.error());
 }
 
+TEST_F(HttpTest, socket_overcrowded_state_tracks_pending_writes) {
+    int fds[2];
+    ASSERT_EQ(0, pipe(fds));
+    butil::fd_guard read_fd(fds[0]);
+    ASSERT_EQ(0, butil::make_non_blocking(read_fd));
+    brpc::SocketOptions options;
+    options.fd = fds[1];
+    brpc::SocketId id;
+    ASSERT_EQ(0, brpc::Socket::Create(options, &id));

Review Comment:
   `fds[1]` is not guarded, so if an `ASSERT_*` fails before `Socket::Create` 
takes ownership (or if ownership semantics change), the write-end FD can leak. 
Wrap `fds[1]` in a `butil::fd_guard` as well, and explicitly `release()` it 
only after successful socket creation if the socket assumes ownership.



##########
test/brpc_http_rpc_protocol_unittest.cpp:
##########
@@ -2319,6 +2320,43 @@ TEST_F(HttpTest, h2_ping_ack_respects_socket_write_cap) {
     ASSERT_EQ(brpc::PARSE_ERROR_ABSOLUTELY_WRONG, pr.error());
 }
 
+TEST_F(HttpTest, socket_overcrowded_state_tracks_pending_writes) {
+    int fds[2];
+    ASSERT_EQ(0, pipe(fds));
+    butil::fd_guard read_fd(fds[0]);
+    ASSERT_EQ(0, butil::make_non_blocking(read_fd));
+    brpc::SocketOptions options;
+    options.fd = fds[1];
+    brpc::SocketId id;
+    ASSERT_EQ(0, brpc::Socket::Create(options, &id));
+    brpc::SocketUniquePtr sock;
+    ASSERT_EQ(0, brpc::Socket::Address(id, &sock));
+
+    GFLAGS_NAMESPACE::FlagSaver flag_saver;
+    brpc::FLAGS_socket_max_unwritten_bytes = 64;
+
+    butil::IOBuf data;
+    data.append(std::string(1024 * 1024, 'x'));
+    ASSERT_EQ(0, sock->Write(&data));
+    ASSERT_TRUE(sock->is_overcrowded());
+
+    brpc::FLAGS_socket_max_unwritten_bytes = 0;
+    ASSERT_FALSE(sock->is_overcrowded());
+    brpc::FLAGS_socket_max_unwritten_bytes = 64;

Review Comment:
   This test mutates a global flag while the socket may have active background 
work (e.g., KeepWrite / transport threads). Since the flag variable is not an 
atomic, concurrent reads/writes can be a data race and may also cause 
cross-test interference in parallel runs. Prefer avoiding mid-test flag flips 
on a live socket (e.g., structure the test to set the flag before 
creating/using the socket, or use separate socket instances per flag setting; 
if runtime toggling must be tested, add synchronization to ensure no concurrent 
readers while updating the flag).



##########
src/brpc/socket.cpp:
##########
@@ -3003,11 +2999,14 @@ void Socket::AddInputMessages(size_t count) {
     GetOrNewSharedPart()->in_num_messages.fetch_add(count, 
butil::memory_order_relaxed);
 }
 void Socket::CancelUnwrittenBytes(size_t bytes) {
-    const int64_t before_minus =
-        _unwritten_bytes.fetch_sub(bytes, butil::memory_order_relaxed);
-    if (before_minus < (int64_t)bytes + FLAGS_socket_max_unwritten_bytes) {
-        _overcrowded = false;
-    }
+    _unwritten_bytes.fetch_sub(bytes, butil::memory_order_relaxed);
+}
+
+bool Socket::is_overcrowded() const {
+    const int64_t unwritten_bytes =
+        _unwritten_bytes.load(butil::memory_order_relaxed);
+    return FLAGS_socket_max_unwritten_bytes > 0 &&
+           unwritten_bytes >= FLAGS_socket_max_unwritten_bytes;

Review Comment:
   `FLAGS_socket_max_unwritten_bytes` is read twice, which can yield 
inconsistent results if the flag is changed at runtime (e.g., first read > 0, 
second read becomes 0), potentially making `is_overcrowded()` return an 
incorrect value. Read the flag once into a local `const int64_t limit` and use 
`limit` for both comparisons.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to