• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Обсуждение Утечка памяти

Atom

Member
Создал из примера простой скетч:
Код:
/*
  ESP8266 mDNS responder sample

  This is an example of an HTTP server that is accessible
  via http://esp8266.local URL thanks to mDNS responder.

  Instructions:
  - Update WiFi SSID and password as necessary.
  - Flash the sketch to the ESP8266 board
  - Install host software:
    - For Linux, install Avahi (http://avahi.org/).
    - For Windows, install Bonjour (http://www.apple.com/support/bonjour/).
    - For Mac OSX and iOS support is built in through Bonjour already.
  - Point your browser to http://esp8266.local, you should see a response.

*/


#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiClient.h>

const char* ssid = "CODEROPI";
const char* password = "SDFys!ccX7";

// TCP server at port 80 will respond to HTTP requests
WiFiServer server(80);

void setup(void)
{ 
  Serial.begin(115200);

    uint32_t realSize = ESP.getFlashChipRealSize();
    uint32_t ideSize = ESP.getFlashChipSize();
    FlashMode_t ideMode = ESP.getFlashChipMode();

    Serial.printf("Flash real id:   %08X\n", ESP.getFlashChipId());
    Serial.printf("Flash real size: %u\n\n", realSize);

    Serial.printf("Flash ide  size: %u\n", ideSize);
    Serial.printf("Flash ide speed: %u\n", ESP.getFlashChipSpeed());
    Serial.printf("Flash ide mode:  %s\n", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN"));

    if(ideSize != realSize) {
        Serial.println("Flash Chip configuration wrong!\n");
    } else {
        Serial.println("Flash Chip configuration ok.\n");
    }


  //****************************
 
  // Connect to WiFi network
  WiFi.begin(ssid, password);
  Serial.println(""); 
 
  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Set up mDNS responder:
  // - first argument is the domain name, in this example
  //   the fully-qualified domain name is "esp8266.local"
  // - second argument is the IP address to advertise
  //   we send our IP address on the WiFi network
  if (!MDNS.begin("esp8266")) {
    Serial.println("Error setting up MDNS responder!");
    while(1) {
      delay(1000);
    }
  }
  Serial.println("mDNS responder started");
 
  // Start TCP (HTTP) server
  server.begin();
  Serial.println("TCP server started");
 
  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
}

void loop(void)
{
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }
  Serial.println("");
  Serial.println("New client");

  // Wait for data from client to become available
  while(client.connected() && !client.available()){
    delay(1);
  }
 
  // Read the first line of HTTP request
  String req = client.readStringUntil('\r');
 
  // First line of HTTP request looks like "GET /path HTTP/1.1"
  // Retrieve the "/path" part by finding the spaces
  int addr_start = req.indexOf(' ');
  int addr_end = req.indexOf(' ', addr_start + 1);
  if (addr_start == -1 || addr_end == -1) {
    Serial.print("Invalid request: ");
    Serial.println(req);
    return;
  }
  req = req.substring(addr_start + 1, addr_end);
  Serial.print("Request: ");
  Serial.println(req);
  client.flush();
 
  String s;
  if (req == "/")
  {
    IPAddress ip = WiFi.localIP();
    String ipStr = String(ip[0]) + '.' + String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]);
    s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>Hello from ESP8266 at ";
    s += ipStr;
    s += "<br>";
    s += String(ESP.getFreeHeap());
    s += "</html>\r\n\r\n";
    Serial.println("Sending 200");
  }
  else
  {
    s = "HTTP/1.1 404 Not Found\r\n\r\n";
    Serial.println("Sending 404");
  }
  client.print(s);
 
  Serial.println("Done with client");
}
Как видно из кода, на странице вывожу размер свободного хипа. Каждый раз при обращении отгрызается кусок свободной памяти, который обратно не возвращается. За три минуты нажатия кноповки F5 в браузере размер хипа доходит до нуля и модуль крушится.

Кто нить знает как победить такую беду?
C ESP8266WebServer такая же проблема.
 

pvvx

Активный участник сообщества
Создал из примера простой скетч:

Как видно из кода, на странице вывожу размер свободного хипа. Каждый раз при обращении отгрызается кусок свободной памяти, который обратно не возвращается. За три минуты нажатия кноповки F5 в браузере размер хипа доходит до нуля и модуль крушится.

