use super::SharedLog;
use super::log::{Mark, Sink};
use super::shard::{Inbox, Outbox};
use crate::proto::{self, req, resp};
use crate::shm::{self, Doorbell, Shared};
use fusee_core::frame;
use fusee_core::wire::{Reader, put_bytes, put_u32, put_u64};
use std::collections::HashSet;
use std::io;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;
pub(crate) fn ring_head(out: &mut Vec<u8>, kind: u8, shard: u32, epoch: u64, flags: u8) {
out.push(kind);
put_u32(out, shard);
put_u64(out, epoch);
out.push(flags);
}
const SPIN_CAP: Duration = Duration::from_micros(100);
pub(crate) struct ShmConn {
pub(crate) conn: u64,
pub(crate) shared: Shared,
head: AtomicU64,
outbox: Arc<Outbox>,
inboxes: Mutex<Vec<Option<Arc<Inbox>>>>,
refused: Mutex<HashSet<u32>>,
closed: AtomicBool,
pub(crate) corrupt: Mutex<Option<&'static str>>,
pub(crate) revoke: tokio::sync::Notify,
}
impl ShmConn {
pub(crate) fn new(conn: u64, shared: Shared, outbox: Arc<Outbox>, shards: u32) -> Self {
Self {
conn,
head: AtomicU64::new(shared.consumed()),
shared,
outbox,
inboxes: Mutex::new(vec![None; shards as usize]),
refused: Mutex::new(HashSet::new()),
closed: AtomicBool::new(false),
corrupt: Mutex::new(None),
revoke: tokio::sync::Notify::new(),
}
}
pub(crate) fn granted(&self, shard: u32, epoch: u64, inbox: Arc<Inbox>) {
if let Some(slot) = self
.inboxes
.lock()
.unwrap_or_else(|e| e.into_inner())
.get_mut(shard as usize)
{
*slot = Some(inbox);
}
self.refused
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&shard);
if let Some(line) = self.shared.line(shard) {
line.failed().store(0, Ordering::Relaxed);
line.epoch().store(epoch, Ordering::Release);
}
}
pub(crate) fn close(&self) {
self.closed.store(true, Ordering::Release);
self.shared.closed().store(1, Ordering::Release);
for shard in 0..self.shared.layout().shards {
if let Some(line) = self.shared.line(shard) {
line.epoch().store(0, Ordering::Release);
line.publish();
}
}
}
fn refuse(&self, shard: u32, message: &str) {
self.refused
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(shard);
if let Some(line) = self.shared.line(shard) {
line.failed().store(1, Ordering::Release);
line.publish();
}
let mut b = Vec::new();
put_u64(&mut b, 0);
put_u32(&mut b, shard);
put_bytes(&mut b, message.as_bytes());
let mut f = Vec::new();
frame::encode(resp::IO_FAILED, &b, &mut f);
self.outbox.send(&f);
}
fn take(&self, log: &SharedLog, payload: &[u8]) -> Result<(), &'static str> {
let mut r = Reader::new(payload);
let (Ok(kind), Ok(shard), Ok(epoch), Ok(_flags)) = (r.u8(), r.u32(), r.u64(), r.u8())
else {
return Err("ring frame too short");
};
if kind != req::APPEND {
return Err("unknown ring frame");
}
if shard >= self.shared.layout().shards {
return Err("ring frame for an unknown shard");
}
if self
.refused
.lock()
.unwrap_or_else(|e| e.into_inner())
.contains(&shard)
{
return Ok(());
}
let stopped = self
.inboxes
.lock()
.unwrap_or_else(|e| e.into_inner())
.get(shard as usize)
.and_then(Option::as_ref)
.is_none_or(|i| i.is_stopped());
if stopped {
self.refuse(
shard,
&format!("shard {shard}: this worker does not hold it or it stopped"),
);
return Ok(());
}
let res = proto::decode_section(&mut r)
.map_err(|e| io::Error::other(e.to_string()))
.and_then(|batch| log.stage(shard, epoch, batch));
if let Err(e) = res {
self.refuse(shard, &format!("shard {shard}: {e}"));
}
Ok(())
}
}
pub(crate) struct ShmSink {
pub(crate) conn: Arc<ShmConn>,
}
impl Sink for ShmSink {
fn durable(&self, marks: &[Mark], position: u64, segment: u64) {
let s = &self.conn.shared;
s.position().fetch_max(position, Ordering::AcqRel);
s.segment().fetch_max(segment, Ordering::AcqRel);
let mut socket = Vec::new();
for m in marks {
let Some(line) = s.line(m.shard) else {
continue;
};
line.pins().store(u32::from(m.pins), Ordering::Relaxed);
line.durable().fetch_max(m.lsn, Ordering::AcqRel);
line.publish();
if line.notify().load(Ordering::Acquire) != 0 {
socket.push(*m);
}
}
if !socket.is_empty() {
self.conn
.outbox
.send(&super::durable_frame(&socket, position, segment));
}
}
fn failed(&self, shards: &[u32], message: &str) {
for &shard in shards {
self.conn.refuse(shard, message);
}
}
}
pub(crate) struct Hub {
pub(crate) bell: Doorbell,
conns: Mutex<Vec<Arc<ShmConn>>>,
generation: AtomicU64,
log: Arc<SharedLog>,
stop: AtomicBool,
}
impl Hub {
pub(crate) fn start(log: Arc<SharedLog>) -> io::Result<Arc<Self>> {
let hub = Arc::new(Self {
bell: Doorbell::create()?,
conns: Mutex::new(Vec::new()),
generation: AtomicU64::new(0),
log,
stop: AtomicBool::new(false),
});
let weak = Arc::downgrade(&hub);
std::thread::Builder::new()
.name("fusee-server-shm".into())
.spawn(move || poll(weak))?;
Ok(hub)
}
pub(crate) fn add(&self, conn: Arc<ShmConn>) {
self.conns
.lock()
.unwrap_or_else(|e| e.into_inner())
.push(conn);
self.generation.fetch_add(1, Ordering::AcqRel);
self.bell.ring_always();
}
pub(crate) fn remove(&self, conn: u64) {
self.conns
.lock()
.unwrap_or_else(|e| e.into_inner())
.retain(|c| c.conn != conn);
self.generation.fetch_add(1, Ordering::AcqRel);
}
}
impl Drop for Hub {
fn drop(&mut self) {
self.stop.store(true, Ordering::Release);
}
}
fn poll(hub: Weak<Hub>) {
let mut conns: Vec<Arc<ShmConn>> = Vec::new();
let mut seen = u64::MAX;
let mut buf = Vec::new();
loop {
let Some(h) = hub.upgrade() else { return };
if h.stop.load(Ordering::Acquire) {
return;
}
let generation = h.generation.load(Ordering::Acquire);
if generation != seen {
conns = h.conns.lock().unwrap_or_else(|e| e.into_inner()).clone();
seen = generation;
}
let mut staged = false;
for c in &conns {
if c.closed.load(Ordering::Acquire) {
continue;
}
let mut head = c.head.load(Ordering::Relaxed);
loop {
match c.shared.pop(&mut head, &mut buf) {
Ok(true) => match c.take(&h.log, &buf) {
Ok(()) => staged = true,
Err(why) => {
corrupt(c, why);
break;
}
},
Ok(false) => break,
Err(shm::Corrupt(why)) => {
corrupt(c, why);
break;
}
}
}
c.head.store(head, Ordering::Relaxed);
c.shared.release(head);
}
if staged {
let _ = h.log.write_staged(true);
continue;
}
let pending = |conns: &[Arc<ShmConn>]| {
conns.iter().any(|c| {
!c.closed.load(Ordering::Acquire)
&& c.shared.pending(c.head.load(Ordering::Relaxed))
})
};
let expect = h.log.last_flush_us().max(h.log.turnaround_us()).max(1);
let budget = crate::spin::budget(Duration::from_micros(expect), SPIN_CAP);
if crate::spin::spin_until(budget, &|| pending(&conns)) {
continue;
}
let bell = &h.bell;
let gen_now = &h.generation;
bell.sleep(
&|| pending(&conns) || gen_now.load(Ordering::Acquire) != seen,
Duration::from_millis(100),
);
}
}
fn corrupt(c: &ShmConn, why: &'static str) {
c.closed.store(true, Ordering::Release);
*c.corrupt.lock().unwrap_or_else(|e| e.into_inner()) = Some(why);
c.revoke.notify_one();
}
#[cfg(test)]
mod tests;