-
Notifications
You must be signed in to change notification settings - Fork 85
Push Events and Listeners
This document is deprecated. Please see the corresponding Xous Book chapters.
A typical xous
server is built with the following pattern:
- Register your server name with the Xous name server
- A main loop that starts with listening for messages, either using
xous::receive_message()
orxous::try_receive_message()
-
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 asleep_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 callxous::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.
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:
- Start a thread in the client's process space to receive the callback message
- Assign a handler server to the client's callback handler
- Send a message to the push event server with the
SID
client's handler server - 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:
- Iterate through the callback table extracting the connection IDs
- Send the callback message to each of the clients
- The client callback handler server will receive this message, and then call the locally-hooked callback function
The client's callback function will typically:
- Create a new message to the "main" server of this process, and send this on
- 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.