me-no-dev / ESPAsyncWebServer

Async Web Server for ESP8266 and ESP32
3.71k stars 1.21k forks source link

uploading multiple files raises HEAP and AsyncTCP errors #1381

Closed tobiasfaust closed 2 months ago

tobiasfaust commented 7 months ago

Hi, I'm using a simple fileupload to littleFS partition.

input type="file" name='filesystem' webkitdirectory mozdirectory 
server->on("/doUpload", HTTP_POST, [](AsyncWebServerRequest *request) {},
                                    std::bind(&handleFiles::handleUpload, this, std::placeholders::_1, 
                                        std::placeholders::_2,
                                        std::placeholders::_3,
                                        std::placeholders::_4,
                                        std::placeholders::_5,
                                        std::placeholders::_6));
void handleFiles::handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
  AsyncResponseStream *response = request->beginResponseStream("text/json");
  response->addHeader("Server","ESP Async Web Server");

  rtc_wdt_feed();

    String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
    Serial.println(logmessage);

  if (!index) {
    // open the file on first call and store the file handle in the request object
    request->_tempFile = LittleFS.open("/" + filename, "w");
      Serial.printf("Upload Start: %s\n", filename.c_str());
  }

  if (len) {
    // stream the incoming chunk to the opened file
    request->_tempFile.write(data, len);
    rtc_wdt_feed();
    Serial.printf("Writing file: %s ,index=%d len=%d bytes\n", filename.c_str(), index, len);
  }

  if (final) {
    // close the file handle as the upload is now done
    request->_tempFile.close();
    Serial.printf("Upload Complete: %s ,size: %d Bytes\n", filename.c_str(), (index + len));
  }
}

With only one file it works perfectly, but if i want to upload multiple files by selectiong a directory, the esp crashes after uploadfing 1-2 files

Upload Complete: /regs/Deye_SUN_SG04LP3.json ,size: 41580 Bytes
Upload Complete: /regs/Growatt-SPH.json ,size: 6992 Bytes
CORRUPT HEAP: Bad tail at 0x3ffd1602. Expected 0xbaad5678 got 0xbaad0000

assert failed: multi_heap_free multi_heap_poisoning.c:253 (head != NULL)

Backtrace:0x40083735:0x3ffdc4a00x4008d0c1:0x3ffdc4c0 0x400924c9:0x3ffdc4e0 0x4009210f:0x3ffdc610 0x40083bc9:0x3ffdc630 0x400924f9:0x3ffdc650 0x40188eaa:0x3ffdc670 0x400ea561:0x3ffdc690 0x400ecf86:0x3ffdc6c0 0x400ef3f1:0x3ffdc6e0 0x400ea763:0x3ffdc700 0x400ea771:0x3ffdc720 0x400e9aba:0x3ffdc740 0x400e9cc9:0x3ffdc770 0x400e9f7b:0x3ffdc7a0 

  #0  0x40083735:0x3ffdc4a0 in panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:402
  #1  0x4008d0c1:0x3ffdc4c0 in esp_system_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c:128
  #2  0x400924c9:0x3ffdc4e0 in __assert_func at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/assert.c:85 
  #3  0x4009210f:0x3ffdc610 in multi_heap_free at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/heap/multi_heap_poisoning.c:253
      (inlined by) multi_heap_free at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/heap/multi_heap_poisoning.c:245
  #4  0x40083bc9:0x3ffdc630 in heap_caps_free at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/heap/heap_caps.c:340
  #5  0x400924f9:0x3ffdc650 in free at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/heap.c:39
  #6  0x40188eaa:0x3ffdc670 in VFSFileImpl::close() at C:/Users/Tobias/.platformio/packages/framework-arduinoespressif32/libraries/FS/src/vfs_api.cpp:333
  #7  0x400ea561:0x3ffdc690 in fs::File::close() at C:/Users/Tobias/.platformio/packages/framework-arduinoespressif32/libraries/FS/src/FS.cpp:145
  #8  0x400ecf86:0x3ffdc6c0 in AsyncWebServerRequest::~AsyncWebServerRequest() at .pio/libdeps/firmware_ESP32/ESP Async WebServer/src/WebRequest.cpp:97
  #9  0x400ef3f1:0x3ffdc6e0 in AsyncWebServer::_handleDisconnect(AsyncWebServerRequest*) at .pio/libdeps/firmware_ESP32/ESP Async WebServer/src/WebServer.cpp:102 (discriminator 1)
  #10 0x400ea763:0x3ffdc700 in AsyncWebServerRequest::_onDisconnect() at .pio/libdeps/firmware_ESP32/ESP Async WebServer/src/WebRequest.cpp:229
  #11 0x400ea771:0x3ffdc720 in std::_Function_handler::_M_invoke(std::_Any_data const&, void*&&, AsyncClient*&&) at .pio/libdeps/firmware_ESP32/ESP Async WebServer/src/WebRequest.cpp:74
      (inlined by) _M_invoke at c:\users\tobias\.platformio\packages\toolchain-xtensa-esp32@8.4.0+2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\bits/std_function.h:297
  #12 0x400e9aba:0x3ffdc740 in std::function::operator()(void*, AsyncClient*) const at c:\users\tobias\.platformio\packages\toolchain-xtensa-esp32@8.4.0+2021r2-patch3\xtensa-esp32-elf\include\c++\8.4.0\bits/std_function.h:687     
  #13 0x400e9cc9:0x3ffdc770 in AsyncClient::_error(signed char) at .pio/libdeps/firmware_ESP32/AsyncTCP/src/AsyncTCP.cpp:860     
  #14 0x400e9f7b:0x3ffdc7a0 in AsyncClient::_s_error(void*, signed char) at .pio/libdeps/firmware_ESP32/AsyncTCP/src/AsyncTCP.cpp:1207
      (inlined by) _handle_async_event at .pio/libdeps/firmware_ESP32/AsyncTCP/src/AsyncTCP.cpp:171
      (inlined by) _async_service_task at .pio/libdeps/firmware_ESP32/AsyncTCP/src/AsyncTCP.cpp:194

