Zephyr k_poll: Separate Readiness Notification from Taking the Object
A successful k_poll() call tells a dispatcher which conditions became ready. It does not reserve a semaphore token or remove an item from a queue. Design the loop as three separate operations: interpret the poll result, attempt the actual acquisition, and rearm the event records. Treating those operations as one transaction is a common reason a dispatcher blocks unexpectedly or processes stale state.
This guide covers upstream Zephyr 3.7.0 with CONFIG_POLL, using a thread that polls a semaphore, FIFO and optional signal. The recommended architecture has one dispatcher acquiring these objects. Competing consumers are included as a diagnostic fault case, not a performance recommendation. Userspace allocation failures, application shutdown and signal delivery require explicit branches; target timing remains application-specific.
Keep two kinds of state separate
The object has persistent state: a semaphore count, queued items or a signal’s raised flag. Each k_poll_event also has a state field describing what polling observed. Resetting the event field does not consume an object. Consuming a FIFO item does not automatically clear the event field. A signal adds another reset requirement because its raised flag remains set until explicitly reset. The versioned polling guide describes these separate lifetimes.
For example, a FIFO event may be ready and a subsequent k_fifo_get(..., K_NO_WAIT) may return no item because a competing thread took it first. The correct response is to record contention and return to the dispatch policy. Waiting indefinitely at that point could prevent handling a ready shutdown signal or another queue.

Build the loop around the return code
The tagged API declarations distinguish these outcomes:
- Zero: inspect every event in the array. Several conditions may be ready together. Use independent checks or an iteration, rather than an exclusive chain that handles only the first ready entry.
-EAGAIN: the wait expired. Run the timeout policy; do not treat an incidental event-state value as a successful poll.-EINTR: inspect the valid output events, includingK_POLL_STATE_CANCELLED. Cancellation is a control result, not an instruction to dereference a missing FIFO item.- Other errors: use the configuration-specific error path. In userspace, the documented resource-pool and parameter errors must not be converted into normal idle timeouts.
The timeout distinction matters at a boundary race. The 3.7.0 implementation explicitly allows conditions to change while registrations are being removed after timeout. The return code must be interpreted before the event list. An application that branches only on state fields can manufacture work from a poll that actually timed out.
Use acquisition results as the processing gate
- Initialize each event with its correct object, event type and
K_POLL_MODE_NOTIFY_ONLY. Keep the event array and objects alive throughout the call. - After an eligible return, check each event for its expected state bits. Dispatch cancellation independently from data readiness.
- For a semaphore, attempt a nonblocking take and process only on success. For a FIFO, attempt a nonblocking get and process only a returned item.
- Limit work per source per pass. A permanently busy FIFO must not prevent checking the control signal or other ready inputs.
- Before reusing the array, reset its event states to
K_POLL_STATE_NOT_READY. Object state may still be ready, so an immediate subsequent return can be correct.
Ordinary threads pending directly on an object have precedence over poll notification. Therefore, a missing notification is not automatically a broken interrupt or semaphore producer. First inventory every direct take/get call, including diagnostics and background threads. A dispatcher that is supposed to be the sole consumer needs that rule enforced in code review, not merely mentioned in its function name.
Give poll signals a narrower job
A poll signal is suitable for announcing a condition or completion when coalescing is acceptable. It is not a queue of every raise and every result. Read the signal with k_poll_signal_check(); use the result only when the returned signaled flag is nonzero. Have the polling thread own signal reset, then reset the associated event state separately.
This ownership rule alone does not make a repeated producer notification lossless. A producer can raise again between the consumer’s observation and reset. If every event matters, use a counting or queued representation, or an application handshake that prevents a second publication until acknowledgment. Do not infer event counts from the number of successful poll returns. Also, a signal-raise -EAGAIN during poll timeout does not mean its stored raised flag and result were discarded; the tagged API documents that they remain observable on a later poll.
A hypothetical dispatcher qualification
Suppose a controller has a command FIFO, a sample-ready semaphore and a stop signal. The following is a proposed test plan, not a measured product result. Record poll return, event index/state, acquisition result and dispatch action with sequence identifiers. Avoid synchronous logging in the timing-sensitive path.
- Make command and sample conditions ready before entering the call. Verify that one pass considers both, regardless of array ordering.
- Insert a controlled competing consumer between readiness and acquisition. Verify that a failed nonblocking acquisition neither processes stale data nor blocks control handling.
- Leave another command queued after processing one. Verify that rearming permits another immediate ready result without inventing a second command.
- Raise the stop signal and deliberately omit only the signal reset in a test build. Confirm repeated wakeups, then restore both resets and repeat.
- Exercise timeout and queue-wait cancellation separately. Verify distinct recorded outcomes and no item access on cancellation alone.
The acceptance criterion is a consistent state transition record, not a particular number of microseconds. This guide includes no executed Zephyr fixture. Once functional behavior is established, measure dispatch latency on the chosen board with the real producer priorities and workload. If contention persists, simplify ownership before adding retries or longer waits.