Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switching from ArduinoJson 6 to 7 leads to a random assertion failure at runtime: const VariantPoolList.hpp:98 (poolIndex < count_ #2034

Closed
mathieucarbou opened this issue Jan 11, 2024 · 10 comments
Labels
question v7 ArduinoJson 7

Comments

@mathieucarbou
Copy link

mathieucarbou commented Jan 11, 2024

I cannot use the the Arduino Json Troubleshooter since the issue happens when building a json document, when allocating memory.

I do not use any custom allocator.

Board is the standard ESP32 NodePCU Dev Kit from AZ-Delivery.

I have no idea what to do or how to troubleshoot this...

How I am using the JsonDocument:

JsonDocument doc;
// adding fields
// check if overflowed(doc) is true
// if yes, I am calling send(...) which serialises and clears the doc
// then the caller continues to fill the doc for the next websocket batch
// check if overflowed(doc) is true
// etc...

void ESPDash::send(AsyncWebSocketClient* client, JsonDocument& json) {
  const size_t size = measureJson(json);
  AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(size);

  if (!buffer) {
    json.clear();
    return;
  }

  serializeJson(json, buffer->get(), size);

#ifdef DASH_DEBUG
  Serial.printf("T [DASH] client=%u, measureJson=%u\n", (client == nullptr ? -1 : client->id()), size);
#endif

  if (client != nullptr) {
    client->text(buffer);
  } else {
    _ws->textAll(buffer);
  }

  json.clear();
}

bool ESPDash::overflowed(JsonDocument& json) {
#if ARDUINOJSON_VERSION_MAJOR == 7
  return measureJson(json) > DASH_JSON_SIZE;
#else
  return json.overflowed();
#endif
}

Stack trace:


Backtrace: 0x400838e5:0x3ffe9ae0 0x4008dbcd:0x3ffe9b00 0x40093719:0x3ffe9b20 0x400dd240:0x3ffe9c50 0x40124079:0x3ffe9cf0 0x40126375:0x3ffe9d10 0x4012880e:0x3ffea410 0x40128d31:0x3ffea490 0x401c579d:0x3ffea4c0 0x401c57c2:0x3ffea510 0x401c609d:0x3ffea540 0x401c6211:0x3ffea580 0x401c2d17:0x3ffea5a0 0x401c2d66:0x3ffea5d0 0x401c2da9:0x3ffea5f0 0x401c2f82:0x3ffea610 0x401c2ff5:0x3ffea630

  #0  0x400838e5:0x3ffe9ae0 in panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:408
  #1  0x4008dbcd:0x3ffe9b00 in esp_system_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c:137
  #2  0x40093719:0x3ffe9b20 in __assert_func at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/assert.c:85
  #3  0x400dd240:0x3ffe9c50 in ArduinoJson::V701PB2::detail::VariantPoolList::getSlot(unsigned short) const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp:98
      (inlined by) ArduinoJson::V701PB2::detail::VariantPoolList::allocFromFreeList() at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/VariantPoolImpl.hpp:71
      (inlined by) ArduinoJson::V701PB2::detail::VariantPoolList::allocSlot(ArduinoJson::V701PB2::Allocator*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp:73
      (inlined by) ArduinoJson::V701PB2::detail::ResourceManager::allocSlot() at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/ResourceManager.hpp:53
      (inlined by) ArduinoJson::V701PB2::detail::CollectionData::addSlot(ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp:52
      (inlined by) ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::ObjectData::addMember<ArduinoJson::V701PB2::detail::StaticStringAdapter>(ArduinoJson::V701PB2::detail::StaticStringAdapter, ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/ObjectData.hpp:27
      (inlined by) ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::ObjectData::getOrAddMember<ArduinoJson::V701PB2::detail::StaticStringAdapter>(ArduinoJson::V701PB2::detail::StaticStringAdapter, ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp:24
  #4  0x40124079:0x3ffe9cf0 in ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::VariantData::getOrAddMember<ArduinoJson::V701PB2::detail::StaticStringAdapter>(ArduinoJson::V701PB2::detail::StaticStringAdapter, ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp:226
  #5  0x40126375:0x3ffe9d10 in ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*>::getOrCreateData() const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp:59 (discriminator 4)
      (inlined by) ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::VariantAttorney::getOrCreateData<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*> const>(ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*> const&) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantAttorney.hpp:32 (discriminator 4)
      (inlined by) ArduinoJson::V701PB2::detail::VariantRefBase<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*> >::getOrCreateData() const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp:268 (discriminator 4)
      (inlined by) ArduinoJson::V701PB2::detail::VariantRefBase<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*> >::getOrCreateVariant() const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp:100 (discriminator 4)
      (inlined by) bool ArduinoJson::V701PB2::detail::VariantRefBase<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*> >::set<char const>(char const*) const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp:144 (discriminator 4)
      (inlined by) ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*>& ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonDocument&, char const*>::operator=<char const>(char const*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp:39 (discriminator 4)
      (inlined by) ESPDash::generateLayoutJSON(AsyncWebSocketClient*, bool, Card*) at lib/ESPDASHPro/src/ESPDashPro.cpp:365 (discriminator 4)
  #6  0x4012880e:0x3ffea410 in ESPDash::ESPDash(AsyncWebServer*, char const*, bool)::{lambda(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int)#3}::operator()(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int) const at lib/ESPDASHPro/src/ESPDashPro.cpp:91
  #7  0x40128d31:0x3ffea490 in std::_Function_handler<void (AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int), ESPDash::ESPDash(AsyncWebServer*, char const*, bool)::{lambda(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int)#3}>::_M_invoke(std::_Any_data const&, AsyncWebSocket*&&, AsyncWebSocketClient*&&, AwsEventType&&, void*&&, unsigned char*&&, unsigned int&&) at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:297
  #8  0x401c579d:0x3ffea4c0 in std::function<void (AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int)>::operator()(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int) const at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:687
  #9  0x401c57c2:0x3ffea510 in AsyncWebSocket::_handleEvent(AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int) at .pio/libdeps/pro-esp32-debug/ESPAsyncWebServer-esphome/src/AsyncWebSocket.cpp:862
  #10 0x401c609d:0x3ffea540 in AsyncWebSocketClient::_onData(void*, unsigned int) at .pio/libdeps/pro-esp32-debug/ESPAsyncWebServer-esphome/src/AsyncWebSocket.cpp:682
  #11 0x401c6211:0x3ffea580 in std::_Function_handler<void (void*, AsyncClient*, void*, unsigned int), AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest*, AsyncWebSocket*)::{lambda(void*, AsyncClient*, void*, unsigned int)#7}>::_M_invoke(std::_Any_data const&, void*&&, AsyncClient*&&, std::_Any_data const&, unsigned int&&) at .pio/libdeps/pro-esp32-debug/ESPAsyncWebServer-esphome/src/AsyncWebSocket.cpp:481
      (inlined by) _M_invoke at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:297
  #12 0x401c2d17:0x3ffea5a0 in std::function<void (void*, AsyncClient*, void*, unsigned int)>::operator()(void*, AsyncClient*, void*, unsigned int) const at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:687
  #13 0x401c2d66:0x3ffea5d0 in AsyncClient::_recv(tcp_pcb*, pbuf*, signed char) at .pio/libdeps/pro-esp32-debug/AsyncTCP-esphome/src/AsyncTCP.cpp:961
  #14 0x401c2da9:0x3ffea5f0 in AsyncClient::_s_recv(void*, tcp_pcb*, pbuf*, signed char) at .pio/libdeps/pro-esp32-debug/AsyncTCP-esphome/src/AsyncTCP.cpp:1253
  #15 0x401c2f82:0x3ffea610 in _handle_async_event(lwip_event_packet_t*) at .pio/libdeps/pro-esp32-debug/AsyncTCP-esphome/src/AsyncTCP.cpp:164
  #16 0x401c2ff5:0x3ffea630 in _async_service_task(void*) at .pio/libdeps/pro-esp32-debug/AsyncTCP-esphome/src/AsyncTCP.cpp:199
@bblanchon
Copy link
Owner

Hi @mathieucarbou,

This looks like a corrupted JsonDocument.
Are you sure the received reference is pointing to a valid object?

The stack trace shows a generateLayoutJSON() called from a lambda, called by a callback handler.
None of this is visible in the code you shared.
Could you reproduce this bug in an MCVE?

Best regards,
Benoit

@mathieucarbou
Copy link
Author

generateLayoutJSON

Hello,

Yes that's what I am thinking: I discovered yesterday that the library I use is not thread safe and is calling this generateLayoutJSON method that is populating a json document both from the async_http task and the loop task. I am digging into the issue and close this one. thanks!

@mathieucarbou
Copy link
Author

mathieucarbou commented Jan 12, 2024

@bblanchon : I have to re-open it... I have removed any potential concurrent access: now, the generateLayout method is only called once from the async_http task (websocket handler) and nowhere else.. Also, the json document reads data that is initialised once, stays as references in private fields in a static class, and this data is not modified concrurently.

While the json document is building, I have this error again, and constantly:

D         5142 YaSolR-I/O (1) HA-DISCO Publishing HA Discovery configuration for sensor output2_state...
D         5186 YaSolR-I/O (1) HA-DISCO Publishing HA Discovery configuration for sensor output2_routed_power...
D         5290 YaSolR-I/O (1)       HA Published in 2176 ms
W         5293 YaSolR-I/O (1)      I/O Abnormal loop duration: 2179 ms
D         5434 loopTask   (1)  DISPLAY Updated in 49 ms
[  6805][W][AsyncTCP.cpp:950] _poll(): rx timeout 4
D         7610 YaSolR-I/O (1)      I/O Published in 2262 ms
W         7613 YaSolR-I/O (1)      I/O Abnormal loop duration: 2265 ms
T         8130 async_tcp  (1) ESP-DASH client=1, measureJson=612

assert failed: ArduinoJson::V701PB2::detail::VariantSlot* ArduinoJson::V701PB2::detail::VariantPoolList::getSlot(ArduinoJson::V701PB2::detail::SlotId) const VariantPoolList.hpp:98 (poolIndex < count_


Backtrace: 0x400838e5:0x3ffecc70 0x4008dbcd:0x3ffecc90 0x40093719:0x3ffeccb0 0x400dd150:0x3ffecde0 0x40121d5b:0x3ffece80 0x401240ad:0x3ffecea0 0x40126636:0x3ffed5b0 0x40126b31:0x3ffed630 0x401c3bf1:0x3ffed660 0x401c3c16:0x3ffed6b0 0x401c4fd9:0x3ffed6e0 0x401c50b5:0x3ffed720 0x401c0a1f:0x3ffed740 0x401c0a6e:0x3ffed770 0x401c0ab1:0x3ffed790 0x401c0c8d:0x3ffed7b0 0x401c0d09:0x3ffed7d0

  #0  0x400838e5:0x3ffecc70 in panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:408
  #1  0x4008dbcd:0x3ffecc90 in esp_system_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c:137
  #2  0x40093719:0x3ffeccb0 in __assert_func at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/assert.c:85
  #3  0x400dd150:0x3ffecde0 in ArduinoJson::V701PB2::detail::VariantPoolList::getSlot(unsigned short) const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp:98
      (inlined by) ArduinoJson::V701PB2::detail::VariantPoolList::allocFromFreeList() at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/VariantPoolImpl.hpp:71
      (inlined by) ArduinoJson::V701PB2::detail::VariantPoolList::allocSlot(ArduinoJson::V701PB2::Allocator*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/VariantPoolList.hpp:73
      (inlined by) ArduinoJson::V701PB2::detail::ResourceManager::allocSlot() at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Memory/ResourceManager.hpp:53
      (inlined by) ArduinoJson::V701PB2::detail::CollectionData::addSlot(ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Collection/CollectionImpl.hpp:52
      (inlined by) ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::ObjectData::addMember<ArduinoJson::V701PB2::detail::StaticStringAdapter>(ArduinoJson::V701PB2::detail::StaticStringAdapter, ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/ObjectData.hpp:27
      (inlined by) ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::ObjectData::getOrAddMember<ArduinoJson::V701PB2::detail::StaticStringAdapter>(ArduinoJson::V701PB2::detail::StaticStringAdapter, ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/ObjectImpl.hpp:24
  #4  0x40121d5b:0x3ffece80 in ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::VariantData::getOrAddMember<ArduinoJson::V701PB2::detail::StaticStringAdapter>(ArduinoJson::V701PB2::detail::StaticStringAdapter, ArduinoJson::V701PB2::detail::ResourceManager*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantData.hpp:226
  #5  0x401240ad:0x3ffecea0 in ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*>::getOrCreateData() const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp:59
      (inlined by) ArduinoJson::V701PB2::detail::VariantData* ArduinoJson::V701PB2::detail::VariantAttorney::getOrCreateData<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*> const>(ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*> const&) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantAttorney.hpp:32
      (inlined by) ArduinoJson::V701PB2::detail::VariantRefBase<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*> >::getOrCreateData() const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantRefBase.hpp:268
      (inlined by) ArduinoJson::V701PB2::detail::VariantRefBase<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*> >::getOrCreateVariant() const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp:100
      (inlined by) bool ArduinoJson::V701PB2::detail::VariantRefBase<ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*> >::set<char const>(char const*) const at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Variant/VariantRefBaseImpl.hpp:144
      (inlined by) ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*>& ArduinoJson::V701PB2::detail::MemberProxy<ArduinoJson::V701PB2::JsonObject, char const*>::operator=<char const>(char const*) at .pio/libdeps/pro-esp32-debug/ArduinoJson/src/ArduinoJson/Object/MemberProxy.hpp:39
      (inlined by) ESPDash::generateLayoutJSON(AsyncWebSocketClient*, bool, Card*) at lib/ESPDASHPro/src/ESPDashPro.cpp:334
  #6  0x40126636:0x3ffed5b0 in ESPDash::ESPDash(AsyncWebServer*, char const*, bool)::{lambda(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int)#3}::operator()(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int) const at lib/ESPDASHPro/src/ESPDashPro.cpp:91
  #7  0x40126b31:0x3ffed630 in std::_Function_handler<void (AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int), ESPDash::ESPDash(AsyncWebServer*, char const*, bool)::{lambda(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int)#3}>::_M_invoke(std::_Any_data const&, AsyncWebSocket*&&, AsyncWebSocketClient*&&, AwsEventType&&, void*&&, unsigned char*&&, unsigned int&&) at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:297
  #8  0x401c3bf1:0x3ffed660 in std::function<void (AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int)>::operator()(AsyncWebSocket*, AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int) const at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:687
  #9  0x401c3c16:0x3ffed6b0 in AsyncWebSocket::_handleEvent(AsyncWebSocketClient*, AwsEventType, void*, unsigned char*, unsigned int) at .pio/libdeps/pro-esp32-debug/ESP Async WebServer/src/AsyncWebSocket.cpp:771
  #10 0x401c4fd9:0x3ffed6e0 in AsyncWebSocketClient::_onData(void*, unsigned int) at .pio/libdeps/pro-esp32-debug/ESP Async WebServer/src/AsyncWebSocket.cpp:563
  #11 0x401c50b5:0x3ffed720 in std::_Function_handler<void (void*, AsyncClient*, void*, unsigned int), AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest*, AsyncWebSocket*)::{lambda(void*, AsyncClient*, void*, unsigned int)#5}>::_M_invoke(std::_Any_data const&, void*&&, AsyncClient*&&, std::_Any_data const&, unsigned int&&) at .pio/libdeps/pro-esp32-debug/ESP Async WebServer/src/AsyncWebSocket.cpp:272
      (inlined by) _M_invoke at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:297
  #12 0x401c0a1f:0x3ffed740 in std::function<void (void*, AsyncClient*, void*, unsigned int)>::operator()(void*, AsyncClient*, void*, unsigned int) const at /Users/mat/.platformio/packages/toolchain-xtensa-esp32@8.4.0+2021r2-patch5/xtensa-esp32-elf/include/c++/8.4.0/bits/std_function.h:687
  #13 0x401c0a6e:0x3ffed770 in AsyncClient::_recv(tcp_pcb*, pbuf*, signed char) at .pio/libdeps/pro-esp32-debug/AsyncTCP/src/AsyncTCP.cpp:915
  #14 0x401c0ab1:0x3ffed790 in AsyncClient::_s_recv(void*, tcp_pcb*, pbuf*, signed char) at .pio/libdeps/pro-esp32-debug/AsyncTCP/src/AsyncTCP.cpp:1191
  #15 0x401c0c8d:0x3ffed7b0 in _handle_async_event(lwip_event_packet_t*) at .pio/libdeps/pro-esp32-debug/AsyncTCP/src/AsyncTCP.cpp:159
  #16 0x401c0d09:0x3ffed7d0 in _async_service_task(void*) at .pio/libdeps/pro-esp32-debug/AsyncTCP/src/AsyncTCP.cpp:194




ELF file SHA256: 51ad2d96511c2487

E (8608) esp_core_dump_flash: Core dump flash config is corrupted! CRC=0x7bd5c66f instead of 0x0
Rebooting...
ets Jul 29 2019 12:21:46

rst:0xc (SW_CPU_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:1184
load:0x40078000,len:13232
load:0x40080400,len:3028
entry 0x400805e4
E (732) esp_core_dump_flash: No core du����ѥѥ���found!
E (732) esp_core_dump_flash: No core dump partition found!
I           35 loopTask   (1)   YASOLR Booting YaSolR Pro main_a96ee3d8_modified...
I           36 loopTask   (1)   SYSTEM Initializing File System...

It also also fails at the place:

 (inlined by) ESPDash::generateLayoutJSON(AsyncWebSocketClient*, bool, Card*) at lib/ESPDASHPro/src/ESPDashPro.cpp:334

This line matches doc["command"] = changes_only ? "update:components" : "update:layout:next"; here:

    JsonObject doc = root.to<JsonObject>();
    doc["command"] = changes_only ? "update:components" : "update:layout:next";
    for (int i = 0; i < cards.Size(); i++) {

So it is like a document becomes unusable after being cleared. If I do not use .to<> but .clear() instead, I have the same behaviour.

We can see in the logs above that the first call to send() works, then the document is cleared for re-use and it crashes.

Here is part of the code:

// generates the layout JSON string to the frontend
void ESPDash::generateLayoutJSON(AsyncWebSocketClient* client, bool changes_only, Card* onlyCard) {
#if ARDUINOJSON_VERSION_MAJOR == 6
  DynamicJsonDocument root(DASH_JSON_SIZE);
#else
  JsonDocument root;
#endif

  // preparing layout
  {
    JsonObject doc = root.to<JsonObject>();

    if (!changes_only) {
      doc["command"] = "update:layout:begin";
      // OTHER DOC SETTERS
    } else {
      doc["command"] = "update:components";
    }

    // Generate Tab JSON
    for (int i = 0; i < tabs.Size(); i++) {
      // [...]
      doc["tabs"][i]["id"] = t->_id;
      // [...]
      // OTHER DOC SETTERS
    }

    if (!changes_only || doc["tabs"].as<JsonArray>().size() > 0) {
      send(client, doc);
    }
  }

  // Generate JSON for all Cards
  {
    JsonObject doc = root.to<JsonObject>();
    doc["command"] = changes_only ? "update:components" : "update:layout:next";
    for (int i = 0; i < cards.Size(); i++) {
      // [...]

      // Generate JSON
#if ARDUINOJSON_VERSION_MAJOR == 6
      JsonObject obj = doc["cards"].createNestedObject();
#else
      JsonObject obj = doc["cards"].add<JsonObject>();
#endif
      generateComponentJSON(obj, c, changes_only);

      if (overflowed(root)) {
        doc["cards"].as<JsonArray>().remove(doc["cards"].as<JsonArray>().size() - 1);
        send(client, doc);
        doc = root.to<JsonObject>();
        doc["command"] = changes_only ? "update:components" : "update:layout:next";
        i--;
        continue;
      }

      // [...]
    }

    if (doc["cards"].as<JsonArray>().size() > 0)
      send(client, doc);
  }

  // Generate JSON for all Charts
  {
    JsonObject doc = root.to<JsonObject>();
    doc["command"] = changes_only ? "update:components" : "update:layout:next";
    for (int i = 0; i < charts.Size(); i++) {
      // [...]

      // Generate JSON
#if ARDUINOJSON_VERSION_MAJOR == 7
      JsonObject obj = doc["charts"].add<JsonObject>();
#else
      JsonObject obj = doc["charts"].createNestedObject();
#endif
      generateComponentJSON(obj, c, changes_only);

      if (overflowed(root)) {
        doc["charts"].as<JsonArray>().remove(doc["charts"].as<JsonArray>().size() - 1);
        send(client, doc);
        doc = root.to<JsonObject>();
        doc["command"] = changes_only ? "update:components" : "update:layout:next";
        i--;
        continue;
      }

      // [...]
    }

    if (doc["charts"].as<JsonArray>().size() > 0)
      send(client, doc);
  }

  // Generate JSON for all Statistics
  {
    JsonObject doc = root.to<JsonObject>();
    doc["command"] = changes_only ? "update:components" : "update:layout:next";
    int idx = 0;

    // Check if default statistics are needed
    if (default_stats_enabled) {
      if (!changes_only) {
        // OTHER DOC SETTERS
    }

    // Loop through user defined stats
    for (int i = 0; i < statistics.Size(); i++, idx++) {
      // [...]
      // OTHER DOC SETTERS

      if (overflowed(root)) {
        doc["stats"].as<JsonArray>().remove(idx);
        send(client, doc);
        doc = root.to<JsonObject>();
        doc["command"] = changes_only ? "update:components" : "update:layout:next";
        i--;
        idx = 0;
        continue;
      }

      // Clear change flags
      if (changes_only) {
        s->_changed = false;
      }
    }

    if (doc["stats"].as<JsonArray>().size() > 0)
      send(client, doc);
  }
}

void ESPDash::send(AsyncWebSocketClient* client, JsonObject& doc) {
  const size_t len = measureJson(doc);
  auto buffer = std::make_shared<std::vector<uint8_t>>(len);

  if (!buffer) {
    doc.clear();
    return;
  }

  serializeJson(doc, buffer->data(), len);

#ifdef DASH_DEBUG
  Serial.printf("T %12u %-10.10s (%u) ESP-DASH client=%u, measureJson=%u\r\n", static_cast<uint32_t>(esp_timer_get_time() >> 10), pcTaskGetName(NULL), xPortGetCoreID(), (client == nullptr ? -1 : client->id()), len);
#endif

  if (client != nullptr) {
    client->text(buffer);
  } else {
    _ws->textAll(buffer);
  }

  doc.clear();
}

bool ESPDash::overflowed(JsonDocument& json) {
  return json.overflowed() || measureJson(json.as<JsonObject>()) > DASH_JSON_SIZE;
}

@mathieucarbou mathieucarbou reopened this Jan 12, 2024
@mathieucarbou
Copy link
Author

Ok so there is definitely something wrong with reusing a Json doc after a clear().

I've implemented a new version which is creating a new JsonDocument each time, and it works.

@bblanchon
Copy link
Owner

Could you provide an MCVE?

@mathieucarbou
Copy link
Author

We have a big internet outage here but as soon as I can, yes.

@mathieucarbou
Copy link
Author

mathieucarbou commented Jan 15, 2024

Here is a simple .ino file to reproduce:

#include <ArduinoJson.h>

void fill(JsonObject &obj) {
  obj["foo-bar-baz"] = "foo-bar-baz-foo-bar-baz";
}

void send(JsonObject &obj) {
  serializeJson(obj, Serial);
  Serial.println("");
  obj.clear();
}

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

  {
    JsonObject obj = doc.to<JsonObject>();
    obj["command"] = "update:components";
    obj["tabs"][0]["id"] = "0";
    send(obj);
  }

  {
    JsonObject obj = doc.to<JsonObject>();
    obj["command"] = "update:components";
    JsonObject card = doc["cards"].add<JsonObject>();
    fill(card);
    send(obj);
  }
}

void loop() { }

Please note that if I comment out the obj.clear() line, it works:

void send(JsonObject &obj) {
  serializeJson(obj, Serial);
  Serial.println("");
  //obj.clear();
}

logs:

23:31:55.567 -> A6      : 0x00000000  A7      : 0x00000001  A8      : 0x800d142c  A9      : 0x3ffc5040  
23:31:55.599 -> A10     : 0x00000010  A11     : 0x00000010  A12     : 0x0000ffff  A13     : 0x0000ffff  
23:31:55.599 -> A14     : 0x0000ffff  A15     : 0xff000000  SAR     : 0x00000020  EXCCAUSE: 0x0000001c  
23:31:55.599 -> EXCVADDR: 0x0000001a  LBEG    : 0x400862d9  LEND    : 0x400862e9  LCOUNT  : 0xffffffff  
23:31:55.599 -> 
23:31:55.599 -> 
23:31:55.599 -> Backtrace: 0x400d1429:0x3ffc5060 0x400d198e:0x3ffc50a0 0x400d19d3:0x3ffc50e0 0x400d1f22:0x3ffc5100 0x400d301a:0x3ffc51b0
23:31:55.633 -> 
23:31:55.633 -> 
23:31:55.633 -> 
23:31:55.633 -> 
23:31:55.633 -> ELF file SHA256: 10fa9a3b88ba00e6
23:31:55.633 -> 
23:31:55.829 -> Rebooting...

What is the recommandation for such usage: use only one JsonDocument and scope the construction of inner batches to send through websocket, or constructing a completely new JsonDocument for each batch, scoped by brackets ?

My assumption was that destroying and creating a new JsonDocument each time would be slower, so that is why I've tried to reuse it with clear() and .to() calls.

@bblanchon
Copy link
Owner

Thank you very much for this code. ❤️
It's now fixed in the main branch, and I'll publish a new revision soon.

What is the recommandation for such usage: use only one JsonDocument and scope the construction of inner batches to send through websocket, or constructing a completely new JsonDocument for each batch, scoped by brackets ?

I recommend short lifetimes to release memory as soon as possible and reduce the cognitive load.
In your case, this would mean creating a new JsonDocument for each batch.

My assumption was that destroying and creating a new JsonDocument each time would be slower, so that is why I've tried to reuse it with clear() and .to() calls.

Calling JsonDocument::clear() and destructing a JsonDocument are roughly the same; I don't expect any measurable difference.
Choose the one that makes the code easier to read and reason about.

@mathieucarbou
Copy link
Author

Thanks for the advices, and glad I was able to help :-)

@bblanchon
Copy link
Owner

The fix was published in ArduinoJson 7.0.2.

@bblanchon bblanchon added the v7 ArduinoJson 7 label Feb 1, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
question v7 ArduinoJson 7
Projects
None yet
Development

No branches or pull requests

2 participants