Skip to content

Instantly share code, notes, and snippets.

@k0nserv
Created July 27, 2024 16:46
Show Gist options
  • Save k0nserv/5f6addd0cc687c59f5966d988888a85c to your computer and use it in GitHub Desktop.
Save k0nserv/5f6addd0cc687c59f5966d988888a85c to your computer and use it in GitHub Desktop.
sans-io with coroutines
#![feature(stmt_expr_attributes, coroutines, coroutine_trait)]
use std::ops::Coroutine;
use std::pin::pin;
use std::thread::sleep;
use std::time::{Duration, Instant};
fn main() {
let mut machine = pin!(make_machine());
let resp = machine.as_mut().resume(Input::ping(Instant::now()));
println!("{resp:?}");
sleep(Duration::from_millis(150));
let resp = machine.as_mut().resume(Input::ping(Instant::now()));
println!("{resp:?}");
}
const NUM_PINGS_FOR_EVERY_PONG: u64 = 2;
const MAX_DURATION_BETWEEN_PINGS: Duration = Duration::from_millis(200);
fn make_machine() -> impl Coroutine<Input, Yield = Output, Return = ()> {
return #[coroutine]
move |mut input: Input| {
let mut last_ping_at: Option<Instant> = None;
let mut outstanding_pings = NUM_PINGS_FOR_EVERY_PONG;
loop {
let now = input.now;
let since_last = last_ping_at.map(|l| now.duration_since(l));
let expired = since_last
.map(|d| d > MAX_DURATION_BETWEEN_PINGS)
.unwrap_or(false);
if expired {
// Failure
return ();
}
match input.kind {
Kind::Ping => {
last_ping_at = Some(now);
outstanding_pings -= 1;
if outstanding_pings == 0 {
last_ping_at = None;
outstanding_pings = NUM_PINGS_FOR_EVERY_PONG;
input = yield Output::Pong;
} else {
let expires_at = last_ping_at
.map(|d| MAX_DURATION_BETWEEN_PINGS - now.duration_since(d))
.or(Some(MAX_DURATION_BETWEEN_PINGS));
input = yield Output::Wait(expires_at);
}
}
Kind::Time => {
let remaining = since_last.map(|d| MAX_DURATION_BETWEEN_PINGS - d);
input = yield Output::Wait(remaining);
}
}
}
};
}
struct Input {
now: Instant,
kind: Kind,
}
#[derive(Debug, PartialEq, Eq)]
enum Kind {
Ping,
Time,
}
impl Input {
fn ping(now: Instant) -> Self {
Self {
now,
kind: Kind::Ping,
}
}
fn time(now: Instant) -> Self {
Self {
now,
kind: Kind::Time,
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum Output {
Pong,
Wait(Option<Duration>),
}
#[cfg(test)]
mod tests {
use super::*;
use std::ops::CoroutineState::*;
#[test]
fn test_two_pings_to_a_pong() {
let start = Instant::now();
let mut machine = pin!(make_machine());
let resp = machine.as_mut().resume(Input::ping(start));
assert_eq!(
resp,
Yielded(Output::Wait(Some(Duration::from_millis(200))))
);
let resp = machine
.as_mut()
.resume(Input::ping(start + Duration::from_millis(150)));
assert_eq!(resp, Yielded(Output::Pong));
}
#[test]
fn test_too_late() {
let start = Instant::now();
let mut machine = pin!(make_machine());
let resp = machine.as_mut().resume(Input::ping(start));
assert_eq!(
resp,
Yielded(Output::Wait(Some(Duration::from_millis(200))))
);
let resp = machine
.as_mut()
.resume(Input::ping(start + Duration::from_millis(250)));
assert_eq!(resp, Complete(()));
}
#[test]
fn test_no_second_ping() {
let start = Instant::now();
let mut machine = pin!(make_machine());
let resp = machine.as_mut().resume(Input::ping(start));
assert_eq!(
resp,
Yielded(Output::Wait(Some(Duration::from_millis(200))))
);
let resp = machine
.as_mut()
.resume(Input::time(start + Duration::from_millis(250)));
assert_eq!(resp, Complete(()));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment