wwbmmm commented on code in PR #3690:
URL: https://github.com/apache/brpc/pull/3690#discussion_r4500539185


##########
src/brpc/policy/http2_rpc_protocol.cpp:
##########
@@ -2019,57 +2182,151 @@ H2UnsentResponse::AppendAndDestroySelf(butil::IOBuf* 
out, Socket* socket) {
         return butil::Status::OK();
     }
     H2Context* ctx = static_cast<H2Context*>(socket->parsing_context());
+    if (ctx == NULL || ctx->is_client_side() || _stream_id == 0) {
+        return butil::Status::OK();
+    }
+
+    H2StreamContext* overcrowded_sctx = nullptr;
+    H2StreamContext* finished_sctx = nullptr;
+    H2StreamContext* notify_failed_sctx = nullptr;
+    bthread_id_t deferred_notify_id = INVALID_BTHREAD_ID;
+    int notify_reg_error = 0;
+    {
+        std::unique_lock<butil::Mutex> mu(ctx->_stream_mutex);
+        H2StreamContext** psctx = ctx->_pending_streams.seek(_stream_id);
+        if (psctx == nullptr || (*psctx)->_rejected_error != H2_NO_ERROR) {
+            // Stream was already reset by client, abandoned, or rejected.
+            return butil::Status::OK();
+        }
+        H2StreamContext* sctx = *psctx;
+        const int64_t conn_window =
+            ctx->_remote_window_left.load(butil::memory_order_relaxed);
+        const int64_t stream_window =
+            sctx->_remote_window_left.load(butil::memory_order_relaxed);
+        size_t sendable_size = 0;
+        if (conn_window > 0 && stream_window > 0) {
+            sendable_size = std::min(
+                _data.size(),
+                static_cast<size_t>(std::min(conn_window, stream_window)));
+        }
+        const size_t pending_size = _data.size() - sendable_size;
+        const int64_t max_pending = GetH2MaxPendingDataBytes();
+        if (max_pending > 0 && pending_size > 0) {
+            const auto limit = static_cast<size_t>(max_pending);
+            const size_t cur_pending =
+                ctx->_pending_data_size.load(butil::memory_order_relaxed);
+            if (cur_pending > limit || pending_size > limit - cur_pending) {
+                overcrowded_sctx = ctx->RemoveStreamLocked(_stream_id);
+            }
+        }
+        if (overcrowded_sctx == nullptr) {
+            HPacker& hpacker = ctx->hpacker();
+            butil::IOBufAppender appender;
+            HPackOptions options;
+            options.encode_name = FLAGS_h2_hpack_encode_name;
+            options.encode_value = FLAGS_h2_hpack_encode_value;
+            if (ctx->_remote_settings.header_table_size == 0) {
+                options.index_policy = HPACK_NEVER_INDEX_HEADER;
+            }
+            for (size_t i = 0; i < _size; ++i) {
+                hpacker.Encode(&appender, _list[i], options);
+            }
+            if (_http_response) {
+                for (HttpHeader::HeaderIterator it = 
_http_response->HeaderBegin();
+                     it != _http_response->HeaderEnd(); ++it) {
+                    HPacker::Header header(it->first, it->second);
+                    hpacker.Encode(&appender, header, options);
+                }
+            }
+            butil::IOBuf frag;
+            appender.move_to(frag);
+            PackH2Headers(out, frag, _stream_id,
+                          ctx->_remote_settings.max_frame_size,
+                          _data.empty() && !_is_grpc);
+
+            if (_is_grpc) {
+                HPackOptions trailer_options = options;
+                trailer_options.index_policy = HPACK_NOT_INDEX_HEADER;
+                HPacker::Header status_header(
+                    "grpc-status", butil::string_printf("%d", _grpc_status));
+                HPacker::EncodeWithoutDynamicTable(
+                    &appender, status_header, trailer_options);
+                if (!_grpc_message.empty()) {
+                    HPacker::Header msg_header("grpc-message", _grpc_message);
+                    HPacker::EncodeWithoutDynamicTable(
+                        &appender, msg_header, trailer_options);
+                }
+                appender.move_to(sctx->_pending_trailer);
+            }
 
-    // flow control
-    // NOTE: Currently the stream context is definitely removed and updating
-    // window size is useless, however it's not true when progressive request
-    // is supported.
-    // TODO(zhujiashun): Instead of just returning error to client, a better
-    // solution to handle not enough window size is to wait until WINDOW_UPDATE
-    // is received, and then retry those failed response again.
-    if (!MinusWindowSize(&ctx->_remote_window_left, _data.size())) {
+            sctx->_response_committed = true;
+            if (!_data.empty()) {
+                CHECK(sctx->_pending_data.empty());
+                sctx->_pending_data = _data;
+                ctx->AddPendingDataSize(_data.size());
+            }
+            ctx->AppendPendingDataLocked(sctx, out);
+            if (sctx->_pending_data.empty() && sctx->_pending_trailer.empty()) 
{
+                finished_sctx = ctx->RemoveStreamLocked(_stream_id);

Review Comment:
   [replied by brpc-oncall robot] Erasing the stream here (and the two 
`RemoveStreamLocked()` calls in `FlushPendingData()`) drops the stream's 
deferred request-side WINDOW_UPDATE, unlike `RemoveStreamAndDeferWU()` (also 
used by `OnSocketFailed`) and the overcrowded / notify-failure branches above, 
which all call `DeferWindowUpdate(sctx->ReleaseDeferredWindowUpdate())`. This 
matters when the response completes before the request does (a progressive-read 
method whose handler answers early, i.e. `_remote_ended == false`): the request 
bytes already buffered in `H2StreamContext::_deferred_window_update` (up to one 
per-stream window quota) are never added back to the connection window, the 
peer never regains that connection-level send credit, and after enough early 
responses the peer's connection window is exhausted and the connection stalls 
even though every remaining stream looks idle. Please migrate the credit on 
every server-side erase of a response stream (collect it under `_stream_m
 utex` and call `DeferWindowUpdate()` after unlocking, as the overcrowded path 
does).
   
   In the same early-response scenario the remaining request DATA frames hit 
the `FindOpenStream() == nullptr` branch of `H2Context::OnData()` and log 
`LOG(ERROR) "Fail to find stream_id"` per frame; that is a normal flow now (RFC 
9113 section 8.1 would have us send `RST_STREAM(NO_ERROR)` after a complete 
response), so the log level should be relaxed there.



##########
test/brpc_h2_unsent_message_unittest.cpp:
##########
@@ -216,7 +221,7 @@ TEST(H2UnsentMessage, 
request_does_not_fail_when_body_exceeds_window) {
     brpc::policy::H2StreamContext* sctx = ctx->FindStream(1);
     ASSERT_TRUE(sctx != nullptr);
     EXPECT_EQ(7u, sctx->_pending_data.size());
-    EXPECT_EQ(7u, ctx->_pending_data_size);
+    EXPECT_EQ(7u, ctx->_pending_data_size.load());
     EXPECT_EQ(0, ctx->_remote_window_left);
     EXPECT_EQ(0, sctx->_remote_window_left);

Review Comment:
   [replied by brpc-oncall robot] `sctx->Destroy()` releases the reference that 
`_pending_streams` owns (`TryToInsertStream()` took over the constructor 
reference), while the stream is still in the map. It happens to be harmless 
only because this test never destroys the `H2Context`; on a teardown path that 
does, `~H2Context()` would call `RemoveOneRefForStage2()` on the already-freed 
stream (double release / use-after-free). Please use `RemoveStreamAndDeferWU()` 
+ `RemoveOneRefForStage2()` here, or let the context release the stream.



-- 
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