xStreamBufferReceive(..., portMAX_DELAY) Task Moves to Suspended and Never Wakes on Data

Problem Description

One of my task(task0) uses:
xStreamBufferReceive(xStream, &buf, 1, portMAX_DELAY);
to block indefinitely until new serial data arrives.
Meanwhile, an interrupt (ISR0) uses:
xStreamBufferSendFromISR(xStream, p, len, NULL);
to send incoming serial data into the stream buffer.

However, after running for a certain period of time, I observed that task0 enters the Suspended state and never resumes, even though data continues to arrive.

Debug Content
In a separate task (task1), I periodically call eTaskGetState() to monitor the state of task0. Under normal conditions, it reports eBlocked, as expected. However, after the issue occurs, the task unexpectedly switches to eSuspended and stays there permanently.

In ISR0, I also check the stream buffer status using xStreamBufferBytesAvailable(). Normally, the available byte count remains near zero because task0 reads data as it arrives. But once the problem occurs, the byte count keeps increasing until the stream is full. Note that ISR0 is triggered every few seconds and typically sends a few to a few dozen bytes each time.

Relevant FreeRTOSConfig.h Settings

#define INCLUDE_vTaskPrioritySet             1
#define INCLUDE_uxTaskPriorityGet            1
#define INCLUDE_vTaskDelete                  1
#define INCLUDE_vTaskCleanUpResources        0
#define INCLUDE_vTaskSuspend                 1
#define INCLUDE_vTaskDelayUntil              1
#define INCLUDE_vTaskDelay                   1
#define INCLUDE_xTaskGetSchedulerState       1
#define INCLUDE_xTimerPendFunctionCall       1
#define INCLUDE_xQueueGetMutexHolder         1
#define INCLUDE_uxTaskGetStackHighWaterMark  1
#define INCLUDE_xTaskGetCurrentTaskHandle    1
#define INCLUDE_eTaskGetState                1

My Questions

  1. Has anyone encountered a situation where xStreamBufferReceive(..., portMAX_DELAY) never returns, even though data is being sent via xStreamBufferSendFromISR()?
  2. What are the possible causes that could lead to the task moving from Blocked to Suspended without an explicit call to vTaskSuspend()?
  3. What debugging methods would you recommend to investigate why the task is not being returned to the Ready state when data is available in the stream buffer?

Any guidance or insight would be greatly appreciated!

Do you have configASSERT defined properly and enabled stack checking ?
That’s very helpful during development.
Which FreeRTOS version and MCU resp. port do you use ?
Could you also share the relevant code (snippets) for task creation, task and ISR code ?
Otherwise it’s hard to guess..
An unexpected suspended task state could be caused by some kind of memory corruption (of internal FreeRTOS data), which in turn could be caused by stack overflow or wrong interrupt priority of the interrupt corresponding to your ISR0 (depending on the MCU/port).

1 Like

One thought is that the trigger level for the stream might have gotten changed/corrupted. That would cause the SendFromISR to not wake up the task until it reached the trigger level, and with an infinite timeout, that won’t happen either.

If it doesn’t wake up even if full, then it might be that something corrupted the level with a wild-write that broke the stream.

1 Like

Currently, configASSERT is not yet enabled , but I understand it’s very useful for catching subtle issues. I plan to enable it shortly to help with debugging.

I’m using FreeRTOS v10.3.1 on an STM32H750 MCU, with CMSIS-RTOS2 layer provided by STM32CubeMX.

In this setup, I’m using STM32 + FreeRTOS + UART with DMA in ReceiveToIdle mode. Each UART uses a StreamBuffer to transfer received data to a task. However, when I perform a UART DMA reset test from another task, task0 eventually becomes suspended and never resumes.

Below is the reduced and focused version of the system that reproduces the issue.

1.task creation

// Task attribute definitions (abstracted names for discussion clarity)
const osThreadAttr_t task0_attributes = {
    .name = "task0",            // Processes UART0 -> Stream0 -> Queue0
    .stack_size = 1024 * 1,
    .priority = (osPriority_t) osPriorityNormal,
};

const osThreadAttr_t task1_attributes = {
    .name = "task1",            // Processes UART1 -> Stream1, may reset UART1
    .stack_size = 128 * 4,
    .priority = (osPriority_t) osPriorityNormal2,
};

const osThreadAttr_t task2_attributes = {
    .name = "task2",            // Consumes Queue0, may reset UART0
    .stack_size = 128 * 4,
    .priority = (osPriority_t) osPriorityNormal,
};

// Thread creation
task0Handle = osThreadNew(task0, NULL, &task0_attributes); // <-- this one gets suspended unexpectedly
task1Handle = osThreadNew(task1, NULL, &task1_attributes);
task2Handle = osThreadNew(task2, NULL, &task2_attributes);

osKernelStart();

2.stream init

typedef struct _GLOBAL_CONTEXT {
    StreamBufferHandle_t stream0;
    StreamBufferHandle_t stream1;
} GLOBAL_CONTEXT, *pGLOBAL_CONTEXT;

pGLOBAL_CONTEXT globalHandle;

// Create 2 independent stream buffers for UART/IO channels
globalHandle->stream0 = xStreamBufferCreate(1024, 1);
globalHandle->stream1 = xStreamBufferCreate(1024, 1);

