Fix Connection timeout

This commit is contained in:
2026-08-16 19:18:03 -03:00
parent c8e3011f21
commit 2ff2cdf0c5
8 changed files with 235 additions and 14 deletions
+40 -7
View File
@@ -263,20 +263,31 @@ func (l *requestLane) single(mode byte, sid wire.SessionID, seq uint64, payload
var lastErr error
for attempt := 0; attempt < 2; attempt++ {
if err := l.ensureLocked(); err != nil {
// A dial timeout is a dead path for this logical tunnel. Do not
// spend another timeout replaying the same request.
if isTransportTimeout(err) {
return 0, nil, err
}
lastErr = err
continue
}
reused := l.pc.requests > 0
_ = l.pc.conn.SetDeadline(time.Now().Add(timeout))
if err := wire.WriteRequestProfileEncoding(l.pc.conn, mode, sid, seq, payload, l.headerMask, l.coverProfile.Clear); err != nil {
lastErr = err
l.transportFailureLocked(reused)
if isTransportTimeout(err) {
return 0, nil, err
}
lastErr = err
continue
}
status, body, err := wire.ReadResponseProfile(l.pc.conn, l.headerMask)
if err != nil {
lastErr = err
l.transportFailureLocked(reused)
if isTransportTimeout(err) {
return 0, nil, err
}
lastErr = err
continue
}
l.pc.requests++
@@ -307,14 +318,20 @@ func (l *requestLane) download(sid wire.SessionID, startOffset, ackOffset uint64
var lastErr error
for attempt := 0; attempt < 2; attempt++ {
if err := l.ensureLocked(); err != nil {
if isTransportTimeout(err) {
return nil, 0, err
}
lastErr = err
continue
}
reused := l.pc.requests > 0
_ = l.pc.conn.SetDeadline(time.Now().Add(timeout))
if err := wire.WriteRequestProfileEncoding(l.pc.conn, wire.ModeDownload, sid, startOffset, payload, l.headerMask, l.coverProfile.Clear); err != nil {
lastErr = err
l.transportFailureLocked(reused)
if isTransportTimeout(err) {
return nil, 0, err
}
lastErr = err
continue
}
@@ -324,8 +341,11 @@ func (l *requestLane) download(sid wire.SessionID, startOffset, ackOffset uint64
for i := 0; i < count; i++ {
status, body, err := wire.ReadResponseProfile(l.pc.conn, l.headerMask)
if err != nil {
lastErr = err
l.transportFailureLocked(reused)
if isTransportTimeout(err) {
return nil, 0, err
}
lastErr = err
goto retry
}
lastStatus = status
@@ -585,6 +605,7 @@ type chunkConn struct {
maxPipeline int
closeOnce sync.Once
dead atomic.Bool
}
// appendChunkParts keeps the single-response fast path zero-copy. For a batch,
@@ -755,6 +776,10 @@ func (c *chunkConn) fillReadBuffer() error {
minFailures = 0
}
if err != nil {
if isTransportTimeout(err) {
c.dead.Store(true)
return err
}
if c.pipeline > c.minPipeline {
old := c.pipeline
c.pipeline /= 2
@@ -829,6 +854,10 @@ func (c *chunkConn) Write(p []byte) (int, error) {
n := minInt(size, len(p))
status, body, err := c.uploadLane.single(wire.ModeUpload, c.sid, c.upOffset, p[:n])
if err != nil {
if isTransportTimeout(err) {
c.dead.Store(true)
return total, err
}
old, next := c.upSizer.FailureReason(size, err)
if old == next && next == c.opts.minSize {
minFailures++
@@ -858,9 +887,13 @@ func (c *chunkConn) Write(p []byte) (int, error) {
func (c *chunkConn) Close() error {
c.closeOnce.Do(func() {
// Reuse the upload lane rather than dialling a connection just to say
// goodbye; that was a second wasted dial per flow.
_, _, _ = c.uploadLane.single(wire.ModeClose, c.sid, 0, nil)
// A timed-out tunnel is already dead. Do not redial merely to send a
// graceful CLOSE for a session that the caller is abandoning.
if !c.dead.Load() {
// Reuse the upload lane rather than dialling a connection just to say
// goodbye; that was a second wasted dial per flow.
_, _, _ = c.uploadLane.single(wire.ModeClose, c.sid, 0, nil)
}
c.uploadLane.Close()
c.downloadLane.Close()
})