Advice on designing task deletion

My application has two tasks:
Task A - Listens to new user requests
Task B - Implements current user request

If Task A detects new user request, ongoing Task B is deleted, and new Task B is created with updated request.

Task B is pretty complex( uses Wifi, I2S, SD card…) and incorporates a lot of features such as:

  • Memory allocation/deallocation
  • Opening/closing files
  • Enabling/disabling peripherals

So, when Task A detects new request and kills Task B, I need to free its heap memory and bring the application back to the valid state. For example, if task B has enabled microphone, I need to disable it back.

What I initially planned to do is following:
Have an external struct which will hold all the required data to free the resources, for example:

struct Task_Resources {
  bool USB_is_enabled;
  bool file_is_opened;
  FILE* file_name;
  ...
}

and a function to clean the resources:

void task_cleaner(Task_Resources* res) {
  if(res->USB_is_enabled) {
    disable_USB();
  }
  if(res->file_is_opened) {
    close(file_name);
    remove(file_name);
  }
  ...
} 

Once vTaskDelete(Task_B_handle) would be called, task_cleaner() function will free the memory and bring the application back to valid state.

The problem is that there are too many things to keep track of. And I will also have to suspend Task A a lot of times to get atomic behavior. For example:

Task B code {
...
ENABLE_USB();
TASK_RESOURCES.USB_is_enabled = true; 
...
}

If TASK A kills the task between these two lines, peripheral will be enabled, but task_cleaner() won’t know about it. So, I will probably have to do something like that to prevent task deletion in between those lines:

Task B code {
...
  xTaskSuspend(TASK_A);
  TASK B - ENABLE_USB();
  TASK B - TASK_RESOURCES.USB_is_enabled = true; 
  xTaskResume(TASK_A);
...
}

I was wondering your opinion on this approach. And if someone had similar situation, I would appreciate if you can tell me how did you solve it.

My advice would be not to delete tasks if possible. Also task B sounds far to complex. Why not have separate tasks that live for ever to control each peripheral? I would suggest you think about your architecture in more detail.

Thank you very much. I will try your approach. I am doing audio download/streaming. I guess you mean something like that for my example:

void request_listener_task(void* params) {
  for(;;){
    if(new_request_recived) {
      notify(download_task_handler);
    }
  }
}

void download_task_handler(void* params) {
  TaskHandle_t dowload_handle;
  bool download_ongoing = false;
  for(;;) {
    wait_for_notification_from_request_listener_task();
    if(download_ongoing) {
      vTaskDelete(download_handle);
      clear_download_task_resources();
      download_ongoing = false;
    }
    // this task will notify play task on end of download
    xTaskCreate(downloader,...,download_handle);
    download_ongoing = true;
  }
}

void play_task_handler(void* params) {
  TaskHandle_t play_handle;
  bool play_ongoing = false;
  for(;;) {
  wait_for_notification_from_download_task();
  if(play_ongoing ) {
      vTaskDelete(play_handle);
      clear_play_task_resources();
      play_ongoing = false;
    }
    xTaskCreate(player,...,play_handle);
    play_ongoing = true;
  }
  }
}

Did I understand it correct?

As for deleting tasks, I am not sure that I can avoid them. I have to stop the function execution if new request is received, and go back to the start of the function with new parameters.

Deleting other tasks while they are running is inherently problematical, because it is very hard to know what resources they have and to release them. That pattern works with an isolated process type system where the OS does keep track of all that sort of information (but even then you need to be careful about certain types of globally shared resources).

For a system the size of typical FreeRTOS systems, it is much better to do the ‘abort’ in a cooperative manner, frequently checking a flag, and then having the task do the cleanup.

I want to underline Rik’s recommendation not to delete tasks (asynchronously) from another task.
You’ll almost certainly run into troubles because you kill a task in the middle of arbitrary code execution i.e. in an unknown state. Even worse if it’s a complex task this might also lead to unrecoverable resource leaks. Also keep in mind that task deletion only cleans up task (FreeRTOS) resources. All other application related resources (e.g. memory allocated from heap, etc.) have to be cleaned up by the application. This in turn is usually done by the task itself if desired/needed because the task knows when safe to e.g. free up memory etc.
You should rework your design to add a synchronous way to stop>restart the task.
The most simple way of doing that might be global (volatile) flag variable which is checked/polled by the task to stop as part of it’s task loop.

