Stack backoff rework (#83)

* huge Stack backoff rework

* lneto bump: huge Stack backoff rework
This commit is contained in:
Pat Whittingslow
2026-04-14 21:05:02 -03:00
committed by GitHub
parent 75a812a8d7
commit 16c2c3de36
13 changed files with 153 additions and 85 deletions
+4 -3
View File
@@ -17,13 +17,14 @@ const (
// Useful for when the BackoffStrategy implements its own yield. // Useful for when the BackoffStrategy implements its own yield.
// - Returns [BackoffFlagGosched]: Signal [runtime.Gosched] should be called. // - Returns [BackoffFlagGosched]: Signal [runtime.Gosched] should be called.
// //
// consecutiveBackoffs starts at 1 and increments by 1 every time the operation is retried. // Despite the name(changes welcome) consecutiveBackoffs starts at 0 and
// increments by 1 every time the operation is retried.
// See internal/backoff.go for a implementation example. // See internal/backoff.go for a implementation example.
type BackoffStrategy func(consecutiveBackoffs int) (sleepOrFlag time.Duration) type BackoffStrategy func(consecutiveBackoffs uint) (sleepOrFlag time.Duration)
// Do applies the backoff strategy by calling backoff(consecutiveBackoffs) // Do applies the backoff strategy by calling backoff(consecutiveBackoffs)
// and then the corresponding yield function for the returned value. See [BackoffStrategy]. // and then the corresponding yield function for the returned value. See [BackoffStrategy].
func (backoff BackoffStrategy) Do(consecutiveBackoffs int) { func (backoff BackoffStrategy) Do(consecutiveBackoffs uint) {
sleep := backoff(consecutiveBackoffs) sleep := backoff(consecutiveBackoffs)
switch sleep { switch sleep {
case BackoffFlagNop: case BackoffFlagNop:
@@ -199,7 +199,7 @@ func run() error {
}() }()
// Create blocking + Berkeley stack // Create blocking + Berkeley stack
blocking := stack.StackBlocking(5 * time.Millisecond) blocking := stack.StackBlocking(stackBackoff)
berkeley := blocking.StackGo(xnet.StackGoConfig{ berkeley := blocking.StackGo(xnet.StackGoConfig{
ListenerPoolConfig: xnet.TCPPoolConfig{ ListenerPoolConfig: xnet.TCPPoolConfig{
PoolSize: uint16(flagPoolSize), PoolSize: uint16(flagPoolSize),
@@ -212,7 +212,7 @@ func run() error {
}) })
// Perform DHCP to get address. // Perform DHCP to get address.
rstack := stack.StackRetrying(5 * time.Millisecond) rstack := stack.StackRetrying(stackBackoff)
const dhcpTimeout = 6 * time.Second const dhcpTimeout = 6 * time.Second
const dhcpRetries = 2 const dhcpRetries = 2
results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries) results, err := rstack.DoDHCPv4([4]byte{192, 168, 1, 96}, dhcpTimeout, dhcpRetries)
@@ -399,3 +399,10 @@ func mockClient(stack *xnet.StackAsync, port uint16, subnet netip.Prefix) {
fmt.Println("mockclient: received response:\n", string(page)) fmt.Println("mockclient: received response:\n", string(page))
mockConn.Close() mockConn.Close()
} }
func stackBackoff(consecutiveBackoffs uint) time.Duration {
if consecutiveBackoffs < 10 {
return time.Millisecond
}
return 10 * time.Millisecond
}
+8 -1
View File
@@ -190,7 +190,7 @@ func run() (err error) {
} }
}() }()
rstack := stack.StackRetrying(5 * time.Millisecond) rstack := stack.StackRetrying(stackBackoff)
const ( const (
dhcpTimeout = 6 * time.Second dhcpTimeout = 6 * time.Second
@@ -354,3 +354,10 @@ func tryPoll(iface ltesto.Interface, poll time.Duration) (dataMayBeReady bool, _
dataMayBeReady = true dataMayBeReady = true
return dataMayBeReady, nil return dataMayBeReady, nil
} }
func stackBackoff(consecutiveBackoffs uint) time.Duration {
if consecutiveBackoffs < 10 {
return time.Millisecond
}
return 10 * time.Millisecond
}
+9 -2
View File
@@ -79,7 +79,7 @@ func run(ctx context.Context, stack *xnet.StackAsync) error {
// Other option is to instead use async API which leads to more verbose // Other option is to instead use async API which leads to more verbose
// and more stateful code. // and more stateful code.
go stackLoop(ctx, stack) go stackLoop(ctx, stack)
rstack := stack.StackRetrying(pollTime) rstack := stack.StackRetrying(stackBackoff)
results, err := rstack.DoDHCPv4([4]byte{}, protoTimeout, protoRetries) results, err := rstack.DoDHCPv4([4]byte{}, protoTimeout, protoRetries)
if err != nil { if err != nil {
return fmt.Errorf("doing DHCP: %w", err) return fmt.Errorf("doing DHCP: %w", err)
@@ -93,7 +93,7 @@ func run(ctx context.Context, stack *xnet.StackAsync) error {
return fmt.Errorf("resolving router MAC: %w", err) return fmt.Errorf("resolving router MAC: %w", err)
} }
stack.SetGateway6(gateway) stack.SetGateway6(gateway)
berkstack := stack.StackBlocking(pollTime).StackGo(xnet.StackGoConfig{ berkstack := stack.StackBlocking(stackBackoff).StackGo(xnet.StackGoConfig{
ListenerPoolConfig: xnet.TCPPoolConfig{ ListenerPoolConfig: xnet.TCPPoolConfig{
PoolSize: tcpConnPoolSize, PoolSize: tcpConnPoolSize,
QueueSize: tcpPacketQueueSize, QueueSize: tcpPacketQueueSize,
@@ -171,3 +171,10 @@ func must(err error) {
panic(err) panic(err)
} }
} }
func stackBackoff(consecutiveBackoffs uint) time.Duration {
if consecutiveBackoffs < 10 {
return time.Millisecond
}
return 10 * time.Millisecond
}
+8 -1
View File
@@ -212,7 +212,7 @@ func run() (err error) {
} }
}() }()
rstack := stack.StackRetrying(5 * time.Millisecond) rstack := stack.StackRetrying(stackBackoff)
const ( const (
dhcpTimeout = 6 * time.Second dhcpTimeout = 6 * time.Second
@@ -382,3 +382,10 @@ func tryPoll(iface ltesto.Interface, poll time.Duration) (dataMayBeReady bool, _
dataMayBeReady = true dataMayBeReady = true
return dataMayBeReady, nil return dataMayBeReady, nil
} }
func stackBackoff(consecutiveBackoffs uint) time.Duration {
if consecutiveBackoffs < 10 {
return time.Millisecond
}
return 10 * time.Millisecond
}
+22 -4
View File
@@ -4,13 +4,31 @@ import (
"time" "time"
) )
// ConnRWBackoff implements exponential backoff suitable for TCP connection // BackoffConnRW implements exponential backoff suitable for TCP connection
// read/write polling. It starts at 1us and caps at 5ms, doubling on each consecutive backoff. // read/write polling. It starts at 1us and caps at 5ms, doubling on each consecutive backoff.
func ConnRWBackoff(consecutiveBackoffs int) { func BackoffConnRW(consecutiveBackoffs uint) {
const ( const (
minWait = uint32(time.Microsecond) >> 1 minWait = uint32(time.Microsecond)
maxWait = 5 * uint32(time.Millisecond) maxWait = 5 * uint32(time.Millisecond)
maxShift = 23 maxShift = 22
_overflowCheck = minWait << maxShift
)
wait := minWait << min(consecutiveBackoffs, maxShift)
if wait > maxWait {
wait = maxWait
}
time.Sleep(time.Duration(wait))
}
// BackoffStackProto implements exponential backoff suitable for stack-level
// protocol processing polling. It starts at 1us and caps at 100ms, doubling on each consecutive backoff.
func BackoffStackProto(consecutiveBackoffs uint) {
const (
minWait = uint32(time.Microsecond)
maxWait = 100 * uint32(time.Millisecond)
// Statically calculated numbers below.
maxShift = 22
_overflowCheck = minWait << maxShift _overflowCheck = minWait << maxShift
) )
wait := minWait << min(consecutiveBackoffs, maxShift) wait := minWait << min(consecutiveBackoffs, maxShift)
-14
View File
@@ -271,20 +271,6 @@ func (r *Ring) addOff(a, b int) int {
return result return result
} }
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (r *Ring) string() string { func (r *Ring) string() string {
var b bytes.Buffer var b bytes.Buffer
r2 := *r r2 := *r
+9 -9
View File
@@ -56,7 +56,7 @@ type ConnConfig struct {
TxBuf []byte TxBuf []byte
TxPacketQueueSize int TxPacketQueueSize int
// RWBackoff sets the backoff policy for backoff when data unavailable on Read or buffer full on Write. // RWBackoff sets the backoff policy for backoff when data unavailable on Read or buffer full on Write.
// If not set a default backoff strategy will be used. See [internal.ConnRWBackoff]. // If not set a default backoff strategy will be used. See [internal.BackoffConnRW].
RWBackoff lneto.BackoffStrategy RWBackoff lneto.BackoffStrategy
Logger *slog.Logger Logger *slog.Logger
} }
@@ -212,7 +212,7 @@ func (conn *Conn) Write(b []byte) (int, error) {
return 0, nil return 0, nil
} }
n := 0 n := 0
backoffs := 0 var backoffs uint
for len(b) > 0 { for len(b) > 0 {
if err := conn.checkPipe(connid, &conn.wdead); err != nil { if err := conn.checkPipe(connid, &conn.wdead); err != nil {
return n, err return n, err
@@ -238,8 +238,8 @@ func (conn *Conn) Write(b []byte) (int, error) {
if conn.deadlineExceeded(&conn.wdead) { if conn.deadlineExceeded(&conn.wdead) {
return n, errDeadlineExceeded return n, errDeadlineExceeded
} }
backoffs++
conn.backoff(backoffs) conn.backoff(backoffs)
backoffs++
} }
} }
return n, err return n, err
@@ -250,13 +250,13 @@ func (conn *Conn) Flush() error {
if err != nil { if err != nil {
return err return err
} }
backoffs := 0 var backoffs uint
for conn.BufferedUnsent() != 0 { for conn.BufferedUnsent() != 0 {
if err := conn.checkPipe(connid, &conn.wdead); err != nil { if err := conn.checkPipe(connid, &conn.wdead); err != nil {
return err return err
} }
backoffs++
conn.backoff(backoffs) conn.backoff(backoffs)
backoffs++
} }
return nil return nil
} }
@@ -271,7 +271,7 @@ func (conn *Conn) Read(b []byte) (int, error) {
rport := conn.h.remotePort rport := conn.h.remotePort
conn.mu.Unlock() conn.mu.Unlock()
conn.trace("TCPConn.Read:start", slog.Uint64("lport", uint64(lport)), slog.Uint64("rport", uint64(rport))) conn.trace("TCPConn.Read:start", slog.Uint64("lport", uint64(lport)), slog.Uint64("rport", uint64(rport)))
backoffs := 0 var backoffs uint
n := 0 n := 0
for len(b) > 0 { for len(b) > 0 {
conn.mu.Lock() conn.mu.Lock()
@@ -302,8 +302,8 @@ func (conn *Conn) Read(b []byte) (int, error) {
} else if conn.deadlineExceeded(&conn.rdead) { } else if conn.deadlineExceeded(&conn.rdead) {
return n, errDeadlineExceeded return n, errDeadlineExceeded
} }
backoffs++
conn.backoff(backoffs) conn.backoff(backoffs)
backoffs++
} }
} }
return n, nil return n, nil
@@ -465,10 +465,10 @@ func (conn *Conn) ConnectionID() *uint64 {
return conn.h.ConnectionID() return conn.h.ConnectionID()
} }
func (conn *Conn) backoff(consecutiveBackoffs int) { func (conn *Conn) backoff(consecutiveBackoffs uint) {
if conn._backoff != nil { if conn._backoff != nil {
conn._backoff.Do(consecutiveBackoffs) conn._backoff.Do(consecutiveBackoffs)
} else { } else {
internal.ConnRWBackoff(consecutiveBackoffs) internal.BackoffConnRW(consecutiveBackoffs)
} }
} }
+7 -7
View File
@@ -41,7 +41,7 @@ type ConnConfig struct {
TxQueueSize int TxQueueSize int
// RWBackoff sets the backoff policy for backoff when data unavailable on Read or buffer full on Write. // RWBackoff sets the backoff policy for backoff when data unavailable on Read or buffer full on Write.
// This field is ineffective on configuring a [Handler]. // This field is ineffective on configuring a [Handler].
// If not set a default backoff strategy will be used. See [internal.ConnRWBackoff]. // If not set a default backoff strategy will be used. See [internal.BackoffConnRW].
RWBackoff lneto.BackoffStrategy RWBackoff lneto.BackoffStrategy
} }
@@ -130,7 +130,7 @@ func (conn *Conn) Write(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
backoffs := 0 var backoffs uint
for { for {
conn.mu.Lock() conn.mu.Lock()
if conn.h.closeCalled || connID != conn.h.connid { if conn.h.closeCalled || connID != conn.h.connid {
@@ -145,8 +145,8 @@ func (conn *Conn) Write(b []byte) (int, error) {
if conn.deadlineExceeded(&conn.wdead) { if conn.deadlineExceeded(&conn.wdead) {
return 0, os.ErrDeadlineExceeded return 0, os.ErrDeadlineExceeded
} }
backoffs++
conn.backoff(backoffs) conn.backoff(backoffs)
backoffs++
} }
} }
@@ -157,7 +157,7 @@ func (conn *Conn) Read(b []byte) (int, error) {
if err != nil { if err != nil {
return 0, err return 0, err
} }
backoffs := 0 var backoffs uint
for { for {
conn.mu.Lock() conn.mu.Lock()
if conn.h.closeCalled && conn.h.BufferedInput() == 0 || connID != conn.h.connid { if conn.h.closeCalled && conn.h.BufferedInput() == 0 || connID != conn.h.connid {
@@ -172,8 +172,8 @@ func (conn *Conn) Read(b []byte) (int, error) {
if conn.deadlineExceeded(&conn.rdead) { if conn.deadlineExceeded(&conn.rdead) {
return 0, os.ErrDeadlineExceeded return 0, os.ErrDeadlineExceeded
} }
backoffs++
conn.backoff(backoffs) conn.backoff(backoffs)
backoffs++
} }
} }
@@ -301,11 +301,11 @@ func (conn *Conn) FreeInput() int {
return conn.h.FreeInput() return conn.h.FreeInput()
} }
func (conn *Conn) backoff(consecutiveBackoffs int) { func (conn *Conn) backoff(consecutiveBackoffs uint) {
if conn._backoff != nil { if conn._backoff != nil {
conn._backoff.Do(consecutiveBackoffs) conn._backoff.Do(consecutiveBackoffs)
} else { } else {
internal.ConnRWBackoff(consecutiveBackoffs) internal.BackoffConnRW(consecutiveBackoffs)
} }
} }
+62 -32
View File
@@ -8,6 +8,7 @@ import (
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/dhcpv4" "github.com/soypat/lneto/dhcpv4"
"github.com/soypat/lneto/internal"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
) )
@@ -19,22 +20,16 @@ var (
errDeadlineExceed = errors.New("cywnet: deadline exceeded") errDeadlineExceed = errors.New("cywnet: deadline exceeded")
) )
func (s *StackAsync) StackBlocking(loopSleep time.Duration) StackBlocking { func (s *StackAsync) StackBlocking(stackProtoBackoff lneto.BackoffStrategy) StackBlocking {
if loopSleep < 0 {
panic("invalid sleep")
} else if loopSleep > 3*time.Second {
// loopSleep should be a very small amount of time for stack to remain responsive.
panic("StackBlocking sleep too large")
}
return StackBlocking{ return StackBlocking{
async: s, async: s,
loopSleep: loopSleep, _backoff: stackProtoBackoff,
} }
} }
type StackBlocking struct { type StackBlocking struct {
async *StackAsync async *StackAsync
loopSleep time.Duration _backoff lneto.BackoffStrategy
} }
func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPResults, error) { func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPResults, error) {
@@ -42,20 +37,31 @@ func (s StackBlocking) DoDHCPv4(reqAddr [4]byte, timeout time.Duration) (*DHCPRe
if err != nil { if err != nil {
return nil, err return nil, err
} }
sleep := s.loopSleep var backoffs uint
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
requested := false requested := false
var lastState dhcpv4.ClientState
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
s.async.mu.Lock()
state := s.async.dhcp.State() state := s.async.dhcp.State()
requested = requested || state > dhcpv4.StateInit s.async.mu.Unlock()
if requested && state == dhcpv4.StateInit { if state == lastState {
return nil, errors.New("DHCP NACK") if err = s.checkDeadline(deadline); err != nil {
} else if state == dhcpv4.StateBound { return nil, err
break // DHCP done succesfully. }
} else if err = s.checkDeadline(deadline); err != nil { s.backoff(backoffs)
return nil, err backoffs++
} else {
// State change indicates something happened.
backoffs = 0
lastState = state
requested = requested || state > dhcpv4.StateInit
if requested && state == dhcpv4.StateInit {
return nil, errors.New("DHCP NACK")
} else if state == dhcpv4.StateBound {
break // DHCP done succesfully.
}
} }
time.Sleep(sleep)
} }
return s.async.ResultDHCP() return s.async.ResultDHCP()
} }
@@ -65,24 +71,30 @@ func (s StackBlocking) DoPing(hostAddr netip.Addr, timeout time.Duration) (round
return 0, lneto.ErrInvalidAddr return 0, lneto.ErrInvalidAddr
} }
var buf [16]byte var buf [16]byte
s.async.mu.Lock()
s.async.prandRead(buf[:]) s.async.prandRead(buf[:])
key, err := s.async.icmp.PingStart(hostAddr.As4(), buf[:], 56) // size=56 so ICMP size is 64, like linux. key, err := s.async.icmp.PingStart(hostAddr.As4(), buf[:], 56) // size=56 so ICMP size is 64, like linux.
s.async.mu.Unlock()
if err != nil { if err != nil {
return 0, err return 0, err
} }
start := time.Now() start := time.Now()
sleep := timeout / maxIter var backoffs uint
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
time.Sleep(sleep) s.async.mu.Lock()
elapsed := time.Since(start)
completed, exists := s.async.icmp.PingPop(key) completed, exists := s.async.icmp.PingPop(key)
s.async.mu.Unlock()
if !exists { if !exists {
return 0, net.ErrClosed // lneto.ErrAborted return 0, net.ErrClosed // lneto.ErrAborted
} else if completed { }
elapsed := time.Since(start)
if completed {
return elapsed, nil return elapsed, nil
} else if elapsed > timeout { } else if elapsed > timeout {
break break
} }
s.backoff(backoffs)
backoffs++
} }
return 0, errDeadlineExceed return 0, errDeadlineExceed
} }
@@ -92,9 +104,10 @@ func (s StackBlocking) DoNTP(hostAddr netip.Addr, timeout time.Duration) (offset
if err != nil { if err != nil {
return -1, err return -1, err
} }
sleep := s.loopSleep
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
var done bool var done bool
var backoffs uint
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
offset, done = s.async.ResultNTPOffset() offset, done = s.async.ResultNTPOffset()
if done { if done {
@@ -102,7 +115,8 @@ func (s StackBlocking) DoNTP(hostAddr netip.Addr, timeout time.Duration) (offset
} else if err = s.checkDeadline(deadline); err != nil { } else if err = s.checkDeadline(deadline); err != nil {
return -1, err return -1, err
} }
time.Sleep(sleep) s.backoff(backoffs)
backoffs++
} }
return -1, errDeadlineExceed return -1, errDeadlineExceed
} }
@@ -112,7 +126,7 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
if err != nil { if err != nil {
return hw, err return hw, err
} }
sleep := s.loopSleep var backoffs uint
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
hw, err = s.async.ResultResolveHardwareAddress6(addr) hw, err = s.async.ResultResolveHardwareAddress6(addr)
@@ -121,7 +135,8 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
} else if err = s.checkDeadline(deadline); err != nil { } else if err = s.checkDeadline(deadline); err != nil {
break break
} }
time.Sleep(sleep) s.backoff(backoffs)
backoffs++
err = errDeadlineExceed // Ensure that if iterations done error is returned. err = errDeadlineExceed // Ensure that if iterations done error is returned.
} }
ip4 := addr.As4() ip4 := addr.As4()
@@ -134,8 +149,9 @@ func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []n
if err != nil { if err != nil {
return nil, err return nil, err
} }
sleep := s.loopSleep
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
var backoffs uint
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
addrs, completed, err := s.async.ResultLookupIP(host) addrs, completed, err := s.async.ResultLookupIP(host)
if completed { if completed {
@@ -143,7 +159,8 @@ func (s StackBlocking) DoLookupIP(host string, timeout time.Duration) (addrs []n
} else if err = s.checkDeadline(deadline); err != nil { } else if err = s.checkDeadline(deadline); err != nil {
return nil, err return nil, err
} }
time.Sleep(sleep) s.backoff(backoffs)
backoffs++
} }
return nil, errDeadlineExceed return nil, errDeadlineExceed
} }
@@ -155,8 +172,8 @@ func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.A
if err != nil { if err != nil {
return err return err
} }
sleep := s.loopSleep
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
var backoffs uint
for i := 0; i < maxIter; i++ { for i := 0; i < maxIter; i++ {
state := conn.State() state := conn.State()
if state == tcp.StateEstablished { if state == tcp.StateEstablished {
@@ -166,12 +183,13 @@ func (s StackBlocking) DoDialTCP(conn *tcp.Conn, localPort uint16, addrp netip.A
conn.Abort() conn.Abort()
return err return err
} }
time.Sleep(sleep)
} else { } else {
// Unexpected state, abort and terminate connection. // Unexpected state, abort and terminate connection.
conn.Abort() conn.Abort()
return errTCPFailedToConnect return errTCPFailedToConnect
} }
s.backoff(backoffs)
backoffs++
} }
return errDeadlineExceed return errDeadlineExceed
} }
@@ -182,3 +200,15 @@ func (s StackBlocking) checkDeadline(deadline time.Time) error {
} }
return nil return nil
} }
func (s StackBlocking) backoff(consecutiveBackoffs uint) {
backoff(s._backoff, consecutiveBackoffs)
}
func backoff(bo lneto.BackoffStrategy, consecutiveBackoffs uint) {
if bo != nil {
bo.Do(consecutiveBackoffs)
} else {
internal.BackoffStackProto(consecutiveBackoffs)
}
}
+11 -7
View File
@@ -5,7 +5,6 @@ import (
"net" "net"
"net/netip" "net/netip"
"syscall" "syscall"
"time"
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
@@ -22,8 +21,8 @@ type StackGoConfig struct {
ListenerPoolConfig TCPPoolConfig ListenerPoolConfig TCPPoolConfig
} }
func (s *StackAsync) StackGo(loopSleep time.Duration, cfg StackGoConfig) StackGo { func (s *StackAsync) StackGo(stackProtoBackoff lneto.BackoffStrategy, cfg StackGoConfig) StackGo {
return s.StackBlocking(loopSleep).StackGo(cfg) return s.StackBlocking(stackProtoBackoff).StackGo(cfg)
} }
func (s StackBlocking) StackGo(cfg StackGoConfig) StackGo { func (s StackBlocking) StackGo(cfg StackGoConfig) StackGo {
@@ -131,8 +130,10 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
if err != nil { if err != nil {
return nil, err return nil, err
} }
var backoffs uint
for { for {
time.Sleep(s.blk.loopSleep) s.blk.backoff(backoffs)
backoffs++
state := conn.State() state := conn.State()
if state == tcp.StateEstablished { if state == tcp.StateEstablished {
tc := tcpconn{ tc := tcpconn{
@@ -159,7 +160,7 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
} }
var l tcplistener var l tcplistener
l.localAddr = net.TCPAddrFromAddrPort(laddr) l.localAddr = net.TCPAddrFromAddrPort(laddr)
l.sleep = s.blk.loopSleep l.sleep = s.blk._backoff
err = l.l.Reset(laddr.Port(), pool) err = l.l.Reset(laddr.Port(), pool)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -177,7 +178,7 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
type tcplistener struct { type tcplistener struct {
l tcp.Listener l tcp.Listener
closed bool closed bool
sleep time.Duration sleep lneto.BackoffStrategy
localAddr net.Addr localAddr net.Addr
} }
@@ -191,12 +192,15 @@ func (l *tcplistener) Accept() (net.Conn, error) {
if l.closed { if l.closed {
return nil, net.ErrClosed return nil, net.ErrClosed
} }
var backoffs uint
for { for {
n := l.l.NumberOfReadyToAccept() n := l.l.NumberOfReadyToAccept()
if n == 0 { if n == 0 {
time.Sleep(l.sleep) backoff(l.sleep, backoffs)
backoffs++
continue continue
} }
backoffs = 0
c, _, err := l.l.TryAccept() c, _, err := l.l.TryAccept()
if err != nil { if err != nil {
return nil, err return nil, err
+3 -2
View File
@@ -5,12 +5,13 @@ import (
"net/netip" "net/netip"
"time" "time"
"github.com/soypat/lneto"
"github.com/soypat/lneto/tcp" "github.com/soypat/lneto/tcp"
) )
func (s *StackAsync) StackRetrying(loopSleep time.Duration) StackRetrying { func (s *StackAsync) StackRetrying(stackProtoBackoff lneto.BackoffStrategy) StackRetrying {
return StackRetrying{ return StackRetrying{
block: s.StackBlocking(loopSleep), block: s.StackBlocking(stackProtoBackoff),
} }
} }
+1 -1
View File
@@ -432,6 +432,6 @@ func testCloseTransmitsPending(tst *tester, s1, s2 *StackAsync, c1, c2 *tcp.Conn
} }
func backoffGosched(consecutiveBackoffs int) (sleep time.Duration) { func backoffGosched(consecutiveBackoffs uint) (sleep time.Duration) {
return lneto.BackoffFlagGosched return lneto.BackoffFlagGosched
} }