I understand the advantage of posting just pointers to queues but wonder if it's possible to create a race condition with this approach. For example, I have an ISR grabbing characters as they show up at a serial port connected to a GPS receiver. As the characters arrive, they get appended to a GPSMessage character array. The GPS messages is terminated with a LF character so when the ISR sees a LF, it posts the pointer to GPSMessage to the appropriate queue where the ProcessGPSMessage state deals with it. If I was passing a copy of GPSMessage, after posting the message the ISR could clear GPSMessage and starting adding new characters as they arrive. But since I'm just passing a pointer to the ISR's instance of GPSMessage, wouldn't I have to wait until the ProcessGPSMessage state is done dealing with it? I can see a variety of ways to deal with this situation, double buffering in the ISR or making sure the ProcessGPSMessage state makes it's own copy immediately for example, but I want to make sure I understand the situation.
Thanks - Gene
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Nooooo! Don't post pointers to data inside events! This is data sharing pure and simple that goes squarely against the "share-nothing" principle.
Posting pointers to events does NOT mean that you all of the sudden are allowed to share data. The pointers to events you post can be only of two origins: (1) they are static constant immutable events or (2) they are dynamic events allocated with Q_NEW() or Q_NEW_X(). Either way, the events cannot have pointers inside.
The immutable events never change and so they are evidently thread-safe.
The dynamic events are managed by the framework, and there a copule of simple ownership rules you need to obey. In exchange, the framework does all the heavy lifting of thread-safe passing of such events. This is the most important service of an event-driven framework.
So, specifically to your GPS message, you need to create a specific QEvt subclass with the following layout:
Then you dynamically allocate your MyGpsEvt instance and you receive all the bytes of the GPS message directly into this event. This can happen over multiple invocations of your ISR or whatever else you are using. But eventually you post this event and immediagely allocate another one to start filling it up again. That way, you let the framework do the buffering for you.
I hope that this starts to make sense...
--MMS
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Your understanding is correct. Double buffering is the common approach. Note that if you know the max message size, you can allocate the event via Q_NEW() and then fill it in over a succession of ISRs before posting. Then you get the benefits of the framework's garbage collection too.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Well now I'm really, really glad I asked this question. I think the issue might be that I incorrectly described what I'm doing. The only thing I know how to do is what I'm learning from the QP examples. So I'm really hoping this is close to right or at least not as wrong as Miro thinks it is.
Here's how I post an event now.
Q_STATE_DEF(Blinky, on) {
QP::QStatestatus_;switch(e->sig) {
//${AOs::Blinky::SM::on}
caseQ_ENTRY_SIG : {
cout<<"Blinky on state: Enter"<<endl;BSP::ledOn();OnEvent*onEvent=Q_NEW(OnEvent, MsgInQ_SIG);onEvent->parameter=1;AO_Logger->POST(onEvent, this);cout<<"Posted event to logger"<<endl;status_=Q_RET_HANDLED;break;
}
And now that you point it out, I think I see my misunderstanding. As long as I dynamically allocate a new event (like I'm doing now?) every time I want to post the most recent GPS message, the framework takes care of it. And now you're suggesting that since I'm using QNEW, the framework will even take care of GC'ing the memory I used every time I dynamically created a new event. If that's correct, can you tell how QP knows when an event is garbage and collectible by the GC? I'm used to the C# Garbage Collector which is a bit of an uncontrolled, undocumented monster with a mind of its own.
Last edit: GeneM 2020-05-29
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
QP's garbage collection is explained PSiCC2 Book, page 339. The framework uses a reference count for each dynamically allocated event. Garbage collection is well-defined and deterministic in QP.
Last edit: Panopticon 2020-05-29
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I understand the advantage of posting just pointers to queues but wonder if it's possible to create a race condition with this approach. For example, I have an ISR grabbing characters as they show up at a serial port connected to a GPS receiver. As the characters arrive, they get appended to a GPSMessage character array. The GPS messages is terminated with a LF character so when the ISR sees a LF, it posts the pointer to GPSMessage to the appropriate queue where the ProcessGPSMessage state deals with it. If I was passing a copy of GPSMessage, after posting the message the ISR could clear GPSMessage and starting adding new characters as they arrive. But since I'm just passing a pointer to the ISR's instance of GPSMessage, wouldn't I have to wait until the ProcessGPSMessage state is done dealing with it? I can see a variety of ways to deal with this situation, double buffering in the ISR or making sure the ProcessGPSMessage state makes it's own copy immediately for example, but I want to make sure I understand the situation.
Thanks - Gene
Nooooo! Don't post pointers to data inside events! This is data sharing pure and simple that goes squarely against the "share-nothing" principle.
Posting pointers to events does NOT mean that you all of the sudden are allowed to share data. The pointers to events you post can be only of two origins: (1) they are static constant immutable events or (2) they are dynamic events allocated with Q_NEW() or Q_NEW_X(). Either way, the events cannot have pointers inside.
The immutable events never change and so they are evidently thread-safe.
The dynamic events are managed by the framework, and there a copule of simple ownership rules you need to obey. In exchange, the framework does all the heavy lifting of thread-safe passing of such events. This is the most important service of an event-driven framework.
So, specifically to your GPS message, you need to create a specific QEvt subclass with the following layout:
Then you dynamically allocate your
MyGpsEvt
instance and you receive all the bytes of the GPS message directly into this event. This can happen over multiple invocations of your ISR or whatever else you are using. But eventually you post this event and immediagely allocate another one to start filling it up again. That way, you let the framework do the buffering for you.I hope that this starts to make sense...
--MMS
Your understanding is correct. Double buffering is the common approach. Note that if you know the max message size, you can allocate the event via Q_NEW() and then fill it in over a succession of ISRs before posting. Then you get the benefits of the framework's garbage collection too.
Well now I'm really, really glad I asked this question. I think the issue might be that I incorrectly described what I'm doing. The only thing I know how to do is what I'm learning from the QP examples. So I'm really hoping this is close to right or at least not as wrong as Miro thinks it is.
Here's how I post an event now.
and here's the onEvent definition
So, how badly did I screw this up?
The code looks correct as far as dynamically allocating an event, setting the parameter(s) and posting it.
I wouldn't contaminate the code with the "printfs" (cout <<), though. I would use the QP/Spy software tracing instead.
--MMS
Great. Learning QP/Spy and then QUTest are next on my list but I'm a one step at a time kind of learner. But I make up for it by being really slow.
Thanks for all the help.
And now that you point it out, I think I see my misunderstanding. As long as I dynamically allocate a new event (like I'm doing now?) every time I want to post the most recent GPS message, the framework takes care of it. And now you're suggesting that since I'm using QNEW, the framework will even take care of GC'ing the memory I used every time I dynamically created a new event. If that's correct, can you tell how QP knows when an event is garbage and collectible by the GC? I'm used to the C# Garbage Collector which is a bit of an uncontrolled, undocumented monster with a mind of its own.
Last edit: GeneM 2020-05-29
QP's garbage collection is explained PSiCC2 Book, page 339. The framework uses a reference count for each dynamically allocated event. Garbage collection is well-defined and deterministic in QP.
Last edit: Panopticon 2020-05-29