use std::any::Any;
use std::cell::RefCell;
use std::collections::{BTreeMap, VecDeque};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock, RwLock};
use crate::join::JoinReply;
pub type Feed = BTreeMap<String, String>;
type Take<T> = Arc<dyn Fn(&T) + Send + Sync>;
pub struct Bus<T: Clone + PartialEq + Send + Sync + 'static> {
now: RwLock<Option<(u64, Arc<T>)>>,
takers: Mutex<Vec<Taker<T>>>,
delivering: Mutex<()>,
queued: Mutex<VecDeque<(u64, Arc<T>)>>,
}
struct Taker<T> {
name: &'static str,
take: Take<T>,
seen: Arc<AtomicU64>,
}
impl<T> Clone for Taker<T> {
fn clone(&self) -> Self {
Taker { name: self.name, take: Arc::clone(&self.take), seen: Arc::clone(&self.seen) }
}
}
thread_local! {
static DELIVERING: RefCell<Vec<usize>> = const { RefCell::new(Vec::new()) };
}
impl<T: Clone + PartialEq + Send + Sync + 'static> Default for Bus<T> {
fn default() -> Self {
Bus { now: RwLock::new(None), takers: Mutex::new(Vec::new()), delivering: Mutex::new(()), queued: Mutex::new(VecDeque::new()) }
}
}
impl<T: Clone + PartialEq + Send + Sync + 'static> Bus<T> {
pub fn new() -> Arc<Bus<T>> {
Arc::new(Bus::default())
}
fn id(&self) -> usize {
self as *const Bus<T> as usize
}
fn delivery<R>(&self, f: impl FnOnce() -> R) -> R {
let id = self.id();
if DELIVERING.with(|d| d.borrow().contains(&id)) {
return f();
}
let _one = self.delivering.lock().unwrap_or_else(|p| p.into_inner());
DELIVERING.with(|d| d.borrow_mut().push(id));
let r = f();
loop {
let next = self.queued.lock().unwrap_or_else(|p| p.into_inner()).pop_front();
let Some((generation, v)) = next else { break };
let takers: Vec<Taker<T>> = self.takers.lock().unwrap_or_else(|p| p.into_inner()).clone();
for t in &takers {
give(t, generation, &v);
}
}
DELIVERING.with(|d| {
let mut d = d.borrow_mut();
if let Some(i) = d.iter().rposition(|x| *x == id) {
d.remove(i);
}
});
r
}
pub fn publish(&self, value: T) -> bool {
self.delivery(|| {
let mut now = self.now.write().unwrap_or_else(|p| p.into_inner());
if now.as_ref().map(|(_, v)| v.as_ref()) == Some(&value) {
return false;
}
let generation = now.as_ref().map(|(g, _)| *g).unwrap_or(0) + 1;
let value = Arc::new(value);
*now = Some((generation, Arc::clone(&value)));
drop(now);
self.queued.lock().unwrap_or_else(|p| p.into_inner()).push_back((generation, value));
true
})
}
pub fn on_published(&self, name: &'static str, take: impl Fn(&T) + Send + Sync + 'static) {
let t = Taker { name, take: Arc::new(take), seen: Arc::new(AtomicU64::new(0)) };
self.delivery(|| {
self.takers.lock().unwrap_or_else(|p| p.into_inner()).push(t.clone());
let now = self.now.read().unwrap_or_else(|p| p.into_inner()).clone();
if let Some((generation, v)) = now {
give(&t, generation, &v);
}
})
}
pub fn on_change<V>(&self, name: &'static str, view: impl Fn(&T) -> V + Send + Sync + 'static, take: impl Fn(&V) + Send + Sync + 'static)
where
V: PartialEq + Send + 'static,
{
let last: Mutex<Option<V>> = Mutex::new(None);
self.on_published(name, move |t| {
let v = view(t);
let mut seen = last.lock().unwrap_or_else(|p| p.into_inner());
if seen.as_ref() == Some(&v) {
return;
}
take(&v);
*seen = Some(v);
});
}
pub fn current(&self) -> Option<Arc<T>> {
self.now.read().unwrap_or_else(|p| p.into_inner()).as_ref().map(|(_, v)| Arc::clone(v))
}
pub fn generation(&self) -> u64 {
self.now.read().unwrap_or_else(|p| p.into_inner()).as_ref().map(|(g, _)| *g).unwrap_or(0)
}
pub fn subscribers(&self) -> Vec<&'static str> {
self.takers.lock().unwrap_or_else(|p| p.into_inner()).iter().map(|t| t.name).collect()
}
}
fn give<T>(t: &Taker<T>, generation: u64, v: &T) {
if t.seen.fetch_max(generation, Ordering::SeqCst) >= generation {
return;
}
call(t.name, &t.take, v);
}
fn call<T>(name: &str, take: &Take<T>, v: &T) {
if let Err(p) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| take(v))) {
let why = panic_words(&*p);
eprintln!("feed bus: the subscriber `{name}` panicked on a published feed ({why}); the others still run");
}
}
fn panic_words(p: &(dyn Any + Send)) -> String {
p.downcast_ref::<&str>().map(|s| s.to_string()).or_else(|| p.downcast_ref::<String>().cloned()).unwrap_or_else(|| "no message".into())
}
pub type ReplyBus = Bus<JoinReply>;
pub fn global() -> &'static Bus<Feed> {
static GLOBAL: OnceLock<Bus<Feed>> = OnceLock::new();
GLOBAL.get_or_init(Bus::default)
}
pub fn publish(feed: Feed) -> bool {
global().publish(feed)
}
pub fn on_published(name: &'static str, take: impl Fn(&Feed) + Send + Sync + 'static) {
global().on_published(name, take)
}
pub fn current() -> Option<Arc<Feed>> {
global().current()
}
pub fn value(feed: &Feed, key: &str) -> Option<String> {
feed.get(key).map(|v| v.trim().to_string()).filter(|v| !v.is_empty())
}
pub fn offered(feed: &Feed, key: &str) -> Option<String> {
feed.iter()
.filter_map(|(k, v)| k.strip_prefix("offer.")?.strip_suffix(key)?.strip_suffix('.').filter(|vm| !vm.is_empty() && !vm.contains('.')).map(|_| v.trim()))
.find(|v| !v.is_empty())
.map(str::to_string)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
fn feed(pairs: &[(&str, &str)]) -> Feed {
pairs.iter().map(|(k, v)| (k.to_string(), v.to_string())).collect()
}
#[test]
fn a_subscriber_gets_the_value_in_force_and_every_change() {
let bus: Arc<Bus<Feed>> = Bus::new();
assert!(bus.publish(feed(&[("a", "1")])));
let seen = Arc::new(Mutex::new(Vec::new()));
let s = Arc::clone(&seen);
bus.on_published("t", move |f| s.lock().unwrap().push(f.get("a").cloned().unwrap_or_default()));
assert!(!bus.publish(feed(&[("a", "1")])), "the same value again is no change");
assert!(bus.publish(feed(&[("a", "2")])));
assert!(bus.publish(feed(&[("a", "3")])));
assert_eq!(*seen.lock().unwrap(), vec!["1", "2", "3"]);
assert_eq!(bus.generation(), 3);
assert_eq!(bus.subscribers(), vec!["t"]);
assert_eq!(bus.current().unwrap().get("a").map(String::as_str), Some("3"));
}
#[test]
fn a_view_subscriber_runs_only_when_its_view_changes() {
let bus: Arc<Bus<Feed>> = Bus::new();
let seen = Arc::new(Mutex::new(Vec::new()));
let s = Arc::clone(&seen);
bus.on_change("fleet", |f: &Feed| value(f, "fleet"), move |v: &Option<String>| s.lock().unwrap().push(v.clone()));
bus.publish(feed(&[("token", "t")]));
bus.publish(feed(&[("token", "t"), ("clock", "x")]));
bus.publish(feed(&[("token", "t"), ("clock", "x"), ("fleet", "https://one")]));
bus.publish(feed(&[("token", "u"), ("clock", "x"), ("fleet", "https://one")]));
bus.publish(feed(&[("fleet", "https://two")]));
assert_eq!(*seen.lock().unwrap(), vec![None, Some("https://one".to_string()), Some("https://two".to_string())]);
}
#[test]
fn a_subscriber_may_subscribe_and_publish_from_inside_its_call() {
let bus: Arc<Bus<Feed>> = Bus::new();
let seen = Arc::new(Mutex::new(Vec::new()));
let (b, s) = (Arc::clone(&bus), Arc::clone(&seen));
bus.on_published("outer", move |f| {
s.lock().unwrap().push(format!("outer {}", f.get("n").unwrap()));
if f.get("n").map(String::as_str) == Some("1") {
let s2 = Arc::clone(&s);
b.on_published("inner", move |f| s2.lock().unwrap().push(format!("inner {}", f.get("n").unwrap())));
b.publish(feed(&[("n", "2")]));
}
});
bus.publish(feed(&[("n", "1")]));
assert_eq!(*seen.lock().unwrap(), vec!["outer 1", "inner 1", "outer 2", "inner 2"]);
}
#[test]
fn a_panicking_subscriber_is_dropped_from_that_delivery_only() {
let bus: Arc<Bus<Feed>> = Bus::new();
let n = Arc::new(AtomicUsize::new(0));
bus.on_published("panics", |_| panic!("boom"));
let m = Arc::clone(&n);
bus.on_published("counts", move |_| {
m.fetch_add(1, Ordering::SeqCst);
});
bus.publish(feed(&[("a", "1")]));
bus.publish(feed(&[("a", "2")]));
assert_eq!(n.load(Ordering::SeqCst), 2);
}
#[test]
fn concurrent_publishes_are_delivered_one_at_a_time() {
let bus: Arc<Bus<Feed>> = Bus::new();
let busy = Arc::new(AtomicUsize::new(0));
let overlap = Arc::new(AtomicUsize::new(0));
let (b2, o2) = (Arc::clone(&busy), Arc::clone(&overlap));
bus.on_published("slow", move |_| {
if b2.fetch_add(1, Ordering::SeqCst) > 0 {
o2.fetch_add(1, Ordering::SeqCst);
}
std::thread::sleep(std::time::Duration::from_millis(2));
b2.fetch_sub(1, Ordering::SeqCst);
});
let threads: Vec<_> = (0..4)
.map(|t| {
let bus = Arc::clone(&bus);
std::thread::spawn(move || {
for i in 0..10 {
bus.publish(feed(&[("v", &format!("{t}-{i}"))]));
}
})
})
.collect();
for t in threads {
t.join().unwrap();
}
assert_eq!(overlap.load(Ordering::SeqCst), 0, "two deliveries overlapped");
}
#[test]
fn the_front_offer_is_read_from_any_vm() {
let f = feed(&[("offer.front.monetize_pubkey", " ab "), ("fleet", " ")]);
assert_eq!(offered(&f, "monetize_pubkey").as_deref(), Some("ab"));
assert_eq!(offered(&f, "fleet"), None);
assert_eq!(value(&f, "fleet"), None);
}
#[test]
fn a_value_offered_by_a_vm_is_read_and_a_longer_key_is_another_key() {
let f = feed(&[("offer.front.monetize_pubkey", "A"), ("offer.front.clock_key", "ssh-ed25519 AAAAk"), ("offer.front.xmonetize_pubkey", "B")]);
assert_eq!(offered(&f, "monetize_pubkey").as_deref(), Some("A"));
assert_eq!(offered(&f, "clock_key").as_deref(), Some("ssh-ed25519 AAAAk"));
assert_eq!(offered(&feed(&[("offer.front.xmonetize_pubkey", "B")]), "monetize_pubkey"), None);
}
#[test]
fn an_offer_from_no_vm_name_is_not_taken() {
assert_eq!(offered(&feed(&[("offer..monetize_pubkey", "A"), ("offer.a.b.monetize_pubkey", "A")]), "monetize_pubkey"), None, "not a VM name");
assert_eq!(offered(&feed(&[("offer..monetize_pubkey", "A")]), "monetize_pubkey"), None);
assert_eq!(offered(&feed(&[("offer.a.b.monetize_pubkey", "A")]), "monetize_pubkey"), None);
assert_eq!(offered(&feed(&[("offer.monetize_pubkey", "A")]), "monetize_pubkey"), None, "no VM at all");
assert_eq!(offered(&feed(&[("offer.a.b.monetize_pubkey", "A"), ("offer.front.monetize_pubkey", "C")]), "monetize_pubkey").as_deref(), Some("C"), "a VM's own offer still counts");
}
#[test]
fn the_first_vm_by_name_offers_and_blank_is_absent() {
let f = feed(&[("offer.vm2.ticket_pubkey", "A"), ("offer.vm3.ticket_pubkey", "B")]);
assert_eq!(offered(&f, "ticket_pubkey").as_deref(), Some("A"), "the first by name");
let f = feed(&[("offer.vm1.ticket_pubkey", " "), ("offer.vm3.ticket_pubkey", " B ")]);
assert_eq!(offered(&f, "ticket_pubkey").as_deref(), Some("B"), "blank is absent");
}
}