vTaskDelete(NULL) sometimes not working

maxime8848
Posts: 4
Joined: Mon Dec 23, 2024 10:48 am

vTaskDelete(NULL) sometimes not working

Postby maxime8848 » Thu Jan 09, 2025 8:57 am

I have a use case where I have a constantly running task, which I occasionally need to stop and restart with a different configuration. To do this I check a flag in the task loop to let it terminate gracefully when requested, and then restart it from the main task as soon as it is in the eDeleted state.

Code: Select all

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <atomic>
#include <WebServer.h>

std::atomic<bool> taskRun(true);
TaskHandle_t taskHandle = NULL;

WebServer webServer(80);

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("==== START ====");

  WiFi.mode(WIFI_STA);
  WiFi.begin("ssid", "password");
  while(WiFi.status() != WL_CONNECTED);
  Serial.println(WiFi.localIP().toString());

  webServer.on("/reset", []() {
    webServer.send(200, "text/html", "ok");
    resetTask();
  });
  webServer.begin();
}

void loop() {
  // Watch the task and start it if necessary
  if (!taskHandle || eTaskGetState(taskHandle) == eDeleted) { // sometimes the task finished but is still in eReady state, so it does not restart
    Serial.println(millis());
    taskRun.store(true);
    xTaskCreatePinnedToCore(taskFunction, "task", 10000, NULL, 1, &taskHandle, 1);
    Serial.println("task created");
  }

  webServer.handleClient(); // allow the user to stop and restart the task through the web server

  if (Serial.available()){  // allow the user to stop and restart the task through the serial monitor
    Serial.read();
    //delay(500);
    resetTask();
    //delay(500);
  }
}

void resetTask() {
  taskRun.store(false);
  Serial.println("reset task");
}

void taskFunction(void* pvParameters) {
  while(taskRun.load()) {
    // do work
    taskYIELD(); // give cpu time to other tasks
  }
  
  Serial.println("task finished");
  vTaskDelete(NULL);
}

The issue is that under certain circumstances, for example when requesting to stop the task from a WebServer, or by adding some delay before and after the call to resetTask, the task function will reach the end as expected, but it will stay in the eReady state, which means it will not get restarted by the main task. This means that the call to vTaskDelete(NULL) either blocks indefinitely, or does not put the task in the eDeleted state.

What is happening, why does it sometimes work and sometimes not? Maybe I'm doing something weird or wrong, what would be a better way to acheive what I want?

ESP_Sprite
Posts: 9883
Joined: Thu Nov 26, 2015 4:08 am

Re: vTaskDelete(NULL) sometimes not working

Postby ESP_Sprite » Fri Jan 10, 2025 3:56 am

A task handle is a pointer to an internal task structure, and when you call vTaskDelete that structure is free()ed. That means the task handle now points to (effectively) uninitialized memory, and any operation you do with it is undefined. Rather than using the task handle, you should have some other variable or something (of which you yourself control the lifetime) indicating the task deleted itself.

maxime8848
Posts: 4
Joined: Mon Dec 23, 2024 10:48 am

Re: vTaskDelete(NULL) sometimes not working

Postby maxime8848 » Fri Jan 10, 2025 10:18 am

Thanks the the reply, that would indeed work. I figured I should probably understand what's wrong before resorting to just using another variable.
ESP_Sprite wrote: and when you call vTaskDelete that structure is free()ed
Why is the eDeleted state even a thing then, if you can't read the state of a deleted task anymore?

MicroController
Posts: 1954
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: vTaskDelete(NULL) sometimes not working

Postby MicroController » Fri Jan 10, 2025 11:46 am

A possible solution is to let the task annouce that it is ready to be deleted, and when it is delete it from another task.

Using a semaphore, it could look like

Code: Select all

void taskFunction(void*) {
  {
  ...
  }
  
  Serial.println("task finished");
  
  // Announce that we're done:
  xSemaphoreGive( taskEndedSem );
  // Go dormant until deleted from elsewhere
  while(1) {
    vTaskSuspend(NULL);
  }
  
}

void resetTask() {
  if(taskHandle) {
    taskRun.store(false); // Tell the task to terminate
  
    // Wait until the task is ready to be deleted:
    xSemaphoreTake( taskEndedSem, portMAX_DELAY );  
  
    // Delete task (taskHandle still valid):
    vTaskDelete( taskHandle );
  
    // Task is deleted, taskHandle is now invalid
    taskHandle = NULL;
    taskRun.store(true);
  
    Serial.println("reset task");
  }
}

void loop() {
  ...
  if(...) {
    resetTask();
  
    // Recreate task:
    xTaskCreatePinnedToCore(taskFunction, "task", 10000, NULL, 1, &taskHandle, 1);
    Serial.println("task created");
  }
  ...
}
But do you really need to end and restart the task? Can you send the new configuration to the running task and let it keep running?

maxime8848
Posts: 4
Joined: Mon Dec 23, 2024 10:48 am

Re: vTaskDelete(NULL) sometimes not working

Postby maxime8848 » Fri Jan 10, 2025 11:08 pm

MicroController wrote:
Fri Jan 10, 2025 11:46 am
But do you really need to end and restart the task? Can you send the new configuration to the running task and let it keep running?
The task actually runs an MQTT client, which can be turned on or off via the WebServer, which also provides settings like server, topic, etc. I thought it was simpler to do it that way, and it allows the task to stay stopped if MQTT is deactivated, or stop and restart if the settings changed. That way the code in the task function also stays clean and simple: setup/connect -> infinite loop to send messages -> disconnect/cleanup. The settings are of course rarely modified so I'm not concerned about the overhead of retarting the task

ESP_Sprite
Posts: 9883
Joined: Thu Nov 26, 2015 4:08 am

Re: vTaskDelete(NULL) sometimes not working

Postby ESP_Sprite » Sat Jan 11, 2025 2:14 pm

maxime8848 wrote:
Fri Jan 10, 2025 10:18 am
Why is the eDeleted state even a thing then, if you can't read the state of a deleted task anymore?
Likely because of the way FreeRTOS works. vTaskDelete(NULL) cannot free the TCB as it contains e.g. the stack of that task, so code running inside the task context cannot free it. Instead, it marks it as 'deleted'; part of the responsibilities of FreeRTOS's idle task is to clean up tasks marked like that.

Who is online

Users browsing this forum: No registered users and 71 guests