Indexed notifications best practices

Hi,

I have three tasks, P1, P2 (‘P’ is from “Producer”), and C (from “Consumer”). P1 and P2 have higher priority than C.

P1 produces the values: 1, 3, 5, …; while P2 produces the values: 2, 4, 6, … .

P1 notifies on task’s C slot 1, while P2 notifies on task’s C slot 2. Task C does something with the value sent by P1, and does other thing with the value sent by P2.

However, while experimenting I noticed that, as my C’s setup, C task is missing data. My C task is as follows:

void Consumer_task( void* pvParameters )
{
   uint32_t p1_value;
   uint32_t p2_value;

   while( 1 )
   {
      if( xTaskNotifyWaitIndexed( 1, 0xffffffff, 0x00, &p1_value, pdMS_TO_TICKS( 2000 ) ) != pdPASS ){
         DEBUGOUT( "Error on P1\n\r" );
      } else{
         DEBUGOUT( "In slot 1 I received the value: %d\n\r", p1_value );
      }

      if( xTaskNotifyWaitIndexed( 2, 0xffffffff, 0x00, &p2_value, pdMS_TO_TICKS( 2000 ) ) != pdPASS ){
         DEBUGOUT( "Error on P2\n\r" );
      } else{
         DEBUGOUT( "In slot 2 I received the value: %d\n\r", p2_value );
      }
   }
}

An output from this program is:

P1 is about to notify: 7…
P1 is about to notify: 9…
P2 is about to notify: 6…
In slot 2 I received the value: 6
In slot 1 I received the value: 9
P1 is about to notify: 11…
P1 is about to notify: 13…
P2 is about to notify: 8…
In slot 2 I received the value: 8
In slot 1 I received the value: 13
P1 is about to notify: 15…
P2 is about to notify: 10…
In slot 2 I received the value: 10
In slot 1 I received the value: 15

In this excerpt values 7 and 11 are never received by the task C. In this experiment the tasks P1 and P2 are almost identical:

TaskHandle_t consumer_h;

void Producer1_task( void* pvParameters )
{
   pvParameters = pvParameters;

   TickType_t last_wake_time = xTaskGetTickCount();

   uint32_t cont = 1;

   while( 1 )
   {
      vTaskDelay( pdMS_TO_TICKS( 997 ) );

      DEBUGOUT( "P1 is about to notify: %d...\n\r", cont );

      xTaskNotifyIndexed( consumer_h, 1, (uint32_t) cont, eSetValueWithOverwrite );

      cont += 2;

   }
}

I’m pretty sure that I’m doing something wrong in the way the consumer task is sitting waiting for the notifications due to the fact that it blocks twice. That’s way I’m asking you:

What is the best way for a task to wait for notifications from two or more slots from two or more tasks? Is that posible? Is that the intention of indexed notifications?

Thank you in advanced :slight_smile: !

One thing about notify, is the sending task doesn’t get held for the notification to be ‘empty’, so if P1 sends 7 and then sends 9 before the 7 is taken (because C is waiting for P2) then the 7 will be lost.

You need to do something to prevent this from happening.

As the code waiting for data from P1 doesn’t execute while task C is blocked waiting for data from P2, it loses the data from P1. That’s the root of my question, Is there any advice or good practice on how to handle such situation?

A workaround is to use a third slot as notification field from P1 and P2, and then task C blocks only on this third slot. Was it considered in the FreeRTOS’ indexed notifications feature?

I think a (Unix) select style API of indexed notifications is not yet available.
I mean waiting for any (1 or more) of the indexed notifications with a single blocking call as a group. The already supported way of implementing an event group with notifications is using the individual bits of a notification as event slots in your example as explained here FreeRTOS task notifications, fast Real Time Operating System (RTOS) event mechanism

Edit: As Richard pointed out your application should be aware that setting a notification value using eSetValueWithOverwrite just overwrites a previously set value in case it wasn’t processed by the receiving task in time.

When I read the first time about the indexed notifications it sounded great. But now that I’m seeing them in detail I don’t know what to think. Maybe the scenario I set up wasn’t intended for such feature, but at the same time I can’t imagine any other.

I’ll try the workaround that I mentioned earlier:

I believe this is a prime case for using queues/mailboxes instead of notifications. Is there a particular reason why you insist in notifications?

A key thing to remember is that the notification system is intentionally a ‘lite’ interface to keep it fast. If it works for what you want, it is great. If it doesn’t, then you have the fuller options available: EventGroups, Queues, Semaphores, and the like.

You problem sounds made for Queue, and maybe add a QueueSet so you can wait on both Queues at once.

Just use the right tool.

Hi,

I guess I didn’t explain myself. I already know better and powerfool features in FreeRTOS than those implemented based on notifications. That’s clear.

What is the purpose of having slots? How do you handle the event when a task is waiting on two or more slots from two or more tasks? Why did Richard Barry even bother in writing this feature if you are pointing me out to others already in the OS?

How do you imagine using the slots?

