Potential memory leak of 312 bytes when using the SSE feature #170
Replies: 7 comments 2 replies
-
Hello @stephanpelikan : you would need to determine first if the leak is caused by ESPAsyncTCP (ESP8266) or AsyncTCP (ESP32) or ESPAsyncWebServer. We do not support ESP8266 anymore. No dev and no fix on ESPAsyncTCP. anymore Also, FYI, on ESP32, there is also a decrease of heap as soon as LWIP layers are called. So for this issue:
Until then, there is nothing to work on on our side, sorry... |
Beta Was this translation helpful? Give feedback.
-
I ordered an ESP32 and will try to reproduce it. An additional note that may be of interest to other readers: I noticed already crashes when connecting a client from time to time. I couldn't figure out why this sometimes happens and why not. Yesterday, I kept the site open in Chrome and noticed a connection loss of SSE request every minute (approximately) which is recovered by the client. In this scenario each lost connection causes a heap loss. When accessing the site from a second device, shipping the site takes to much memory and it crashes. As a workaround I send a "ping" message every 30 seconds. This keeps the connection open and heap loss only occurs when closing the site in the browser. Heap loss without the workaround:
Heap loss having the workaround in place:
Actually, there is memory freed. Not all, but more than without. |
Beta Was this translation helpful? Give feedback.
-
@stephanpelikan : what you can do is also try with previous versions of ESPAsyncWebServer. |
Beta Was this translation helpful? Give feedback.
-
@mathieucarbou: I received the ESP32 32u and ported the software. The leak is still there. With ESP32 it is 432 bytes. First connection consumed more (1648 bytes) what is fine, but subsequent were 432 each and last 440 bytes:
I also did further tests: Sometimes the leak is 600, 564 bytes, sometimes, 400, 436 or 432 bytes per client connection. Versions used:
This is the code used: #define WIFI_SSID "YOUR-SSID"
#define WIFI_PASSWORD "YOUR-PWD"
#define WWW_USERNAME "username"
#define WWW_PASSWORD "password"
#include <esp_wifi.h>
#include <ESPAsyncWebServer.h>
#include "LittleFS.h"
AsyncWebServer httpRestServer(80);
WiFiEventId_t wifiConnectEvent;
WiFiEventId_t wifiDisconnectEvent;
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(WiFiEvent_t event, WiFiEventInfo_t info) {
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(WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.println(F("Disconnected from WiFi, will reconnect"));
httpRestServer.end();
delay(1000);
reconnect();
}
void reconnect() {
WiFi.persistent(false);
WiFi.setAutoReconnect(false); // reconnect is done manually every minute
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
}
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;
}
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
wifiConnectEvent = WiFi.onEvent(onWifiConnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP);
wifiDisconnectEvent = WiFi.onEvent(onWifiDisconnect, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
WiFi.disconnect(true, true);
delay(1000);
reconnect();
}
unsigned long previousTime = 0;
unsigned long previousPing = 0;
void loop() {
unsigned long currentMillis = millis();
if (currentMillis < previousTime) { // handle overflow
previousTime = currentMillis;
previousPing = currentMillis;
return;
}
if (currentMillis - previousPing > 30000) {
statusEvents.send(F("{}"), F("PING"), currentMillis);
previousPing = currentMillis;
}
if (currentMillis - previousTime < 60000) {
return;
}
previousTime = currentMillis;
Serial.println(ESP.getFreeHeap(), DEC);
} |
Beta Was this translation helpful? Give feedback.
-
I will have a deeper look when I will have more time. fyi, only |
Beta Was this translation helpful? Give feedback.
-
@stephanpelikan : I think I know what is happening... When you do that: _client->onDisconnect(
[this](void *r, AsyncClient *c) {
static_cast<AsyncEventSourceClient *>(r)->_onDisconnect();
delete c;
},
this
); So these cleanup lines are not called: static_cast<AsyncEventSourceClient *>(r)->_onDisconnect(); Which obviously leaks memory. Please have a look at the You must NEVER use the AsyncTCP client() internals. Please use instead the callbacks provided by the events.onConnect([](AsyncEventSourceClient *client) {
Serial.printf("SSE Client connected! ID: %p\n", client);
client->send("hello!", NULL, millis(), 1000);
});
events.onDisconnect([](AsyncEventSourceClient *client) {
Serial.printf("SSE Client disconnected! ID: %p\n", client);
}); |
Beta Was this translation helpful? Give feedback.
-
Thank you for this clarification! Maybe I simply didn't see that |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
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:
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:
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:
I confirm that:
Beta Was this translation helpful? Give feedback.
All reactions