Skip to content

Potential memory leak of 312 bytes when using the SSE feature #167

@stephanpelikan

Description

@stephanpelikan

Platform

ESP8266

IDE / Tooling

Arduino (IDE/CLI)

What happened?

I'm using SSE feature. It seems, there is a leek of 312 bytes for each client connected. I've setup a minimum application (see MRE). It prints the HEAP every minute and also prints clients connecting and disconnecting:

12:06:08.501 -> Connected to WiFi: 192.168.88.27
12:07:04.537 -> 45992
12:08:04.544 -> 45992
12:08:41.651 -> Connect client: 42510 -> 1
12:09:04.577 -> 45464
12:10:04.571 -> 45464
12:10:05.887 -> Disconnect client: 0 -> 0
12:11:04.556 -> 45680
12:12:04.565 -> 45680
12:12:17.605 -> Connect client: 43500 -> 1
12:13:04.565 -> 45152
12:13:06.468 -> Disconnect client: 0 -> 0
12:14:04.582 -> 45368
12:15:04.576 -> 45368

As you can see the difference of heap before a client is connected and after a client disconnected is always 312 bytes never returned to heap. With this leak after 144 clients connected the system will crash. I guess even earlier because actions taken by clients also need heap temporarily. It is totally OK to spent a fraction of the heap for clients but this leak seems to be linear.

Is there something I'm doing wrong or is this a bug?

Versions used:

  • ESP Async TCP 2.0.0
  • ESP Async Webserver 3.7.7

Stack Trace

I'v a menu "Debug level" in ArduinoIDE but setting to any value like "CORE" or "HTTP_CLIENT+HTTP_SERVER" did not change the output in serial monitor which above.

Minimal Reproductible Example (MRE)

I created a tiny React web-application using the SSE: data.zip.
It is uploaded using the ArduinoIDE plugin as shown here.

This is the ESP8266 application the web-client connects to:

#define WIFI_SSID "YOUR-SSID"
#define WIFI_PASSWORD "YOUR-PWD"
#define WWW_USERNAME "username"
#define WWW_PASSWORD "password"

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"

AsyncWebServer httpRestServer(80);
WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
AsyncEventSource statusEvents("/api/status-events");
uint8_t numberOfStatusEventsClients = 0;

void webappInitStatus(AsyncEventSourceClient *client) {

  if(client->lastId()){
    Serial.printf_P(PSTR("Client reconnected! Last message ID that it got is: %u\n"), client->lastId());
  }
  if (client->connected()) {
    numberOfStatusEventsClients += 1;
    Serial.printf_P(PSTR("Connect client: %u -> %u\n"), client->client()->remotePort(), numberOfStatusEventsClients);
    client->client()->onDisconnect(disconnectStatusClient, NULL);
    updateStatusClients();
  }

}

void updateStatusClients() {

  statusEvents.send(F("{}"), F("INIT"), millis());

}

void disconnectStatusClient(void *arg, AsyncClient *client) {

  numberOfStatusEventsClients -= 1;
  Serial.printf_P(PSTR("Disconnect client: %u -> %u\n"), client->remotePort(), numberOfStatusEventsClients);

}

void handleNotFound(AsyncWebServerRequest *request) {

  Serial.println("Not found");
  String message = F("File Not Found\n\n");
  message += F("URI: ");
  message += request->url();
  message += F("\nMethod: ");
  message += request->methodToString();
  message += "\n";
  request->send(404, F("text/plain"), message);

}

void onWifiConnect(const WiFiEventStationModeGotIP& event) {

  Serial.print(F("Connected to WiFi: "));
  Serial.println(WiFi.localIP().toString());

  httpRestServer.onNotFound(handleNotFound);
  httpRestServer
      .serveStatic("/", LittleFS, "/www/")
      .setDefaultFile("index.html")
      .setCacheControl("no-cache, no-store, max-age=0")
      .setAuthentication(WWW_USERNAME, WWW_PASSWORD);
  statusEvents.onConnect(webappInitStatus);
  httpRestServer.addHandler(&statusEvents);
  httpRestServer.begin();

}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {

  httpRestServer.end();

}

void setup() {

  Serial.begin(115200);
  Serial.println("\n\n\n\nStarted\n\n\n\n");

  if(!LittleFS.begin()) {
    Serial.println(F("An error has occurred on mounting LittleFS"));
    return;
  }

  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

  WiFi.persistent(false);
  WiFi.setAutoReconnect(false); // reconnect is done manually every minute
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

}

unsigned long previousTime = 0;

void loop() {

  unsigned long currentMillis = millis();
  if (currentMillis < previousTime) { // handle overflow
    previousTime = currentMillis;
    return;
  }
  if (currentMillis - previousTime < 60000) {
    return;
  }
  previousTime = currentMillis;

  Serial.println(ESP.getFreeHeap(), DEC);

  updateStatusClients(); // ping SSE clients

}

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions