GyverLibs / FastBot

Многофункциональная быстрая библиотека для Телеграм бота на esp8266/esp32
MIT License
186 stars 31 forks source link

Unhandled C++ exception: OOM #3

Closed szabota closed 2 years ago

szabota commented 3 years ago

Прикрепил бота к проекту, который использует EmbUI. Всё хорошо работает, до момента обращения к web-интерфейсу из браузера - сразу вылетает сабж. Отключаю бота - проект работает.

GyverLibs commented 3 years ago

к сожалению ничем не могу помочь) бот использует стандартную SecureBearSSL либу и делает обычный GET запрос

szabota commented 3 years ago

И отдельно сам по себе бот норм работает

GyverLibs commented 3 years ago

И отдельно сам по себе бот норм работает

С этим не поспоришь)

szabota commented 3 years ago

Понял

GyverLibs commented 3 years ago

можно попробовать зарулить в код с EmbUI вот эти либы и создать экземпляр

#include <WiFiClientSecure.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
static std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
static HTTPClient https;

И сделать запрос

client->setInsecure();
https.begin(*client, (String)"https://api.telegram.org/")
https.GET();
https.end();
szabota commented 3 years ago

В общем, сейчас заметил, что при первом же вызове bot.tick(), сжирается сразу 20кб памяти. И вылеты с ошибкой связаны как раз с нехваткой памяти.

Обработчик сообщений пустой - просто Serail.print(msg.text)

GyverLibs commented 3 years ago

В чате много сообщений непрочитанных ботом? В библиотеке можно настроить лимит по количеству сообщений и количеству символов, которые бот вытащит из чата

szabota commented 3 years ago

Да нет, в чате несколько тестовых сообщений

GyverLibs commented 3 years ago

Ну вот попробовать сделать чистый гет запрос по токену и посмотреть что будет. Может библиотека ссл клиента выделяет себе буфер под страницу

szabota commented 3 years ago

Да, так и есть, память жрёт https.GET()

GyverLibs commented 3 years ago

Можно попробовать открыть либу, мб там есть настройки размера буфера

szabota commented 3 years ago

В общем, если объявление std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure) (без static) перенести в функции tickManualи sendRequest(и ещё туда же нужно перенести и client->setInsecure() из инициализации), то тогда "умный указатель" std::unique_ptr начнёт работать как положено и освобождать память. Вот тестовый скетч для проверки:

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>

#define BAUD_RATE 74880
#define SSID "SSID"
#define PASS "12345678"
#define URL  "https://api.telegram.org/bot1234567890:12345678901234567890123456789012345/getUpdates?limit=10&offset=0"

static HTTPClient https;

void logVar(const String &sender, const String &varName, long long varVal){
  Serial.println(sender + F("(): ") + varName + F(" = ") + String(varVal));
}

void logVar(const String &sender, const String &varName, const String &varVal){
  Serial.println(sender + F("(): ") + varName + F(" = ") + varVal);
}

bool isTimeHasCome(unsigned long seconds, unsigned long &prevMillis){
  bool it_is = millis() >= (prevMillis + seconds*1000);
  if ( it_is ) prevMillis = millis();
  return( it_is );
}

void TestGet(){
  int err;
  std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
  client->setInsecure();
  String url = URL;
  logVar(F("TestGetBegin"), F("FreeHeap"), ESP.getFreeHeap());
  logVar(F("TestGet"), F("url"), url);
  https.begin(*client, url);
    err=https.GET();
    if (err == HTTP_CODE_OK || err == HTTP_CODE_MOVED_PERMANENTLY) {
          String payload = https.getString();
          logVar(F("TestGet"), F("payload"), payload);
        }
    else logVar(F("TestGet"), F("https error:"), err);
  https.end();
  logVar(F("TestGetEnd"), F("FreeHeap"), ESP.getFreeHeap());
}

void connect(){
  Serial.print(F("Connecting to WiFi"));
  WiFi.begin(F(SSID), F(PASS));
  while (WiFi.status() != WL_CONNECTED) {
    delay(500); Serial.print(".");
    if (millis() > 15000) ESP.restart();
  }
  Serial.println(F("Connected!"));
}

void setup() {
  Serial.begin(BAUD_RATE);
  connect();
}