sorry, what do you mean by “slots?” To my best knowledge, that’s not a term known to FreeRTOS.

Answering your second question: richard-damon already answered that. Notifications came to FreeRTOS relatively late as an efficieny booster for a common application that didn’t need the full power of queues, namely, tasks wakeups (eg from an ISR). Queue-based synchroisation primitives cost too many CPU cycles during an an enforced task switch that doesn’t need to carry information.

Yet your application needs to carry information from one task to the other, hence it would appear to be the better solution to use the tools that were made for exactly that purpose.

Hi,

“Slots” is the term that I use when I teach C arrays to my students. My mistake.

Notifications are great, I love them! I just don’t understand the purpose of having an array of entries (that’s the correct term). The examples given in the official documentation don’t show how a task can sit and wait for notifications from two or more tasks, each one writing in one array’s entry. That’s what I’m asking.

I already know the capabilites and how and when to use queues, messages, streams, etc. My question is still how to handle that given situation. That’s way I asked you guys if you can think of any other scenario for the indexed entries.

The only reason I can think of the indexed notifications entries is that both streams and messages use the array index 0, so if your app needs messages and notifications, then you must use an index greater than 0 for the latter.

Maybe that’s the core and only purpose of the indexed notif’s.

Well, I think it’s sometimes a good idea to start with a basic implementation of a new feature and see how it’s used in practice to improve it in a next step. It’s often impossible to foresee every use case. Often when trying to provide a complete API up front it just reflects the thoughts and vision of the (one) implementer at a certain time and in practice it turns out that people use a feature in a different way the implementer wasn’t aware of. This is especially true for more complex features, of course.
Notifications started with a single integer value. Later stream buffers and message buffers were added due to user demand, but coupled with the same notification mechanism keeping the kernel lean and the mechanism fast. Using non-indexed notifications and e.g. stream buffers have the side effect of false unblocking of xTaskNotifyWait when something was put into the stream buffer.
Hence indexed notifications were added to decouple those mechanisms while again striving for minimal overhead in code and task data.
I also think kind of xTaskNotifyWaitIndexedAll API could be added in the future to support waiting for multiple/all indexed notifications because I think there are use cases like yours where this is desirable.
It might be relatively easy to implement a simple version just handling ALL configured indices maybe applying the same ulBitsToClearOnEntry/Exit argument to all notifications. It’s getting more complicated if one just wants a sub-range of all possible indices and maybe with different ulBitsToClearOnEntry/Exit arguments per index…
Another one want’s to specify a table of arbitrary indices to wait for.
It’s easy to stumble into the problem of an exploding or overly complicated API when trying to cover the variety of all possible use cases. Therefore I’m pretty sure @rtel already thinks about it and discusses possible ways to extend the recently added indexed notifications in the way you and probably others need. We just have to be patient even if it’s hard :wink:

Edit:

I agree that’s probably one of the important reasons as mentioned above.

1 Like

I found a workaround, as I’ve mentioned in thread: Use an exclusive notification index to notify that a task (or tasks) has (have) put a value. Not sure it’s the best, but there are no missing data.

I’ll show you the complete code (the underlying hardware isn’t important) and then the output:

#include "board.h" //(LPC1549 Xpresso V2)
#include <cr_section_macros.h>

#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
#include "task.h"

typedef enum { RED, GREEN, BLUE } LEDS_ON_BOARD;

#define C_NOTIFS_SLOT 0

#define P1_READY 0x01
#define P1_SLOT  1

#define P2_READY 0x02
#define P2_SLOT  2


#define configASSERT( cond ) if( ( cond ) == 0 ){ taskDISABLE_INTERRUPTS(); while( 1 ); }

TaskHandle_t consumer_h;

void Producer1_task( void* pvParameters )
{
   pvParameters = pvParameters;
   
   uint32_t cont = 1;

   while( 1 )
   {
      vTaskDelay( pdMS_TO_TICKS( 997 ) );

      DEBUGOUT( "P1 is about to notify: %d...\n\r", cont );

      xTaskNotifyIndexed( consumer_h, 1, (uint32_t) cont, eSetValueWithOverwrite );
      xTaskNotifyIndexed( consumer_h, C_NOTIFS_SLOT, P1_READY, eSetBits );

      cont += 2;

   }
}

void Producer2_task( void* pvParameters )
{
	pvParameters = pvParameters;

   uint32_t cont = 2;

   while( 1 )
   {
      vTaskDelay( pdMS_TO_TICKS( 1747 ) );

      DEBUGOUT( "P2 is about to notify: %d...\n\r", cont );

      xTaskNotifyIndexed( consumer_h, 2, (uint32_t) cont, eSetValueWithOverwrite );
      xTaskNotifyIndexed( consumer_h, C_NOTIFS_SLOT, P2_READY, eSetBits );

      cont += 2;
   }
}

