Last active
October 20, 2021 03:35
-
-
Save mryndzionek/8641641f2e1cf877b867f186591cc20c to your computer and use it in GitHub Desktop.
Rust implementation of the 'Happy Eyeballs' (also called 'Fast Fallback') algorithm - https://en.wikipedia.org/wiki/Happy_Eyeballs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "happy_eyeballs" | |
version = "0.1.0" | |
edition = "2018" | |
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | |
[dependencies] | |
rand = { version = "0.8" } | |
log = { version = "0.4" } | |
env_logger = { version = "0.9" } | |
chrono = { version = "0.4" } | |
ctrlc = { version = "3.1" } | |
[profile.release] | |
opt-level = 'z' | |
lto = true | |
debug = false | |
codegen-units = 1 | |
panic = "abort" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use chrono::Local; | |
use log::LevelFilter; | |
use log::{info, warn}; | |
use rand::Rng; | |
use std::io::Write; | |
use std::sync::mpsc::{self, Receiver, RecvTimeoutError, Sender}; | |
use std::thread::JoinHandle; | |
use std::{thread, time::Duration}; | |
const NUM_ACTIONS: usize = 5; | |
const LAST_ACTION: usize = NUM_ACTIONS - 1; | |
const ACTION_TIMEOUT_RANGE_MS: std::ops::Range<u64> = 2000..6000; | |
const WAIT_TIME_MS: u64 = 2000; | |
enum Event { | |
ActionFinished(usize), | |
KeyboardInterrupt, | |
} | |
fn action(id: usize, sch: Sender<Event>, rch: Receiver<()>) { | |
let delay_ms = rand::thread_rng().gen_range(ACTION_TIMEOUT_RANGE_MS); | |
info!("Action {:?} sleeping for {:?} ms", id, delay_ms); | |
match rch.recv_timeout(Duration::from_millis(delay_ms)) { | |
Ok(()) => { | |
warn!("Action {:?} canceled", id) | |
} | |
Err(_) => { | |
info!("Action {:?} finished", id); | |
sch.send(Event::ActionFinished(id)).unwrap(); | |
} | |
} | |
} | |
fn spawn_new_action( | |
sch: Sender<Event>, | |
handles: &mut Vec<JoinHandle<()>>, | |
channels: &mut Vec<Sender<()>>, | |
id: usize, | |
) { | |
let (a_sch, a_rch) = mpsc::channel(); | |
handles.push(thread::spawn(move || { | |
action(id, sch, a_rch); | |
})); | |
channels.push(a_sch); | |
} | |
fn ctrl_channel(sch: Sender<Event>) { | |
ctrlc::set_handler(move || { | |
let _ = sch.send(Event::KeyboardInterrupt); | |
}) | |
.unwrap(); | |
} | |
fn main() { | |
env_logger::Builder::new() | |
.format(|buf, record| { | |
writeln!( | |
buf, | |
"{} [{}] - {}", | |
Local::now().format("%H:%M:%S.%f"), | |
record.level(), | |
record.args() | |
) | |
}) | |
.filter(None, LevelFilter::Info) | |
.init(); | |
let (send, recv) = mpsc::channel(); | |
let mut curr_id: usize = 0; | |
let mut handles = Vec::new(); | |
let mut channels = Vec::new(); | |
let mut actions_left = true; | |
ctrl_channel(send.clone()); | |
// Spawn the first action | |
spawn_new_action(send.clone(), &mut handles, &mut channels, curr_id); | |
loop { | |
let r = if !actions_left { | |
// No new actions to spawn - wait indefinitely for some actions to finish | |
recv.recv().or(Err(RecvTimeoutError::Timeout)) | |
} else { | |
// Wait with timeout if there are more actions to spawn | |
recv.recv_timeout(Duration::from_millis(WAIT_TIME_MS)) | |
}; | |
match r { | |
Ok(ev) => { | |
match ev { | |
Event::KeyboardInterrupt => { | |
warn!("Keyboard interrupt! Exiting..."); | |
} | |
Event::ActionFinished(id) => { | |
info!("Action {:?} finished first", id); | |
info!("Canceling other actions"); | |
channels.remove(id); | |
} | |
} | |
// Just send a 'cancel' message and wait for threads to finish | |
for ch in channels { | |
ch.send(()).unwrap(); | |
} | |
for h in handles { | |
h.join().unwrap(); | |
} | |
break; | |
} | |
Err(_) => { | |
warn!("Timeout!"); | |
// Spawn new action if there are any left | |
if actions_left { | |
curr_id += 1; | |
info!("Spawning another action ({:?})", curr_id); | |
spawn_new_action(send.clone(), &mut handles, &mut channels, curr_id); | |
if curr_id == LAST_ACTION { | |
actions_left = false; | |
} | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment