Zephyr OS DNS Callbacks: Initialize Query State Before a Cached Result Can Complete
A DNS operation that normally waits for network traffic may complete through callbacks before the initiating function returns. If an application initializes its query state only after submission, a cached result or numeric address can expose a race that never appears during a cold-cache demonstration. Prepare callback-visible state before invoking the resolver.
The implementation details below come from Zephyr 4.4.0's resolver. The proposed ownership pattern is an application design recommendation, not an executed stress test. DNS service-discovery queries have additional behavior and are not treated as identical to ordinary address resolution here.

Separate an address result from the end of results
The current official DNS resolver guide illustrates callbacks that handle DNS_EAI_INPROGRESS with non-NULL information separately from DNS_EAI_ALLDONE. It is a live documentation page; the tagged source controls the version-specific observations in this article.
The tagged resolver implementation invokes a result callback and an ALLDONE callback directly for a recognized numeric address. Its cache-hit path also invokes result callbacks followed by ALLDONE before returning. The normal address-response path likewise distinguishes individual results from terminal notification.
Treat these as different events in application code. A callback carrying one address is an opportunity to copy or evaluate that result; it is not a universal signal to destroy the entire query context. Conversely, a terminal notification need not carry an address structure. Check both status and pointer before accessing result fields.
Prepare state before submission
Allocate or reserve the query context first. Initialize its identity, result capacity, completion state, and any synchronization primitive that the callback will use. Only then call the resolver with that context as user data.
A hypothetical bug sets a “pending” flag immediately after the resolver returns. On a cache hit, the callback can set the operation complete during the call, only for the caller to overwrite that completion with pending afterward. The application then waits for a notification that already occurred.
Use a state transition that remains correct whether callbacks are immediate or delayed. Inspect the submission return value without unconditionally resetting callback-produced state. A successful submission and a completed query are separate observations, even when both happen in one call stack.
Copy results into owned storage
The numeric-address path passes a local result structure, and cache results also come from temporary storage in the resolver call. Keep only a pointer to that callback argument and later work can observe invalid or overwritten data.
Copy the address and the metadata the application actually needs while the callback is running. Bound the number of stored results and define what happens if more addresses arrive than the application can retain. Do not let a harmlessly larger answer list turn into uncontrolled memory growth or silent overwriting of an unrelated result.
If the application selects the first acceptable address and starts another operation, keep resolver context ownership separate from the connection attempt. A connection failure may justify trying another retained address, but it does not retroactively change the resolver's completion event.
Keep cancellation and replacement explicit
A new request can supersede an older one while old work is still being processed. Attach an application generation or request identity to deferred work so it cannot update the newest connection state accidentally. Mark the old operation as no longer wanted, and use the selected resolver API's cancellation and completion contract to decide when its storage may be reclaimed.
Do not infer a universal reclamation rule from this article's immediate-result examples. Cancellation, timeout and service discovery require their own documented paths. Inspect those paths for the exact version and API used before freeing callback-visible context.
Test a numeric address, a cache hit, an uncached name, an empty or failed result, and replacement of an outstanding operation. The design passes when caller state remains consistent regardless of whether the first callback happens before or after submission returns. That is the boundary a network-only test can easily miss.