To avoid a possible deadlock as in the previous example, we can add a timeout when acquiring the mutexes in both tasks. This way, the tasks will not wait indefinitely if they fail to obtain the resource, which could indicate a design problem (such as a deadlock).

Modifications with Timeout:

We will use a time limit (timeout) instead of portMAX_DELAY when calling xSemaphoreTake. If the mutex is not obtained within this time, the error can be handled appropriately.

Here’s the updated code with a 500-millisecond timeout:

Code Example:

#include "bsp.h"

SemaphoreHandle_t xMutex1;  /* Mutex semaphore 1 */
SemaphoreHandle_t xMutex2;  /* Mutex semaphore 2 */ 

static void Task1( void *pvParameters );   /* Task1 To turn on LEDs */
static void Task2( void *pvParameters );   /* Task2 to turn off LEDs */

int main( void )
{
    HAL_Init();

    SEGGER_SYSVIEW_Conf( );
    SEGGER_SYSVIEW_Start( );

    xMutex1 = xSemaphoreCreateMutex();       /* Create the first Mutex Semaphore */
    xMutex2 = xSemaphoreCreateMutex();       /* Create the second Mutex Semaphore */
    
    xTaskCreate( Task1, "Task1", 240, NULL, 2, NULL );  /* Register Task1 to turn on LEDs */
    xTaskCreate( Task2, "Task2", 240, NULL, 1, NULL );  /* Register Task2 to turn off LEDs */
    
    vTaskStartScheduler();                  /* Init the Kernel */
}

void Task1( void *pvParameters )
{
    const TickType_t xTimeout = pdMS_TO_TICKS(500); /* Timeout of 500ms */

    for(;;)
    {
        if (xSemaphoreTake(xMutex1, xTimeout) == pdTRUE) /* Try to take mutex 1 with timeout */
        {
            SEGGER_SYSVIEW_PrintfTarget("Task 1 takes the Mutex 1");
            vTaskDelay(250);
            
            SEGGER_SYSVIEW_PrintfTarget("Task 1 trying to take Resource 2");
            if (xSemaphoreTake(xMutex2, xTimeout) == pdTRUE) /* Try to take mutex 2 with timeout */
            {
                SEGGER_SYSVIEW_PrintfTarget("Task 1 successfully took Resource 2");
                vTaskDelay(250);

                SEGGER_SYSVIEW_PrintfTarget("Task 1 gives the Resource 2");
                xSemaphoreGive(xMutex2); /* Give the mutex 2 */
            }
            else
            {
                SEGGER_SYSVIEW_PrintfTarget("Task 1 failed to take Resource 2");
            }

            SEGGER_SYSVIEW_PrintfTarget("Task 1 gives the Resource 1");
            xSemaphoreGive(xMutex1); /* Give the mutex 1 */
        }
        else
        {
            SEGGER_SYSVIEW_PrintfTarget("Task 1 failed to take Mutex 1");
        }
    }
}

void Task2( void *pvParameters )
{
    const TickType_t xTimeout = pdMS_TO_TICKS(500); /* Timeout of 500ms */

    for(;;)
    {
        if (xSemaphoreTake(xMutex2, xTimeout) == pdTRUE) /* Try to take mutex 2 with timeout */
        {
            SEGGER_SYSVIEW_PrintfTarget("Task 2 takes the Mutex 2");
            vTaskDelay(250);

            SEGGER_SYSVIEW_PrintfTarget("Task 2 trying to take Resource 1");
            if (xSemaphoreTake(xMutex1, xTimeout) == pdTRUE) /* Try to take mutex 1 with timeout */
            {
                SEGGER_SYSVIEW_PrintfTarget("Task 2 successfully took Resource 1");
                vTaskDelay(250);

                SEGGER_SYSVIEW_PrintfTarget("Task 2 gives the Resource 1");
                xSemaphoreGive(xMutex1); /* Give the mutex 1 */
            }
            else
            {
                SEGGER_SYSVIEW_PrintfTarget("Task 2 failed to take Resource 1");
            }

            SEGGER_SYSVIEW_PrintfTarget("Task 2 gives the Resource 2");
            xSemaphoreGive(xMutex2); /* Give the mutex 2 */
        }
        else
        {
            SEGGER_SYSVIEW_PrintfTarget("Task 2 failed to take Mutex 2");
        }
    }
}

Explanation of the Changes:

  1. Defined Timeout:
  • A timeout (xTimeout) of 500 ms is defined using pdMS_TO_TICKS(500).
  • This ensures portability across different system configurations and tick rate settings.
  1. Mutex State Verification:
  • Each time a task attempts to acquire a mutex, it verifies whether the acquisition was successful (pdTRUE).
  • If the acquisition fails, the task logs a message and continues execution, avoiding a potential deadlock scenario.
  1. Error Handling:
  • When a task fails to acquire the second mutex (e.g., due to resource unavailability or a potential deadlock), it performs an alternative action or simply logs the failure.
  • Debug messages are used to notify the issue, helping in troubleshooting and improving system reliability.

SystemView Output:

TimeLine:

Document

Task1 and Task2 initially acquire their respective mutexes: Task1 takes Mutex 1. Task2 takes Mutex 2.

Document

Both tasks attempt to acquire the second mutex: Task1 tries to take Mutex 2. Task2 tries to take Mutex 1.

Document

Timeouts occur due to contention: Task1 and Task2 fail to acquire the second mutex within the specified 500 ms timeout. Debug messages indicate these failures (e.g., "Task 1 failed to take Resource 2").

Document

Tasks release the resources they already hold: After failing, both tasks release their respective mutexes to allow other tasks to proceed.

Document

Cycle repeats:
The tasks retry acquiring the mutexes, demonstrating the absence of a deadlock but showcasing a design limitation where tasks repeatedly contend for resources.

This output confirms that the timeout mechanism prevents indefinite blocking and potential deadlocks. However, the repetitive failure suggests that the resource allocation strategy might require optimization to reduce contention.