use gnitz_wire::{WireFault, WireStatus};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use std::time::Duration;
use crate::client::{offload, BoxFut, DeltaPoll, GnitzClient, Host};
use crate::connection::{DeltaCursor, Polled, RawBlock, RelDescriptor, Target};
use crate::error::ClientError;
use crate::{RelName, Schema, ZSetBatch};
use gnitz_wire::txn_frame::DeltaPollItem;
use gnitz_wire::RelClass;
pub trait MirrorStore: Send {
fn base_dir(&self) -> &str;
fn register(&mut self, name: &RelName, desc: &RelDescriptor) -> Result<(), MirrorError>;
fn forget(&mut self, tid: u64) -> Result<(), MirrorError>;
fn refill(&mut self, tid: u64) -> Result<(), MirrorError>;
fn fill(&mut self, tid: u64, blocks: &[&[u8]]) -> Result<(), MirrorError>;
fn seal(&mut self, tid: u64, cursor: DeltaCursor) -> Result<(), MirrorError>;
fn advance(&mut self, tid: u64, blocks: &[&[u8]], next: DeltaCursor) -> Result<(), MirrorError>;
fn scan_spec(
&mut self,
tid: u64,
spec: gnitz_wire::ReadSpec,
reply_schema: &Schema,
) -> Result<ZSetBatch, MirrorError>;
fn cursor_of(&self, tid: u64) -> Option<DeltaCursor>;
fn checkpoint(&mut self) -> Result<(), MirrorError>;
fn poisoned(&self) -> Option<&str>;
}
#[derive(Debug, Clone)]
pub enum MirrorError {
Engine(String),
Poisoned(String),
}
impl std::fmt::Display for MirrorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MirrorError::Engine(m) => write!(f, "engine: {m}"),
MirrorError::Poisoned(m) => write!(f, "mirror store is poisoned: {m}"),
}
}
}
impl std::error::Error for MirrorError {}
impl From<MirrorError> for ClientError {
fn from(e: MirrorError) -> Self {
ClientError::Mirror(e)
}
}
#[derive(Debug)]
pub struct PollOutcome {
pub view_id: u64,
pub cursor: Option<DeltaCursor>,
pub result: PollResult,
}
#[derive(Debug)]
pub enum PollResult {
Advanced,
Reseeded,
Failed(ClientError),
}
impl PollResult {
pub fn reseeded(&self) -> bool {
matches!(self, PollResult::Reseeded)
}
}
#[derive(Clone)]
pub(crate) struct MirroredView {
pub(crate) name: RelName,
pub(crate) desc: Arc<RelDescriptor>,
upstream: Target,
spec: Vec<u8>,
plan: Option<Planner>,
confirmed: bool,
failed: bool,
owed_reseed: bool,
pub(crate) sub: Option<u64>,
}
#[derive(Clone, Debug)]
pub struct Subscription {
pub upstream: Arc<RelDescriptor>,
pub spec: Vec<u8>,
pub schema: Arc<Schema>,
pub indexes: Vec<gnitz_wire::RelIndex>,
}
pub type Planner =
Arc<dyn for<'a> Fn(&'a mut GnitzClient) -> BoxFut<'a, Result<Subscription, ClientError>> + Send + Sync>;
impl Subscription {
fn whole(upstream: Arc<RelDescriptor>) -> Self {
Subscription {
spec: gnitz_wire::ReadSpec::all_rows(gnitz_wire::ReadBound::None).encode(),
schema: Arc::clone(&upstream.schema),
indexes: upstream.indexes.clone(),
upstream,
}
}
}
fn alias_id(name: &RelName) -> u64 {
1 << 63 | gnitz_wire::checksum(name.name().as_bytes()) >> 1
}
impl MirroredView {
fn new(name: RelName, sub: Subscription, plan: Option<Planner>) -> Result<Self, ClientError> {
let Subscription { upstream, spec, schema, indexes } = sub;
let desc = match &plan {
None => {
refuse_unfed(&format!("'{name}'"), upstream.class)?;
Arc::clone(&upstream)
}
Some(_) => {
refuse_unfed(&format!("what '{name}' reads"), upstream.class)?;
Arc::new(RelDescriptor {
tid: alias_id(&name),
class: RelClass::FedView,
pk_repeats: upstream.pk_repeats,
serial: false,
schema,
indexes,
token: 0,
})
}
};
Ok(MirroredView {
name,
desc,
upstream: Target::from(&*upstream),
spec,
plan,
confirmed: false,
failed: false,
owed_reseed: false,
sub: None,
})
}
pub(crate) fn renamed(&self, name: RelName) -> Self {
MirroredView { name, ..self.clone() }
}
fn reads_what(&self, other: &MirroredView) -> bool {
self.upstream.tid == other.upstream.tid && self.spec == other.spec
}
fn subscribed(&mut self, id: u64) {
self.sub = Some(id);
self.confirmed = true;
self.failed = false;
}
fn due_a_read(&self) -> bool {
self.sub.is_none() && !self.failed
}
fn answers_at(&self, store: &dyn MirrorStore) -> Option<DeltaCursor> {
self.confirmed.then(|| store.cursor_of(self.desc.tid)).flatten()
}
fn poll_item(&self, from: Option<DeltaCursor>) -> DeltaPollItem<'_> {
DeltaPollItem {
view: self.upstream,
from,
reply_layout: self.desc.schema.layout().layout_digest(),
spec: &self.spec,
}
}
}
#[derive(Clone)]
pub(crate) struct Store(Arc<Mutex<dyn MirrorStore>>);
impl Store {
pub(crate) fn get(&self) -> MutexGuard<'_, dyn MirrorStore + 'static> {
self.0.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) async fn run<R: Send + 'static>(
self,
host: &mut dyn Host,
work: impl FnOnce(&mut dyn MirrorStore) -> R + Send + 'static,
) -> Result<R, ClientError> {
offload(host, move || work(&mut *self.get())).await
}
async fn advance(
self,
host: &mut dyn Host,
trains: Vec<(u64, Vec<RawBlock>, DeltaCursor)>,
) -> Result<Vec<(u64, Result<(), ClientError>)>, ClientError> {
let idle = trains.iter().all(|(_, blocks, _)| blocks.is_empty());
let apply = move |s: &mut dyn MirrorStore| {
let advance = |(tid, blocks, next): (u64, Vec<RawBlock>, DeltaCursor)| {
let blocks: Vec<&[u8]> = blocks.iter().map(RawBlock::block).collect();
(tid, s.advance(tid, &blocks, next).map_err(ClientError::from))
};
trains.into_iter().map(advance).collect::<Vec<_>>()
};
Ok(match idle {
true => apply(&mut *self.get()),
false => self.run(host, apply).await?,
})
}
}
pub(crate) struct MirrorState {
pub(crate) store: Store,
pub(crate) views: HashMap<u64, MirroredView>,
}
impl MirrorState {
fn view(&self, tid: u64) -> Result<&MirroredView, ClientError> {
self.views
.get(&tid)
.ok_or_else(|| ClientError::from(format!("relation {tid} is not mirrored")))
}
pub(crate) fn cursor_of(&self, tid: u64) -> Option<DeltaCursor> {
self.views.get(&tid)?.answers_at(&*self.store.get())
}
pub(crate) fn answering(&self, name: &RelName) -> Option<&MirroredView> {
let store = self.store.get();
self.views
.values()
.find(|v| v.name == *name && v.answers_at(&*store).is_some())
}
pub(crate) fn connection_replaced(&mut self) {
for v in self.views.values_mut() {
v.confirmed = false;
v.failed = false;
v.sub = None;
}
}
fn report(&mut self, done: Vec<(u64, Result<(), ClientError>)>) -> Vec<PollOutcome> {
let MirrorState { store, views } = self;
let store = store.get();
done.into_iter()
.filter_map(|(view_id, synced)| {
let view = views.get_mut(&view_id)?;
Some(PollOutcome {
view_id,
cursor: view.answers_at(&*store),
result: match synced {
Err(e) => PollResult::Failed(e),
Ok(()) if std::mem::take(&mut view.owed_reseed) => PollResult::Reseeded,
Ok(()) => PollResult::Advanced,
},
})
})
.collect()
}
}
const SETTLE_ATTEMPTS: usize = 4;
fn refuse_unfed(what: &str, class: RelClass) -> Result<(), ClientError> {
Err(ClientError::from(match class {
RelClass::FedView => return Ok(()),
RelClass::Table | RelClass::Stream | RelClass::Catalog => {
format!("{what} is a {}; only a view can be mirrored", class.noun())
}
RelClass::BoundedView => format!(
"{what} is capacity-bounded, and a capacity and a feed \
are refused together, so it carries no feed to subscribe to"
),
RelClass::View => format!(
"{what} keeps no delta feed; \
create it WITH (delta = '<size>') to mirror it"
),
}))
}
fn no_mirror_store() -> ClientError {
ClientError::from("this client mirrors nothing; attach a store before mirroring a view".to_string())
}
impl GnitzClient {
fn mirror_state(&mut self) -> Result<&mut MirrorState, ClientError> {
self.mirror.as_deref_mut().ok_or_else(no_mirror_store)
}
fn mirrored_view(&mut self, tid: u64) -> Result<&MirroredView, ClientError> {
self.mirror_state()?.view(tid)
}
async fn derive(&mut self, name: RelName, plan: Option<Planner>) -> Result<MirroredView, ClientError> {
let sub = match &plan {
None => Subscription::whole(self.resolve_relation(&name).await?),
Some(plan) => plan(self).await?,
};
MirroredView::new(name, sub, plan)
}
pub(crate) async fn bind(&mut self, mut entry: MirroredView) -> Result<u64, ClientError> {
let tid = entry.desc.tid;
let store = self.mirror_state()?.store.clone();
let (rel, registered) = (entry.name.clone(), Arc::clone(&entry.desc));
store
.run(&mut *self.host, move |s| s.register(&rel, ®istered))
.await??;
let views = &mut self.mirror_state()?.views;
let held = views.get(&tid);
entry.confirmed = held.is_some_and(|held| held.confirmed && held.reads_what(&entry));
entry.owed_reseed |= held.is_some_and(|held| held.owed_reseed);
views.retain(|_, v| v.name != entry.name);
views.insert(tid, entry);
Ok(tid)
}
async fn rebind(&mut self, tid: u64) -> Result<u64, ClientError> {
let v = self.mirrored_view(tid)?;
let (name, plan) = (v.name.clone(), v.plan.clone());
let entry = self.derive(name, plan).await?;
self.bind(entry).await
}
async fn mirror_as(&mut self, name: RelName, plan: Option<Planner>) -> Result<PollOutcome, ClientError> {
self.refuse_poisoned_mirror()?;
let entry = self.derive(name, plan).await?;
let tid = self.bind(entry).await?;
let done = self.settle([tid]).await?;
let out = self.mirror_state()?.report(done).pop();
let out = out.expect("the view settled is registered");
match out.result {
PollResult::Failed(e) => Err(e),
_ => Ok(out),
}
}
async fn settle(
&mut self,
tids: impl IntoIterator<Item = u64>,
) -> Result<Vec<(u64, Result<(), ClientError>)>, ClientError> {
let mut pending: Vec<u64> = tids.into_iter().collect();
let mut done = Vec::with_capacity(pending.len());
let mut expired = Vec::new();
for attempt in 1..=SETTLE_ATTEMPTS {
if attempt > 1 {
let m = self.mirror_state()?;
pending.retain(|tid| m.views.contains_key(tid));
}
if pending.is_empty() {
break;
}
let synced = self.read_copies(&pending, &expired).await?;
pending.clear();
for (tid, result) in synced {
if attempt > 1 {
done.retain(|(synced, _)| *synced != tid);
}
let recovered = match result {
Ok(()) => {
done.push((tid, Ok(())));
continue;
}
Err(e) if attempt == SETTLE_ATTEMPTS => Err(e),
Err(e) => self.recover(tid, e, &mut expired).await,
};
match recovered {
Ok(now) if pending.contains(&now) => {}
Ok(now) => pending.push(now),
Err(e @ ClientError::Interrupted(_)) => return Err(e),
Err(e) => {
if let Some(v) = self.mirror_state()?.views.get_mut(&tid) {
v.failed = true;
}
done.push((tid, Err(e)))
}
}
}
}
Ok(done)
}
async fn recover(&mut self, tid: u64, failure: ClientError, expired: &mut Vec<u64>) -> Result<u64, ClientError> {
let ClientError::Refused(WireFault { status, .. }) = &failure else {
return Err(failure);
};
match status {
WireStatus::StaleCatalog => self.rebind(tid).await,
WireStatus::DeltaExpired => {
expired.push(tid);
Ok(tid)
}
_ => Err(failure),
}
}
async fn read_copies(
&mut self,
tids: &[u64],
whole: &[u64],
) -> Result<Vec<(u64, Result<(), ClientError>)>, ClientError> {
let GnitzClient { session, host, mirror, next_sub, .. } = self;
let MirrorState { store, views } = mirror.as_deref_mut().ok_or_else(no_mirror_store)?;
let host = &mut **host;
let froms: Vec<Option<DeltaCursor>> = {
let store = store.get();
let from = |tid: &u64| store.cursor_of(*tid).filter(|_| !whole.contains(tid));
tids.iter().map(from).collect()
};
let mut poll = {
let item = |(tid, from): (&u64, &Option<DeltaCursor>)| {
let view = views.get(tid);
let view = view.ok_or_else(|| ClientError::from(format!("relation {tid} is not mirrored")))?;
debug_assert!(view.sub.is_none(), "a subscribed copy is moved by its trains alone");
Ok(view.poll_item(*from))
};
let items: Result<Vec<DeltaPollItem>, ClientError> = tids.iter().zip(&froms).map(item).collect();
let items = items?;
let first = std::mem::replace(next_sub, *next_sub + items.len() as u64);
DeltaPoll::start(session, first, &items)
};
let mut done = Vec::with_capacity(tids.len());
let mut blocks = Vec::new();
let (mut opened, mut failed) = (false, None);
let mut ended: Vec<usize> = Vec::new();
let mut due: Vec<(u64, Vec<RawBlock>, DeltaCursor)> = Vec::new();
while let Some((i, polled)) = poll.next(host).await? {
let tid = tids[i];
let end = match polled {
Polled::Block(_) if failed.is_some() => None,
Polled::Block(b) => {
blocks.push(b);
None
}
Polled::End(end) => Some(end),
};
match (froms[i], end) {
(None, end) => {
let seal = end.as_ref().and_then(|end| end.as_ref().ok().copied());
if failed.is_none() && (seal.is_some() || poll.drained() && !blocks.is_empty()) {
let open = (!std::mem::replace(&mut opened, true))
.then(|| views.get(&tid).map(|v| (v.name.clone(), Arc::clone(&v.desc))))
.flatten();
if let (Some(_), Some(view)) = (&open, views.get_mut(&tid)) {
view.owed_reseed = true;
}
let taken = std::mem::take(&mut blocks);
let fill = move |s: &mut dyn MirrorStore| {
if let Some((name, desc)) = open {
s.register(&name, &desc)?;
s.refill(tid)?;
}
if !taken.is_empty() {
let taken: Vec<&[u8]> = taken.iter().map(RawBlock::block).collect();
s.fill(tid, &taken)?;
}
seal.map_or(Ok(()), |cursor| s.seal(tid, cursor))
};
failed = store.clone().run(host, fill).await?.err().map(ClientError::from);
}
if let Some(end) = end {
blocks.clear();
opened = false;
let read = match (failed.take(), end) {
(Some(e), _) | (None, Err(e)) => Err(e),
(None, Ok(_)) => Ok(()),
};
if let (Ok(()), Some(view)) = (&read, views.get_mut(&tid)) {
view.subscribed(poll.kept(i));
}
done.push((tid, read));
}
}
(Some(_), None) => {}
(Some(_), Some(Ok(next))) => {
ended.push(i);
due.push((tid, std::mem::take(&mut blocks), next));
}
(Some(_), Some(Err(e))) => {
blocks.clear();
done.push((tid, Err(e)));
}
}
if poll.drained() && !due.is_empty() {
let advanced = store.clone().advance(host, std::mem::take(&mut due)).await?;
for (i, (tid, advanced)) in ended.drain(..).zip(advanced) {
if let (Ok(()), Some(view)) = (&advanced, views.get_mut(&tid)) {
view.subscribed(poll.kept(i));
}
done.push((tid, advanced));
}
}
}
Ok(done)
}
async fn advance_pushed(
&mut self,
synced: &Result<(), ClientError>,
) -> Result<Vec<(u64, Result<(), ClientError>)>, ClientError> {
let GnitzClient { session, host, mirror, .. } = self;
let MirrorState { views, store, .. } = mirror.as_deref_mut().ok_or_else(no_mirror_store)?;
let (mut done, mut due) = (Vec::new(), Vec::new());
{
let store = store.get();
for (&tid, view) in views.iter_mut() {
let Some(id) = view.sub else { continue };
let pushed = match (store.cursor_of(tid), synced) {
(Some(at), Ok(())) => session.take_pushed(id).ok().map(|taken| (at, taken)),
_ => None,
};
let Some((at, (blocks, next))) = pushed else {
view.sub = None;
continue;
};
done.push((tid, Ok(())));
if next != at || !blocks.is_empty() {
due.push((tid, blocks, next));
}
}
}
let taken: Vec<u64> = due.iter().map(|(tid, ..)| *tid).collect();
let advanced = match store.clone().advance(&mut **host, due).await {
Ok(advanced) => advanced,
Err(lost) => {
for tid in &taken {
if let Some(view) = views.get_mut(tid) {
view.sub = None;
}
}
return Err(lost);
}
};
for (tid, advanced) in advanced {
let Err(e) = advanced else { continue };
if let Some(view) = views.get_mut(&tid) {
view.sub = None;
}
if let Some(failed) = done.iter_mut().find(|(done, _)| *done == tid) {
failed.1 = Err(e);
}
}
Ok(done)
}
}
impl GnitzClient {
pub fn attach_mirror(&mut self, store: impl MirrorStore + 'static) -> Result<(), ClientError> {
if let Some(m) = &self.mirror {
return Err(ClientError::from(format!(
"this client already mirrors at '{}'; close_mirror before attaching another store",
m.store.get().base_dir()
)));
}
self.mirror = Some(Box::new(MirrorState {
store: Store(Arc::new(Mutex::new(store))),
views: HashMap::new(),
}));
Ok(())
}
pub async fn mirror_view(&mut self, name: &RelName) -> Result<PollOutcome, ClientError> {
self.mirror_as(name.clone(), None).await
}
pub async fn mirror_subscription(&mut self, alias: &str, plan: Planner) -> Result<PollOutcome, ClientError> {
self.mirror_as(RelName::new(gnitz_wire::LOCAL_SCHEMA, alias)?, Some(plan))
.await
}
pub async fn forget_view(&mut self, table_id: u64) -> Result<(), ClientError> {
let m = self.mirror_state()?;
m.views.remove(&table_id);
let store = m.store.clone();
store.run(&mut *self.host, move |s| s.forget(table_id)).await??;
Ok(())
}
pub(crate) fn mirror_hold(&mut self, wait: Duration) -> Result<Duration, ClientError> {
if self.mirror.is_none() {
return Ok(wait);
}
self.refuse_poisoned_mirror()?;
let mut views = self.mirror_state()?.views.values();
Ok(if views.any(MirroredView::due_a_read) {
Duration::ZERO
} else {
wait
})
}
pub(crate) async fn advance_mirror(
&mut self,
synced: &Result<(), ClientError>,
) -> Result<Vec<PollOutcome>, ClientError> {
if self.mirror.is_none() {
return Ok(Vec::new());
}
let mut done = self.advance_pushed(synced).await?;
let views = &self.mirror_state()?.views;
let unsubscribed = views.iter().filter(|(_, v)| v.sub.is_none()).map(|(tid, _)| *tid);
let polled: Vec<u64> = unsubscribed
.filter(|tid| done.iter().all(|(done, _)| done != tid))
.collect();
done.extend(self.settle(polled).await?);
Ok(self.mirror_state()?.report(done))
}
pub fn mirrored_ids(&self) -> Vec<u64> {
self.mirror
.as_deref()
.map_or_else(Vec::new, |m| m.views.keys().copied().collect())
}
pub fn mirrors(&self, table_id: u64) -> bool {
self.cursor_of(table_id).is_some()
}
pub fn cursor_of(&self, table_id: u64) -> Option<DeltaCursor> {
self.mirror.as_deref()?.cursor_of(table_id)
}
pub async fn checkpoint_mirror(&mut self) -> Result<(), ClientError> {
let store = self.mirror_state()?.store.clone();
Ok(store.run(&mut *self.host, |s| s.checkpoint()).await??)
}
pub async fn close_mirror(&mut self) -> Result<(), ClientError> {
let Some(m) = self.mirror.take() else {
return Ok(());
};
match m.store.run(&mut *self.host, |s| s.checkpoint()).await? {
Err(MirrorError::Poisoned(_)) => Ok(()),
other => other.map_err(ClientError::from),
}
}
pub fn mirror_poisoned(&self) -> Option<String> {
self.mirror
.as_deref()
.and_then(|m| m.store.get().poisoned().map(str::to_owned))
}
fn refuse_poisoned_mirror(&mut self) -> Result<(), ClientError> {
match self.mirror_state()?.store.get().poisoned() {
Some(why) => Err(MirrorError::Poisoned(why.to_string()).into()),
None => Ok(()),
}
}
}
#[cfg(test)]
#[path = "tests/mirror.rs"]
mod tests;