diff --git a/src/net/quic/quic_proxy_client_socket.cc b/src/net/quic/quic_proxy_client_socket.cc index 2cfb627ce8..a8bc2084a6 100644 --- a/src/net/quic/quic_proxy_client_socket.cc +++ b/src/net/quic/quic_proxy_client_socket.cc @@ -326,6 +326,23 @@ int QuicProxyClientSocket::DoLoop(int last_io_result) { rv = DoReadReplyComplete(rv); net_log_.EndEventWithNetErrorCode( NetLogEventType::HTTP_TRANSACTION_TUNNEL_READ_HEADERS, rv); + // If reading the response itself fails after Fast Open returned from + // Connect(), there is no connect_callback_ left to invoke. Successful + // responses continue through the normal response-processing states; + // HTTP status failures are handled in STATE_PROCESS_RESPONSE_CODE. + if (use_fastopen_ && read_headers_pending_ && rv < 0) { + read_headers_pending_ = false; + // The pending application read observes the closed stream. Any + // subsequent data after this response must be ignored. + next_state_ = STATE_DISCONNECTED; + // A Fast Open Connect() can complete before the app issues a + // Read(); a pending read must observe this failure instead of + // waiting for a stream close that may never come. + FailPendingReadOnFastOpenFailure(rv); + // The Fast Open Connect() already completed; do not report this + // response through connect_callback_. + rv = ERR_IO_PENDING; + } break; case STATE_PROCESS_RESPONSE_HEADERS: DCHECK_EQ(OK, rv); @@ -340,11 +357,16 @@ int QuicProxyClientSocket::DoLoop(int last_io_result) { if (use_fastopen_ && read_headers_pending_) { read_headers_pending_ = false; if (rv < 0) { - // read_callback_ will be called with this error and be reset. - // Further data after that will be ignored. + // The pending application read observes the closed stream. Any + // subsequent data after this response must be ignored. next_state_ = STATE_DISCONNECTED; + // A Fast Open Connect() can complete before the app issues a + // Read(); a pending read must observe this failure instead of + // waiting for a stream close that may never come. + FailPendingReadOnFastOpenFailure(rv); } - // Prevents calling connect_callback_. + // Fast Open already completed Connect(); do not invoke the + // consumed connect_callback_ for the later response. rv = ERR_IO_PENDING; } break; @@ -602,6 +624,19 @@ int QuicProxyClientSocket::ProcessResponseHeaders( return OK; } +void QuicProxyClientSocket::FailPendingReadOnFastOpenFailure(int error) { + if (read_callback_.is_null()) { + // No pending application read; still cancel the response stream so a + // late body cannot be delivered after the failed CONNECT response. + stream_->Reset(quic::QUIC_STREAM_CANCELLED); + return; + } + read_buf_ = nullptr; + stream_->Reset(quic::QUIC_STREAM_CANCELLED); + // May destroy |this|; run last and use no members afterwards. + std::move(read_callback_).Run(error); +} + void QuicProxyClientSocket::OnBeforeTunnelRequestComplete( base::expected result) { if (result.has_value()) { diff --git a/src/net/quic/quic_proxy_client_socket.h b/src/net/quic/quic_proxy_client_socket.h index 4738a96ea7..3fdeecf669 100644 --- a/src/net/quic/quic_proxy_client_socket.h +++ b/src/net/quic/quic_proxy_client_socket.h @@ -106,6 +106,16 @@ class NET_EXPORT_PRIVATE QuicProxyClientSocket : public ProxyClientSocket { void OnReadResponseHeadersComplete(int result); int ProcessResponseHeaders(const quiche::HttpHeaderBlock& headers); + // Fast Open async failure path: a Fast Open Connect() can complete before + // the app has issued a Read(); when the CONNECT response then fails + // asynchronously, a pending application Read() must observe the failure + // instead of waiting indefinitely for a stream close that may never come. + // Must be called after next_state_ is set to STATE_DISCONNECTED. The read + // callback may destroy |this|, so DoLoop must not touch members afterwards + // (its loop condition short-circuits on the ERR_IO_PENDING the caller sets + // next). + void FailPendingReadOnFastOpenFailure(int error); + // Callback for proxy_delegate_->OnBeforeTunnelRequest(). void OnBeforeTunnelRequestComplete( base::expected result);