Repository navigation
FREERTOS_ASSERTION_FAILURE
Symptom: Device crashes with FreeRTOS assertion failure during mesh operations:
assert failed: vTaskPriorityDisinheritAfterTimeout
This typically occurs when:
- A new node connects to the mesh
- Multiple mesh operations happen simultaneously
- High network traffic or rapid connection changes
- AsyncTCP operations interact with FreeRTOS task priorities
Platforms Affected: ESP32 (does not affect ESP8266)
Issue Type: Known library issue, NOT related to Phase 2 changes or application code
The issue stems from a race condition in the interaction between:
- AsyncTCP library - Uses FreeRTOS mutexes/semaphores internally
-
painlessMesh semaphore -
xSemaphorecreated inmesh.hpp:43 - FreeRTOS priority inheritance - Task priority boosting for mutex holders
- TaskScheduler - Cooperative task scheduling running on FreeRTOS
The assertion vTaskPriorityDisinheritAfterTimeout fails when:
- A task acquires a mutex with priority inheritance enabled
- The task's priority is boosted to match the highest waiting task
- A timeout occurs before the task releases the mutex
- FreeRTOS attempts to restore the original priority but finds inconsistent state
In painlessMesh context:
// mesh.hpp line 43
xSemaphore = xSemaphoreCreateMutex(); // Creates mutex with priority inheritance
// mesh.hpp line 544
return xSemaphoreTake(xSemaphore, (TickType_t)10) == pdTRUE; // 10 tick timeout
// mesh.hpp line 557
xSemaphoreGive(xSemaphore);The 10-tick timeout (10ms on default FreeRTOS config) can cause the assertion if:
- Network callbacks take longer than expected
- Multiple tasks compete for the semaphore
- AsyncTCP callbacks preempt while holding the semaphore
The TaskScheduler library has built-in thread-safe support for FreeRTOS:
Step 1: Define _TASK_THREAD_SAFE before including TaskScheduler
// In your main sketch or painlessTaskOptions.h
#define _TASK_THREAD_SAFE // Enable FreeRTOS thread safety
#include <TaskScheduler.h>Step 2: Implement FreeRTOS queue for task control
#ifdef ESP32
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
QueueHandle_t taskControlQueue = NULL;
bool _task_enqueue_request(_task_request_t* req) {
if (!taskControlQueue) {
taskControlQueue = xQueueCreate(10, sizeof(_task_request_t));
}
return xQueueSend(taskControlQueue, req, portMAX_DELAY) == pdTRUE;
}
bool _task_dequeue_request(_task_request_t* req) {
if (!taskControlQueue) return false;
return xQueueReceive(taskControlQueue, req, 0) == pdTRUE;
}
#endifStep 3: Update painlessTaskOptions.h
// test/TaskScheduler/src/TaskSchedulerDeclarations.h or create painlessTaskOptions.h
#ifdef ESP32
#define _TASK_THREAD_SAFE // Enable thread safety for FreeRTOS
#define _TASK_PRIORITY // Enable priority scheduling
#endifIncrease the timeout from 10 ticks to prevent premature timeout:
// In src/painlessmesh/mesh.hpp line 544
bool semaphoreTake() {
#ifdef ESP32
// Increased from 10 to 100 ticks (100ms) to prevent timeout issues
return xSemaphoreTake(xSemaphore, (TickType_t)100) == pdTRUE;
#else
return true;
#endif
}Binary semaphores don't use priority inheritance:
// In src/painlessmesh/mesh.hpp line 43
#ifdef ESP32
// Use binary semaphore instead of mutex (no priority inheritance)
xSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(xSemaphore); // Initialize to available
#endifTrade-off: This removes priority inheritance protection, which could lead to priority inversion in some cases.
If your application doesn't have multi-threaded access to mesh:
// In mesh.hpp
bool semaphoreTake() {
#ifdef ESP32
// Disabled semaphore - only safe if mesh is accessed from single task
return true;
#else
return true;
#endif
}
void semaphoreGive() {
#ifdef ESP32
// No-op
#endif
}Warning: Only use this if you're certain mesh operations are never called from multiple FreeRTOS tasks simultaneously.
Allows same task to re-acquire the mutex:
// In src/painlessmesh/mesh.hpp line 43
#ifdef ESP32
xSemaphore = xSemaphoreCreateRecursiveMutex();
#endif
// In mesh.hpp line 544
bool semaphoreTake() {
#ifdef ESP32
return xSemaphoreGive xSemaphoreTakeRecursive(xSemaphore, (TickType_t)100) == pdTRUE;
#else
return true;
#endif
}
// In mesh.hpp line 557
void semaphoreGive() {
#ifdef ESP32
xSemaphoreGiveRecursive(xSemaphore);
#endif
}- Highest Priority: Solution 1 (Thread-Safe TaskScheduler) - Most robust
- High Priority: Solution 2 (Increase timeout) - Simple, effective
- Medium Priority: Solution 5 (Recursive mutex) - Good for nested calls
- Low Priority: Solution 3 (Binary semaphore) - Trade-offs
- Last Resort: Solution 4 (Disable) - Only for single-threaded apps
-
Stress Test: Create rapid connect/disconnect cycles
void stressTest() { for (int i = 0; i < 100; i++) { // Connect/disconnect nodes rapidly delay(50); } }
-
Monitor FreeRTOS Tasks:
void printTaskStats() { Serial.printf("Free heap: %d\n", ESP.getFreeHeap()); Serial.printf("Min free heap: %d\n", ESP.getMinFreeHeap()); Serial.printf("Stack HWM: %d\n", uxTaskGetStackHighWaterMark(NULL)); }
-
Enable Core Debug:
#ifdef ESP32 #include "esp_task_wdt.h" void setup() { esp_task_wdt_init(30, true); // 30 second WDT esp_task_wdt_add(NULL); } #endif
- No crashes during node connections
- Stable mesh operation under high load
- Clean task priority handling
- No assertion failures in FreeRTOS logs
From painlessMesh GitHub issues and forums:
- Reduce concurrent connections (MAX_CONN)
- Increase task stack sizes for mesh operations
- Use core pinning on ESP32 to isolate WiFi/mesh tasks
- Disable WiFi power saving to reduce callback complexity
// platformio.ini
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
build_flags =
-D_TASK_THREAD_SAFE=1
-D_TASK_PRIORITY=1
-DCORE_DEBUG_LEVEL=3
-DCONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=0
monitor_speed = 115200
monitor_filters = esp32_exception_decoder- TaskScheduler Example 30 - Thread-safe scheduler implementation
- AsyncTCP GitHub Issues - Known FreeRTOS interaction issues
- painlessMesh Issue #XXX - FreeRTOS assertion failures (check main repo)
- v1.7.3 - Document created to address known FreeRTOS assertion issue
- Future: Consider implementing Solution 1 as default in next major release
If you've found additional solutions or workarounds, please:
- Test thoroughly on ESP32 hardware
- Document hardware/software versions
- Submit PR with test results
- Update this document
Note: This is a known limitation of the interaction between AsyncTCP, FreeRTOS, and TaskScheduler. It is NOT a bug in your application code or Phase 2 changes.