Zephyr Message-Queue Purge: Make Blocked-Sender Failure Part of the Reset Contract

k_msgq_purge() is more than a queue-length operation. In upstream Zephyr 3.7.0 it discards buffered messages and releases blocked senders with -ENOMSG. Those send attempts have failed; they are not automatically reinserted into the newly empty queue. A producer that ignores the return value can report delivery of a message that was never accepted.

This guide covers fixed-size copied messages in upstream Zephyr 3.7.0. It focuses on the return-code and scheduling consequences of purge, including a tagged-implementation observation about blocked receivers. It does not provide a pointer-payload destructor, a general shutdown protocol or a guarantee that the queue remains empty after the caller returns. All runtime tests proposed here remain unexecuted.

Separate three message histories

First, a message may already be buffered because its put completed successfully. Purge discards that unreceived copy. The earlier successful return cannot be retroactively changed. Second, a sender may still be blocked because the queue was full; purge fails that pending operation. Third, a consumer may already have received a message; purge cannot revoke the consumer’s private copy or undo application processing.

The 3.7.0 API declarations explicitly document blocked-send failure on purge. The message-queue guide describes copied records and producer/consumer waits. Together, they distinguish queue acceptance from end-to-end delivery. If delivery acknowledgment is required, it needs an application-level record rather than inference from put success.

Purge splits three histories: buffered A is discarded, blocked sender B resumes with ENOMSG, and already received C remains with its consumer; later producer retries are new operations.
Purge changes buffered and pending work differently. A retry after failure is a new send, not completion of the purged attempt.

Use an outcome matrix

Operation and condition Expected result in this scope Application meaning
Put finds capacity or a waiting receiver 0 Copy accepted; not proof of later processing
No-wait put finds a full queue -ENOMSG No copy accepted
Finite blocked put expires normally -EAGAIN Attempt timed out
Blocked put is released by purge -ENOMSG Attempt failed because purge removed the wait
Previously buffered copy is purged No new producer return Historical acceptance does not imply receipt

The same -ENOMSG value serves more than one situation. A generic error counter cannot tell you whether a no-wait full condition or purge caused it. Record operation mode and correlate with reset activity. Do not label every such result “queue empty,” especially in sender code.

Keep the source record valid while a blocking put is outstanding. The tagged implementation retains the blocked sender’s source address internally so a later receiver can copy from it. The queue’s copy semantics permit reuse after the call finishes, not while another thread still expects that pending call to read the bytes.

Do not confuse wakeup with successful insertion

Normal consumption from a full queue can copy a waiting sender’s message into the newly available slot and wake that sender with success. Purge takes a different path: it walks the wait queue, assigns -ENOMSG, makes waiters ready, sets the buffered count to zero and moves the read position to the current write position. It does not perform those pending message copies.

Making a higher-priority sender ready can cause it to run before the purging thread continues. If its error branch immediately retries, the queue may contain a new copy by the time the purge caller inspects occupancy. That observation does not show that purge preserved an old buffered message. Capture attempt identifiers to distinguish new insertion from surviving data.

The purge implementation walks the shared wait queue without checking whether each waiter is a sender. In this exact release, a receiver blocked on an empty queue can also be released with -ENOMSG. The public purge description emphasizes senders, so treat receiver wake behavior as a source-checked release-specific detail and qualify it when upgrading. Regardless, receive code must never process its destination buffer unless get succeeds.

Make the producer’s failure branch explicit

  1. Assign an attempt identifier before the blocking call. Preserve the record until that call returns.
  2. On zero, record acceptance. Advance the producer’s accepted sequence only on this branch.
  3. On -EAGAIN, apply the ordinary timeout policy without pretending the record was queued.
  4. On -ENOMSG, consult the permitted reset/retry policy. Drop, retain or retry deliberately; do not convert failure into success.
  5. If retrying, start a new attempt and recheck whether that record is still valid for the current application state. Bound retries so reset cannot produce an uncontrolled producer loop.

A reset controller that needs a stable empty interval must prevent new admission through an application mechanism. Purge alone supplies no persistent “closed” state. Keep this admission requirement separate from the kernel’s immediate queue mutation; otherwise tests accidentally depend on favorable thread priority.

A hypothetical two-slot qualification

Fill a two-slot queue with A and B, then block two senders with C and D. Confirm that both calls are actually pending before purging; a delay alone is weak evidence of that state. Disable automatic retry for the first test. Expected observations are discarded buffered A/B, failed C/D calls with -ENOMSG, and no delivered C/D copy.

Repeat with one controlled retry enabled and a higher-priority sender. A newly queued C is acceptable only if its second attempt is recorded as a successful new call. Then test an empty queue with a blocked receiver, verify the tagged release’s purge result, and ensure the destination sentinel bytes are not treated as a received record.

Finally, test normal full-queue consumption and a real finite timeout as controls. These controls prove the harness can distinguish successful pending insertion, purge failure and timeout. Review counters against the trace, not merely the final occupancy. The useful reset contract is a consistent explanation for every attempted and received message, including those intentionally discarded.

Similar Posts