FreeRTOS xTaskAbortDelay: Distinguish Cancelled Waiting from Successful Work Completion

A successful xTaskAbortDelay call proves that the kernel removed the target from a blocked state. It does not prove that an application operation was canceled, that no queue item was received, or that the target has completed cleanup. Inspect the wait function’s eventual result and the application’s cancellation state separately.

This guide pins upstream FreeRTOS Kernel V11.1.0, single-core preemptive scheduling and INCLUDE_xTaskAbortDelay=1. The concrete wait is an ordinary queue receive. Five controlled cases were executed using the official POSIX host port; no MCU, ISR or response-time qualification is claimed. Other blocking APIs require their own result-path inspection.

Read the abort result literally

The versioned task API returns pdPASS when the target was blocked and pdFAIL otherwise. It provides no FromISR variant. The target handle must remain valid throughout the request; a remembered handle to a deleted task is not a cancellation capability.

The matching implementation removes the task from its blocked list and, where applicable, its event list, then places it on a ready list. Becoming ready and actually executing are different events. A higher-priority controller can continue running after aborting a lower-priority worker.

A blocked receiver becomes ready after successful abort; if no item arrives it returns empty, while an item arriving before the receiver executes can still produce a successful receive. Application cancellation and cleanup acknowledgement are separate.
Abort-request success and receive success can both occur. The application must interpret the eventual object result before declaring cancellation complete.

Follow the receive loop after the wake

In the V11.1.0 queue implementation, the receiver rechecks queue contents when execution resumes. An available item can satisfy the receive before the timeout-check path is reached. Consequently, aborting an empty wait does not reserve the queue’s future state or prohibit a producer from making data available before the worker runs.

For an event-list wait, the task source records an aborted-delay condition that its timeout-check helper can consume. This supports leaving the wait rather than starting the same blocked interval again. It is not an application operation identifier or a public cancellation-reason channel. Do not make a resource-disposition decision by inspecting private TCB fields.

A failed receive can have the same ordinary API result after timeout and after an aborted empty wait. Conversely, a successful receive means the application has acquired the returned item according to that queue’s contract. Neither result should be overwritten by a controller’s assumption that “abort succeeded, so the operation did nothing.”

Use an ordered test matrix

The host fixture used a controller at priority three and a receiver at priority two. The controller deliberately controlled when the lower-priority receiver could run. All five rows below were executed against the pinned kernel:

Controlled ordering Abort result Eventual receive result
Blocked on empty queue; abort; let worker run pdPASS Empty/failure
Abort blocked worker; enqueue 42 before worker runs pdPASS pdPASS, value 42
Enqueue first, making worker ready; then abort pdFAIL pdPASS, value 42
Receive timeout completes before abort pdFAIL Empty/failure
Abort before worker enters wait pdFAIL Worker later blocks; a new abort is needed

The last row demonstrates that an early abort is not retained as a token for a future wait. In the fixture, a second request was issued only after the controlled setup had established the blocked state. That test arrangement is not a recommendation to poll a live task’s state as an atomic cancellation protocol.

Close the check-to-wait race explicitly

A worker might check an application cancellation flag, be preempted, and only then enter its blocking call. A controller that sets the flag and issues an early abort during that gap can receive pdFAIL. If the subsequent wait is indefinite and nothing else wakes it, the flag alone does not close the race.

Choose a synchronization design that retains the cancellation request through the relevant wait, such as a typed cancellation message in the same owned command channel where that fits the interface. Alternatively, use appropriately bounded waits and recheck a properly synchronized cancellation predicate after each return. The allowed detection interval still needs target scheduling qualification.

Do not use an unsynchronized shared Boolean merely because the target is single-core. Define who publishes and reads the request, its memory/synchronization contract and the operation generation it applies to. Repeatedly calling abort on an arbitrary changing task state is not a substitute for that design.

Account for an item received during cancellation

Consider a hypothetical measurement worker whose result queue carries pointers to allocated sample records. The controller requests cancellation and successfully aborts its wait. Before the worker runs, a producer places one record in the queue. The receive can succeed, transferring a real record to the worker.

The worker must now follow an explicit disposition: process the record if cancellation policy permits, return it to its owner, or release it through the approved path. Ignoring the successful receive can leak that record; pretending the producer still owns it can cause double release. The cancellation acknowledgement should name the operation generation and state what disposition has completed.

Keep this acknowledgement separate from xTaskAbortDelay’s return. It comes from the application owner after interpreting the wait result and finishing the required local action. It need not imply full system shutdown or task deletion.

Collect evidence that distinguishes the races

  1. Record the target task identity, operation generation and exact wait API.
  2. Capture request publication, abort return, object arrival and worker return in order.
  3. Record the actual received item identity and its final disposition.
  4. Exercise early request, empty abort, data-before-abort and data-after-abort orderings deliberately.
  5. Verify that the completion acknowledgement cannot be confused with an earlier operation.

A task-state snapshot can assist diagnosis, but the API documentation warns that such a snapshot may change before it is used. Accept the design when both abort outcomes and both object outcomes have defined handling. The useful guarantee is an explained application transition, not the assumption that removing a delay also erased every possible successful result.

Similar Posts