mirror of
https://github.com/soypat/lneto.git
synced 2026-09-07 15:29:05 +00:00
Reduce heap allocs 2 (#46)
* reduce heap allocations in tcp logging; omit use of AppendFloat which allocates a metric sh*tton * debugheaplog: better heap statistic logging * heap: use string for pcap.Frame.Protocol * add potential to eliminate Flags.String heap alloc, remove incorrect HEAP comments * add StackAsync.DebugErr and httpraw.SetBytes * many heap alloc reductions and replacement of bytes.Equal with internal.BytesEqual
This commit is contained in:
+5
-4
@@ -1,7 +1,6 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
@@ -20,6 +19,8 @@ var (
|
||||
errNoRemoteAddr = errors.New("tcp: no remote address established")
|
||||
errInvalidIP = errors.New("tcp: invalid IP")
|
||||
errMismatchedIPVersion = errors.New("mismatched IP version")
|
||||
errBadDemuxOffset = errors.New("bad offset in TCPConn.Recv")
|
||||
errIPAddrMismatch = errors.New("IP addr mismatch on TCPConn")
|
||||
)
|
||||
|
||||
// Conn builds on the [Handler] abstraction and adds IP header knowledge, time management, and familiar user facing API
|
||||
@@ -329,14 +330,14 @@ func (conn *Conn) Demux(buf []byte, off int) (err error) {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
if off >= len(buf) {
|
||||
return errors.New("bad offset in TCPConn.Recv")
|
||||
return errBadDemuxOffset
|
||||
}
|
||||
raddr, _, id, _, err := internal.GetIPAddr(buf[:off])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conn.isRaddrSet() && !bytes.Equal(conn.remoteAddr, raddr) {
|
||||
return errors.New("IP addr mismatch on TCPConn")
|
||||
if conn.isRaddrSet() && !internal.BytesEqual(conn.remoteAddr, raddr) {
|
||||
return errIPAddrMismatch
|
||||
}
|
||||
conn.trace("tcpconn.Recv", slog.Uint64("lport", uint64(conn.h.LocalPort())), slog.Uint64("rport", uint64(conn.h.remotePort)))
|
||||
err = conn.h.Recv(buf[off:])
|
||||
|
||||
+27
-16
@@ -183,6 +183,8 @@ func (flags Flags) HasAny(mask Flags) bool { return flags&mask != 0 }
|
||||
// Mask returns the flags with non-flag bits unset.
|
||||
func (flags Flags) Mask() Flags { return flags & flagMask }
|
||||
|
||||
func (flags Flags) Invalid() bool { return flags&flagMask != flags }
|
||||
|
||||
// StringFlags returns human readable flag string. i.e:
|
||||
//
|
||||
// "[SYN,ACK]"
|
||||
@@ -201,6 +203,8 @@ func (flags Flags) String() string {
|
||||
return "[FIN,ACK]"
|
||||
case pshack:
|
||||
return "[PSH,ACK]"
|
||||
case FlagFIN | FlagPSH | FlagACK:
|
||||
return "[FIN,PSH,ACK]"
|
||||
case FlagACK:
|
||||
return "[ACK]"
|
||||
case FlagSYN:
|
||||
@@ -210,41 +214,48 @@ func (flags Flags) String() string {
|
||||
case FlagRST:
|
||||
return "[RST]"
|
||||
}
|
||||
if flags&flagMask != flags {
|
||||
if flags.Invalid() {
|
||||
return strInvalidTCPFlags
|
||||
}
|
||||
buf := make([]byte, 0, 2+3*bits.OnesCount16(uint16(flags)))
|
||||
buf = append(buf, '[')
|
||||
buf = flags.AppendFormat(buf)
|
||||
buf = append(buf, ']')
|
||||
return string(buf)
|
||||
// Since Go 1.26 this should not allocate if returned string does not escape and is smaller than 32 bytes.
|
||||
// https://go.dev/blog/allocation-optimizations
|
||||
var buf [2 + 4*9]byte
|
||||
buf[0] = '['
|
||||
n := flags.format((*[36]byte)(buf[1:]))
|
||||
buf[1+n] = ']'
|
||||
return string(buf[:2+n])
|
||||
}
|
||||
|
||||
// AppendFormat appends a human readable flag string to b returning the extended buffer.
|
||||
func (flags Flags) AppendFormat(b []byte) []byte {
|
||||
var buf [36]byte
|
||||
n := flags.format(&buf)
|
||||
return append(b, buf[:n]...)
|
||||
}
|
||||
|
||||
const strInvalidTCPFlags = "<invalid TCP flags>"
|
||||
|
||||
// AppendFormat appends a human readable flag string to b returning the extended buffer.
|
||||
func (flags Flags) AppendFormat(b []byte) []byte {
|
||||
func (flags Flags) format(buf *[4 * 9]byte) (n int) {
|
||||
if flags == 0 {
|
||||
return b
|
||||
} else if flags&flagMask != flags {
|
||||
return append(b, strInvalidTCPFlags...)
|
||||
return 0
|
||||
} else if flags.Invalid() {
|
||||
return copy(buf[:], strInvalidTCPFlags)
|
||||
}
|
||||
|
||||
// String Flag const
|
||||
const flaglen = 3
|
||||
const strflags = "FINSYNRSTPSHACKURGECECWRNS "
|
||||
var addcommas bool
|
||||
for flags != 0 { // written by Github Copilot- looks OK.
|
||||
i := bits.TrailingZeros16(uint16(flags))
|
||||
if addcommas {
|
||||
b = append(b, ',')
|
||||
buf[n] = ','
|
||||
n++
|
||||
} else {
|
||||
addcommas = true
|
||||
}
|
||||
b = append(b, strflags[i*flaglen:i*flaglen+flaglen]...)
|
||||
n += copy(buf[n:], strflags[i*flaglen:i*flaglen+flaglen])
|
||||
flags &= ^(1 << i)
|
||||
}
|
||||
return b
|
||||
return n
|
||||
}
|
||||
|
||||
// State enumerates states a TCP connection progresses through during its lifetime as per RFC9293.
|
||||
|
||||
+7
-1
@@ -216,7 +216,13 @@ func (h *Handler) Recv(incomingPacket []byte) error {
|
||||
}
|
||||
}
|
||||
if h.logenabled(internal.LevelTrace) {
|
||||
h.trace("tcp.Handler:rx-done", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)), slog.String("seg", segIncoming.String()))
|
||||
h.trace("tcp.Handler:rx-done",
|
||||
slog.Uint64("lport", uint64(h.localPort)),
|
||||
slog.Uint64("rport", uint64(remotePort)),
|
||||
slog.Uint64("seg.seq", uint64(segIncoming.SEQ)),
|
||||
slog.Uint64("seg.ack", uint64(segIncoming.ACK)),
|
||||
slog.Uint64("seg.datalen", uint64(segIncoming.DATALEN)),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net"
|
||||
@@ -293,7 +292,7 @@ func getConn(conns []handler, remotePort uint16, remoteAddr []byte) int {
|
||||
}
|
||||
gotPort := conn.RemotePort()
|
||||
gotaddr := conn.RemoteAddr()
|
||||
if remotePort == gotPort && bytes.Equal(remoteAddr, gotaddr) {
|
||||
if remotePort == gotPort && internal.BytesEqual(remoteAddr, gotaddr) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
@@ -788,3 +788,99 @@ func FuzzTCBActions(f *testing.F) {
|
||||
// hasPanicked = false
|
||||
})
|
||||
}
|
||||
|
||||
func TestFlagsString(t *testing.T) {
|
||||
tests := []struct {
|
||||
flags tcp.Flags
|
||||
want string
|
||||
}{
|
||||
{flags: 0, want: "[]"},
|
||||
{flags: tcp.FlagSYN, want: "[SYN]"},
|
||||
{flags: tcp.FlagACK, want: "[ACK]"},
|
||||
{flags: tcp.FlagFIN, want: "[FIN]"},
|
||||
{flags: tcp.FlagRST, want: "[RST]"},
|
||||
{flags: tcp.FlagSYN | tcp.FlagACK, want: "[SYN,ACK]"},
|
||||
{flags: tcp.FlagFIN | tcp.FlagACK, want: "[FIN,ACK]"},
|
||||
{flags: tcp.FlagPSH | tcp.FlagACK, want: "[PSH,ACK]"},
|
||||
// Multi-flag combinations.
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "[FIN,SYN,ACK]"},
|
||||
{flags: tcp.FlagURG | tcp.FlagACK, want: "[ACK,URG]"},
|
||||
{flags: tcp.FlagECE | tcp.FlagCWR, want: "[ECE,CWR]"},
|
||||
{flags: tcp.FlagNS | tcp.FlagACK, want: "[ACK,NS ]"},
|
||||
// All flags set.
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "[FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS ]"},
|
||||
// Invalid flags (bits above mask).
|
||||
{flags: 0xFFFF, want: "<invalid TCP flags>"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
got := tc.flags.String()
|
||||
if got != tc.want {
|
||||
t.Errorf("Flags(%#x).String() = %q; want %q", uint16(tc.flags), got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagsAppendFormat(t *testing.T) {
|
||||
tests := []struct {
|
||||
flags tcp.Flags
|
||||
want string
|
||||
}{
|
||||
{flags: 0, want: ""},
|
||||
{flags: tcp.FlagSYN, want: "SYN"},
|
||||
{flags: tcp.FlagACK, want: "ACK"},
|
||||
{flags: tcp.FlagFIN, want: "FIN"},
|
||||
{flags: tcp.FlagRST, want: "RST"},
|
||||
{flags: tcp.FlagSYN | tcp.FlagACK, want: "SYN,ACK"},
|
||||
{flags: tcp.FlagFIN | tcp.FlagACK, want: "FIN,ACK"},
|
||||
{flags: tcp.FlagPSH | tcp.FlagACK, want: "PSH,ACK"},
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "FIN,SYN,ACK"},
|
||||
{flags: tcp.FlagURG | tcp.FlagACK, want: "ACK,URG"},
|
||||
{flags: tcp.FlagECE | tcp.FlagCWR, want: "ECE,CWR"},
|
||||
{flags: tcp.FlagNS | tcp.FlagACK, want: "ACK,NS "},
|
||||
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS "},
|
||||
{flags: 0xFFFF, want: "<invalid TCP flags>"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
got := string(tc.flags.AppendFormat(nil))
|
||||
if got != tc.want {
|
||||
t.Errorf("Flags(%#x).AppendFormat(nil) = %q; want %q", uint16(tc.flags), got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that AppendFormat appends to existing data.
|
||||
prefix := []byte("flags=")
|
||||
got := string(SYNACK.AppendFormat(prefix))
|
||||
if want := "flags=SYN,ACK"; got != want {
|
||||
t.Errorf("AppendFormat with prefix = %q; want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFlagsStringAppendFormatConsistency(t *testing.T) {
|
||||
// Verify String() and AppendFormat() produce consistent results
|
||||
// for all single-flag and common multi-flag values.
|
||||
allFlags := []tcp.Flags{
|
||||
0,
|
||||
tcp.FlagFIN, tcp.FlagSYN, tcp.FlagRST, tcp.FlagPSH,
|
||||
tcp.FlagACK, tcp.FlagURG, tcp.FlagECE, tcp.FlagCWR, tcp.FlagNS,
|
||||
SYNACK, FINACK, PSHACK,
|
||||
tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS,
|
||||
}
|
||||
for _, flags := range allFlags {
|
||||
str := flags.String()
|
||||
appended := string(flags.AppendFormat(nil))
|
||||
// String() wraps in brackets, AppendFormat() does not.
|
||||
if flags == 0 {
|
||||
if str != "[]" {
|
||||
t.Errorf("Flags(0).String() = %q; want %q", str, "[]")
|
||||
}
|
||||
if appended != "" {
|
||||
t.Errorf("Flags(0).AppendFormat() = %q; want empty", appended)
|
||||
}
|
||||
continue
|
||||
}
|
||||
wantStr := "[" + appended + "]"
|
||||
if str != wantStr {
|
||||
t.Errorf("Flags(%#x): String()=%q inconsistent with AppendFormat()=%q (want %q)", uint16(flags), str, appended, wantStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -7,7 +7,12 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errPacketQueueFull = errors.New("packet queue full")
|
||||
errPacketQueueFull = errors.New("packet queue full")
|
||||
errQueuedPacketsLEZ = errors.New("queued packets <=0")
|
||||
errInvalidBufSize = errors.New("invalid buffer size")
|
||||
errSeqLessThanLast = errors.New("sequence number less than last sequence number")
|
||||
errNoPacketToAck = errors.New("no packet to ack")
|
||||
errAckUnsent = errors.New("ack of unsent packet")
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -55,9 +60,9 @@ type ringidx struct {
|
||||
func (rtx *ringTx) Reset(buf []byte, maxqueuedPackets int, iss Value) error {
|
||||
buf = buf[:len(buf):len(buf)] // safely omit capacity section.
|
||||
if maxqueuedPackets <= 0 {
|
||||
return errors.New("queued packets <=0")
|
||||
return errQueuedPacketsLEZ
|
||||
} else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets {
|
||||
return errors.New("invalid buffer size")
|
||||
return errInvalidBufSize
|
||||
}
|
||||
|
||||
*rtx = ringTx{
|
||||
@@ -126,7 +131,7 @@ func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) {
|
||||
}
|
||||
endSeq, ok := rtx.sentEndSeq()
|
||||
if ok && currentSeq.LessThan(endSeq) {
|
||||
return 0, errors.New("sequence number less than last sequence number")
|
||||
return 0, errSeqLessThanLast
|
||||
}
|
||||
// Reading unsent ring consumes unsent and converts it to "sent".
|
||||
unsent, _ := rtx.unsentRing()
|
||||
@@ -316,11 +321,11 @@ func (sl *sentlist) AddPacket(datalen, off, bufsize int, seq Value) *ringidx {
|
||||
func (sl *sentlist) RecvAck(ack Value, bufsize int) error {
|
||||
newest := sl.Newest()
|
||||
if newest == nil {
|
||||
return errors.New("no packet to ack")
|
||||
return errNoPacketToAck
|
||||
}
|
||||
endseq := newest.endSeq()
|
||||
if endseq.LessThan(ack) {
|
||||
return errors.New("ack of unsent packet")
|
||||
return errAckUnsent
|
||||
}
|
||||
// Mark fully acked.
|
||||
for i := 0; i < len(sl.pkts); i++ {
|
||||
|
||||
Reference in New Issue
Block a user