Files
brianmcgee 6c5328a54f feat: protect against slow nodes
Protects against slow or unresponsive nodes during file transfers by enforcing per-operation deadlines and adding an overall timeout for file downloads.

- adds `DeadlineConn` wrapper that resets read/write deadlines after each I/O operation, ensuring data must flow continuously
- replaces `FileClient` with `FileDownloader` - a background download manager that:
    - limits concurrent downloads to 2x CPU count
    - deduplicates in-flight requests for the same file
    - cancels obsolete downloads when a newer file version becomes available
    - enforces an overall download timeout (default 10 minutes)

Closes #286
2026-01-26 11:40:31 +00:00

41 lines
1.2 KiB
Go

package net
import (
"net"
"time"
)
// DeadlineConn wraps a net.Conn with per-operation deadline resets.
// This protects against slow or unresponsive nodes by ensuring each read/write must complete within the specified
// timeout.
//
// Each Read() call sets a fresh read deadline, and each Write() call sets a fresh write deadline.
// As long as data flows, the deadlines are continually extended.
// If no bytes arrive/are sent within the timeout, the operation fails with os.ErrDeadlineExceeded.
type DeadlineConn struct {
net.Conn
ReadDeadline time.Duration
WriteDeadline time.Duration
}
// Read implements io.Reader with per-read deadline enforcement.
func (c *DeadlineConn) Read(p []byte) (int, error) {
if c.ReadDeadline > 0 {
_ = c.SetReadDeadline(time.Now().Add(c.ReadDeadline))
}
//nolint:wrapcheck // io.Reader interface requires unwrapped errors
return c.Conn.Read(p)
}
// Write implements io.Writer with per-write deadline enforcement.
func (c *DeadlineConn) Write(p []byte) (int, error) {
if c.WriteDeadline > 0 {
_ = c.SetWriteDeadline(time.Now().Add(c.WriteDeadline))
}
//nolint:wrapcheck // io.Writer interface requires unwrapped errors
return c.Conn.Write(p)
}