Кто нить знает как победить такую беду?
C ESP8266WebServer такая же проблема.
По видимому вы закрываете TCP соединение первым, что сервер обычно не должен делать. Остается открытая структура TCP с состоянием TIME_WAIT на 120 секунд: TIME_WAIT and its design implications for protocols and scalable client server systems - AsynchronousEvents
 

pvvx

Активный участник сообщества
delete s; после client.print(s); пробовали?
Зачем? там стоит s="текст". Вынести s в глобал и всё.
B нет никакой уверенности, что в ESP адрес переменной s другой. От куда в нем возьмется смещение стека и прочего? Это не новый task ;)
Т.е. хотите наплодить TIME_WAIT? ;)
 

pvvx

Активный участник сообщества
И это правильно товарищи...
Ну тогда и перетранслировать LwIP с опцией MEMP_MEM_MALLOC = 0
#if MEMP_MEM_MALLOC == 0 tcp_alloc() уже управляет убийством последней pcb TIME_WAIT при создании новой pcb от безысходности :)
#else при закрытии по своей инициативе соединений TCP будет создано и оставлено pcb с TIME_WAIT на 120 секунд пока не закончится HEAP у ESP8266б, т.к. он мал, а закрыть TCP соединений на нем можно до нескольких десятков в секунду ....
 
Последнее редактирование:

Atom

Member
Зачем? там стоит s="текст". Вынести s в глобал и всё.
B нет никакой уверенности, что в ESP адрес переменной s другой. От куда в нем возьмется смещение стека и прочего? Это не новый task ;)
Т.е. хотите наплодить TIME_WAIT? ;)
Это это модифицированный код из примера. То есть я не изобретал велосипед - добавил только вывод сообщения о размере хипа.

Провел эксперименты с ESP8266WebServer. Картина такая же - отгрызает кусок в 184 байт. вот код нового скетча.
Код:
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

const char* ssid = "AQUAFILL";
const char* password = "aSdf77!c";

ESP8266WebServer server(80);

const int led = 13;

void handleRoot() {
  digitalWrite(led, 1);
  
  server.send(200, "text/html", "hello from esp8266!");
  
  digitalWrite(led, 0);

  Serial.println(ESP.getFreeHeap());
}

void handleNotFound(){
  digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  digitalWrite(led, 0);
}

