diwhyyyyy
diwhyyyyy

Reputation: 6372

FreeACT active object event synchronisation/safety

I'm thinking of writing an embedded application using the FreeACT framework to implement "Active Objects" event-driven system on a FreeRTOS system.

Fundamentally a FreeACT active object contains a FreeRTOS queue and a task. Events are posted to the active object, which unblocks the task to process the queue items in that tasks context (in a non-blocking way). So it's non-blocking all the way down, and asynchronous back up.

However, something I do not understand is how the event are typically synchronised in such a system. This is the basic event type in FreeACT, it's a single uint16:

typedef uint16_t Signal; /* event signal */


/* Event base class */
typedef struct {
    Signal sig; /* event signal */
    /* event parameters added in subclasses of Event */
} Event;

An event queue buffer looks like this, it's statically allocated somewhere and given to the constructor:

    Event *active_queue[10];

So there are ten slots (say) for ten pointers to events, which is the working memory for a FreeRTOS queue, which is initialised like this:

void Active_start(Active * const me,...) {
    ....
    me->queue = xQueueCreateStatic(
                   queueLen,            /* queue length - provided by user (10) */
                   sizeof(Event *), ... /* a pointer's size */
    ....
}

Posting an event onto an Active Object's queue is done with Active_post, which copies the pointer (note that &e is a pointer to the pointer to the event, FreeRTOS queue APIs copy the pointed-to thing, so, in this case, the pointer) to the event onto the Active Object's internal FreeRTOS queue (and there's an from-ISR counterpart):

void Active_post(Active * const me, Event const * const e) {
    BaseType_t status = xQueueSendToBack(me->queue, (void *)&e, (TickType_t)0);
}

And the Active Object's task (i.e. thread) will notice that and dispatch:

    for (;;) {   /* for-ever "superloop" */
        Event const *e; /* pointer to event object ("message") */

        /* wait for any event and receive it into object 'e' */
        xQueueReceive(me->queue, &e, portMAX_DELAY); /* BLOCKING! */
        configASSERT(e != (Event const *)0);

        /* dispatch event to the active object 'me' */
        (*me->dispatch)(me, e); /* NO BLOCKING! */
    }

And the dispatch function will get that event, and act:

static void BlinkyButton_dispatch(BlinkyButton * const me, Event const * const e) {
    switch (e->sig) {
        ...act!
    }
}

My question is: when you have posted your pointer to an event, how do you keep the event data safe until the Active Object 100% surely has acted on it?

For example, here is the blinky example from the FreeACt repo, where events come from a timer interrupt context:

void vApplicationTickHook(void) {
    ....
    if (press) {
        static Event const buttonPressedEvt = {BUTTON_PRESSED_SIG};
        Active_postFromISR(AO_blinkyButton, &buttonPressedEvt,
                           &xHigherPriorityTaskWoken);
    }
    ....
}

So there is a single static Event there, and the pointer to that will go off onto the Active Object's queue, eventually (when the task awakens to deal with it) to be dequeued and handled.

But what is stopping vApplicationTickHook firing again and overwriting the event structure? The queue may be 10 items deep, but they'll all be pointing to the same address.

This is OK in this exact context, because the event is absolutely fixed: the sig at that static memory address will always be BUTTON_PRESSED_SIG. But what if you want to report other data?

Is it just that the FreeACT demo project is missing that part of the puzzle here?

Upvotes: 1

Views: 55

Answers (0)

Related Questions