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" "time"
"github.com/soypat/lneto/http/httphi" "github.com/soypat/lneto/http/httphi"
"github.com/soypat/lneto/internal/rawsock" "github.com/soypat/lneto/x/rawsock"
) )
const ( const (
@@ -34,7 +34,8 @@ func main() {
} }
func run() error { func run() error {
ln, err := rawsock.Listen(listenPort) var ln rawsock.Listener
err := ln.Listen(listenPort)
if err != nil { if err != nil {
return err return err
} }
@@ -17,6 +17,7 @@ import (
"net/netip" "net/netip"
"syscall" "syscall"
"time" "time"
"unsafe"
) )
// The interfaces this package exists to satisfy: an http.Server and a heapless // 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 // Listen creates a listening TCP socket bound to port on all interfaces. A
// zero port lets the kernel choose one, see [Listener.Addr]. // 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) fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil { if err != nil {
return nil, err return err
} }
// Allow quick rebind after restart. // Allow quick rebind after restart.
if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil { if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1); err != nil {
syscall.Close(fd) syscall.Close(fd)
return nil, err return err
} }
addr := &syscall.SockaddrInet4{Port: int(port)} addr := &syscall.SockaddrInet4{Port: int(port)}
if err = syscall.Bind(fd, addr); err != nil { if err = syscall.Bind(fd, addr); err != nil {
syscall.Close(fd) syscall.Close(fd)
return nil, err return err
} }
if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil { if err = syscall.Listen(fd, syscall.SOMAXCONN); err != nil {
syscall.Close(fd) syscall.Close(fd)
return nil, err return err
} }
l := &Listener{fd: fd} l.fd = fd
l.local = Addr{}
bound, err := syscall.Getsockname(fd) bound, err := syscall.Getsockname(fd)
if err != nil { if err != nil {
syscall.Close(fd) syscall.Close(fd)
return nil, err return err
} }
if sa4, ok := bound.(*syscall.SockaddrInet4); ok { if sa4, ok := bound.(*syscall.SockaddrInet4); ok {
l.local = Addr(netip.AddrPortFrom(netip.AddrFrom4(sa4.Addr), uint16(sa4.Port))) 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 // 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, // AcceptConn blocks until an incoming connection arrives and stores it in conn,
// reusing whatever conn already held. Nothing is allocated. // 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 { func (l *Listener) AcceptConn(conn *Conn) error {
nfd, sa, err := syscall.Accept(l.fd) var rsa syscall.RawSockaddrAny
if err != nil { salen := uint32(unsafe.Sizeof(rsa))
return err 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} *conn = Conn{fd: int(nfd), local: l.local}
if sa4, ok := sa.(*syscall.SockaddrInet4); ok { if rsa.Addr.Family == syscall.AF_INET {
conn.remote = Addr(netip.AddrPortFrom(netip.AddrFrom4(sa4.Addr), uint16(sa4.Port))) 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 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)
}
}