use crate::client::Client;
use crate::error::{Error, Result};
use crate::wire::{self, WatchEventKind};
use std::collections::{HashMap, VecDeque};
use std::io;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex, MutexGuard};
use std::time::{Duration, Instant, SystemTime};
const LISTENER_QUEUE: usize = 1024;
const WARM_CACHE: usize = 1024;
pub type Logger = Arc<dyn Fn(&str) + Send + Sync>;
pub(crate) fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EventKind {
WarmDone,
WarmFailed,
Change,
Overflow,
Closing,
ConnectionLost,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChangeKind {
Create,
Remove,
Write,
Rename,
Attrib,
}
#[derive(Debug)]
pub struct Event {
pub kind: EventKind,
pub sequence: u64,
pub ref_id: u64,
pub path: String,
pub old_path: String,
pub change: Option<ChangeKind>,
pub error: Option<Error>,
pub time: SystemTime,
}
impl Clone for Event {
fn clone(&self) -> Event {
Event {
kind: self.kind,
sequence: self.sequence,
ref_id: self.ref_id,
path: self.path.clone(),
old_path: self.old_path.clone(),
change: self.change,
error: self.error.as_ref().map(Error::dup),
time: self.time,
}
}
}
impl Event {
pub(crate) fn local(kind: EventKind, ref_id: u64, error: Option<Error>) -> Event {
Event {
kind,
sequence: 0,
ref_id,
path: String::new(),
old_path: String::new(),
change: None,
error,
time: SystemTime::now(),
}
}
pub(crate) fn from_wire(m: wire::Notice) -> Option<Event> {
let mut change = None;
let kind = match wire::NoticeKind::try_from(m.kind).ok()? {
wire::NoticeKind::WarmDone => EventKind::WarmDone,
wire::NoticeKind::WarmFailed => EventKind::WarmFailed,
wire::NoticeKind::Closing => EventKind::Closing,
wire::NoticeKind::Overflow => EventKind::Overflow,
wire::NoticeKind::Change => {
change = match WatchEventKind::try_from(m.change_kind).ok()? {
WatchEventKind::Create => Some(ChangeKind::Create),
WatchEventKind::Remove => Some(ChangeKind::Remove),
WatchEventKind::Write => Some(ChangeKind::Write),
WatchEventKind::Rename => Some(ChangeKind::Rename),
WatchEventKind::Attrib => Some(ChangeKind::Attrib),
WatchEventKind::Overflow => None,
};
if change.is_some() {
EventKind::Change
} else {
EventKind::Overflow
}
}
};
let error = (kind == EventKind::WarmFailed).then_some(Error::Server {
code: m.code,
message: m.message,
});
Some(Event {
kind,
sequence: m.sequence,
ref_id: m.ref_id,
path: m.path,
old_path: m.old_path,
change,
error,
time: SystemTime::now(),
})
}
fn log_line(&self) -> String {
let err = self
.error
.as_ref()
.map_or_else(|| "none".to_string(), |e| e.to_string());
format!(
"event kind={:?} ref_id={} path={:?} error={}",
self.kind, self.ref_id, self.path, err
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ListenerId(u64);
struct ChanState {
items: VecDeque<Event>,
overflowed: bool,
closed: bool,
}
pub(crate) struct Chan {
cap: usize,
ref_id: AtomicU64,
st: Mutex<ChanState>,
cv: Condvar,
}
impl Chan {
pub(crate) fn new(cap: usize, ref_id: u64) -> Chan {
Chan {
cap,
ref_id: AtomicU64::new(ref_id),
st: Mutex::new(ChanState {
items: VecDeque::new(),
overflowed: false,
closed: false,
}),
cv: Condvar::new(),
}
}
pub(crate) fn set_ref_id(&self, id: u64) {
self.ref_id.store(id, Ordering::SeqCst);
}
pub(crate) fn push(&self, ev: Event) -> bool {
let mut st = lock(&self.st);
if st.closed {
return false;
}
let mut started = false;
if st.items.len() >= self.cap {
st.items.pop_front();
started = !st.overflowed;
st.overflowed = true;
}
st.items.push_back(ev);
drop(st);
self.cv.notify_one();
started
}
pub(crate) fn close(&self, discard: bool) {
let mut st = lock(&self.st);
st.closed = true;
if discard {
st.items.clear();
st.overflowed = false;
}
drop(st);
self.cv.notify_all();
}
pub(crate) fn pop(&self, timeout: Option<Duration>) -> Option<Event> {
let deadline = timeout.and_then(|t| Instant::now().checked_add(t));
let mut st = lock(&self.st);
loop {
if st.overflowed {
st.overflowed = false;
return Some(Event::local(
EventKind::Overflow,
self.ref_id.load(Ordering::SeqCst),
None,
));
}
if let Some(ev) = st.items.pop_front() {
return Some(ev);
}
if st.closed {
return None;
}
st = match deadline {
None => self.cv.wait(st).unwrap_or_else(|e| e.into_inner()),
Some(d) => {
let left = d.saturating_duration_since(Instant::now());
if left.is_zero() {
return None;
}
self.cv.wait_timeout(st, left).unwrap_or_else(|e| e.into_inner()).0
}
};
}
}
}
type Listener = Arc<dyn Fn(&Event) + Send + Sync>;
#[derive(Default)]
struct Warms {
done: HashMap<u64, std::result::Result<(), (String, String)>>,
order: VecDeque<u64>,
closed: bool,
}
struct WatchEntry {
conn: u64,
chan: Arc<Chan>,
}
pub(crate) struct Hub {
logger: Logger,
listeners: Mutex<Vec<(ListenerId, Listener)>>,
next_listener: AtomicU64,
queue: Chan,
warms: Mutex<Warms>,
warm_cv: Condvar,
watches: Mutex<HashMap<u64, WatchEntry>>,
threads: Arc<AtomicUsize>,
}
pub(crate) struct ThreadGuard(Arc<AtomicUsize>);
impl Drop for ThreadGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::SeqCst);
}
}
impl Hub {
pub(crate) fn thread_guard(&self) -> ThreadGuard {
self.threads.fetch_add(1, Ordering::SeqCst);
ThreadGuard(self.threads.clone())
}
pub(crate) fn thread_probe(&self) -> Arc<AtomicUsize> {
self.threads.clone()
}
pub(crate) fn new(logger: Option<Logger>) -> Hub {
Hub {
logger: logger.unwrap_or_else(|| Arc::new(|line: &str| eprintln!("mountos: {line}"))),
listeners: Mutex::new(Vec::new()),
next_listener: AtomicU64::new(1),
queue: Chan::new(LISTENER_QUEUE, 0),
warms: Mutex::new(Warms::default()),
warm_cv: Condvar::new(),
watches: Mutex::new(HashMap::new()),
threads: Arc::new(AtomicUsize::new(0)),
}
}
pub(crate) fn log(&self, line: &str) {
(self.logger)(line);
}
pub(crate) fn add_watch(&self, id: u64, conn: u64, chan: Arc<Chan>) {
chan.set_ref_id(id);
lock(&self.watches).insert(id, WatchEntry { conn, chan });
}
pub(crate) fn remove_watch(&self, id: u64) {
lock(&self.watches).remove(&id);
}
pub(crate) fn conn_lost(&self, conn: u64) {
lock(&self.watches).retain(|_, w| {
if w.conn == conn {
w.chan.close(false);
false
} else {
true
}
});
}
pub(crate) fn publish(&self, conn: Option<u64>, ev: Event) {
match ev.kind {
EventKind::WarmDone | EventKind::WarmFailed => self.record_warm(&ev),
EventKind::Overflow if ev.ref_id == 0 => {
if let Some(conn) = conn {
let chans: Vec<Arc<Chan>> = lock(&self.watches)
.values()
.filter(|w| w.conn == conn)
.map(|w| w.chan.clone())
.collect();
for c in chans {
c.push(ev.clone());
}
}
}
EventKind::Change | EventKind::Overflow if ev.ref_id != 0 => {
let chan = lock(&self.watches).get(&ev.ref_id).map(|w| w.chan.clone());
if let Some(c) = chan {
if c.push(ev.clone()) {
self.log(&format!("watch {} queue full, dropped oldest events", ev.ref_id));
}
}
}
_ => {}
}
if self.queue.push(ev) {
self.log("listener queue full, dropped oldest events");
}
}
fn record_warm(&self, ev: &Event) {
let r = match &ev.error {
Some(Error::Server { code, message }) => Err((code.clone(), message.clone())),
_ => Ok(()),
};
let mut w = lock(&self.warms);
if w.done.insert(ev.ref_id, r).is_none() {
w.order.push_back(ev.ref_id);
}
while w.order.len() > WARM_CACHE {
if let Some(old) = w.order.pop_front() {
w.done.remove(&old);
}
}
drop(w);
self.warm_cv.notify_all();
}
pub(crate) fn wait_warm(&self, id: u64, timeout: Duration) -> Result<()> {
let deadline = Instant::now().checked_add(timeout);
let mut w = lock(&self.warms);
loop {
if w.closed {
return Err(Error::Io(io::Error::new(
io::ErrorKind::ConnectionAborted,
"client closed",
)));
}
match w.done.get(&id) {
Some(Ok(())) => return Ok(()),
Some(Err((code, message))) => {
return Err(Error::Server {
code: code.clone(),
message: message.clone(),
})
}
None => {}
}
let left = match deadline {
Some(d) => d.saturating_duration_since(Instant::now()),
None => Duration::from_secs(3600),
};
if left.is_zero() {
return Err(Error::Io(io::Error::new(
io::ErrorKind::TimedOut,
"wait_warm timed out",
)));
}
w = self.warm_cv.wait_timeout(w, left).unwrap_or_else(|e| e.into_inner()).0;
}
}
pub(crate) fn shutdown(&self) {
self.queue.close(true);
for (_, w) in lock(&self.watches).drain() {
w.chan.close(false);
}
lock(&self.warms).closed = true;
self.warm_cv.notify_all();
}
pub(crate) fn clear_listeners(&self) {
let old = std::mem::take(&mut *lock(&self.listeners));
drop(old);
}
pub(crate) fn dispatch_loop(&self) {
while let Some(ev) = self.queue.pop(None) {
let ls: Vec<Listener> = lock(&self.listeners).iter().map(|(_, l)| l.clone()).collect();
if ls.is_empty() {
self.log(&ev.log_line());
continue;
}
for l in ls {
if catch_unwind(AssertUnwindSafe(|| l(&ev))).is_err() {
self.log(&format!("listener panicked on {}", ev.log_line()));
}
}
}
}
}
impl Client {
pub fn add_listener(&self, f: impl Fn(&Event) + Send + Sync + 'static) -> ListenerId {
let hub = &self.inner.hub;
let id = ListenerId(hub.next_listener.fetch_add(1, Ordering::SeqCst));
lock(&hub.listeners).push((id, Arc::new(f)));
id
}
#[doc(hidden)]
pub fn thread_probe(&self) -> Arc<AtomicUsize> {
self.inner.hub.thread_probe()
}
pub fn remove_listener(&self, id: ListenerId) {
lock(&self.inner.hub.listeners).retain(|(i, _)| *i != id);
}
pub fn wait_warm(&self, id: u64, timeout: Duration) -> Result<()> {
self.inner.hub.wait_warm(id, timeout)
}
}