How to see if a FreeRTOS task has been deleted

Note: this is a horrible design pattern, and ephemeral FreeRTOS tasks should not be created. See my FreeRTOS best practices bullet #17 here:

[SAFETY-CRITICAL] Use software timers and callbacks, not short-lived (temporary) tasks that just time out, do something, and delete themselves.

…but I’m stuck in a legacy code base and fixing things one-at-a-time, so for now…


What’s the best way (perhaps even better: what are various valid ways) to see if a FreeRTOS task has been deleted? Currently, I’m tracking its handle and setting it to NULL before calling vTaskDelete() on it, like this. Notice I’m calling myTaskHandle = NULL; before vTaskDelete(NULL);:

static TaskHandle_t myTaskHandle = NULL;

void myTask(void* params)
{
    for (;;)
    {
        // do stuff
        // - the code may exit this for loop to be killed under
        //   certain conditions
    }

    // Manually force the handle to NULL to track when I've deleted it!
    myTaskHandle = NULL;
    vTaskDelete(NULL); // Delete self
}

// This function can be called multiple times, so it must ensure it doesn't
// try to create the same task again until it's been previously deleted.
void myTaskInit()
{
    // Here I check if the task is already running, and don't spawn it again
    // if it is.
    if (myTaskHandle != NULL)
    {
        printf("Error: myTask task already running");
        return;
    }

    static StackType_t taskStack[configMINIMAL_STACK_SIZE];
    static StaticTask_t taskControlBlock;
    myTaskHandle = xTaskCreateStatic(
        myTask, 
        "My Task", 
        configMINIMAL_STACK_SIZE, 
        NULL,
        tskIDLE_PRIORITY + 1,
        taskStack, 
        &taskControlBlock);
}

Is there a better way to see if the task is deleted or not? Currently I’m doing the if (myTaskHandle != NULL) check.

There’s a little bit of a race condition here, too:

    myTaskHandle = NULL;
    vTaskDelete(NULL);

It’s possible that myTask gets preempted by another task right after myTask calls myTaskHandle = NULL; but before it calls vTaskDelete(NULL). If this happens, and the other task calls myTaskInit() before myTask gets a chance to call vTaskDelete(NULL), then myTaskInit() will think that myTask is already deleted (since its handle is NULL) and recreate the task on top of an already-existing task which is about to be deleted! This will result in undefined behavior.

So, part of me thinks I need these guards:

    taskENTER_CRITICAL();  // disable FreeRTOS context switches
    myTaskHandle = NULL;
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();   // re-enable FreeRTOS context switches

…but I don’t think that will work, because the taskEXIT_CRITICAL(); line will never be reached after vTaskDelete(NULL); is called, right? Or am I wrong?

Note also that I don’t think eTaskGetState() can tell me if the task is deleted. One of the valid states is indeed Deleted, but I think that means it is simply waiting for the idle task to clean it up, and once it does, it will no longer have a state at all.

State     Returned Value
...  
Deleted	eDeleted (the tasks TCB is waiting to be cleaned up)

First of all, revisit your design. All the effort and brain power put into this use case make sense if and only if tasks are deleted and recreated frequently at runtime. As we have discussed many many times here, that is a bad pattern to render in an RTOS. Tasks are really not meant to be used that way for various reasons. 99+% of all tasks running under FreeRTOS implement infinite loops. The rest are one shot computations that execute once during the life time of the system until reset and rarely need to be synchronized with the rest in any way.

Why do you think you need to design your system such that this juggling water balloons becomes necessary?

I just added this to the top of my question:

Note: this is a horrible design pattern, and ephemeral FreeRTOS tasks should not be created. See my FreeRTOS best practices bullet #17 here (I’m not allowed to post links):

[SAFETY-CRITICAL] Use software timers and callbacks, not short-lived (temporary) tasks that just time out, do something, and delete themselves.

…but I’m stuck in a legacy code base and fixing things one-at-a-time, so for now…

In other words, yeah, this is a horrible design pattern. Meanwhile I have to live with it. I’ve got to fix things one step at a time.

When will I be allowed to post links, by the way? I can’t link to anything.

You should be able to post links now.

1 Like

@aggarg , I can post links now. Thanks. What triggered the change?

I think one of my primary questions is: is this legal? Will these critical section enter and exit macros work appropriately here? I am concerned because the vTaskDelete() function calls portYIELD_WITHIN_API() which tries to do a context switch and make it never return, as far as I can tell.

Or, would taskENTER_CRITICAL() stop that context switch from happening until the delete function returns and taskEXIT_CRITICAL() is called? I can’t tell.

A better way to check if the task still exists would be to call xTaskGetHandle to see if a task by that name still exists. This is only fool-resistant, as the answer might be wrong as soon as the function returns.

Part of your problem is you have a fundamental race condition.

Much better would be to change the task to rather than deleting itself, to wait for a notification and then loop back to its start, and make the creation task just notify the task instead of recreating it.

