[core] wakeable_delay: yield on already-woken fast path (ESP8266, RP2040)

When `g_main_loop_woke` is already set on entry to `wakeable_delay()`,
both the ESP8266 and RP2040 paths consume the flag and return without
yielding. That's safe in isolation, but if a caller loops on
`wakeable_delay()` (e.g. `LWIPRawImpl::wait_for_data_()` waiting for
SO_RCVTIMEO), and ISR sources (GPIO, timer, WiFi RX on ESP8266; alarm
/ async on RP2040) keep re-setting the flag between iterations, every
iteration takes the fast path and the loop never yields.

That can starve the SDK / async context, blocking actual TCP delivery
to our socket and causing OTA reads to time out on busy devices even
though there is data in flight. The PR that introduced
`wait_for_data_()` (#14675) relied on `wakeable_delay()` to yield on
every iteration; this restores that property on the fast path.

Adds `delay(0)` on ESP8266 and `yield()` on RP2040 to the fast path,
matching the yield behaviour those platforms already use for the
`ms == 0` poll case.
This commit is contained in:
J. Nick Koston
2026-04-27 04:55:43 -05:00
parent 83625d2c0e
commit c30aa4aad4
2 changed files with 8 additions and 0 deletions
+4
View File
@@ -36,6 +36,10 @@ inline void ESPHOME_ALWAYS_INLINE wakeable_delay(uint32_t ms) {
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
// Yield even on the already-woken fast path so callers in tight loops
// (e.g. lwIP raw TCP wait_for_data_) make forward progress when ISRs
// keep re-setting g_main_loop_woke between iterations.
delay(0);
return;
}
esp_delay(ms, []() { return !g_main_loop_woke; });
+4
View File
@@ -36,6 +36,10 @@ void wakeable_delay(uint32_t ms) {
}
if (g_main_loop_woke) {
g_main_loop_woke = false;
// Yield even on the already-woken fast path so callers in tight loops
// (e.g. lwIP raw TCP wait_for_data_) make forward progress when async
// wakes keep re-setting g_main_loop_woke between iterations.
yield();
return;
}
s_delay_expired = false;