void loop() {
  static unsigned long  tmr1=0;
  static unsigned long  tmr2=0;

  if (isTimeHasCome(1,  tmr1)) logVar(F("loop"), F("FreeHeap"), ESP.getFreeHeap());
  if (isTimeHasCome(5,  tmr2)) TestGet();

}
GyverLibs commented 3 years ago

звучит логично. Но не будет ли слишком затратно на каждом тике всё это загружать и выгружать? Так то можно и переделать!

szabota commented 3 years ago

Тогда нужен выбор режима - для нагруженных систем "скоростной" либо для экономии памяти "тормозной"

szabota commented 3 years ago

А вообще, бот тикает раз в секунду и есть ли большой смысл занимать почти половину оперативки такое длительное время вообще без всякой на то нужды ?

GyverLibs commented 3 years ago

ну, я не знал что эта библа столько сожрёт если честно

GyverLibs commented 3 years ago

так не пойдёт, если отправлять реквест внутри тика (как в примере EchoBot, отправка сообщения из обработчика) - отправка не работает, а также создаются сразу два WiFiClientSecure. Я сейчас попробовал сделать так:
Создаём BearSSL::WiFiClientSecure *client; и bool clientExist = false; как члены класса И дальше вот такая конструкция для tickManual и sendRequest, которая позволяет им обоим работать с одним и тем же указателем и удалять его в любом случае.

    uint8_t tickManual() {
        uint8_t status = 1;
        clientExist = true;
        client = new BearSSL::WiFiClientSecure();
        client->setInsecure();
        if (https.begin(*client, (String)_FB_host + _token + "/getUpdates?limit=" + _limit + "&offset=" + ID)) {
            if (https.GET() == HTTP_CODE_OK) status = parse(https.getString());
            else status = 3;
            https.end();
        } else status = 4;
        delete client;
        clientExist = false;
        return status;
    }
    uint8_t sendRequest(String& req) {
        uint8_t status = 1;
        if (!clientExist) {
            client = new BearSSL::WiFiClientSecure();
            client->setInsecure();
        }
        if (https.begin(*client, req)) {
            if (https.GET() != HTTP_CODE_OK) status = 3;
            https.end();
        } else status = 4;
        if (!clientExist) delete client;
        return status;
    }

Всё работает. Можно так сделать?

szabota commented 3 years ago

Потестировал и так, и эдак, и стало понятно, что это всё безполезно. Дело в том, что запрос https.GET() отрабатывает в среднем 1200 миллисекунд и всё это время занято больше половины оперативки. И не получается отрисовать вэб-нитерфейс с такими затратами ресурсов - постоянно крашится с ошибкой OOM. Кстати, пока это всё тестировал, заметил, что нет смысла тикать ботом чаще, чем раз в 4000мс., не позволяет сервер - там похоже ограничение где-то секунд 5 между ответами.

GyverLibs commented 3 years ago

Не, где то ошибка. К серверу можно стучаться раз 5 в секунду, я обрабатывал почти сотню сообщений в секунду за 4 запроса во время эксперимента

szabota commented 3 years ago

Всё-таки есть возможность управлять размером буфера: client->setBufferSizes(512, 512); (перед https.begin) Судя по тестам экономия памяти довольно существенная!

szabota commented 3 years ago

Вот с этими правками всё работает отлично и памяти жрет примерно в три раза меньше:

В функции tickManual:

        #ifdef FB_DYNAMIC_HTTP
        clientExist = true;
        client = new BearSSL::WiFiClientSecure();
        client->setBufferSizes(512, 512);
        client->setInsecure();
        #endif

и в функции sendRequest:

        #ifdef FB_DYNAMIC_HTTP
        if (!clientExist) {
            client = new BearSSL::WiFiClientSecure();
            client->setBufferSizes(512, 512);
            client->setInsecure();
        }
        #endif
GyverLibs commented 2 years ago

добавил возможность задать размер буфера в дефайне, появится в v1.7

GyverLibs commented 2 years ago

так погоди, динамический режим вообще нужен? По моим тестам создание нового WiFiClientSecure занимает 1 секунду, это дофига. В то же время объект сожрёт минимум 1 кб рамы, так как там 2x512 буфер минимум. Но его можно сделать общим на всю библиотеку, мало ли несколько ботов надо. В то же время оперативки на борту 50 килобайт

GyverLibs commented 2 years ago

убрал крч