Possible bug in ulTaskNotifyTake: it can return incorrectly when the notification-value is 0

There is a possible bug in the API ulTaskNotifyTake, in the code and/or documentation.

Problem Summary:

From the docs, how ulTaskNotifyTake is supposed to work:

  • According to the ulTaskNotifyTake documentation, it returns when the “task’s notification value is not zero”.
  • The documentation also states that if a timeout parameter was specified, and a zero notification-value is returned, then a timeout occurred.
  • Also, these two statements imply that ulTaskNotifyTake does not return when the task’s notification value is zero. The only exception is a timeout.

However, there’s a scenario in which ulTaskNotifyTake does return when the notification value is zero, and a timeout has not occurred. According to the documentation, this should not occur.

Details are below, in the spirit of open-source contribution.
I can provide more info, if of interest.

A List of the Sections Below

  • Context
  • Example Scenario
  • Analysis
  • Possible Fixes
  • FreeRTOS Documentation Excerpts
  • Demo-Program that Creates the Problem

Context:

The problem is in the current FreeRTOS, V202012.00
The problem probably goes back to V8, or earlier.
The problem appears to be platform independent, and I’ve seen it on Windows and a STM32 dev-board.
The problem does not appear to be reported already, based on a search of the FreeRTOS Kernel forum.

Example Scenario:

This example uses two tasks: A and B.
Code and test-results are also provided for the example, and they are described later.

Step 1, in Task A:

  • Task A has a notification value of 0, and a notification is “not-pending”.
  • In particular, the notification-state is taskNOT_WAITING_NOTIFICATION (defined in task.c).
  • Task A calls ulTaskNotifyTake, with a timeout value of X (X>0).
  • ulTaskNotifyTake changes Task A’s notification-state to taskWAITING_NOTIFICATION.
  • Task A blocks, to wait for a notification.

Step 2, in Task B:

  • Task B has a priority less-than A’s, and Task B runs.
  • Task B calls xTaskNotify, passing: a) Task A’s handle, b) a notification-value of 0,
    and c) the eAction parameter eSetValueWithOverwrite.
  • xTaskNotify sets Task A’s notification-value to 0, and
    it sets Task A’s notification-state to taskNOTIFICATION_RECEIVED.
  • (So now, a notification is “pending”.)

Step 3, in Task A:

  • Task A runs, since Task A’s priority is higher than Task B’s, and Task A has a pending notification.
  • Task A resumes in ulTaskNotifyTake, and the ulTaskNotifyTake timeout has not expired.
  • ulTaskNotifyTake returns Task A’s notification value, which is 0.
  • ulTaskNotifyTake alters Task A’s notification value, as specified by the parameter xClearCountOnExit.
  • ulTaskNotifyTake sets Task A’s notification-state to taskNOT_WAITING_NOTIFICATION.
  • (So now, a notification is “not-pending”.)

According to the documentation, ulTaskNotifyTake should not have returned in Step 3, since Task A’s notification-value was zero, and no timeout occurred.

Analysis:

The example’s use of ulTaskNotifyTake is different than the intended use for ulTaskNotifyTake, as described in the FreeRTOS docs.

However, the docs don’t say such use is not supported, nor that ulTaskNotifyTake would work differently with such use (i.e., work differently when using xTaskNotify with eSetValueWithOverwrite).

From the FreeRTOS Reference Manual (page 123), the intended-use for ulTaskNotifyTake is:

“ulTaskNotifyTake() is intended for use when a task notification is used as a faster and lighter weight alternative to a binary semaphore or a counting semaphore. FreeRTOS semaphores are taken using the xSemaphoreTake() API function, ulTaskNotifyTake() is the equivalent that uses a task notification value instead of a separate semaphore object.”

“When a task is using its notification value as a binary or counting semaphore other tasks and interrupts should send notifications to it using either the xTaskNotifyGive() macro, or the xTaskNotify() function with the function’s eAction parameter set to eIncrement (the two are equivalent).”

In the example above, if Step 2 is modified to use ulTaskNotifyTake in the intended manner,
then ulTaskNotifyTake will work as documented (i.e., it does not return a notification-value of 0). The xTaskNotify call would use the eAction parameter eIncrement, and the notification-value would not be specified.

Possible Fixes:

It seems the code could be changed to match the documentation, or the documentation changed to match the code.

FreeRTOS Documentation Excerpts:

This section has excerpts from the ulTaskNotifyTake documentation, that are relevant to the problem.

From the pdf document: “Reference Manual for FreeRTOS”, version 10.0.0 issue 1 (page 123):

“An RTOS task can use ulTaskNotifyTake() to [optionally] block to wait for a task’s notification value to be non-zero.”

“Where as xTaskNotifyWait() will return when a notification is pending, ulTaskNotifyTake() will return when the task’s notification value is not zero, decrementing the task’s notification value before it returns.”

From the pdf document: “Mastering the FreeRTOS Real Time Kernel”, Pre-release 161204 Edition (pdf page 327):

“ulTaskNotifyTake() allows a task to wait in the Blocked state for its notification value to be greater than zero…”

"Returned value…
If a block time was specified (xTicksToWait was not zero), and the return value is not zero, then it is possible the calling task was placed into the Blocked state, to wait for its notification value to be greater than zero, and its notification value was updated before the block time expired.

If a block time was specified (xTicksToWait was not zero), and the return value is zero, then the calling task was placed into the Blocked state, to wait for its notification value to be greater than zero, but the specified block time expired before that happened."

Demo-Program that Creates the Problem:

The demo-program creates the reported problem:
http://jimyuill.org/freertos/main_blinky.c
The program was created by modifying the FreeRTOS demo-program Blinky Demo.

Console output from the demo-program:
http://jimyuill.org/freertos/main_blinky_c–console-output.txt

Thanks for taking the time to provide such a detailed report. I think in this case we will update the documentation to make some of the descriptions clearer as to when they do and don’t apply.

Richard,
You’re welcome.
I’m new to FreeRTOS and am working through a book on it. The book’s sample-code includes this use of ulTaskNotifyTake, which I described. I noticed it was inconsistent with the FreeRTOS documentation. Digging through the docs and kernel code was instructive.

Your solution of updating the docs seems best.
I assume you all will take care of creating an issue for it, if appropriate.

Thanks,
Jim