HTTP and Listener improvements and bugfixes (#17)

* listener revamp

* improvements to conn lifetimes
This commit is contained in:
Pat Whittingslow
2026-01-11 10:29:16 -03:00
committed by GitHub
parent be194ad7ea
commit 151133dfb4
8 changed files with 145 additions and 77 deletions
+27 -16
View File
@@ -15,6 +15,7 @@ type TCPPool struct {
mu sync.Mutex
naqcuired int
conns []tcp.Conn
userData []any
acquiredAt []time.Time
closingAt []time.Time
abortedAt []time.Time
@@ -31,9 +32,11 @@ func _() {
}
type TCPPoolConfig struct {
PoolSize int
QueueSize int
BufferSize int
PoolSize int
QueueSize int
TxBufSize int
RxBufSize int
Logger *slog.Logger
ConnLogger *slog.Logger
Now func() time.Time
@@ -43,6 +46,8 @@ type TCPPoolConfig struct {
// ClosingTimeout sets the timeout for a TCP connection to close and be returned to Pool.
// If the connection is not closed in this time it will be aborted by the pool.
ClosingTimeout time.Duration
// NewUserData is used to create user data used for each individual TCP connection and returned on GetTCP.
NewUserData func() any
}
func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
@@ -50,29 +55,34 @@ func NewTCPPool(cfg TCPPoolConfig) (*TCPPool, error) {
return nil, errors.New("invalid timeout")
}
n := cfg.PoolSize
bufsize := cfg.BufferSize
pool := &TCPPool{
acquiredAt: make([]time.Time, n),
closingAt: make([]time.Time, n),
abortedAt: make([]time.Time, n),
conns: make([]tcp.Conn, n),
userData: make([]any, n),
_now: cfg.Now,
estbTimeout: cfg.EstablishedTimeout,
closingTimeout: cfg.ClosingTimeout,
logger: cfg.Logger,
}
bufSpace := make([]byte, 2*n*bufsize)
allocPerConn := cfg.TxBufSize + cfg.RxBufSize
bufSpace := make([]byte, n*allocPerConn)
for i := range pool.conns {
bufoff := 2 * i * bufsize
bufoff := i * allocPerConn
txOff := bufoff + cfg.RxBufSize
err := pool.conns[i].Configure(tcp.ConnConfig{
RxBuf: bufSpace[bufoff : bufoff+bufsize],
TxBuf: bufSpace[bufoff+bufsize : bufoff+2*bufsize],
RxBuf: bufSpace[bufoff:txOff],
TxBuf: bufSpace[txOff : txOff+cfg.TxBufSize],
TxPacketQueueSize: cfg.QueueSize,
Logger: cfg.ConnLogger,
})
if err != nil {
return nil, err
}
if cfg.NewUserData != nil {
pool.userData[i] = cfg.NewUserData()
}
}
return pool, nil
}
@@ -83,7 +93,7 @@ func (p *TCPPool) NumberOfAcquired() int {
return p.naqcuired
}
func (p *TCPPool) GetTCP() (*tcp.Conn, tcp.Value) {
func (p *TCPPool) GetTCP() (conn *tcp.Conn, userData any, SuggestedISS tcp.Value) {
p.mu.Lock()
defer p.mu.Unlock()
p.debug("TCPPool:get")
@@ -92,10 +102,10 @@ func (p *TCPPool) GetTCP() (*tcp.Conn, tcp.Value) {
p.acquiredAt[i] = p.now()
p.nextISS += 1000
p.naqcuired++
return &p.conns[i], p.nextISS
return &p.conns[i], p.userData[i], p.nextISS
}
}
return nil, 0
return nil, nil, 0
}
func (p *TCPPool) PutTCP(conn *tcp.Conn) {
@@ -122,7 +132,8 @@ func (p *TCPPool) CheckTimeouts() {
defer p.mu.Unlock()
p.debug("TCPPool:checktimeouts", slog.Int("acq", p.naqcuired))
for i := range p.conns {
st := p.conns[i].State()
conn := &p.conns[i]
st := conn.State()
if st == tcp.StateEstablished {
continue
}
@@ -134,18 +145,18 @@ func (p *TCPPool) CheckTimeouts() {
} else if st.IsPreestablished() && p.since(acq) > p.estbTimeout {
// Was acquired and did not reach establishment state so we close.
// This is part of a syn-flood defense mechanism.
p.conns[i].Close()
conn.Close()
} else if st.IsClosed() || st.IsClosing() {
// p.mu.Lock()
if p.closingAt[i].IsZero() {
p.closingAt[i] = p.now()
} else if p.abortedAt[i].IsZero() && p.since(p.closingAt[i]) > p.closingTimeout {
p.abortedAt[i] = p.now()
p.conns[i].Abort()
} else if p.since(p.abortedAt[i]) > 10*time.Second {
conn.Abort()
} else if !p.abortedAt[i].IsZero() && p.since(p.abortedAt[i]) > 10*time.Second {
println("connection aborted and still not returned to TCPPool")
println("source", conn.LocalPort(), "remote", conn.RemotePort(), "state", conn.State().String())
}
// p.mu.Unlock()
}
}
}