expose rawsock as experimental package (will use for external benchmarks)

This commit is contained in:
Patricio Whittingslow
2026-07-27 17:45:43 -03:00
parent ccb2762fc2
commit 659e7c7ba9
4 changed files with 94 additions and 16 deletions
+3 -2
View File
@@ -10,7 +10,7 @@ import (
"time"
"github.com/soypat/lneto/http/httphi"
"github.com/soypat/lneto/internal/rawsock"
"github.com/soypat/lneto/x/rawsock"
)
const (
@@ -34,7 +34,8 @@ func main() {
}
func run() error {
ln, err := rawsock.Listen(listenPort)
var ln rawsock.Listener
err := ln.Listen(listenPort)
if err != nil {
return err
}
@@ -17,6 +17,7 @@ import (
"net/netip"
"syscall"
"time"
"unsafe"
)
// The interfaces this package exists to satisfy: an http.Server and a heapless
@@ -173,35 +174,36 @@ type Listener struct {
// Listen creates a listening TCP socket bound to port on all interfaces. A
// zero port lets the kernel choose one, see [Listener.Addr].
func Listen(port uint16) (*Listener, error) {
func (l *Listener) Listen(port uint16) error {
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
return nil, err
return err
}
// Allow quick rebind after restart.
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd)
return nil, err
return err
}
addr := &syscall.SockaddrInet4{Port: int(port)}
if err = syscall.Bind(fd, addr); err != nil {
syscall.Close(fd)
return nil, err
return err
}
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
syscall.Close(fd)
return nil, err
return err
}
l := &Listener{fd: fd}
l.fd = fd
l.local = Addr{}
bound, err := syscall.Getsockname(fd)
if err != nil {
syscall.Close(fd)
return nil, err
return err
}
if sa4, ok := bound.(*syscall.SockaddrInet4); ok {
l.local = Addr(netip.AddrPortFrom(netip.AddrFrom4(sa4.Addr), uint16(sa4.Port)))
}
return l, nil
return nil
}
// Accept blocks until an incoming connection arrives and returns it. It
@@ -218,14 +220,25 @@ func (l *Listener) Accept() (net.Conn, error) {
// AcceptConn blocks until an incoming connection arrives and stores it in conn,
// reusing whatever conn already held. Nothing is allocated.
//
// The syscall is made by hand because [syscall.Accept] allocates the
// [syscall.Sockaddr] it returns, one per accepted connection: the kernel is
// given address storage this call owns instead, and the address is read out of
// it into conn.
func (l *Listener) AcceptConn(conn *Conn) error {
nfd, sa, err := syscall.Accept(l.fd)
if err != nil {
return err
var rsa syscall.RawSockaddrAny
salen := uint32(unsafe.Sizeof(rsa))
nfd, _, errno := syscall.Syscall6(syscall.SYS_ACCEPT4, uintptr(l.fd),
uintptr(unsafe.Pointer(&rsa)), uintptr(unsafe.Pointer(&salen)), 0, 0, 0)
if errno != 0 {
return errno
}
*conn = Conn{fd: nfd, local: l.local}
if sa4, ok := sa.(*syscall.SockaddrInet4); ok {
conn.remote = Addr(netip.AddrPortFrom(netip.AddrFrom4(sa4.Addr), uint16(sa4.Port)))
*conn = Conn{fd: int(nfd), local: l.local}
if rsa.Addr.Family == syscall.AF_INET {
sa4 := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&rsa))
// Port is in network byte order in the sockaddr the kernel filled.
port := uint16(sa4.Port<<8) | uint16(sa4.Port>>8)
conn.remote = Addr(netip.AddrPortFrom(netip.AddrFrom4(sa4.Addr), port))
}
return nil
}
+64
View File
@@ -0,0 +1,64 @@
//go:build !tinygo && linux
package rawsock
import (
"net"
"runtime"
"testing"
)
// TestAcceptConnDoesNotAllocate pins down what AcceptConn exists for. A server
// that owns its connection storage still pays an allocation per connection if
// accepting one allocates, which is what syscall.Accept does with the peer
// address it returns.
func TestAcceptConnDoesNotAllocate(t *testing.T) {
var ln Listener
err := ln.Listen(0)
if err != nil {
t.Fatal(err)
}
defer ln.Close()
addr := ln.Addr().String()
const n = 32
dialed := make([]net.Conn, 0, n)
defer func() {
for _, c := range dialed {
c.Close()
}
}()
for i := 0; i < n; i++ {
c, err := net.Dial("tcp", addr)
if err != nil {
t.Fatal(err)
}
dialed = append(dialed, c)
}
var conn Conn
// The first accept warms whatever the runtime wants to warm, and is where
// the peer address is checked: reading it hands a value to a net.Addr
// interface, which allocates whatever the accept did.
if err = ln.AcceptConn(&conn); err != nil {
t.Fatal(err)
}
if conn.RemoteAddr().String() != dialed[0].LocalAddr().String() {
t.Errorf("accepted peer %s, dialer says %s", conn.RemoteAddr(), dialed[0].LocalAddr())
}
conn.Close()
var before, after runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&before)
for i := 1; i < n; i++ {
if err = ln.AcceptConn(&conn); err != nil {
t.Fatal(err)
}
conn.Close()
}
runtime.ReadMemStats(&after)
if allocs := after.Mallocs - before.Mallocs; allocs != 0 {
t.Errorf("AcceptConn allocated %d times over %d accepts, want 0", allocs, n-1)
}
}