fix(api): bound the stream drain so a full config dump can't starve the watchdog (#11164)
writeStream() drained the whole queue in one call - "send every packet we can". A client asking for the full config gets the node database, then the file manifest, then the packet backlog and the position replay, and none of that returns to loop(). On a full node database (120 entries) the dump runs past eight seconds, so on RP2350, where rp2040Loop() arms an 8s hardware watchdog and is the only thing that calls watchdog_update(), the board resets in the middle of the manifest. Reproducible on every connection; with a small node database the dump finished under the timeout and nothing looked wrong. Measured on a pico2_w5500_e22: last loop iteration at millis=27242, ServerAPI kept logging until uptime 35s, reset at 35.2s = 27.242 + 8.0. Take a slice instead. The PhoneAPI state machine is resumable, so writeStream() stops after STREAM_WRITE_BUDGET_MSEC and reports whether anything is left; runOncePart() then asks to be re-run immediately rather than sleeping out readStream's idle delay, so the dump keeps its throughput while loop() gets to feed the watchdog between slices. Backpressure on a retained frame still returns the normal delay - re-running at once would just spin on a full transport. Verified on hardware: 10/10 full config dumps against a node with 120 entries, no resets, dump still completes in ~8s.
This commit is contained in:
2 files changed
+33
-23
No files matched your search
+26
-19
@@ -13,7 +13,9 @@
|
||||
int32_t StreamAPI::runOncePart()
|
||||
{
|
||||
auto result = readStream();
|
||||
writeStream();
|
||||
// More to send: come straight back instead of sleeping out readStream's idle delay.
|
||||
if (writeStream())
|
||||
result = 0;
|
||||
checkConnectionTimeout();
|
||||
return result;
|
||||
}
|
||||
@@ -22,7 +24,8 @@ int32_t StreamAPI::runOncePart()
|
||||
int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
|
||||
{
|
||||
auto result = readStream(buf, bufLen);
|
||||
writeStream();
|
||||
if (writeStream())
|
||||
result = 0;
|
||||
checkConnectionTimeout();
|
||||
return result;
|
||||
}
|
||||
@@ -44,25 +47,29 @@ int32_t StreamAPI::readStream(const char *buf, uint16_t bufLen)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||
*/
|
||||
void StreamAPI::writeStream()
|
||||
/// Emit a slice of pending output. True means "more to send, come straight back"; false covers
|
||||
/// both a drained queue and backpressure, where retrying at once would only spin.
|
||||
bool StreamAPI::writeStream()
|
||||
{
|
||||
if (canWrite) {
|
||||
// A transport that retained a short frame must complete it before
|
||||
// getFromRadio() advances the PhoneAPI state to the next packet.
|
||||
if (!finishPendingFrame())
|
||||
return;
|
||||
if (!canWrite)
|
||||
return false;
|
||||
|
||||
uint32_t len;
|
||||
do {
|
||||
// Send every packet we can
|
||||
len = getFromRadio(txBuf + HEADER_LEN);
|
||||
if (len != 0 && !emitTxBuffer(len))
|
||||
break;
|
||||
} while (len);
|
||||
}
|
||||
// A retained short frame must complete before getFromRadio() advances the PhoneAPI state.
|
||||
if (!finishPendingFrame())
|
||||
return false;
|
||||
|
||||
// Draining a full dump in one call never returns to loop(), so the 8s hardware watchdog
|
||||
// fires mid-dump. PhoneAPI is resumable, so stop at the budget and continue next dispatch.
|
||||
uint32_t len;
|
||||
uint32_t started = millis();
|
||||
do {
|
||||
// Send every packet we can, up to this slice's budget
|
||||
len = getFromRadio(txBuf + HEADER_LEN);
|
||||
if (len != 0 && !emitTxBuffer(len))
|
||||
return false;
|
||||
} while (len && Throttle::isWithinTimespanMs(started, STREAM_WRITE_BUDGET_MSEC));
|
||||
|
||||
return len != 0;
|
||||
}
|
||||
|
||||
/// Parse supplied bytes through the framed ToRadio receive state machine.
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
// A To/FromRadio packet + our 32 bit header
|
||||
#define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t))
|
||||
|
||||
// Cap on one writeStream() slice: an uncapped dump never reaches loop(), so a board with a
|
||||
// hardware watchdog (RP2350 arms 8s) resets mid-dump.
|
||||
#define STREAM_WRITE_BUDGET_MSEC 100
|
||||
|
||||
/**
|
||||
* A version of our 'phone' API that talks over a Stream. So therefore well suited to use with serial links
|
||||
* or TCP connections.
|
||||
@@ -64,10 +68,9 @@ class StreamAPI : public PhoneAPI
|
||||
int32_t readStream(const char *buf, uint16_t bufLen);
|
||||
int32_t handleRecStream(const char *buf, uint16_t bufLen);
|
||||
|
||||
/**
|
||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||
*/
|
||||
void writeStream();
|
||||
/// Emit a slice of pending output. True asks the caller to come straight back; false covers
|
||||
/// both a drained queue and backpressure, so it does not mean the queue is empty.
|
||||
bool writeStream();
|
||||
|
||||
protected:
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user