forked from quic-go/quic-go
* ackhandler: optimize memory layout of packet struct The packet number can be derived from the position that this packet is stored at in the packets slice in the sent packet history. There is no need to store the packet number, saving 8 bytes per packet. * ackhandler: avoid copying the packet struct
234 lines
5.9 KiB
Go
234 lines
5.9 KiB
Go
package ackhandler
|
|
|
|
import (
|
|
"fmt"
|
|
"iter"
|
|
|
|
"github.com/quic-go/quic-go/internal/protocol"
|
|
)
|
|
|
|
type sentPacketHistory struct {
|
|
packets []*packet
|
|
pathProbePackets []packetWithPacketNumber
|
|
|
|
numOutstanding int
|
|
|
|
firstPacketNumber protocol.PacketNumber
|
|
highestPacketNumber protocol.PacketNumber
|
|
}
|
|
|
|
func newSentPacketHistory(isAppData bool) *sentPacketHistory {
|
|
h := &sentPacketHistory{
|
|
highestPacketNumber: protocol.InvalidPacketNumber,
|
|
firstPacketNumber: protocol.InvalidPacketNumber,
|
|
}
|
|
if isAppData {
|
|
h.packets = make([]*packet, 0, 32)
|
|
} else {
|
|
h.packets = make([]*packet, 0, 6)
|
|
}
|
|
return h
|
|
}
|
|
|
|
func (h *sentPacketHistory) checkSequentialPacketNumberUse(pn protocol.PacketNumber) {
|
|
if h.highestPacketNumber != protocol.InvalidPacketNumber {
|
|
if pn != h.highestPacketNumber+1 {
|
|
panic("non-sequential packet number use")
|
|
}
|
|
}
|
|
h.highestPacketNumber = pn
|
|
if len(h.packets) == 0 {
|
|
h.firstPacketNumber = pn
|
|
}
|
|
}
|
|
|
|
func (h *sentPacketHistory) SkippedPacket(pn protocol.PacketNumber) {
|
|
h.checkSequentialPacketNumberUse(pn)
|
|
h.packets = append(h.packets, &packet{skippedPacket: true})
|
|
}
|
|
|
|
func (h *sentPacketHistory) SentNonAckElicitingPacket(pn protocol.PacketNumber) {
|
|
h.checkSequentialPacketNumberUse(pn)
|
|
if len(h.packets) > 0 {
|
|
h.packets = append(h.packets, nil)
|
|
}
|
|
}
|
|
|
|
func (h *sentPacketHistory) SentAckElicitingPacket(pn protocol.PacketNumber, p *packet) {
|
|
h.checkSequentialPacketNumberUse(pn)
|
|
h.packets = append(h.packets, p)
|
|
if p.outstanding() {
|
|
h.numOutstanding++
|
|
}
|
|
}
|
|
|
|
func (h *sentPacketHistory) SentPathProbePacket(pn protocol.PacketNumber, p *packet) {
|
|
h.checkSequentialPacketNumberUse(pn)
|
|
h.packets = append(h.packets, &packet{isPathProbePacket: true})
|
|
h.pathProbePackets = append(h.pathProbePackets, packetWithPacketNumber{PacketNumber: pn, packet: p})
|
|
}
|
|
|
|
func (h *sentPacketHistory) Packets() iter.Seq2[protocol.PacketNumber, *packet] {
|
|
return func(yield func(protocol.PacketNumber, *packet) bool) {
|
|
// h.firstPacketNumber might be updated in the yield function,
|
|
// so we need to save it here.
|
|
firstPacketNumber := h.firstPacketNumber
|
|
for i, p := range h.packets {
|
|
if p == nil {
|
|
continue
|
|
}
|
|
if !yield(firstPacketNumber+protocol.PacketNumber(i), p) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (h *sentPacketHistory) PathProbes() iter.Seq2[protocol.PacketNumber, *packet] {
|
|
return func(yield func(protocol.PacketNumber, *packet) bool) {
|
|
for _, p := range h.pathProbePackets {
|
|
if !yield(p.PacketNumber, p.packet) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FirstOutstanding returns the first outstanding packet.
|
|
func (h *sentPacketHistory) FirstOutstanding() (protocol.PacketNumber, *packet) {
|
|
if !h.HasOutstandingPackets() {
|
|
return protocol.InvalidPacketNumber, nil
|
|
}
|
|
for i, p := range h.packets {
|
|
if p != nil && p.outstanding() {
|
|
return h.firstPacketNumber + protocol.PacketNumber(i), p
|
|
}
|
|
}
|
|
return protocol.InvalidPacketNumber, nil
|
|
}
|
|
|
|
// FirstOutstandingPathProbe returns the first outstanding path probe packet
|
|
func (h *sentPacketHistory) FirstOutstandingPathProbe() (protocol.PacketNumber, *packet) {
|
|
if len(h.pathProbePackets) == 0 {
|
|
return protocol.InvalidPacketNumber, nil
|
|
}
|
|
return h.pathProbePackets[0].PacketNumber, h.pathProbePackets[0].packet
|
|
}
|
|
|
|
func (h *sentPacketHistory) Len() int {
|
|
return len(h.packets)
|
|
}
|
|
|
|
func (h *sentPacketHistory) Remove(pn protocol.PacketNumber) error {
|
|
idx, ok := h.getIndex(pn)
|
|
if !ok {
|
|
return fmt.Errorf("packet %d not found in sent packet history", pn)
|
|
}
|
|
p := h.packets[idx]
|
|
if p.outstanding() {
|
|
h.numOutstanding--
|
|
if h.numOutstanding < 0 {
|
|
panic("negative number of outstanding packets")
|
|
}
|
|
}
|
|
h.packets[idx] = nil
|
|
// clean up all skipped packets directly before this packet number
|
|
for idx > 0 {
|
|
idx--
|
|
p := h.packets[idx]
|
|
if p == nil || !p.skippedPacket {
|
|
break
|
|
}
|
|
h.packets[idx] = nil
|
|
}
|
|
if idx == 0 {
|
|
h.cleanupStart()
|
|
}
|
|
if len(h.packets) > 0 && h.packets[0] == nil {
|
|
panic("remove failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemovePathProbe removes a path probe packet.
|
|
// It scales O(N), but that's ok, since we don't expect to send many path probe packets.
|
|
// It is not valid to call this function in IteratePathProbes.
|
|
func (h *sentPacketHistory) RemovePathProbe(pn protocol.PacketNumber) *packet {
|
|
var packetToDelete *packet
|
|
idx := -1
|
|
for i, p := range h.pathProbePackets {
|
|
if p.PacketNumber == pn {
|
|
packetToDelete = p.packet
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
if idx != -1 {
|
|
// don't use slices.Delete, because it zeros the deleted element
|
|
copy(h.pathProbePackets[idx:], h.pathProbePackets[idx+1:])
|
|
h.pathProbePackets = h.pathProbePackets[:len(h.pathProbePackets)-1]
|
|
}
|
|
return packetToDelete
|
|
}
|
|
|
|
// getIndex gets the index of packet p in the packets slice.
|
|
func (h *sentPacketHistory) getIndex(p protocol.PacketNumber) (int, bool) {
|
|
if len(h.packets) == 0 {
|
|
return 0, false
|
|
}
|
|
if p < h.firstPacketNumber {
|
|
return 0, false
|
|
}
|
|
index := int(p - h.firstPacketNumber)
|
|
if index > len(h.packets)-1 {
|
|
return 0, false
|
|
}
|
|
return index, true
|
|
}
|
|
|
|
func (h *sentPacketHistory) HasOutstandingPackets() bool {
|
|
return h.numOutstanding > 0
|
|
}
|
|
|
|
func (h *sentPacketHistory) HasOutstandingPathProbes() bool {
|
|
return len(h.pathProbePackets) > 0
|
|
}
|
|
|
|
// delete all nil entries at the beginning of the packets slice
|
|
func (h *sentPacketHistory) cleanupStart() {
|
|
for i, p := range h.packets {
|
|
if p != nil {
|
|
h.packets = h.packets[i:]
|
|
h.firstPacketNumber += protocol.PacketNumber(i)
|
|
return
|
|
}
|
|
}
|
|
h.packets = h.packets[:0]
|
|
h.firstPacketNumber = protocol.InvalidPacketNumber
|
|
}
|
|
|
|
func (h *sentPacketHistory) LowestPacketNumber() protocol.PacketNumber {
|
|
if len(h.packets) == 0 {
|
|
return protocol.InvalidPacketNumber
|
|
}
|
|
return h.firstPacketNumber
|
|
}
|
|
|
|
func (h *sentPacketHistory) DeclareLost(pn protocol.PacketNumber) {
|
|
idx, ok := h.getIndex(pn)
|
|
if !ok {
|
|
return
|
|
}
|
|
p := h.packets[idx]
|
|
if p.outstanding() {
|
|
h.numOutstanding--
|
|
if h.numOutstanding < 0 {
|
|
panic("negative number of outstanding packets")
|
|
}
|
|
}
|
|
h.packets[idx] = nil
|
|
if idx == 0 {
|
|
h.cleanupStart()
|
|
}
|
|
}
|