Skip to content

Defer queue livelock problem under high packet loss and high bandwidth scenario #5481

@stevefan1999-personal

Description

@stevefan1999-personal

Thanks for the PR.

A concern with prioritizing connection during the handshake is whether this could have a negative impact on established connections, in particular in a scenario with a very high rate of new connections, and potentially, allow for a DOS.

How much loss/reordering are you seeing and how much load (WorkerQueueDelay) are we talking about?

In our lab test environment with 20% packet loss, 20ms latency, and 1Gbps symmetric uplink/downlink, we've developed a custom benchmark tool to stress-test QUIC connections under adverse conditions. This setup simulates real-world scenarios like satellite links or congested Wi-Fi networks, where packet loss and reordering are common. Our testing revealed a complex failure mode during handshakes that combines packet deferral, work queue scheduling, and interrupt handling issues, leading to connection collapses that appear as "mysterious handshake timeouts."

Also, here's our findings for this issue:

Root Cause Breakdown

  1. Handshake Confirmation Packet Loss in Coalesced Datagrams:
    The client sends a handshake confirmation packet (e.g., containing the HANDSHAKE_DONE frame) to the server, often coalesced with other packets into a single UDP datagram for efficiency (as per QUIC's packet coalescing in src/core/packet_builder.c). However, in lossy networks, the datagram may arrive corrupted (e.g., due to bit errors or interference), causing the entire frame to be dropped at the UDP layer. Wireshark traces show the datagram arriving intact at the network level, but MsQuic's integrity checks (in QuicConnRecvDecryptAndAuthenticate in src/core/connection.c) fail, discarding it. This loss prevents the server from advancing Connection->State.HandshakeConfirmed to TRUE, stalling key upgrades.

  2. Deferred Packet Queue Overflow Due to Unconfirmed Handshake:
    With the handshake unconfirmed, incoming packets encrypted with higher key types (e.g., 1-RTT) cannot be decrypted. They enter the deferred packet path in QuicConnGetKeyOrDeferDatagram (src/core/connection.c, lines ~3702–3779), where Packet->KeyType > Connection->Crypto.TlsState.ReadKey triggers queuing. The per-encryption-level deferred queue (Packets[EncryptLevel]->DeferredPackets) fills rapidly under lossy conditions, hitting the QUIC_MAX_PENDING_DATAGRAMS limit (defined in src/core/quicdef.h). This queue is designed to buffer out-of-order packets during key transitions but becomes a bottleneck when confirmation is lost.

  3. Indiscriminate Dropping of Retransmitted Handshake Packets:
    The client retransmits the handshake confirmation (driven by loss detection timers in src/core/loss_detection.c), but the deferred queue treats all packets fairly as a FIFO buffer. When full, it drops incoming packets indiscriminately, including critical retransmits of HANDSHAKE_DONE. This is logged as "Max deferred packet count reached" in QuicPacketLogDrop, creating a deadlock where the confirmation cannot be processed because the queue is saturated with undecryptable data packets.

  4. Connection Collapse from Full Queue and Timeout:
    With the deferred queue full and no decryption possible (keys remain at handshake level), the connection cannot process protected data. This triggers handshake timeouts via QUIC_CONN_TIMER_SHUTDOWN (set in QuicConnTryClose, src/core/connection.c, lines ~1530–1534), leading to abrupt shutdowns. The issue manifests as a "handshake timeout" in logs, but the root is queue exhaustion, not actual timer expiration—making diagnosis difficult without deep tracing.

  5. Amplification by Worker Rescheduling and Preemption:
    Even worse, if the worker thread is rescheduled (e.g., due to another pending work preempting the queue via QuicWorkerQueueConnection), processing delays increase. This amplifies the problem: the deferred queue fills faster than it drains, and interrupt storms from repeated retransmits overload the system. Our traces show WorkerQueueDelayUpdated events correlating with queue overflows, indicating scheduling contention exacerbates the deadlock.

Broader Implications

This is not merely a handshake timeout issue—it's a compound problem of work queue deadlock and interrupt storm (i.e. livelock). The deferred queue acts as a critical section: once full, it blocks all progress, and scheduling delays prevent draining. In high-concurrency servers (e.g., under load), preemption worsens this, turning transient packet loss into cascading failures. Without mitigation, connections in lossy environments fail at rates proportional to loss percentage, degrading QUIC's reliability claims.

How the Prioritization Fix Helps

The implemented change prioritizes FLUSH_RECV operations during unconfirmed handshakes (in QuicConnQueueRecvPackets, src/core/connection.c, lines ~3256–3277), ensuring faster processing of incoming packets to drain the deferred queue. This reduces scheduling-induced delays but doesn't fully address packet loss—lost confirmations still cause overflows. For complete reliability, consider combining this with loss-adaptive retransmission or queue size tuning, but the fix is a targeted improvement for handshake-critical paths.

Tradeoffs of the Prioritization Fix

Benefits (Why We Prioritize):

  • Minimal Impact on Established Connections: The prioritization is transient (only during handshake confirmation, typically <100ms) and per-connection. Established connections aren't deprioritized indefinitely—only briefly if a new handshake preempts.
  • Addresses Root Cause: Without it, scheduling contention (e.g., from worker preemption) turns packet loss into cascading failures, as described in the PR.

Drawbacks and Risks:

  • Unfairness to Established Connections: In scenarios with high new-connection rates (e.g., 1000+/sec), prioritizing handshakes could delay processing for long-lived connections, increasing their latency or causing minor queue backlogs. This is a fairness issue: new connections "jump the line."
  • DoS Potential: An attacker could flood with incomplete handshakes (e.g., sending Initial packets but dropping responses) to monopolize worker threads, starving established connections. We understand this is a classic "resource exhaustion" attack, amplified by prioritization.
  • Resource Overhead: Slight increase in CPU for priority queue management (via QuicConnQueuePriorityOper), but negligible in practice.

Mitigations and Safeguards:

  • Conditional Prioritization: Limit to scenarios with high loss/reordering (e.g., check WorkerQueueDelay > 10ms or packet loss indicators). Or cap prioritization per worker (e.g., max 10% of operations prioritized).
  • Rate Limiting: Implement per-IP or per-worker handshake rate limits to prevent DoS floods, rejecting excessive incomplete handshakes.
  • Monitoring and Fallbacks: Add telemetry for prioritization impact (e.g., track delays for established vs. new connections). If queue delays exceed thresholds, fall back to normal priority.
  • Alternative Approaches: Instead of blanket prioritization, consider adaptive queuing (e.g., boost priority only if deferred queue > 50% full) or per-packet prioritization for handshake-critical frames like HANDSHAKE_DONE.
  • Testing Bounds: In our lab, prioritization doesn't degrade established connection throughput at <2000 new/sec. Above that, we see <5% latency increase—acceptable for most use cases, but worth benchmarking in your environment.

Overall, the benefits outweigh the risks in lossy networks, but safeguards are key. What do you think—should we add a config flag to enable/disable this, or explore the adaptive approach?

Originally posted by @stevefan1999-personal in #5473 (comment)

Metadata

Metadata

Assignees

Labels

Area: CoreRelated to the shared, core protocol logicNeed More InfoMore information is needed to proceed.TriagedThis item has been triaged by an MsQuic ownerexternalProposed by non-MSFT

Type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions