merge main fixes

This commit is contained in:
soypat
2026-09-07 14:53:30 -07:00
parent 2dda96b610
commit cf6b7e6201
3 changed files with 31 additions and 15 deletions
+12 -2
View File
@@ -12,11 +12,14 @@ import (
"github.com/soypat/lneto" "github.com/soypat/lneto"
"github.com/soypat/lneto/ethernet" "github.com/soypat/lneto/ethernet"
"github.com/soypat/lneto/tcp"
"github.com/soypat/lneto/tcp/rto"
) )
// TestTCPRetransmitsLostSegment drops exactly one data segment and requires the // TestTCPRetransmitsLostSegment drops exactly one data segment and requires the
// bytes to arrive anyway, which is what [TCPPoolConfig.NanoTime] already promises // bytes to arrive anyway. It covers [TCPPoolConfig.NewPolicy] reaching the
// in its own documentation. Without a LossRecovery installed the loss is terminal. // pooled and dialed connections alike: without a [tcp.Policy] installed the
// loss is terminal, which is what this test asserts against.
func TestTCPRetransmitsLostSegment(t *testing.T) { func TestTCPRetransmitsLostSegment(t *testing.T) {
const ( const (
MTU = ethernet.MaxMTU MTU = ethernet.MaxMTU
@@ -55,6 +58,13 @@ func TestTCPRetransmitsLostSegment(t *testing.T) {
EstablishedTimeout: 30 * time.Second, EstablishedTimeout: 30 * time.Second,
ClosingTimeout: 30 * time.Second, ClosingTimeout: 30 * time.Second,
NewBackoff: func() lneto.BackoffStrategy { return backoffYield }, NewBackoff: func() lneto.BackoffStrategy { return backoffYield },
NewPolicy: func() tcp.Policy {
timer := new(rto.Timer)
if err := timer.Configure(func() int64 { return time.Now().UnixNano() }); err != nil {
t.Fatal(err)
}
return timer
},
} }
svGo := sv.StackBlocking(backoffYield).StackGo(StackGoConfig{ListenerPoolConfig: pool}) svGo := sv.StackBlocking(backoffYield).StackGo(StackGoConfig{ListenerPoolConfig: pool})
clGo := client.StackBlocking(backoffYield).StackGo(StackGoConfig{ clGo := client.StackBlocking(backoffYield).StackGo(StackGoConfig{
+8 -6
View File
@@ -176,17 +176,19 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
if isDial { if isDial {
var conn tcp.Conn var conn tcp.Conn
// DIAL TCP: active connection a.k.a TCP Client branch. // DIAL TCP: active connection a.k.a TCP Client branch.
err = conn.Configure(tcp.ConnConfig{ conncfg := tcp.ConnConfig{
// TODO(pato): Eventually add UDP configuration. we use TCP for now for simplicity's sake. // TODO(pato): Eventually add UDP configuration. we use TCP for now for simplicity's sake.
TxBuf: make([]byte, s.plcfg.TxBufSize), TxBuf: make([]byte, s.plcfg.TxBufSize),
RxBuf: make([]byte, s.plcfg.RxBufSize), RxBuf: make([]byte, s.plcfg.RxBufSize),
TxPacketQueueSize: s.plcfg.QueueSize, TxPacketQueueSize: s.plcfg.QueueSize,
RWBackoff: s.plcfg.NewBackoff(), RWBackoff: s.plcfg.NewBackoff(),
// A dialed connection needs a retransmission timer as much as a }
// pooled one. See [NewTCPPool]. if s.plcfg.NewPolicy != nil {
LossRecovery: new(tcp.RTO), // A dialed connection needs loss recovery as much as a pooled
Nanotime: s.blk.nanotime, // one. See [TCPPoolConfig.NewPolicy].
}) conncfg.Policy = s.plcfg.NewPolicy()
}
err = conn.Configure(conncfg)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+11 -7
View File
@@ -42,8 +42,9 @@ type TCPPoolConfig struct {
ConnLogger *slog.Logger ConnLogger *slog.Logger
// NanoTime returns the current monotonic time in nanoseconds. // NanoTime returns the current monotonic time in nanoseconds.
// Used for pool timeout tracking and passed to each [tcp.Conn] for // Used for pool timeout tracking. If nil, defaults to time.Now().UnixNano().
// retransmission timing (RFC 6298). If nil, defaults to time.Now().UnixNano(). // Retransmission timing is not driven by this clock: a [tcp.Policy] carries
// its own. See NewPolicy.
NanoTime func() int64 NanoTime func() int64
// EstablishedTimeout sets the timeout for a TCP connection since it is acquired until it is established. // EstablishedTimeout sets the timeout for a TCP connection since it is acquired until it is established.
// If the connection does not establish in this time it will be closed by the pool. // If the connection does not establish in this time it will be closed by the pool.
@@ -56,6 +57,9 @@ type TCPPoolConfig struct {
// NewBackoff returns the backoff to use for every newly configured TCP connection. Must be non-nil. // NewBackoff returns the backoff to use for every newly configured TCP connection. Must be non-nil.
// This should always return a static(non-method) function unless you know what you are doing. // This should always return a static(non-method) function unless you know what you are doing.
NewBackoff func() lneto.BackoffStrategy NewBackoff func() lneto.BackoffStrategy
// NewPolicy if non-nil creates a [tcp.Policy] for each [tcp.Conn] used by the configured Listener.
// NewPolicy should not return reused policies unless the algorithm is stateless. See [tcp.Policy] for more information.
NewPolicy func() tcp.Policy
} }
func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) { func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
@@ -87,11 +91,11 @@ func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
TxPacketQueueSize: cfg.QueueSize, TxPacketQueueSize: cfg.QueueSize,
Logger: cfg.ConnLogger, Logger: cfg.ConnLogger,
RWBackoff: cfg.NewBackoff(), RWBackoff: cfg.NewBackoff(),
// Retransmission timing, as NanoTime's contract promises. One RTO per }
// connection: it shadows that connection's send sequence space and so if cfg.NewPolicy != nil {
// cannot be shared. // One Policy per connection: it shadows that connection's send
LossRecovery: new(tcp.RTO), // sequence space and so cannot be shared.
Nanotime: pool.now, conncfg.Policy = cfg.NewPolicy()
} }
err := pool.conns[i].Configure(conncfg) err := pool.conns[i].Configure(conncfg)
if err != nil { if err != nil {