A packet that isn’t lost, just very late for a moment, still gets retransmitted if the delay is longer than the RTO, because the sender treats it as lost.
Why Bufferbloat, Wi-Fi power saving, mobile radio state changes, or a virtual machine pause cause momentary delays of hundreds of ms → Effect The RTO expires first and the packet is retransmitted; the original arrives soon after (the receiver gets a duplicate) → On screen The freeze and fast-forward come from the latency spike itself. The spurious retransmission barely lengthens the freeze; it only pushes up retransmission metrics, which get mistaken for loss
Primary owner External (External) · Also Infra team (Server infrastructure), Game team (Client development)
Game team action items
On Android 10 and later clients, request low-latency Wi-Fi mode during play (the WIFI_MODE_FULL_LOW_LATENCY Wi-Fi lock, which applies only while the screen is on and the game is in the foreground) to cut latency spikes caused by power saving.
Infra team action items
Avoid burstable instances, don’t set the RTO minimum too low, keep F-RTO and timestamps on (tcp_frto, tcp_timestamps), read retransmission metrics together with nstat TCPSpuriousRTOs and TCPDSACKRecv so they aren’t mistaken for loss.
External action items
To reduce the latency spikes themselves, tell players to use SQM on their router and turn off Wi-Fi power saving.
Ballpark numbers
Linux uses F-RTO to detect spurious RTOs and can undo the cut in sending rate. Check with nstat’s TCPSpuriousRTOs (times an RTO was judged spurious) and TCPDSACKRecv (times the receiver reported “already got this”).
On the graph
Random spikes · RTT (ping), spurious RTOs
Where to look
Deltas of TcpExtTCPTimeouts (RTO expirations), TcpExtTCPSpuriousRTOs, TcpExtTCPDSACKRecv, and TcpExtTCPLostRetransmit together, from nstat run every minute. With a packet capture, the Wireshark filter tcp.analysis.spurious_retransmission
Confirmed if
When RTOs rise, TcpExtTCPSpuriousRTOs or TcpExtTCPDSACKRecv rise with them, and RTT jumps to hundreds of ms at the same moment. The receiver-side capture shows both the original and the retransmission arriving
Ruled out if
TcpExtTCPSpuriousRTOs and DSACK flat while TcpExtTCPLostRetransmit (even the resent packet lost again) rises: real loss. RTT not jumping but DSACK steadily high: “Spurious fast retransmit from reordering”
WifiManagerAndroid (Google) WIFI_MODE_FULL_LOW_LATENCY (API 29, Android 10): a low-latency Wi-Fi lock that applies only while connected to an AP, with the screen on and the app in the foreground
net/ipv4/proc.cLinux kernel Counter names as shown by nstat: TCPTimeouts, TCPSpuriousRTOs, TCPDSACKRecv, TCPLostRetransmit
net/ipv4/tcp_timer.cLinux kernel TCPTimeouts increments when the retransmission timer (RTO) expires