mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-07-19 14:32:41 +00:00
39961ce5d7
* util: don't let the activity timeout clobber externally-set conn deadlines util.Conn extended the connection deadline at the start of every Read/Write. net/http's server also sets deadlines directly on the same conn - abortPendingRead sets one in the past to interrupt the pending background read after each response. The activity extension raced with and silently overwrote that interrupt, leaving the read blocked (and the server's conn.serve goroutine stuck in abortPendingRead) until the full -idleTimeout (default 30s) expired. Wedged connections count as active, so the volume server's graceful HTTP drain waited out its whole 30s StopTimeout on shutdown - observed as weed mini taking ~30s to exit in the FUSE integration tests after any filer->volume traffic. Track externally-set deadlines, suspend the activity extension while one is in force, and serialize deadline updates with a mutex. Activity still extends both directions at once: a long write-only response must keep the read deadline alive too, or the server's background read would time out and cancel the in-flight request. * util: extend read/write deadlines independently when one side is external A server-configured WriteTimeout keeps an external write deadline in force for the whole request, which previously suspended the activity extension entirely - leaving the read deadline stale from before the request and letting net/http's background read time out mid-response. Extend each direction independently instead.
183 lines
4.7 KiB
Go
183 lines
4.7 KiB
Go
package util
|
|
|
|
import (
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/stats"
|
|
)
|
|
|
|
// Listener wraps a net.Listener, and gives a place to store the timeout
|
|
// parameters. On Accept, it will wrap the net.Conn with our own Conn for us.
|
|
type Listener struct {
|
|
net.Listener
|
|
Timeout time.Duration
|
|
}
|
|
|
|
func (l *Listener) Accept() (net.Conn, error) {
|
|
c, err := l.Listener.Accept()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats.ConnectionOpen()
|
|
tc := &Conn{
|
|
Conn: c,
|
|
Timeout: l.Timeout,
|
|
}
|
|
return tc, nil
|
|
}
|
|
|
|
// Conn wraps a net.Conn and implements a "no activity timeout".
|
|
// Any activity (read or write) resets the deadline, so the connection
|
|
// only times out when there's no activity in either direction.
|
|
//
|
|
// Callers may also set deadlines directly — net/http's server does this
|
|
// to interrupt a blocked read (abortPendingRead sets a deadline in the
|
|
// past) and to enforce its own Read/Write timeouts. The activity
|
|
// extension must never overwrite such an externally-set deadline, or the
|
|
// interrupt is lost and the read stays blocked until the activity
|
|
// timeout fires (wedging connection drain on shutdown for up to the full
|
|
// idle timeout). mu serializes deadline updates, and while an external
|
|
// deadline is in force on a direction the activity extension for that
|
|
// direction is suspended; it resumes once the caller clears the deadline
|
|
// (sets the zero time).
|
|
type Conn struct {
|
|
net.Conn
|
|
Timeout time.Duration
|
|
isClosed bool
|
|
|
|
mu sync.Mutex
|
|
externalReadDeadline bool
|
|
externalWriteDeadline bool
|
|
}
|
|
|
|
func (c *Conn) SetDeadline(t time.Time) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.externalReadDeadline = !t.IsZero()
|
|
c.externalWriteDeadline = !t.IsZero()
|
|
return c.Conn.SetDeadline(t)
|
|
}
|
|
|
|
func (c *Conn) SetReadDeadline(t time.Time) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.externalReadDeadline = !t.IsZero()
|
|
return c.Conn.SetReadDeadline(t)
|
|
}
|
|
|
|
func (c *Conn) SetWriteDeadline(t time.Time) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.externalWriteDeadline = !t.IsZero()
|
|
return c.Conn.SetWriteDeadline(t)
|
|
}
|
|
|
|
// extendDeadline extends the connection deadline from now.
|
|
// This implements "no activity timeout" - any activity keeps the
|
|
// connection alive in both directions (a long write-only response must
|
|
// keep the read deadline alive too, or net/http's background read would
|
|
// time out and cancel the in-flight request). Each direction is extended
|
|
// independently: an externally-managed deadline on one side (e.g. a
|
|
// server WriteTimeout) must not leave the other side's deadline stale.
|
|
func (c *Conn) extendDeadline() error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if c.Timeout <= 0 {
|
|
return nil
|
|
}
|
|
if !c.externalReadDeadline && !c.externalWriteDeadline {
|
|
return c.Conn.SetDeadline(time.Now().Add(c.Timeout))
|
|
}
|
|
if !c.externalReadDeadline {
|
|
if err := c.Conn.SetReadDeadline(time.Now().Add(c.Timeout)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if !c.externalWriteDeadline {
|
|
if err := c.Conn.SetWriteDeadline(time.Now().Add(c.Timeout)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Conn) Read(b []byte) (count int, e error) {
|
|
// Extend deadline before reading - any activity keeps connection alive
|
|
if err := c.extendDeadline(); err != nil {
|
|
return 0, err
|
|
}
|
|
count, e = c.Conn.Read(b)
|
|
if e == nil {
|
|
stats.BytesIn(int64(count))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Conn) Write(b []byte) (count int, e error) {
|
|
// Extend deadline before writing - any activity keeps connection alive
|
|
if err := c.extendDeadline(); err != nil {
|
|
return 0, err
|
|
}
|
|
count, e = c.Conn.Write(b)
|
|
if e == nil {
|
|
stats.BytesOut(int64(count))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (c *Conn) Close() error {
|
|
err := c.Conn.Close()
|
|
if err == nil {
|
|
if !c.isClosed {
|
|
stats.ConnectionClose()
|
|
c.isClosed = true
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
func NewListener(addr string, timeout time.Duration) (ipListener net.Listener, err error) {
|
|
listener, err := net.Listen("tcp", addr)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ipListener = &Listener{
|
|
Listener: listener,
|
|
Timeout: timeout,
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func NewIpAndLocalListeners(host string, port int, timeout time.Duration) (ipListener net.Listener, localListener net.Listener, err error) {
|
|
listener, err := net.Listen("tcp", JoinHostPort(host, port))
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
ipListener = &Listener{
|
|
Listener: listener,
|
|
Timeout: timeout,
|
|
}
|
|
|
|
if host != "localhost" && host != "" && host != "0.0.0.0" && host != "127.0.0.1" && host != "[::]" && host != "[::1]" {
|
|
listener, err = net.Listen("tcp", JoinHostPort("localhost", port))
|
|
if err != nil {
|
|
glog.V(0).Infof("skip starting on %s:%d: %v", host, port, err)
|
|
return ipListener, nil, nil
|
|
}
|
|
|
|
localListener = &Listener{
|
|
Listener: listener,
|
|
Timeout: timeout,
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|