Skip to content

Lost writes using AsyncCallbackResponse during low memory #242

@jonny5532

Description

@jonny5532

Platform

ESP32

IDE / Tooling

Arduino (IDE/CLI)

What happened?

When sending a 16kb response in three parts, using AsyncCallbackResponse, the middle one sometimes goes missing (so the received data is ~5k shorter than expected - the page finishes but with ~5k fewer bytes than the Content-Length indicated).

This seems to be due to this:

_writtenLength += request->client()->write((const char *)buf, outLen);

where the code assumes that the whole buffer got written (it frees it after), even if it was only partially sent. Ideally it could signal to the AsyncCallbackResponse that it needs to rewind, but being in the Abstract superclass it can't access _filledLength to decrement it.

AsyncTCP's write(...) allocates memory by default, and this system is very memory constrained, so that is probably why it is not consuming the whole buffer (probably not any of it). It looks like the code above does check space() though, but that doesn't take into account the free memory that the write() might need to allocate.

Maybe this is a situation you don't want to handle - as there's no guarantee there'll ever be enough free RAM? In practice it works if I rewind (eg. by adding a rewind method which the AsyncCallbackResponse can implement to roll back _filledLength).

Thanks!

Stack Trace

N/A

Minimal Reproductible Example (MRE)

This is the essence of it, but could build a full MRE if needed?

String *largeResponseString = new String("16k chars...");

AsyncWebServerResponse *response = request->beginResponse(
  "text/html",
  largeResponseString->length(),
  [](uint8_t *buffer, size_t maxLen, size_t alreadySent) -> size_t {
    // Calculate how much we can send in this chunk
    size_t remaining = largeResponseString->length() - alreadySent;
    size_t toSend = (remaining < maxLen) ? remaining : maxLen;

    // Copy the data from the content string to the buffer
    memcpy(buffer, largeResponseString->c_str() + alreadySent, toSend);
    // Return the number of bytes sent
    return toSend;
  },
  (AwsTemplateProcessor)nullptr  // No template processor needed
);
request->send(response);

I confirm that:

  • I have read the documentation.
  • I have searched for similar discussions.
  • I have searched for similar issues.
  • I have looked at the examples.
  • I have upgraded to the lasted version of ESPAsyncWebServer (and AsyncTCP for ESP32).

Metadata

Metadata

Assignees

Labels

Type: BugSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions