Commit b498c808 authored by jan.koester's avatar jan.koester
Browse files

fixed

parent 6760fd77
Loading
Loading
Loading
Loading
+28 −7
Original line number Diff line number Diff line
@@ -6315,25 +6315,46 @@ void quic::sendStreamData(uint64_t stream_id, const std::vector<uint8_t>& data,
    // Retry until all data is sent or connection is lost.
    // The size_t variant may return early when congestion control blocks;
    // we must loop to ensure the full message (including FIN) reaches the peer.
    //
    // A zero return is not only a cwnd stall (which the size_t variant
    // already waits out itself) but also an exhausted peer flow-control
    // window (MAX_DATA / MAX_STREAM_DATA), which returns 0 immediately.
    // Lifting that needs a full round trip to the peer, so the stall is
    // bounded by time without progress, not by a retry count: 50 back-to-back
    // non-blocking pumpIncoming() calls finish in microseconds, far below
    // any real network RTT, and used to abandon the stream after ~32KB (the
    // peer's initial stream window) -- leaving it neither finished nor
    // reset, so the client hung on it forever.
    static constexpr auto kStallTimeout = std::chrono::seconds(10);
    static constexpr int kStallWaitSliceMs = 20;
    // Per thread, not a member: several threads may be sending on different
    // streams of this connection at once (see _pump_socketwait's comment for
    // why one socketwait must not be shared across concurrent waiters).
    thread_local std::unique_ptr<socketwait> stall_wait;
    size_t offset = 0;
    size_t remaining = data.size();
    int stall_count = 0;
    auto last_progress = std::chrono::steady_clock::now();
    while (remaining > 0) {
        size_t sent = sendStreamData(stream_id, data.data() + offset, remaining, fin);
        if (sent == 0) {
            if (++stall_count > 50) {
                // Congestion control stall exceeded — data was not fully sent.
                // Throw so the caller knows the stream is incomplete.
            ConnectionState st = _conn_state.load();
            if (st == ConnectionState::Closed || st == ConnectionState::Draining ||
                std::chrono::steady_clock::now() - last_progress > kStallTimeout) {
                // Data was not fully sent. Throw so the caller knows the
                // stream is incomplete.
                NetException exception;
                exception[NetException::Error] << "sendStreamData: congestion stall, "
                exception[NetException::Error] << "sendStreamData: send stall, "
                    << remaining << " of " << data.size() << " bytes unsent on stream " << stream_id;
                throw exception;
            }
            // Pump incoming to process ACKs and open congestion window
            // Wait (bounded) for ACK / MAX_DATA / MAX_STREAM_DATA to arrive,
            // then process it.
            if (!stall_wait) stall_wait = std::make_unique<socketwait>();
            stall_wait->waitRead(*this, kStallWaitSliceMs);
            pumpIncoming();
            continue;
        }
        stall_count = 0;
        last_progress = std::chrono::steady_clock::now();
        offset += sent;
        remaining -= sent;
    }