Menu ▾ ▴

#400 Race condition in QActive::postx_ violates margin guarantee, redundant critical section enter/exit causing performance overhead

QPC
open
nobody
None
1
2026-04-20
2026-04-20
Anonymous
No
bool QActive::postx_(
    QEvt const * const e,
    std::uint_fast16_t const margin,
    void const * const sender) noexcept // !!! let margin == 1
{
#ifndef Q_SPY
    Q_UNUSED_PAR(sender);
#endif

    QF_CRIT_STAT
    QF_CRIT_ENTRY();

    // the event to post must not be NULL
    Q_REQUIRE_INCRIT(100, e != nullptr);

    // the number of free slots available in the FreeRTOS queue
    QEQueueCtr const nFree =
        static_cast<QEQueueCtr>(FREERTOS_QUEUE_GET_FREE()); // !!! let nFree == 2

    bool status = ((margin == QF::NO_MARGIN)
        || (nFree > static_cast<QEQueueCtr>(margin)));      // status == true
    if (status) { // should try to post the event?
#if (QF_MAX_EPOOL > 0U)
        if (e->poolNum_ != 0U) { // is it a mutable event?
            QEvt_refCtr_inc_(e); // increment the reference counter
        }
#endif // (QF_MAX_EPOOL > 0U)

        // assume that event posting will be successful, see NOTE3
        QS_BEGIN_PRE(QS_QF_ACTIVE_POST, m_prio)
            QS_TIME_PRE();      // timestamp
            QS_OBJ_PRE(sender); // the sender object
            QS_SIG_PRE(e->sig); // the signal of the event
            QS_OBJ_PRE(this);   // this active object (recipient)
            QS_2U8_PRE(e->poolNum_, e->refCtr_); // pool-Num & ref-Count
            QS_EQC_PRE(nFree);  // # free entries available
            QS_EQC_PRE(0U);     // min # free entries (unknown)
        QS_END_PRE()

        QF_CRIT_EXIT(); // exit crit.sect. before calling RTOS API

        // !!! let another high-priority task also makes postx_ with margin == 1; now, nFree == 1

        // post the following evtPtr to the RTOS event queue, see NOTE3
        QEvtPtr const evtPtr = {
            e
        };
        status = (xQueueSendToBack(
            m_eQueue, static_cast<void const *>(&evtPtr), 0U) == pdPASS); // !!! status == true, nFree == 0, but it should be at least 1 (margin)

        QF_CRIT_ENTRY(); // re-enter crit.sec.   // !!! enter
    }

    if (!status) { // event NOT posted?
        // posting is allowed to fail only when margin != QF_NO_MARGIN
        Q_ASSERT_INCRIT(130, margin != QF::NO_MARGIN);

        QS_BEGIN_PRE(QS_QF_ACTIVE_POST_ATTEMPT, m_prio)
            QS_TIME_PRE();      // timestamp
            QS_OBJ_PRE(sender); // the sender object
            QS_SIG_PRE(e->sig); // the signal of the event
            QS_OBJ_PRE(this);   // this active object (recipient)
            QS_2U8_PRE(e->poolNum_, e->refCtr_); // pool-Num & ref-Count
            QS_EQC_PRE(nFree);  // # free entries
            QS_EQC_PRE(margin); // margin requested
        QS_END_PRE()

        QF_CRIT_EXIT();

#if (QF_MAX_EPOOL > 0U)
        QF::gc(e); // recycle the event to avoid a leak
#endif
    }
    else {
        QF_CRIT_EXIT(); // !!! and exit without any work between "enter" and "exit"
    }

    return status;
}

=================================================================

The problematic areas are marked with comments containing !!!.

Two issues were identified in QActive::postx_:

Incorrect behavior with margin argument under concurrency (race condition)
Unnecessary critical section re-entry causing performance degradation

Problem 1: Margin guarantee violation (race condition)

Summary:
The function checks available queue slots (nFree) inside a critical section, but performs the actual enqueue operation outside of it. This allows another higher-priority task to modify the queue state in between, violating the intended margin guarantee.

Steps to reproduce:

Call postx_() with: margin == 1, Initial nFree == 2
Condition (nFree > margin) evaluates to true, so posting proceeds
Exit critical section (QF_CRIT_EXIT())
Before xQueueSendToBack() executes, another higher-priority task calls postx_() with margin == 1 and consumes one slot: now nFree == 1
Current function resumes and successfully posts to the queue: xQueueSendToBack() returns pdPASS
Queue becomes full (nFree == 0)

Result:
Margin requirement (>= 1 free slot remaining) is violated

Problem 2: Redundant critical section enter/exit (performance issue)

Summary:
There is an unnecessary QF_CRIT_ENTRY() followed immediately by QF_CRIT_EXIT() with no meaningful work in between.

Code path:

After successful enqueue, code re-enters critical section:

QF_CRIT_ENTRY(); // re-enter crit.sec.

Then reaches:

else {
QF_CRIT_EXIT(); // exit without any work between enter and exit
}

Discussion

Anonymous
Anonymous

Add attachments
Cancel