/**
* ESP32 Deadlock Demo - Timeout
*
* Demonstrate why kernel object timeouts are important to alleviate
* deadlock. Note that this still can cause "livelock."
*
* Date: April 19, 2025
* Author: Mike
* License: 0BSD
*/
// Use only core 1 for demo purposes
#if CONFIG_FREERTOS_UNICORE
static const BaseType_t app_cpu = 0;
#else
static const BaseType_t app_cpu = 1;
#endif
// Settings
static const TickType_t mutex_timeout = 1000 / portTICK_PERIOD_MS;
static const TickType_t deadlock_delay = 20 / portTICK_PERIOD_MS ;
// Globals
static SemaphoreHandle_t mutex_1;
static SemaphoreHandle_t mutex_2;
//*****************************************************************************
// Tasks
// Task A (high priority)
void doTaskA(void* parameter) {
// Loop forever
while(1) {
// Take mutex 1
if (xSemaphoreTake(mutex_1, mutex_timeout) == pdTRUE) {
// Say we took mutex 1 and wait (to force deadlock)
Serial.println("Task A took mutex 1");
vTaskDelay(deadlock_delay);
// Take mutex 2
if (xSemaphoreTake(mutex_2, mutex_timeout) == pdTRUE) {
// Say we took mutex 2
Serial.println("Task A took mutex 2");
// Critical section protected by 2 mutexes
Serial.println("Task A doing some work");
vTaskDelay(500 / portTICK_PERIOD_MS);
} else {
Serial.println("Task A timed out waiting for mutex 2");
}
} else {
Serial.println("Task A timed out waiting for mutex 1");
}
// Give back mutexes
xSemaphoreGive(mutex_2);
xSemaphoreGive(mutex_1);
// Wait to let the other task execute
Serial.println("Task A going to sleep");
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
// Task B (low priority)
void doTaskB(void *parameters) {
// Loop forever
while (1) {
// Take mutex 2
if (xSemaphoreTake(mutex_2, mutex_timeout) == pdTRUE) {
// Say we took mutex 2 and wait (to force deadlock)
Serial.println("Task B took mutex 2");
vTaskDelay(deadlock_delay);
// Take mutex 1
if (xSemaphoreTake(mutex_1, mutex_timeout) == pdTRUE) {
// Say we took mutex 1
Serial.println("Task B took mutex 1");
// Critical section protected by 2 mutexes
Serial.println("Task B doing some work");
vTaskDelay(500 / portTICK_PERIOD_MS);
} else {
Serial.println("Task B timed out waiting for mutex 1");
}
} else {
Serial.println("Task B timed out waiting for mutex 2");
}
// Give back mutexes
xSemaphoreGive(mutex_1);
xSemaphoreGive(mutex_2);
// Wait to let the other task execute
Serial.println("Task A going to sleep");
vTaskDelay(500 / portTICK_PERIOD_MS);
}
}
//*****************************************************************************
// Main (runs as its own task with priority 1 on core 1)
void setup() {
// Serial configuration.
Serial.begin(115200);
// Wait a moment to start (so we don't miss Serial output)
vTaskDelay(1000 / portTICK_PERIOD_MS);
Serial.println();
Serial.println("---ESP32 Timer Interrupt Demo---");
// Create mutexes before starting tasks
mutex_1 = xSemaphoreCreateMutex();
mutex_2 = xSemaphoreCreateMutex();
xTaskCreatePinnedToCore( // Use xTaskCreate() in vanilla FreeRTOS.
doTaskA, // Function to be called.
"Task A", // Name of task.
2048, // Stack size (bytes in ESP32, words in FreeRTOS).
NULL, // Parameter to pass to function.
2, // Task priority (o to configMAX_PRIORITIES - 1)
NULL, // Task handle
app_cpu); // Run on one core for demo purposes (ESP32 only)
xTaskCreatePinnedToCore( // Use xTaskCreate() in vanilla FreeRTOS.
doTaskB, // Function to be called.
"Task B", // Name of task.
2048, // Stack size (bytes in ESP32, words in FreeRTOS).
NULL, // Parameter to pass to function.
1, // Task priority (o to configMAX_PRIORITIES - 1)
NULL, // Task handle
app_cpu); // Run on one core for demo purposes (ESP32 only)
// Delete setup and loop task.
vTaskDelete(NULL);
}
void loop() {
// Do nothing
}