xTimerChangePeriod(timer, ...) failing with errQUEUE_FULL from within timer's own callback

Symptom:

xTimerChangePeriod(timer, …) is consistently returning pdFAIL

Details:

Inside the timer’s callback, I have code along these lines. Note that it is calling xTimerChangePeriod(timer, ...) from within the timer’s own callback:

static void timer_cb(xTimerHandle xTimer) {
    BaseType_t ret;
    timer_state_t state = (timer_state_t)pvTimerGetTimerID(xTimer);
    if (state == STATE1) {
        ret = xTimerChangePeriod(xTimer, DURATION1, 100);
        ASSERT(ret == pdPASS);
        vTimerSetTimerID(xTimer, STATE2);
    } else if state == STATE2) {
        ret = xTimerChangePeriod(xTimer, DURATION2, 100);
        ASSERT(ret == pdPASS);
        vTimerSetTimerID(xTimer, STATE1);
    }
}

(This isn’t really what the code does, but I suspect it’s enough to point to the issue.)

The code will trigger the ASSERT with ret == pdFAIL. When I step through the code prior to the fail, I notice that it hits the clause in xQueueGenericSend whose comment reads:

/* The queue was full and a block time was specified so configure the timeout structure. */

… and the code eventually returns with a errQUEUE_FULL (aka pdFAIL).

I am not an expert on the internals of the FreeRTOS timers, but my hunch is that because xTimerChangePeriod() is being called repeatedly inside the timer’s callback, the original calls haven’t yet been removed from the queue and thus the queue fills up.

So, the questions:

  • Is my hunch correct?
  • If not, what might be causing the queue to fill up?
  • Regardless, what’s the FreeRTOS idiom for doing what I’m trying to do?

First rule, TImer Call Back functions really should be short and not block. While the timer callback is running, no other timer operations can happen.

In this case, if the timer process queue is full, waiting can’t help as you are blocking in the timer task so it isn’t going to take any items off the queue to make space.

While it is allowed for timer callback to do timer operations, your method of changing on EVERY call can overload the system, and in your example, each timer is always being changed to the same period each time which is just creating work that doesn’t need to be done.

@richard-damon Though I’d not heard about the restrictions within timer callbacks, it makes perfectly good sense. One small comment:

The actual code is more complex (multiple state branches with different timeouts), but that doesn’t negate your point about doing work within the callback.

My challenge is now deciding how to drive state machine: While some of the state transitons happen when a button changes state, most of the state transitions happen when the timer expires. And within some of those states, an arbitrary amount of work gets done (e.g. updating a display, etc), so clearly that can’t be inside a callback. Due to memory limitations, I’m loath to create a new task just for driving the state machine.

Is it legit for a timer callback to generate a notification for the task that it’s running in? If so, I can use the callback to generate a notification when one of those longer-running actions need to be performed.

Or is there an obvious simpler way to go about this?

Are you asking if you can send a notification to a task (which perform the long running actions) from a timer callback? If yes, then yes you can do so.

1 Like

Timer Callbacks generally should NOT do longer running operations, as those operations will affect all other usages of timers. One way to think of it is that generally, the timer task should be one of the highest priority tasks in the system as timer callbacks can easily be a critical timing action. Any code executed in a timer callback is run as part of that timer task, and thus at that high priority. (Your question about is it legit for a timer callback to generate a notification for the task that is running is perhaps shows a misunderstanding, ALL timer callbacks run as part of the Timer Task, so ‘the task’ referred to above is only the Timer Task, not the task that created the timer).

Longer actions almost always should be done in lower priority tasks, and thus the timer callback is NOT the right place for it.

Yes, when set up right, generally a timer callback can make a limited number of timer operations within it, but this presumes that the timer task HAS been set as a high priority task so that tasks of higher priority than the timer task can fill its queue, and that the timer queue has been made big enough to handle the load.

Running a ‘State Machine’ is a reasonable operation for a timer callback, but the ‘payload’ of a state might not be, and you may need to have a lower priority task for the timer to signal to handle any slow payload needed by the state machine. In your case, since you get transitions from more than just timer events, and that some of the operations needed are ‘large’, having a task to do that seems like the right choice. Don’t think of the task as just being the ‘state machine’ but where those longer actions occur, and the whole purpose of tasks is to provide places for longer operations to occur with possible execution overlap.

So the question comes, do those longer operations already have tasks to perform them, so the timer only needs to signal them (which would be quick) or are they part of the ‘state machine task’ which takes signals for sources (like the buttons and timers).

Naturally, there is a task outside of the timer callbacks that allocates the timers, starts the timers, listens for button events, etc. So that’s the task I was referring to when I asked if I could use timer callbacks to “generate a notification for the task that it’s running in”.

In my app, that task is already set up to wait for notifications, so it shouldn’t be difficult to generate notifications from within the timer callback to trigger the longer-running actions.

But note, the timer call backs don’t ‘run in’ the task that created them, but as part of the timer task.

The callbacks can, and probably should, at least for the longer operations, send a notification to the task processing those events. I would likely make timer events look very much like the current button events that you say that task is waiting for.