Skip to content

Push Events and Listeners

bunnie edited this page Apr 7, 2022 · 5 revisions

This document is deprecated. Please see the corresponding Xous Book chapters.

A typical xous server is built with the following pattern:

  1. Register your server name with the Xous name server
  2. A main loop that starts with listening for messages, either using xous::receive_message() or xous::try_receive_message()

Event Loop: To Block or Not to Block

  • receive_message() blocks until a message comes in, freeing up time slices for other servers to run; so if you have no periodic tasks, this is preferred. If you have periodic tasks, it's recommended to start a thread that sends a "pump" message that schedules the periodic task, with a sleep_ms() call in a small loop. This blocking timer-thread + blocking event thread Has structural benefits in terms of making the scheduling run more efficiently and idling the CPU, but the single non-blocking loop is easier to code up. Note that the overhead of the extra thread is exactly one page of memory (4kiB).

  • try_receive_message() doesn't block; you may call xous::yield_slice() at the end of the loop to yield your time slice back to the scheduler if you want to be friendly (but if you aren't you get preempted anyways). This idiom is easier to code, but will tend to busy-wait the scheduler, and will eventually cause more power consumption as the CPU can't ever truly idle.

Receiving Messages

The messages received with the *receive_message() calls can also be blocking or non blocking.

  • Blocking incoming messages require a response. They should be immediate, otherwise the requestor that sent the message is blocked
  • Non-blocking incoming messages are fire-and-forget, but there is no path for returning values to the sender

In other words, messages either support synchronous-read, or write-only semantics. For async-read, Xous uses a "push event" model. For the server that may can produce push events, we use a callback model.

See the Xous v0.8 release notes for an example of callbacks in action.

The push event server provides a hook_callback() style function, which takes as an argument a pointer to a function which performs the asynchronous callback. Hooking the callback will:

  1. Start a thread in the client's process space to receive the callback message
  2. Assign a handler server to the client's callback handler
  3. Send a message to the push event server with the SID client's handler server
  4. The push event server will receive this SID, create a connection to the client server, and store the client connection ID in a table for future callback processing.

When the time comes for a callback, the push event server will:

  1. Iterate through the callback table extracting the connection IDs
  2. Send the callback message to each of the clients
  3. The client callback handler server will receive this message, and then call the locally-hooked callback function

The client's callback function will typically:

  1. Create a new message to the "main" server of this process, and send this on
  2. The actual data processing will then happen.

The client's callback function structure is slightly tortured as of Xous v0.8 because we lack a Box type and can't use closures, and we also lack a Mutex. Thus the way to safely repatriate data from the callback to the main loop is through the message passing mechanism. In a future revision of Xous this could be simplified, once we have the ability to either store a closure as the callback and/or use Mutex types to guard shared data between the receiver thread and the main thread.

The keyboard server gives a live example of a push event server, and the ime-frontend gives an example of a client server to the keyboard push event server.

For callbacks that are guaranteed to be very simple, e.g. that just need to be a scalar message to simply notify a server of an event, without any complicated attached data, one can skip the client-side callback, and just store the connection ID to the client server plus the scalar message number directly. This is possible so long as the client strictly adheres to the Xous v0.8 convention of using a simple enum type to encode the possible Opcodes of incoming messages, which can be converted into a unique integer. Unfortunately, more complicated enum types do not have a discriminant in Rust.

Clone this wiki locally