Thank you. So, if I understand it correctly:

Old configuration:

Task_A () {
   if(new_request_received){
     vTaskDelete(Task_B_handle);
     clean_TASK_B_resources();
     xTaskCreate(Task_B_handle);
   }
}

Your suggestions:

volatile bool new_task_received = false;

xTaskCreate(Task_B_handle); // not to be deleted

Task_A () {
   if(user_request_received()){
     new_task_received = true;
   }
}

Task_B() {
  func1(); // init peripheral

  if(new_task_received ) {
    clear_resources(); // close peripheral
    start_again_with_new_params();
  }

  func2(); // allocate memory

  if(new_task_received ) {
    clear_resources(); // close peripheral && free memory
    start_again_with_new_params();
  }

  func3();
  ...
}

But how can I restart the task? Only thing that comes to my mind is to use assembly jump instruction. Could you please elaborate on how it can be achieved?

Currently my task code is not inside a loop. I guess I will have to put it inside a loop, and perhaps use continue keyword?

I would say your task code should definitely be in a loop. You should never run off the end of a task.

Tasks tend to be build with an outer infinite loop and in that loop the task blocks waiting for a request, and then does it. With such a structure, the task just returns into the loop and lets it go back to the beginning of the loop. Something like:

TaskB()
initB();
while(1){
waitforTaskToDo();
do_task();
}

Thank you very much for your help. It was really enlightening.

I pick one point to say my opinion :create/delete task.
Unlike other’s advise, I thought your app is very suitable using create/delete mode.because you want to restart a task with new parameters.
And this create/delete mode will make you app’s struct to be more concise, I thought Create/delete task is not that big fear.At least it is provided by FreeRTOS.or why it is not be remove from kernel.
But I also agree not to use create/delete mode at some critical situations.
And besides,your taskB is too complex,I thought you can implement it by cutting it into more small tasks.Last note: In the “dynamic task” which you wanna create/delete, guarantee that there are not any resource like mutex,semaphore or memory or others, which can be “taken”.
I assume your app has enough ram to allocate to those tasks.

Creating tasks as needed and having them delete themselves when done isn’t THAT bad of a structure, the one BIG thing you need to watch out for is that this tends to imply that the tasks will need to be created with dynamic memory, and thus you need to add programming for the eventuality that creating the task might fail, or you need to go through the (very complicated) process of making sure that this can never happen. This is the big issue with dynamic tasks.

‘Big’ machines have less of this problem, as with virtual memory, you tend to hit a grinding to a halt system overload situation well before you actually get out of memory errors.

The key thing for me in implying this would work well as a single always living task was that there seems to be an implication that there will be at most one instance of this task running at any given time, and that if a new request comes in while processing the previous request, the task wants to abort that old operation and start back up on the new. For this case, rather than trying to build some structure to externally keep track of everything that needs to be cleaned up from the task, and then rebuild the task structure, you make the task internally do a controlled shutdown, so cleanup of resources is always in the vicinity of the acquisition of them, and then not need to use the overhead of rebuilding the task structures,

One thing to note, people often come in with a structure based on a ‘big’ os type environment with real processes that keep track of (most or) their resources, that allow killing such a process and continuing. FreeRTOS is not like that, and takes a lot less resources because of it. If you really want that type of system, use that sort of system, and upgrade your processor to something that can run a real embedded linux type system.

IF you aren’t going to go that route, don’t think in terms of the primitives that such a system provides, but in terms of the sort of design that works for a system like FreeRTOS, where there will be more design coupling of the tasks in a system. Tasks have a degree of cooperation with each other (but maybe not as far as trying to use cooperative scheduling, that is a bit different), This is where the idea of telling a task via a global flag that it should terminate what it is doing, and letting it unwind itself comes from.

1 Like

Here’s an example of how I did it, with the option of pausing or deleting itself, which is the key idea here.