@richard-damon , agreed. Meanwhile, I have to do a couple hundred mass fixes to xTaskCreate() calls before I can consider a proper rearchitecture. The code in the question is my proposed mass fix for these hundreds of calls, going from xTaskCreate() to xTaskCreateStatic() en-route towards a re-architecture.

Any idea regarding my taskENTER_CRITICAL()/taskEXIT_CRITICAL() question just above?

Also, xTaskGetHandle() is apparently slow:

NOTE: This function takes a relatively long time to complete and should only be called once for each task. Once the handle of a task has been obtained it can be stored locally for re-use.

I’m not certain about a task deleting itself in a critical section.

I would NOT change from xTaskCreate to a xTaskCreateStatic, as the latter is much more susceptible to the race condition. With xTaskCreate, if you create a new task while to old one is going away, there is not problem, as they are two totally different tasks (just using the same task function). With xTaskCreateStatic, you are reusing the memory so an overlap in time is a disaster.

If you plan on touching all the calls, change it from an xTaskCreate call to a call to a stub function that at first is just the xTaskCreate call, and after you change all of them, you now have just one place to change the code.

Even if it happened to work, I would not rely on it. The critical section stalls the entire OS while it is claimed, and there is no way to predict how long xTaskDelete() takes and thus how long the system remains stalled.

You should really consider Richard’s suggestion to use wrappers such that your tasks do not really disappear and reappear but sleep and will be rewoken instead. One typical pitfall is that any object (queue, mutex, whatever) held by a deleted task will leave the system in an unpredictable state, and except for muteces, there is no way for FreeRTOS to house keep those objects (even if there was - there is no way to determine what the intended semantics are). Thus, it would be the respnsibility of your delete logic to clean up after itself. Best of luck!

As a side note, there are a number of entries in that list that I would disagree with, for example #10. Reducing the number of tasks at any prices is not a valid tenet in itself. For example, if your hardware services, say, 10 UARTs in a perfectly uniform manner (example: Virtual serial adapters that funnel the UARTs over a network), you definitly want 10 independent tasks to service them. The important issue here is that all the tasks will run perfectly independent of each other, so there is a high degree of concurrency which is most naturally modelled by separate tasks. Any attempt to collapse several UARTs into a single task will lead to convoy effects or other cross dependencies that violate the idea of the ports being independent of each other.

The metaphore here is “organic modelling -” find an architecture that most naturally models the challenge to be rendered on the device.

@RAc , thank you. If you have other feedback about my bullets, feel free to continue that discussion here: NOTES_architecture_and_best_practices.md: FreeRTOS best practices · ElectricRCAircraftGuy/eRCaGuy_hello_world · Discussion #18 · GitHub

For the specific data race problem, why not make myTaskHandle itself atomic?
Either add _Atomic specifier to myTaskHandle, or wrap its read write operations in critical section.

void myTask(void* params) {
  // ...
  taskENTER_CRITICAL();
  myTaskHandle = NULL;
  taskEXIT_CRITICAL();
  vTaskDelete(NULL);
}
void myTaskInit() {
  taskENTER_CRITICAL();
  TaskHandle_t h = myTaskHandle;
  taskEXIT_CRITICAL();
  if (h != NULL)
  // ...
}

This would only “order” accesses to the myTaskHandle variable but not solve the race condition behind it. You may have a “valid” copy of it in your local variable h, but if another task/isr invalidates the handle concurrently, subsequent attempts to use the handle via the local copy will still fail.

By the way: I now believe this to be illegal:

    taskENTER_CRITICAL();  // disable FreeRTOS context switches
    myTaskHandle = NULL;
    vTaskDelete(NULL);
    taskEXIT_CRITICAL();   // re-enable FreeRTOS context switches

From my notes here: eRCaGuy_Engineering/FreeRTOS/README.md at main · ElectricRCAircraftGuy/eRCaGuy_Engineering · GitHub :

AI: Which FreeRTOS API calls are and are not allowed to be called within a critical section specified by taskENTER_CRITICAL() and taskEXIT_CRITICAL()?

My own research

Important! (emphasis added):

13.5.7 Symptom: Calling API functions while the scheduler is suspended, or from inside a critical section, causes the application to crash

The scheduler is suspended by calling vTaskSuspendAll() and resumed (unsuspended) by calling xTaskResumeAll(). A critical section is entered by calling taskENTER_CRITICAL(), and exited by calling taskEXIT_CRITICAL().

Do not call API functions while the scheduler is suspended, or from inside a critical section.

Source: FreeRTOS-Kernel-Book/ch13.md at main · FreeRTOS/FreeRTOS-Kernel-Book · GitHub

And:

FreeRTOS API functions must not be called from within a critical section.

Source: taskENTER_CRITICAL(), taskEXIT_CRITICAL() - FreeRTOS™


So, vTaskDelete() is not allowed to be called from within a critical section.