mirror of
https://github.com/soypat/lneto.git
synced 2026-09-10 16:49:37 +00:00
expose rawsock as experimental package (will use for external benchmarks)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user