void Consumer_task( void* pvParameters )
{
   uint32_t p1_value;
   uint32_t p2_value;

   uint32_t         notified_value;
   const TickType_t max_block_time = pdMS_TO_TICKS( 2000 );

   while( 1 )
   {
      BaseType_t 
      ret_val = xTaskNotifyWaitIndexed(
         C_NOTIFS_SLOT,
         0,                       // do not clear bits on entry
         ( P1_READY | P2_READY ), // clear bits on exit
         &notified_value,
         max_block_time );

      if( ret_val != pdFALSE ){
         
         if( notified_value & P1_READY ){
            xTaskNotifyWaitIndexed( 
                  P1_SLOT,    // slot
                  0,          // do not clear bits on entry
                  0xffffffff, // clear all bits on exit (set the internal notif value to zero)
                  &p1_value, 
                  0 );        // don't wait, we already know there's data on this slot

            DEBUGOUT( "In slot 1 I received the value: %d\n\r", p1_value );
            Board_LED_Toggle( RED );
         }

         if( notified_value & P2_READY ){
            xTaskNotifyWaitIndexed( P2_SLOT, 0, 0xffffffff, &p2_value, 0 );

            DEBUGOUT( "In slot 2 I received the value: %d\n\r", p2_value );
            Board_LED_Toggle( BLUE );
         }

      }
   }
}


int main(void)
{

   SystemCoreClockUpdate();
   Board_Init();

   Board_LED_Set(RED, false);
   Board_LED_Set(GREEN, false);
   Board_LED_Set(BLUE, false);

   if( xTaskCreate( Producer1_task, "PRD1", 256, NULL, tskIDLE_PRIORITY+1, NULL ) != pdPASS )
      configASSERT( 0 );

   if( xTaskCreate( Producer2_task, "PRD2", 256, NULL, tskIDLE_PRIORITY+1, NULL ) != pdPASS )
      configASSERT( 0 );

   if( xTaskCreate( Consumer_task, "CONS", 256, NULL, tskIDLE_PRIORITY, &consumer_h ) != pdPASS )
      configASSERT( 0 );


   vTaskStartScheduler();

   while( 1 );
}

As seen in the output there aren’t missing values:

P1 is about to notify: 2361...
In slot 1 I received the value: 2361
P2 is about to notify: 1350...
In slot 2 I received the value: 1350
P1 is about to notify: 2363...
In slot 1 I received the value: 2363
P1 is about to notify: 2365...
In slot 1 I received the value: 2365
P2 is about to notify: 1352...
In slot 2 I received the value: 1352
P1 is about to notify: 2367...
In slot 1 I received the value: 2367
P1 is about to notify: 2369...
In slot 1 I received the value: 2369
P2 is about to notify: 1354...
In slot 2 I received the value: 1354
P1 is about to notify: 2371...
In slot 1 I received the value: 2371
P1 is about to notify: 2373...
In slot 1 I received the value: 2373

Thank you all people that respond to this post. Happy coding!

1 Like

Hi Xavier,

thanks for posting your solution!

I’ll look at the code more in depth later, but just one remark at this point: I’m sure you’re aware that you can use the same task function for your producers, encapsulating the slight differences in the pvParameters parameter.

Hi,

Yes, I’m aware. Actually I wrote something about it in a free course (in spanish) on “Arduino in RealTime”:

Passing compound parameters to tasks

But for the example in this post I chose simple tasks so we could focused in my question. In fact, one or more of those tasks that notifies to the consumer task might be one or several ISRs, in which case we cannot use the pvParameters parameter.

If you find any flaw in my workaround, please let me know.

Greetings :slight_smile: !

One thing I’d like to see is random (or pseudo) random delay times in between productions…

Hi,

I added some randomness to my test program, and although I didn’t automate the success/failure outputs rate, I can say this workaround is better than nothing: The program didn’t miss any data and performs far my expectations.

I use the standard function rand() which is ok for the purpose of this experiment. I got the seed squaring the cumulative of 16 analog reads from the chip’s internal temperature sensor.

void Producer1_task( void* pvParameters )
{
   pvParameters = pvParameters;

   uint32_t cont = 1;

   while( 1 )
   {
      vTaskDelay( pdMS_TO_TICKS( ( rand() % 100 ) + 100 ) );

      xTaskNotifyIndexed( consumer_h, P1_SLOT, (uint32_t) cont, eSetValueWithOverwrite );
      xTaskNotifyIndexed( consumer_h, C_NOTIFS_SLOT, P1_READY, eSetBits );

      cont += 2;
   }
}

void Producer2_task( void* pvParameters )
{
	pvParameters = pvParameters;

   uint32_t cont = 2;

   while( 1 )
   {
      vTaskDelay( pdMS_TO_TICKS( ( rand() % 100 ) + 125 ) );
      // 75% of overlap

      xTaskNotifyIndexed( consumer_h, P2_SLOT, (uint32_t) cont, eSetValueWithOverwrite );
      xTaskNotifyIndexed( consumer_h, C_NOTIFS_SLOT, P2_READY, eSetBits );

      cont += 2;
   }
}

Greetings!

1 Like