Menu

#209 Require new Port API function: QActive_join(), when macro: QACTIVE_CAN_STOP is defined

QP-C
open
None
1
2025-10-17
2025-09-26
No

I need a way to know a thread’s kernel resources have all been reclaimed and the RAM being utilized by those resources can be repurposed. I have a set of AOs that are transient (their underlying thread is created, started, stopped, and then destroyed). However, the QP-C API currently lacks an API function that I can call for each transient AO to ensure upon return, the target thread is actually been terminated and all resources returned.

The following discussion/description is for the FreeRTOS port. But this issue can likely be extended to any and all 3rd-Party RTOS ports of QP. With respect to the FreeRTOS port, when macro: QACTIVE_CAN_STOP is defined, the target thread will eventually calls: vTaskDelete(), passing a value of ‘0’ for the thread handle. This function is called from the context of the AO as it self terminates in response to it calling: QActive_stop(). Unfortunately, vTaskDelete() does NOT immediately delete all of the resources associated with the thread. Instead, the Task Control Block (TCB) remains linked into the FreeRTOS kernel. It is at this point the TCB lingers, waiting to be removed during the next execution of the Idle Thread.

I'm proposing a new QP-C API function be introduced - calling it: QActive_join(). The purpose of this new API function is to block a calling thread (not the terminating thread who had called QActive_stop), until the self-terminating thread has actually been fully removed from the kernel. In essence I’m advocating that transient threads NOT be detached (or assumed to be detached) but instead require they be joinable. And that a client thread would be required to call: QActive_join(), on any AO that has terminated.

To implement a joinable thread in the FreeRTOS port is to simply allocate a binary semaphore prior to the AO being constructed and started. This would be done in QActive_start(). Then when a given AO were to self-terminate (or terminates in response to some specific event), the target AO would, as expected, call: QActive_stop(). Eventually, the target thread would exit the dispatch loop and then immediately invoke two FreeRTOS functions where it is currently calling: vTaskDelete(). The replacement code would first “give” the binary semaphore created and initialized in QActive_start(). This is accomplished by calling: xSemaphoreGive(). It would then call: vTaskSuspend(), passing ‘0’. This prepares the target thread to be joined...it will remain suspended until QActive_join() is invoked by some other thread.

With the conditions setup above, a client thread would eventually call: QActive_join(). This call can actually be invoked either before or after the target thread has called: QActive_stop(). If called before, the client thread will block, waiting for the semaphore to be given. Otherwise, the semaphore will be immediately taken and the client will then call: vTaskDelete(), passing the thread handle of the target thread. Upon return from vTaskDelete(), the target thread is fully joined and the only outstanding processing items are to clean up the other resources (i.e. the semaphore, queues, etc), all allocated and initialized in: QActive_start().

The goal with this request is to impose a reasonable degree of deterministic behavior for transient threads. This implies thread are: constructed, started, stopped, and destroyed. All part of a threads normal life cycle.

I noticed the POSIX port explicitly creates detached threads. I would advocate this be changed and all ports that define: QACTIVE_CAN_STOP, be required to call: QActive_join(), or expect to leak resources. This change should only impact existing applications that define: QACTIVE_CAN_STOP, which is likely to be a small number of applications.

Discussion

  • Quantum Leaps

    Quantum Leaps - 2025-10-03

    Hi Philip,
    Thank you for the feature request. The issues related to stopping Active Objects have been discussed in the Free Support Forum before. Stopping/terminating an Active Object is not that difficult. The tricky part is to do this cleanly, so that the application can safely continue.
    My recommendation is still NOT to stop/terminate an AO but instead use its state machine to change its behavior at runtime. If you don't need a particular AO for a while, trigger a transition to some "dormant" state. If you wish to change what the AO is doing, transition to a different part of its hierarchical state machine. This is the cleanest and most efficient by far.
    I would like to hear your specific use case for this feature. Please note that if this is for testing AOs, I recommend applying QUTest, where tests reset the whole system so explicit stopping and re-starting AOs between tests is unnecesary.

    --MMS

     
  • Philip Fleege

    Philip Fleege - 2025-10-17

    Just a quick follow-up in response to your question above, i.e. the use case.

    Given a resource constrained runtime environment, the goal was to implement a "memory overlay" solution where major modes of operations were realized by starting and stopping a 'set' of different transient AOs - one unique set of AOs for each major mode. To realize this, the RAM would be shared between one or more different mutually exclusive sets of AOs. Thus, the need to shutdown (cleanly and deterministically) all AOs associated with a given set of AOs was needed. Given this use case, the need to free up and reclaim RAM occupied by one set of AOs and re-allocated for a different set was needed (a memory management solution outside the scope of the QP/C).

    I've produced a solution for this by adding an additional FreeRTOS semaphore to the QP/C macro: QACTIVE_OS_OBJ_TYPE. NOTE: I had to intoduce a structure type that now includes both a semaphore and a message queue). The additional AO semaphore is given (by the terminating AO) only when the AO has exited the dispatch loop, and just before it calls vTaskSuspend(0).

    Meanwhile, a 'controlling' thread (i.e. the thread or AO responsible for constructing and starting the AO) would eventually block on this semaphore, waiting for it to be given by the AO about to self-terminate. And only once the semaphore has been given AND the target AO has self-suspended, will the controlling thread actually destroy (and free up) the RAM resources being occupied by the target AO.

    It's actually fairly trivial, but the change does require threads be "joinable" rather than being "detached". Thus, that was my ultimate motivation for this QP/C feature request.

     
  • Quantum Leaps

    Quantum Leaps - 2025-10-17

    Hi Phillip,
    As I tried to explain in my previous post, I think that dynamically stopping/starting Active Objects is problematic and most likely unsafe. For that reason, I'm reluctant to add/implement such a feature in the baseline QP (and certainly NOT in SafeQP editions).

    However, if you believe that such a feature could be useful, please add to the contributed features. There is a special public GitHub repo for such contributions.

    --MMS

     

    Last edit: Quantum Leaps 2025-10-17

Anonymous
Anonymous

Add attachments
Cancel





MongoDB Logo MongoDB