2.uart ISR
// ISR callback for UART data reception
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t size)
{
    int offset, len;

    if (huart == &UART0) {
        log("ISR: stream0 used, available=%d", xStreamBufferBytesAvailable(globalHandle->stream0));
        // Determine offset/len from DMA buffer if needed...
        xStreamBufferSendFromISR(globalHandle->stream0, buf0 + offset, len, NULL);
    }

    if (huart == &UART1) {
        // Same logic for UART1
        xStreamBufferSendFromISR(globalHandle->stream1, buf1 + offset, len, NULL);
    }
}

3.task code

// task0: Blocks on stream0 (UART0), forwards data to queue0
void task0(void *arg)
{
    uint8_t data;
    while (1) {
        log("task0: waiting on stream0");
        xStreamBufferReceive(globalHandle->stream0, &data, 1, portMAX_DELAY);
        log("task0: received byte, forwarding to queue0");

        xQueueSend(gpGlobal->queue0, &data, 0);
    }
}

// task1: Receives from stream1 (UART1) and may reset the UART for testing
void task1(void *arg)
{
    uint8_t data;
    HAL_UARTEx_ReceiveToIdle_DMA(&UART1, buf1, 1024);

    while (1) {
        log("task1: waiting on stream1");
        xStreamBufferReceive(globalHandle->stream1, &data, 1, portMAX_DELAY);
        log("task1: received byte");

        // Debug output: inspect task0 state
        log("task0 state = %d", eTaskGetState(task0Handle));

        // === The following code triggers the issue: task0 becomes suspended after several iterations ===
        if (testIsEnabled) {
            // Simulate DMA reset on UART1 + flush stream buffer
            HAL_UART_DMAStop(&UART1);
            xStreamBufferReset(globalHandle->stream1);
            HAL_UARTEx_ReceiveToIdle_DMA(&UART1, buf1, 1024);
        }
    }
}

// task2: Consumes from queue0, optionally resets UART0 and queue
void task2(void *arg)
{
    uint8_t data;
    HAL_UARTEx_ReceiveToIdle_DMA(&UART0, buf0, 1024);

    while (1) {
        log("task2: waiting on queue0");
        ret = xQueueReceive(gpGlobal->queue0, &data, timeout);
        log("task2: received byte or overtime");

        HAL_UART_DMAStop(&UART0);
        xQueueReset(gpGlobal->queue0);
        HAL_UARTEx_ReceiveToIdle_DMA(&UART0, buf0, 1024);
    }
}

Thanks — that’s a good point. I’m currently looking into how to check or confirm the trigger level of a stream buffer at runtime, to see if it might have been corrupted.

In addition to enabling configASSERT, I’d also suggest to monitor stack usage in runtime by invoking uxTaskGetStackHighWaterMark(). This API provides valuable insights into your tasks’ actual stack usage patterns during execution.

1 Like

Also using printf family logging functions in ISRs might be a problem.
They usually need quite a lot of stack (main stack in case of Cortex-M CPUs like your Cortex-M7) and often are not safe to be used in ISRs).
I’d just omit that in the ISRs.
In this regard stack checking for tasks is useful to verify that you provided enough stack even when using logging.
I think with your FreeRTOS version configASSERT would catch wrongly configured interrupt priorities. But it seems you’re using cube to generate parts of your code this might be configured correctly..

Here’s an update on my investigation progress:

  1. I removed all printf calls from the ISR to eliminate any risk of blocking operations within the interrupt.

  1. I added uxTaskGetStackHighWaterMark(NULL) in each task to monitor stack usage at runtime. The current results are:
  • task0: 194
  • task1: 0
  • task2: 27
    (Yes, task1 returning 0 is suspicious — might be a sign of overflow.)

  1. I added runtime inspection of the stream buffer used in task2 (stream0). Under normal operation, it reports:
[Debug] stream0: Bytes=0, Space=1024, Trigger=1

After task0 gets suspended, the Bytes count gradually increases and Space decreases, but the Trigger level stays at 1 — it never changes.


  1. I enabled configCHECK_FOR_STACK_OVERFLOW = 1 and defined configASSERT like this:
#define configASSERT( x ) if ((x) == 0) { taskDISABLE_INTERRUPTS(); for( ;; ); }

However, I haven’t observed any assert or crash. The system continues to run, but task0 stays stuck in the Suspended state and never wakes up.


Let me know if any of this suggests new angles to investigate.

I agree and it’s also documented:

The value returned is the high water mark in words (for example, on a 32 bit machine a return value of 1 would indicate that 4 bytes of stack were unused). If the return value is zero then the task has likely overflowed its stack. If the return value is close to zero then the task has come close to overflowing its stack.

I’d set configCHECK_FOR_STACK_OVERFLOW = 2.
But without CPU support (like stack limit registers of e.g. Cortex-M33 CPUs) SW stack checking it’s not covering 100%.
You could re-run your tests with an increased stack size for task 1.

After increasing the stack size of task 1,everything is back to normal. So, stack overflow might be the root cause.However, I’m still wondering if the triggering level of the flow has not been disrupted, what causes the task to be suspended and unable to be resumed?

A stack overflow can cause undefined behavior. For example, it might corrupt the program counter or stack frame of another task, potentially causing the task to jump to an incorrect address and transition to the eSuspended state instead of the intended eBlocked state.

1 Like

Thanks everyone for your help!

It turned out to be a simple stack overflow.
Apologies for missing such a basic issue — I should’ve checked the stack usage more carefully.

The explanation about undefined behavior causing the task to enter the eSuspended state makes sense. That clears up my confusion.

I won’t dig further into the exact corruption, since the root cause is now resolved.

Really appreciate all your input — marking this as closed.

Thanks again! :folded_hands: