• Уважаемые посетители сайта esp8266.ru!
    Мы отказались от размещения рекламы на страницах форума для большего комфорта пользователей.
    Вы можете оказать посильную поддержку администрации форума. Данные средства пойдут на оплату услуг облачных провайдеров для сайта esp8266.ru
  • Система автоматизации с открытым исходным кодом на базе 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 датчиком
 
Сверху Снизу