Set of I/O-free Rust coroutines to manage timers, based on io-stream.
This library allows you to manage timers using an I/O-agnostic approach, based on 3 concepts:
A coroutine is an I/O-free, resumable and composable state machine that emits I/O requests. A coroutine is considered terminated when it does not emit I/O requests anymore.
See available coroutines at ./src.
A runtime contains all the I/O logic, and is responsible for processing I/O requests emitted by coroutines.
See available runtimes at pimalaya/io-stream.
The loop is the glue between coroutines and runtimes. It makes the coroutine progress while allowing runtime to process I/O.
use std::net::TcpStream;
use io_stream::runtimes::std::handle;
use io_timer::client::coroutines::{GetTimer, StartTimer};
let mut stream = TcpStream::connect("localhost:1234").unwrap();
// start the timer
let mut arg = None;
let mut start = StartTimer::new();
while let Err(io) = start.resume(arg.take()) {
arg = Some(handle(&mut stream, io).unwrap());
}
// wait few seconds, then get the timer
let mut arg = None;
let mut get = GetTimer::new();
let timer = loop {
match get.resume(arg.take()) {
Ok(timer) => break timer,
Err(io) => arg = Some(handle(&mut stream, io).unwrap()),
}
};
See complete example at ./examples/std-tcp.rs.
use std::{sync::Arc, time::Duration};
use io_stream::runtimes::tokio::handle;
use io_timer::{
server::coroutines::HandleRequest,
timer::{TimerConfig, TimerCycles, TimerLoop},
Timer,
};
let config = TimerConfig {
cycles: TimerCycles::from([("Work", 2).into(), ("Rest", 3).into()]),
cycles_count: TimerLoop::Infinite,
};
let timer = Arc::new(tokio::sync::Mutex::new(Timer::new(config)));
// use an unbounded channel for receiving timer events
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
// start the timer event notifier
tokio::spawn(async move {
loop {
let _event = rx.recv().await.unwrap();
// do whatever with the event
}
});
// define and spawn the timer tick
// this is needed to update the internal state of the timer
tokio::spawn({
let timer = timer.clone();
let tx = tx.clone();
async move {
loop {
let mut timer = timer.lock().await;
let events = timer.update();
drop(timer);
for event in events {
tx.send(event).unwrap();
}
// the timer can be refreshed every seconds
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
});
// listen to the unix socket for client <-> server communication
let listener = tokio::net::UnixListener::bind("/tmp/timer.sock").unwrap();
let (mut stream, _) = listener.accept().await.unwrap();
loop {
let mut arg = None;
let mut handler = HandleRequest::new();
let events = loop {
let mut timer = timer.lock().await;
let output = handler.resume(&mut timer, arg.take());
drop(timer);
match output {
Ok(events) => break events,
Err(io) => arg = Some(handle(&mut stream, io).await.unwrap()),
}
};
for event in events {
tx.send(event).unwrap();
}
}
See complete example at ./examples/tokio-unix.rs.
See projects built at the top of this library:
- comodoro: CLI to manage timers
Special thanks to the NLnet foundation and the European Commission that helped the project to receive financial support from various programs:
- NGI Assure in 2022
- NGI Zero Entrust in 2023
- NGI Zero Core in 2024 (still ongoing)
If you appreciate the project, feel free to donate using one of the following providers: