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:
@@ -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;
|
||||
@@ -0,0 +1,74 @@
|
||||
Keep-alive like librespot (core/src/session.rs): the server sends a Ping every ~2 min; send
|
||||
the Pong 60 s later, not at once. With an immediate Pong the server reset the AP connection
|
||||
(ECONNRESET) on the third Ping, i.e. every 6 minutes, and the Spotify app lost the device.
|
||||
|
||||
--- a/cspot/include/MercurySession.h
|
||||
+++ b/cspot/include/MercurySession.h
|
||||
@@ -120,6 +120,14 @@
|
||||
|
||||
unsigned long long timestampDiff;
|
||||
unsigned long long lastPingTimestamp = -1;
|
||||
+
|
||||
+ // Keep-alive as librespot does it: Ping -> wait 60 s -> Pong -> PongAck. Answering at once made
|
||||
+ // the server reset the connection on the third Ping (every 6 minutes).
|
||||
+ const int PONG_DELAY_MS = 60 * 1000;
|
||||
+ bool pongPending = false;
|
||||
+ unsigned long long pongDueTimestamp = 0;
|
||||
+ std::vector<uint8_t> pongData;
|
||||
+ void sendPendingPong();
|
||||
std::string countryCode = "";
|
||||
|
||||
std::mutex isRunningMutex;
|
||||
--- a/cspot/src/MercurySession.cpp
|
||||
+++ b/cspot/src/MercurySession.cpp
|
||||
@@ -45,7 +45,10 @@
|
||||
timeProvider->syncWithPingPacket(packet.data);
|
||||
|
||||
this->lastPingTimestamp = timeProvider->getSyncedTimestamp();
|
||||
- this->shanConn->sendPacket(0x49, packet.data);
|
||||
+ // Pong 60 s later (sent from triggerTimeout(), called on every 3 s receive timeout)
|
||||
+ this->pongData = packet.data;
|
||||
+ this->pongDueTimestamp = this->lastPingTimestamp + PONG_DELAY_MS;
|
||||
+ this->pongPending = true;
|
||||
} else {
|
||||
this->packetQueue.push(packet);
|
||||
}
|
||||
@@ -80,6 +83,7 @@
|
||||
BELL_SLEEP_MS(100);
|
||||
|
||||
lastPingTimestamp = timeProvider->getSyncedTimestamp();
|
||||
+ pongPending = false; // a Ping on the old connection needs no Pong on the new one
|
||||
isReconnecting = false;
|
||||
|
||||
this->executeEstabilishedCallback = true;
|
||||
@@ -98,9 +102,30 @@
|
||||
this->connectionReadyCallback = callback;
|
||||
}
|
||||
|
||||
+void MercurySession::sendPendingPong() {
|
||||
+ if (!pongPending ||
|
||||
+ timeProvider->getSyncedTimestamp() < pongDueTimestamp)
|
||||
+ return;
|
||||
+ pongPending = false;
|
||||
+ std::shared_ptr<ShannonConnection> shanConn;
|
||||
+ {
|
||||
+ std::scoped_lock lock(connMutex);
|
||||
+ shanConn = this->shanConn;
|
||||
+ }
|
||||
+ if (!shanConn)
|
||||
+ return;
|
||||
+ try {
|
||||
+ shanConn->sendPacket(0x49, pongData);
|
||||
+ CSPOT_LOG(info, "Sent delayed Pong");
|
||||
+ } catch (...) {
|
||||
+ CSPOT_LOG(error, "Failed to send Pong");
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
bool MercurySession::triggerTimeout() {
|
||||
if (!isRunning)
|
||||
return true;
|
||||
+ sendPendingPong();
|
||||
auto currentTimestamp = timeProvider->getSyncedTimestamp();
|
||||
|
||||
if (currentTimestamp - this->lastPingTimestamp > PING_TIMEOUT_MS) {
|
||||
Reference in New Issue
Block a user