vTaskDelete() doesn't trigger scope exit of class objects (destructors not called) C++

I’m using C++ objects inside my tasks and I want them to be properly destroyed when the task is exited via vTaskDelete(). This is what I found in the archives:
[Delete task but local variables (objects) are not correctly deleted with the destructor - FreeRTOS](https://Delete task but local variables (objects) are not correctly deleted with the destructor)

But when I understand it correctly the proposed solution of calling vTaskDelete at the end of task life doesn’t really solve the problem. Consider this minimal reproducible example:

#include <stdio.h>
#include <iostream>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

struct some_obj
{
    int a;
    ~some_obj() {
        std::cout << "destructor called" << std::endl; 
        }
};

void some_task(void* param)
{
    std::cout << "hello task" << std::endl;

    // {
    //     // this calls destructor
    //     some_obj obj;
    // }

    // this not
    some_obj obj2;

    vTaskDelay(portMAX_DELAY);
}


extern "C" void app_main(void)
{
    TaskHandle_t xHandle;
    xTaskCreate(&some_task, "hello task", 1024, NULL, 10, &xHandle);

    vTaskDelay(100);

    vTaskDelete(xHandle);

    vTaskDelay(portMAX_DELAY);
}

This just prints hello task but the destructor is not invoked after vTaskDelete(). What can I do if I want to secure the proper object lifetime even on external delete calls?

you should delete your objects in the same scope where you created them which is the task fn (ie after your task terminates its infinite loop). Of course this implies that you create them explicitly.

I strongly advise against impicitly constructed C++ objects in the main scope of task functions. In any other scope that is fine. You could for example simply add one set of curly brackets to your code.

@RAc Maybe I’m understanding you wrong but with my understanding that doesn’t solve the problem either. vTaskDelete() will end the task within whatever scope the task was residing. Just adding brackets doesn’t fix that.

E.g. when I substitute the previous task fn with this

void some_task(void* param)
{
    {
    std::cout << "hello task" << std::endl;

    // {
    //     // this calls destructor
    //     some_obj obj;
    // }

    // this not
    some_obj obj2;

    vTaskDelay(portMAX_DELAY);
    }
}

I still don’t see the objects deconstructor called. This is bad because it means object lifetime can’t be guaranteed. Of course you could say a task should never be deleted when invariants are not reestablished, but in my implementation this is handled differently.

Then there is something wrong with your runtime support. The C++ runtime routines MUST call the dtor when your object goes out of scope.

Edit: There is also an error in your code, I believe. The task deletion is being called before the variable goes out of scope. Relocatd your vTaskDelay to your outer scope. Note that the code still is buggy and proof of concept.

Well, deleting the task while it’s in it’s vTaskDelay just asynchronously kills the thread of execution. Regardless of the code like the dtor. What exactly do you expect when killing a task from another task ?
You could use vTaskDelete(NULL); to cleanup yourself in the dtor.

@hs2 intuitively I would have thought that the code stops in its tracks but would delete every object on stack (or at least calling its dtors).

@RAc : concerning your edit: Thats precisely the point. The task is supposed to wait until it is called from another task which (as I assumed) would throw it out of any scope and destroy all objects along with it.

Rich, I do not think that this is the way vTaskDelete() works. For a real clean wrap up, rhe Os would need to know about all wait/delay conditions, allow the task to gracefully recover (how is it supposed to do that? There is no regular return from an infinite wait, so the task fn could not handle this condition; in return then, before REALLY deleting the task there would need to be some signal back mechanism for the task to inform the OS.

Given that 99.9% of all FreeRTOS tasks never return or do delete themselves controlled, such a mechanism would gain as practically nothing but cause a lot of grief.

Asynchronously changing a task’s state from somewhere else is never a good idea (query this forum for vTaskSuspend() for some reasoning why). That also includes asynchronous terminating.

The key is your vTaskDelete must be OUTSIDE the scope of all your variables. Your examples having it at the end, but still INSIDE the scope.

I would make the base task function be a very minimal function that calls your ‘real’ task function, and then when that returns, calls vTaskDelete().

It could also create a scope within it and have the real stuff within the scope. Something like:

void task_fun(void* ptr) {
    {
        // Task operations here
     }. // End Scope
     vTaskDelete(0);
}

vTaskDelete is not aware of C++ objects. It’s NOT part of the C++ language spec or of a task management C++ framework. It’s just a C-function cleaning up OS task resources like (raw) stack frame (if self created) and task management data in the kernel. That’s it. That’s common to all task/thread C-libraries known from other OS like pthread etc.
Only if you properly encapsulate task/thread creation and deletion (considered as resource allocation/deallocation) in a class it can be self-managed with this regard.

@RAc Alright got to accept the facts I guess :wink: The point is I want to have tasks that do not share state and only communicate via message passing (like in erlang). This way there would be no harm in just killing a task in the midst of it.

@richard-damon I’m not sure I understand you correctly. What exactly do you mean by vTaskDelete() must be outside of scope? It is outside of scope of the deleted task.

@hs2 I want to allow for hard kills of tasks in my application that’s why I was wondering. I would also like to have the system recover when one task fails (not just abort) but I don’t know if that can be done. In any other case I wouldn’t implement it that way.

No, your call to vTaskDelete was INSIDE the scope of your variable, which means there destructor would not be called until after it returned and the scope ended (which won’t happen).

This would be somewhat like calling abort at that point (vs exit which has special processing to make the destructors happen), which is basically what vTaskDelete(NULL) is as far as this task is concerned.

Note, even in C, deleting a task that you don’t know EXACTLY where it is is dangerous, as the operation does NO cleanup except for the actual ‘task’ resources (the TCB and Stack), and not any other resources that the task has obtained.

It is NOT like a ‘big system’ process, where the OS keeps track of the resources the process owns to clean up on termination. They are much more like threads in that respect.

@richard-damon Now I understand. You mean that vTaskDelete() aborts the task before the brackets close and exit scope which is of course correct. But cleanup only needs to happen when heap is involved. My idea was to redirect processes using the heap to very small auxiliary tasks that are bound to finish and leave the heap in a valid state while the main task is allowed to totally collapse in any way possible.

With C++, it isn’t just the ‘heap’ that you need to be concerned about, but ANY object that actually needs its destructor called.

For me, most of my tasks are immortal, so it isn’t the concern, but any task that DOES ‘exit’ should have the delete call at a scope that has NO ‘non-trivial’ objects declared. This isn’t hard for me as all my C++ tasks are built with a wrapper that calls a function, and deletes the task when that function returns, so the ONLY way I delete a task is by having it return from its task function (so the ONLY call to vTaskDelete allowed in my code is in that wrapper layer)

In order for vTaskDelete() to clean up all of the objects that are created and controlled by the task being deleted, it would have to unroll the stack, calling the destructors for every object that goes out of scope until it cleans up from the task’s “main” function context.

This probably requires some C++ runtime specific calls, possibly involving C++ structured exceptions to perform the unrolling, and would likely require a C++ support library that is specific to FreeRTOS along with configuration flags whether to include C++ unrolling support in the TCB. Perhaps there would need to be a different task creation call for C++ vs. non-C++ tasks. Deleting a C++ task would be quite different than a non-C++ task.

I ran into a tangentially related issue with C++ and a different embedded RTOS where the runtime calls all the constructors for global and static objects prior to starting the default task. In that OS, the RTOS does not depend on the default task to remain running, however when that task exits, all of the destructors for those global and static objects are invoked. If you have multiple tasks, each with their own static objects and that reference any global objects, you’re out of luck. At the time I ran into this, the behavior was not described in any of the documentation provided by the RTOS vendor.

@richard-damon I had a look at your task wrappers and saw that we arrived at the same conclusions except that I wanted to allow for hard aborts like described. But for the reasons discussed above I don’t think anymore that its sensible to design around that support, given also that FreeRTOS always aborts the whole program and not just an individual task. Which means I will probably design my task wrappers pretty similar to yours and return task function from synchronisation points when external tasks wish for it.

@danielglasser I see that the implications for C++ object cleanup are massive and probably should go along with the design goals of how the RTOS is supposed to be used. I wanted to create something more erlang-like where every task could fail on its own and be recovered by an observing task. But FreeRTOS is not built for that so I would probably run against more problems in the future. Thank you for your insights.

Unless YOU provide your own set of wrappers around all resources so YOU can unwind resource usage when you abort a task, the asynchronous destruction of tasks can not be safely done in general.

The routines like vTaskDelete exist because there are designs where you KNOW that the operation is safe at specific times to use it, but like most powerful tools, it is easy to misuse it and shoot yourself in the foot.