pub struct Mainloop<Ctx, AllM> { /* private fields */ }
Expand description
The mainloop for applications.
This is a simple event loop to enable asynchronous programming without
Rust’s async fn
s. It is designed to be used in the main
function of
applications.
§Per-object state
Each object in the mainloop has its own state. For example, a TCP socket channel would have a state to track the connection state, timers, and TX/RX buffers.
See the example below for how to define and use per-object state.
§Why not async Rust?
This API is very similar to epoll
+ non-blocking I/O in Linux. An event
loop API like this means that you need to write state machines manually, which
async Rust (async fn
) does automatically.
However, explicit state machines make debugging easier because the execution flow is crystal clear. Also we don’t have to care about pitfalls like cancellation safety. Moreover, my observation is that most of OS components are very simple and manual state machines are sufficient.
§Future work
- Support multi-threaded mainloop.
§Example
starina_api::autogen!(); // Include starina_autogen module
use starina_api::channel::Channel;
use starina_api::environ::Environ;
use starina_api::mainloop::Event;
use starina_api::mainloop::Mainloop;
use starina_api::prelude::*;
use starina_autogen::idl::ping::PingReply;
use starina_autogen::idl::Message;
// Per-object state.
#[derive(Debug)]
enum Context {
Startup,
Client { counter: i32 },
}
#[no_mangle]
pub fn main(mut env: Environ) {
let mut mainloop = Mainloop::<Context, Message>::new().unwrap();
// Take the startup channel, and start receiving messages through the
// mainloop.
let startup_ch = env.take_channel("dep:startup").unwrap();
mainloop.add_channel(startup_ch, Context::Startup).unwrap();
// Mainloop!
loop {
// Wait for the next event. Use `match` not to miss unexpected cases.
match mainloop.next() {
Event::Message { // The "message received" event.
ctx: Context::Startup, // The message is from startup.
message: Message::NewClient(m), // NewClient message.
..
} => {
// Take the new client's channel and register it to the
// mainloop.
let new_ch = m.handle.take::<Channel>().unwrap();
mainloop
.add_channel(new_ch, Context::Client { counter: 0 })
.unwrap();
}
Event::Message { // The "message received" event.
ctx: Context::Client { counter }, // The message is from a client.
message: Message::Ping(m), // Ping message.
sender, // The channel which received the message.
} => {
// Update the per-object state. It's mutable!
*counter += 1;
// Reply with the counter value.
if let Err(err) = sender.send(PingReply { value: *counter }) {
debug_warn!("failed to reply: {:?}", err);
}
}
ev => {
panic!("unexpected event: {:?}", ev);
}
}
}
}
Implementations§
Source§impl<Ctx, AllM: MessageDeserialize> Mainloop<Ctx, AllM>
impl<Ctx, AllM: MessageDeserialize> Mainloop<Ctx, AllM>
Sourcepub fn add_channel<T: Into<(ChannelSender, ChannelReceiver)>>(
&mut self,
channel: T,
state: Ctx,
) -> Result<(), Error>
pub fn add_channel<T: Into<(ChannelSender, ChannelReceiver)>>( &mut self, channel: T, state: Ctx, ) -> Result<(), Error>
Adds a channel to start receiving messages in the mainloop.