void setup(void){
  pinMode(led, OUTPUT);
  digitalWrite(led, 0);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);

  server.on("/inline", [](){
    server.send(200, "text/plain", "this works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void){
  server.handleClient();
}

Теперь вывод на сериал, а не в браузер и после окончания всех процедур. Вывод коррктный: размер хипа остается прежним. Но такой код не есть цель. Чуть изменим его:
Код:
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

const char* ssid = "AQUAFILL";
const char* password = "aSdf77!c";

ESP8266WebServer server(80);

const int led = 13;

void handleRoot() {
  digitalWrite(led, 1);
  server.setContentLength(200);
  server.send(200, "text/html", "hello from esp8266!");
  server.sendContent("<br>FreeHeap = " + String(ESP.getFreeHeap()));
  server.sendContent("                                                                          ");
  //server.close();
  //server.begin();
  digitalWrite(led, 0);

  Serial.println(ESP.getFreeHeap());
}

void handleNotFound(){
  digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  digitalWrite(led, 0);
}

void setup(void){
  pinMode(led, OUTPUT);
  digitalWrite(led, 0);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);

  server.on("/inline", [](){
    server.send(200, "text/plain", "this works as well");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void){
  server.handleClient();
}

Результат утечки тот же. Причем если подождать две минуты, то хип возвращается назад. Если сразу же обновить страницу, то ткого эффекта уже не получить.
IP address: 192.168.1.4
MDNS responder started
HTTP server started
42856
42672
42488
42304
42120
41936
42832

Между предпоследним вызовом и последним прошло около трех минут. Память почти восстановилась.


По поводу закрытия соединения, как видно из кода, пробовал и такую попытку. Не помогает. Окромя этого в заголовке стоит sendHeader("Connection", "close"). Это значит клиент сам должен закрыть соединение после получения количества байт из заголовка.

Получается, что чаще, чем раз в две минуты к модулю лучше не обращаться. иначе неизбежен крэш.
 

Atom

Member
Ну тогда и перетранслировать LwIP с опцией MEMP_MEM_MALLOC = 0
#if MEMP_MEM_MALLOC == 0 tcp_alloc() уже управляет убийством последней pcb TIME_WAIT при создании новой pcb от безысходности :)
#else при закрытии по своей инициативе соединений TCP будет создано и оставлено pcb с TIME_WAIT на 120 секунд пока не закончится HEAP у ESP8266б, т.к. он мал, а закрыть TCP соединений на нем можно до нескольких десятков в секунду ....
Эмммм. А можно для нубов как то по проще объяснить? Что нужно сделать и где?
 

pvvx

Активный участник сообщества
Эмммм. А можно для нубов как то по проще объяснить? Что нужно сделать и где?
Там, в SDK, где-то есть опция убивать TIME_WAIT. Но это не совсем корректно.
Как её включить в Arduino IDE я не знаю. Обратитесь к писателям класса сервера и/или клиента на данную Arduino.
Более правильнее поставить проверку, к примеру на накопление более N значений pcb с TIME_WAIT и удалять самую старую. Так-же в сервере не закрывать соединение, а поставить задержку на N секунд для ожидания закрытия со стороны клиента, на случай если клиент не хочет закрывать соединение первым, по протоколу (так ведут себя многие прокси).
LwIP с опцией MEMP_MEM_MALLOC = 0 - это статическая модель буферов у LwIP и она постоянно занимает память в bss, под массив pcb. Для ESP это ещё ограничит память.
 
Последнее редактирование:

Atom

Member
Ну тогда и перетранслировать LwIP с опцией MEMP_MEM_MALLOC = 0
#if MEMP_MEM_MALLOC == 0 tcp_alloc() уже управляет убийством последней pcb TIME_WAIT при создании новой pcb от безысходности :)
#else при закрытии по своей инициативе соединений TCP будет создано и оставлено pcb с TIME_WAIT на 120 секунд пока не закончится HEAP у ESP8266б, т.к. он мал, а закрыть TCP соединений на нем можно до нескольких десятков в секунду ....

Как я понял, мне нужно было в SDK, файл lwipopts.h изменить этот дефайн на 0. Сделал это, перезалил проект и результат не изменился:
IP address: 192.168.1.4
MDNS responder started
HTTP server started
42856
42672
42488
42304
42120
41936
 

pvvx

Активный участник сообщества
Как я понял, мне нужно было в SDK, файл lwipopts.h изменить этот дефайн на 0. Сделал это, перезалил проект и результат не изменился:
IP address: 192.168.1.4
MDNS responder started
HTTP server started
42856
42672
42488
42304
42120
41936
В Arduino LwIP уже в бинарной либе. Надо транслировать полностью, из исходников. Но их полных нет у Ардуинщиков - Espressif не дает исходник между уровнем WiFi и LwIP. Я использую дизассемблированный в CИ вариант ("реверс" куска их закрытой либы)...
---
Когда испытывал JMeter - он "говорит странную цифру - 228 в секунду" соединений ТСP в секунду может позволить сервер TCP на ESP8266 без всяких убийств time_wait, если всё корректно описано... Разработка ‘библиотеки’ малого webсервера на esp8266.
 

Atom

Member
тут чувак выбирает клиента пока он avalable и закрывает его после тайаута в 500мс running out of heap · Issue #1461 · esp8266/Arduino · GitHub
Чувачек не плох. Да и идея золотая. Небольшой нюанс: примеры - есть примеры. То, что у каждого есть и ниебольшие отличия. В реалии у меня все на ESP8266WebServer заточено. Попробую сначала в оригинальном классе поковыряться, а потом перенаследю в свой новый.

Но думаю, это мало поможет: навоз всплывает где то уровнем ниже. Где - понять трудно. Подозреваю, что в tcp. попозже попробую тест сделать на обычном соединении - например telnet-uart. Хотя это каждый сам может организовать в своем проекте.
Почему грешу на tcp, так это из-за того, что при отправке страницы в одной переменной утечки не возникает. Но это на мелких контекстах. У меня форма собирается из кусков html-тегов на 5кб. И это не предел - будет больше. Создавать переменную со значением такого размера как то боязно. Но ради эксперимента попробую.
 

EzdiliDwaTanka

New member
у меня циклическая утечка памяти еще присутствует(46... ->38... ->46), но теперь хоть не виснет ESP c датчиком
 
Сверху Снизу