ELF file SHA256: 0000000000000000

Rebooting...
Upload Complete: /regs/Growatt-SPH.json ,size: 6992 Bytes
Guru Meditation Error: Core  0 panic'ed (LoadProhibited). Exception was unhandled.

Core  0 register dump:
PC      : 0x4011ff6c  PS      : 0x00060b30  A0      : 0x8011c01c  A1      : 0x3ffb3950  
A2      : 0x3ffd4be4  A3      : 0x00000018  A4      : 0x3ffd4bfc  A5      : 0x3ffc452c
A6      : 0x3ffef474  A7      : 0x00000080  A8      : 0x00007174  A9      : 0x3f40712c  
A10     : 0x00000001  A11     : 0x3ffd4be4  A12     : 0x3ffd4bfc  A13     : 0x00000b38
A14     : 0x3ffdc670  A15     : 0x00000000  SAR     : 0x00000009  EXCCAUSE: 0x0000001c  
EXCVADDR: 0x00000085  LBEG    : 0x4008950c  LEND    : 0x40089522  LCOUNT  : 0xffffffff

Backtrace:0x4011ff69:0x3ffb39500x4011c019:0x3ffb3990 0x400e915a:0x3ffb39b0 0x40118d48:0x3ffb39d0 

  #0  0x4011ff69:0x3ffb3950 in tcp_output at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/lwip/lwip/src/core/tcp_out.c:1305 (discriminator 4)
  #1  0x4011c019:0x3ffb3990 in tcp_recved at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/lwip/lwip/src/core/tcp.c:999
  #2  0x400e915a:0x3ffb39b0 in _tcp_recved_api(tcpip_api_call_data*) at .pio/libdeps/firmware_ESP32/AsyncTCP/src/AsyncTCP.cpp:419  #3  0x40118d48:0x3ffb39d0 in tcpip_thread_handle_msg at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/lwip/lwip/src/api/tcpip.c:172
      (inlined by) tcpip_thread at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/lwip/lwip/src/api/tcpip.c:154   

ELF file SHA256: 0000000000000000

Rebooting...

@me-no-dev do you have any suggestions?

Belleson commented 7 months ago

Maybe it's a watchdog time-out because server->on(...) wants a fast return. Make a separate thread to upload files, and trigger it from handleFiles::handleUpload, passing the parameters. For example, put request data in a struct and pass it (as reference) to a Ticker (separate library) callback.

tobiasfaust commented 7 months ago

Hi, Thanks for your answer. Are you sure of a watchdog Issue? To prevent that I inserted the rtc_wdt_feed(); The HEAP Issue seems like a buffer overflow…..

Belleson commented 7 months ago

Hi Tobias,

I agree it shows a heap error and I'm not sure it's a watchdog timer issue. I only know there are limits on how much can be done in a call back.

I make the suggestion because it solved a similar problem for my application. And it gives flexibility to process the uploads individually to reduce heap usage. Maybe also take a look at chunked response in the readme.

This great library has not been updated in a long time so you may have to find your own work around.

tobiasfaust commented 5 months ago

solved my myself ;)

i opened permanently AsyncResponseStream *response = request->beginResponseStream("text/json"); without using that. Solution is to move that code-fragment into final if-case.

That shows the problem, this call allocates memory without give free if it not used

final solution function:

void handleFiles::handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {

    String logmessage = "Client:" + request->client()->remoteIP().toString() + " " + request->url();
    Serial.println(logmessage);

  if (!index) {
    // open the file on first call and store the file handle in the request object
    request->_tempFile = LittleFS.open("/" + filename, "w");
      Serial.printf("Upload Start: %s\n", filename.c_str());
  }

  if (len) {
    // stream the incoming chunk to the opened file
    request->_tempFile.write(data, len);
    rtc_wdt_feed();
    Serial.printf("Writing file: %s ,index=%d len=%d bytes\n", filename.c_str(), index, len);
  }

  if (final) {
    AsyncResponseStream *response = request->beginResponseStream("text/json");
    response->addHeader("Server","ESP Async Web Server");
    // close the file handle as the upload is now done
    request->_tempFile.close();
    Serial.printf("Upload Complete: %s ,size: %d Bytes\n", filename.c_str(), (index + len));
  }
}