fix: keep wifi OTA off the heap floor (#2074)
OTA install streams the full 5.8MB image over a multi-minute TLS session while wifi/LWIP already holds the big internal arena. Measured on device, the arena bottomed out at ~7.7KB free with the largest contiguous block down to ~2.4KB; for 80% of the download there wasn't even a contiguous 8KB block. It finishes on a clean heap but tips into OOM for anyone carrying more pre-OTA fragmentation. Two avoidable drains, both in OtaUpdater: - The esp_http_client RX/TX buffers were 8192/8192 on both the version check and the install. RX only has to hold response headers (bodies stream through the parser / OTA writer) and TX only carries our GET, so trim both to 4096/1024. 4096 still fits the github->CDN redirect headers; the 512 IDF default truncates them, which is why they got oversized in the first place. - installUpdate fired the progress callback every ~100ms perform iteration, waking the render task on every tick. Its framebuffer work fights the TLS session for the same arena, and epd can't really repaint faster than a percent anyway. Throttle it to whole-percent changes. On device, combined: floor 7.7KB -> 19KB, worstcase contiguous block 2.4KB -> 21KB, zero sub 8KB iterations across the whole download. KOReaderSyncClient already uses small buffers; HttpDownloader is on the Arduino HTTPClient stack with no equivalent knob, so neither changed. Did you use AI tools to help write this code? partial, heap-exploration assisted by Claude.
This commit is contained in:
@@ -33,8 +33,11 @@ OtaUpdater::OtaUpdaterError OtaUpdater::checkForUpdate() {
|
|||||||
esp_http_client_config_t client_config = {
|
esp_http_client_config_t client_config = {
|
||||||
.url = latestReleaseUrl,
|
.url = latestReleaseUrl,
|
||||||
.event_handler = event_handler,
|
.event_handler = event_handler,
|
||||||
.buffer_size = 8192,
|
// 4096 holds the API response headers; the 32KB body streams through the
|
||||||
.buffer_size_tx = 8192,
|
// parser in chunks so RX needn't be larger. TX only carries our GET.
|
||||||
|
// Both free before installUpdate, so smaller leaves it less fragmentation.
|
||||||
|
.buffer_size = 4096,
|
||||||
|
.buffer_size_tx = 1024,
|
||||||
.user_data = &releaseParser,
|
.user_data = &releaseParser,
|
||||||
.skip_cert_common_name_check = true,
|
.skip_cert_common_name_check = true,
|
||||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||||
@@ -151,12 +154,11 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate(ProgressCallback onProgres
|
|||||||
esp_http_client_config_t client_config = {
|
esp_http_client_config_t client_config = {
|
||||||
.url = otaUrl.c_str(),
|
.url = otaUrl.c_str(),
|
||||||
.timeout_ms = 15000,
|
.timeout_ms = 15000,
|
||||||
/* Default HTTP client buffer size 512 byte only
|
// 4096 holds the github->CDN redirect headers (the 512 default truncates
|
||||||
* not sufficient to handle URL redirection cases or
|
// them); TX only carries our GET. Both are contiguous blocks contending
|
||||||
* parsing of large HTTP headers.
|
// with the TLS handshake on a tight internal arena, so keep them minimal.
|
||||||
*/
|
.buffer_size = 4096,
|
||||||
.buffer_size = 8192,
|
.buffer_size_tx = 1024,
|
||||||
.buffer_size_tx = 8192,
|
|
||||||
.skip_cert_common_name_check = true,
|
.skip_cert_common_name_check = true,
|
||||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||||
.keep_alive_enable = true,
|
.keep_alive_enable = true,
|
||||||
@@ -176,10 +178,21 @@ OtaUpdater::OtaUpdaterError OtaUpdater::installUpdate(ProgressCallback onProgres
|
|||||||
return INTERNAL_UPDATE_ERROR;
|
return INTERNAL_UPDATE_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int lastReportedPct = -1;
|
||||||
do {
|
do {
|
||||||
esp_err = esp_https_ota_perform(ota_handle);
|
esp_err = esp_https_ota_perform(ota_handle);
|
||||||
processedSize = esp_https_ota_get_image_len_read(ota_handle);
|
processedSize = esp_https_ota_get_image_len_read(ota_handle);
|
||||||
if (onProgress) onProgress(ctx);
|
// Fire the callback only on whole-percent change. Without this it fired
|
||||||
|
// every ~100ms perform iteration, waking the render task whose framebuffer
|
||||||
|
// work contends with TLS on the same internal arena. E-ink can't repaint
|
||||||
|
// faster than a percent tick anyway.
|
||||||
|
if (onProgress && totalSize > 0) {
|
||||||
|
const int pct = static_cast<int>(static_cast<uint64_t>(processedSize) * 100 / totalSize);
|
||||||
|
if (pct != lastReportedPct) {
|
||||||
|
lastReportedPct = pct;
|
||||||
|
onProgress(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
delay(100); // TODO: should we replace this with something better?
|
delay(100); // TODO: should we replace this with something better?
|
||||||
} while (esp_err == ESP_ERR_HTTPS_OTA_IN_PROGRESS);
|
} while (esp_err == ESP_ERR_HTTPS_OTA_IN_PROGRESS);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user