cspot: delayed Pong (like librespot) stops the 6-minute connection resets

The Spotify AP reset the connection (recv -1, errno 104 ECONNRESET) on
the third Ping, every 6 minutes, like clockwork. cspot answered each
Ping at once; librespot (core/src/session.rs) waits 60 s:
Ping -> 60 s -> Pong -> PongAck -> 60 s -> Ping.
- 0003-delayed-pong: record the Ping, send the Pong 60 s later from
  triggerTimeout() (called on every 3 s receive timeout), through the
  PR #3 connection snapshot; dropped on reconnect.
- 0002-diag-log-recv-errors: log recv's return value/errno before
  "Error in read" (error path only); this is what showed the RST.
Verified: over 8+ minutes Ping/delayed Pong/PongAck every 2 min, no
reset at the 6-minute mark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 20:47:27 +10:00
parent 8008aa175f
commit 5ce4588c80
2 changed files with 89 additions and 0 deletions
@@ -0,0 +1,15 @@
diff --git a/cspot/src/PlainConnection.cpp b/cspot/src/PlainConnection.cpp
index 1bff6cf..b4ee6ff 100644
--- a/cspot/src/PlainConnection.cpp
+++ b/cspot/src/PlainConnection.cpp
@@ -150,6 +150,10 @@ void PlainConnection::readBlock(const uint8_t* dst, size_t size) {
case EINTR:
break;
default:
+ // diagnostic (project patch): n == 0 means the peer closed (FIN); n < 0 with errno
+ // e.g. ECONNRESET (RST). Note errno is not updated when n == 0.
+ CSPOT_LOG(error, "recv returned %d, errno %d, %u of %u bytes, retry %d", (int)n,
+ getErrno(), idx, (unsigned)size, retries);
if (retries++ > 4)
throw std::runtime_error("Error in read");
goto READ;