Zephyr Mailboxes: Bound the Gap Between Message Acceptance and Data Retrieval
A finite timeout on k_mbox_put() does not bound the entire synchronous send. In Zephyr 3.7.0 it limits waiting for a compatible receiver. Once that receiver accepts the message, the sender can remain blocked until the receiver retrieves or discards the data. A robust design therefore needs two separate limits: one for matching and an application-level service rule for an accepted message.
This guide concerns synchronous mailbox exchanges between threads in upstream Zephyr 3.7.0. It excludes ISR use and asynchronous-send resource sizing. The aim is to diagnose an apparently expired send deadline without confusing the mailbox’s matching timeout with completion of data retrieval. All schedules below are hypothetical; no board timing measurements were performed.
Draw the accepted-but-unfinished state
A sender supplies a descriptor and, for a nonempty transfer, a data buffer. A receiver supplies another descriptor specifying its acceptable source and maximum data size. Matching requires compatible sender/receiver identities. A message may be accepted with data copied immediately, or accepted with retrieval deferred by passing a null buffer to k_mbox_get(). The release-tagged mailbox guide describes these distinct stages.
Once a nonzero-size message is accepted for deferred retrieval, the receiver holds an unfinished obligation. Returning from k_mbox_get() is not proof that the sender has returned from its put. The receive descriptor must remain available for the later retrieval or discard operation. The sender’s original bytes must remain valid and unchanged until its synchronous call completes.

Interpret the timeout at the right boundary
The 3.7.0 API contract says the send waits as long as necessary for processing after receipt. A no-wait put fails with -ENOMSG when no compatible receiver is available; a finite unsuccessful matching wait returns -EAGAIN. Neither result describes a message already accepted and awaiting deferred data retrieval.
Even K_NO_WAIT is therefore not a guarantee that a successful synchronous exchange cannot block. If a receiver is already waiting and matches, the sender still waits for processing. In the tagged implementation, this matched-sender path uses an indefinite pending wait. In the opposite arrival order, matching removes the sender from its timed mailbox queue before the receiver completes the message.
For a product with a strict call-return budget, do not solve this by reducing the matching timeout again. Either make the receiver’s completion path bounded under the application’s scheduling assumptions, or select a communication protocol whose completion and cancellation semantics meet that budget. Aborting a blocked sender while a receiver still refers to its descriptor or payload is not a general mailbox cancellation mechanism.
Audit every receiver exit
- Immediate copy: supply an adequately sized destination to
k_mbox_get(). After successful return, use the returned size, not the destination capacity, as the valid byte count. - Deferred nonzero data: retain the receive descriptor and eventually call
k_mbox_data_get()with a sufficiently large destination. - Rejected accepted data: call
k_mbox_data_get()with a null destination to discard it and complete the obligation. Simply returning from the handler leaves the sender waiting. - Returned size zero: the kernel has already completed message disposal; no later data retrieval is necessary.
- Receive failure: do not process the descriptor as an accepted message. Classify no-message, timeout and application errors before entering the completion logic.
Give exactly one receiver-side path responsibility for completion. Avoid copying the descriptor into unrelated work items and letting several handlers attempt retrieval. Treat the descriptor as a live exchange token until completion, not as a reusable request template. Reinitialize the public request fields before using it for a new receive; do not edit private synchronization fields.
Distinguish truncation from deferred progress
The receiver’s requested size limits the transfer. Matching reduces that size when the sender offers fewer bytes; a smaller receiver buffer accepts only the initial portion. The remaining sender bytes do not become a second mailbox message. The sender learns the completed transfer size when its synchronous call returns. A protocol that requires a complete record must compare the agreed size with its expected record length and reject or retry according to an explicit application rule.
Do not reduce the size merely to shorten the sender’s blocked interval unless losing the suffix is acceptable. Conversely, do not advertise a large receive capacity and later supply a smaller destination to deferred retrieval. Check the returned descriptor size against the actual allocated buffer before copying. If allocation fails, the receiver still owes explicit discard rather than abandoning the accepted message.
A hypothetical failure and a useful trace
Imagine a diagnostic worker accepts a variable-length snapshot with deferred retrieval because it wants to allocate only the needed buffer. The sender specifies a 20-millisecond matching wait. The worker accepts promptly, then waits on a resource owned by the blocked sender. The timeout does not break this cycle: the exchange has crossed into accepted processing.
Instrument four boundaries with an exchange identifier: put entry, successful get return, data-get/discard completion, and put return. Store both requested and returned sizes. If put entry has no matching get, investigate identity filters and receiver availability. If get succeeded but completion is absent, inspect the receiver’s allocation, error and dependency paths. If completion occurred but the sender has not run, investigate scheduling separately rather than declaring retrieval incomplete.
Qualification should deliberately test a missing receiver, an incompatible receiver, immediate copy, delayed copy, explicit discard, zero size, truncated data and allocation failure after acceptance. For delayed copy, demonstrate that the sender remains blocked beyond its original matching timeout, then completes after retrieval. These are proposed functional tests, not results reported from hardware.
The final design review should name the receiver completion owner, its permitted blocking dependencies, the maximum accepted work in flight and the recovery policy if that owner fails. Those obligations, rather than the timeout argument alone, determine whether synchronous mailboxes fit the system’s responsiveness requirement.