/**
* ESP32 Deadlock Demo - Hierarchy Solution
*
* Demonstrate the heirarchy solution to preventing deadlock. Assign a priority
* rank to each mutex. Tasks must take multiple mutexes (or semaphores) in
* order of priority every time, and release them in reverse order.
*
* 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 (introduce wait to force deadlock)
xSemaphoreTake(mutex_1, portMAX_DELAY);
Serial.println("Task A took mutex 1");
vTaskDelay(50 / portTICK_PERIOD_MS); // has to be 50 ms to make the deadlock happen
// Take mutex 2
xSemaphoreTake(mutex_2, portMAX_DELAY);
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);
// 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 1 (introduce wait to force deadlock)
xSemaphoreTake(mutex_1, portMAX_DELAY);
Serial.println("Task B took mutex 1");
vTaskDelay(50 / portTICK_PERIOD_MS); // has to be 50 ms to make the deadlock happen
// Take mutex 2
xSemaphoreTake(mutex_2, portMAX_DELAY);
Serial.println("Task B took mutex 2");
// Critical section protected by 2 mutexes
Serial.println("Task B doing some work");
vTaskDelay(500 / portTICK_PERIOD_MS);
// 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);
}
}
//*****************************************************************************
// 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
}