Possibility of using an observer pattern for notification subscription/unsubcription

I am thinking of whether it would be suitable to use a observer design pattern for the use-case where the user can subscribe or unsubscribe to certain notifications even though it may possibly be done easily with RTOS APIs alone.

For the observer pattern:
I’d probably have a sensor class as the subject and, perhaps a notification class as an observer which would attach to Sensor Class, and every time there’s the value is read, the observer class(es) would get notified right away.
Similarly, when the user doesn’t want any notification from Sensor A, a detach() would get invoked and no notifications would get displayed.

My Current Design (with no observer pattern):
I have something like the following where the main thread parses user input over terminal and sensor thread continually reads sensor data, stores into a notification object, which is then displayed it.

My thoughts:
How I’m thinking of integrating the design pattern is by having a Subject class, with a Sensor class being a concrete class inheriting it, whereas the Notification class being an observer. Once the registration is done, every time a sensor value is read inside an IRQ handler, notify() is invoked for all the observers – meaning a notification class gets notified everytime there’s a sensor read

Does anyone see an issue with this approach?

class SensorA : public Subject {
      public:
      void notify() {
         for (uint8_t i = 0; i < observerSize; i++) { 
             observerList[i]->update();
         }
};

class Notification : public Observer {
     public: 
    Notification::Notification(SensorA sensorA) : _subject(sensorA) {
           _subject.attach(this);  
    }
     void update(Subject *subject) {
        if (subject == _subject) {    // making sure the notification is received from sensorA to process it accordingly but the address doesn't match
        
       } 
};

void SensorA::readVal()	{     // invokes within the IRQ
      process();
      notify();  // Subject::notify()
}

// main thread that process user inputs
void SysTsk::mainThread(void)
{
   while(true) 
   {
      if (xQueueReceive(sysTskQueue, &msg, 0) == pdPASS)  // user input passed via Queue from ISR
      {
           // parse user input
      }
   }
}

void SensorA::thread(void)
{
  while(true) 
   {
         uint16_t data = initiateReadI2C();  
   }

}

You don’t show much code but it seems sound conceptually.

Updated the code. Let me know if it’s still unclear. One issue with this approach i’m facing now is not being able to recognize in the observer whether the update() request came from SensorA class

Your code is way too incomplete to make any reasonable sense. For example, you don’t include any class declarations but instead include a mainThread task function with no visible connection to the rest of the code (what is the sysTskQueue? Who feeds it and where and how does that thread relate to your pattern?).

Anyways, yes, you can do that, why not. I’d probably code this differently, eg I’d always send notifications to all tasks and then let each task decide every time whether to process the notification or not. At the very least, that way you don’t need to deal with list coherence on the observerList member (if you allow for dynamic attachment and reattachment of tasks to that list, you’ll need to consider things like mutual exclusion and - if there is a lot of attachment and reattachment - back-door serialization). On the other hand, each task gets woken up more frequently which may burn too many cycles.

Those weigh offs need to be matched against the expected runtime behavior.

unfortunately I can’t seem to edit the description now, but for now, the mainThread will take in user inputs and the relevant user input could be…disable notifications from sensorX or enable them. And if the current observer isn’t attached to the requested subject, it would go on and attach so everytime there’s an update, the observer gets updated rightaway.

I’d always send notifications to all tasks and then let each task decide every time whether to process the notification or not.

Interesting thought. For now though, My Notification class doesn’t have a thread that’s constantly running. I am still architecting the design while exploring feasible options.

For observerList, I’m using a static buffer with a fixed size. Just wanted to avoid any dynamic allocation

I tend to have the subscriber provide a subscription object which contains the callback operation and a linked list pointer. The Subscription class links this into the notification chain, and that chain is walked when the notification happens.

The notifier doesn’t need a dedicated class, whatever decides that it is time to notify (which will either be an ISR or some part of some task, after all that is all that can run) just walks the chain and calls the notifications. It is defined what context this happens (Task or ISR) and these callbacks will all be very short non-blocking operations, especially if done in an ISR).

1 Like

The subscriber (Observer) is already providing a subscription object to the Subscription class (Subject) in my case I believe. What is the linked list pointer for? Where’s it residing? Is this a part of the observer class? And is the idea to only invoke the callback as opposed to a generic update() function?

Still, since obviously your recipients can dynamically subsribe/unsubscribe to notifications, whichever data structure you use needs to be aware of changing subscription lists, be it via a 0 entry in a static array or whatever, and the access to that list must be coordinated in some way to avoid race conditions.

do you still foresee race conditions if attach is invoked from within a single task?

well, without seeing some more detailed outline of a proposed architecture, it’s way too hazy to go into discussions. My initial gut feeling response would be that in that case, you’d need the individual tasks to communicate with this bookkeeping task to request subscriptions/unsubscriptions, yielding even more levels of dependencies. Then again, race conditions may or may not be a problem in a given architecture (possibly tasks don’t care if they receive a single leftover item after they unsubscribed), so the potentail race conditions may be ignorable.

At this very bird’s eye/abstract view, there really isn’t a point yet in going in depth. Doubtlessly there are useful applications of subscription patterns. As you correctly point out, FreeRTOS offers a good platform to start from for that kind of thing. As soon as there is usable code to start with as well as a meaningful use case, it’s time to look at the ramifications.

I build a linked list of subscriptions so I don’t need to preallocate a buffer to put them into. I use a critical section when updating the linked list so any time outside the critical section it is consistent.

so you are using dynamic memory for linked list’s allocation and are allocating nodes in runtime based on the number of subscribers?

No. The subscribed provides a subscription object which is a node suitable for an intrusive linked list, that is normally statically allocate with the data for the task (most of my tasks are objects derived from a Task class, so the subscription object will be a member variable or a mix-in sub-class. The notification service will link the subscription into its list of subscribers using a critical section, to avoid races in the linking, and when the even occurs it walks this list and activates the callback in the subscription object.

Everything is (or at least can be) statically allocated. The only time you would need dynamic allocation would be if some subscribed doesn’t know how many things it might want to subscribe to.

In my case, most subscriptions actually occur before the scheduler is even started as the system configures itself allowing me to use generic code for the subscription service so it doesn’t need to have coded who needs notification to events like we are preparing to go to sleep.

Alright, but as far as I can tell, this use case does not need subscriptions in the first place (since the subscriptions lists do not change during the run time of the application, they might as well be fixed using #ifdefs or the like, correct?) In this case, there isn’t even a need for a critical section because the list will never change as long as the code runs.

This is a very different use cases than one that deals with dynamically changing subsrciptions, though.

As I said, MOST occur at startup, but there can be dynamic connections that occur at times.

I don’t want to use #ifdefs, as the subscription method allows the writing of the service completely independent of who is going to need to subscribe, so it can be used for many different projects.

The linked list is to allow multiple subscribers (of unknown quantity) to subscribe to the notification. The pointer is part of the subscription object, where each subscription object contains a pointer to the next subscription object in the list and a callback object (perhaps a function pointer) that is provided by the subscriber to let them know the event occurred. The idea of the callback is it can notify the subscriber in the way IT wants to get its notification, be it pushing a message onto a queue or sending a direct-to-task notification, or even just setting a variable.

I see.

In my case, Sensors Class being a Subject/Subscription itself sends out the value to the Notification Class as long as Notification Class is a part of the observer list of the Subject.

So a notification object would get populated with the value from SensorA as long as notification class is attached.

Based on your suggestion, I think I could have previous, next, and head pointer as a part of the Notification class which holds the list of Sensors that are subscribed for the notifications…

The service providing the subscription has the head pointer to the first subscription. Each subscription has a next pointer to the next subscription block.

When the even occurs, the service walks the list of subscriptions, and activates the call back in that subscription block, possibly providing the reading. That callback does something quick with the value, perhaps sending it some way to the task that provided it.

In my case the subscription block tends to be a sub-object inside the subscriber (assuming a given subscriber knows the maximum number of subscriptions it will make, often just 1 of a given type).

You don’t NEED a previous pointer, but it can make removing a subscription easier.

but in my case, subscriber (Notification) receives notifications from the service providing the subscription (Sensor) every time their values are read.

So one notification class may be subscribed to multiple services. So does it make sense to have a linked list inside the service providing the subscription given there’s only going to be one subscriber, isn’t it?

What I’m thinking is rather have a linked list of the subscriptions (Sensor classes) inside the Notification/Subscriber class

If the subscription service can ONLY have one particular subscriber, then all it needs is a flag that says it needs to do the notifications or not. That is just a simple conditional notification, not a subscription. You DON’T need to get into things like an observer pattern or anything like that.

If it knows that it can only have one subscriber, but it could be different things in different cases, then it just needs a pointer to where to send the notification.

The key is knowing how general you need to be.