//! The change publisher: after document changes, which store paths they touched //! (`AppState::change_events`, for the API's streams, the dashboard and remakes), and //! a nudge to topology when the member list changed. use std::sync::Arc; use tracing::{info, warn}; use crate::document::store::{self, ChangeSet, InLists}; use crate::state::AppState; pub(crate) async fn run_change_publisher(state: AppState) { let mut changed = state.change_tx.subscribe(); let mut last_heads = state.write_doc(|doc| doc.get_heads()).await; info!("Starting change publisher"); while changed.changed().await.is_ok() { let before = last_heads.clone(); let (heads, paths, conflicts) = state .write_doc(move |doc| { let heads = doc.get_heads(); if heads == before { return (heads, Vec::new(), Vec::new()); } // After an epoch switch the previous heads belong to the old document: // report the root as changed, which every stream is related to. if !before.iter().all(|h| doc.get_change_by_hash(h).is_some()) { return (heads, vec![Vec::new()], Vec::new()); } let mut paths = Vec::new(); let mut conflicts = Vec::new(); for changed in store::changed(doc, &before, &heads, InLists::Index) { if changed.conflict { conflicts.push(changed.path.clone()); } paths.push(changed.path); } (heads, paths, conflicts) }) .await; if heads == last_heads { continue; } last_heads = heads; for path in &conflicts { warn!(path = ?path, "Concurrent writes produced a conflict"); } if paths .iter() .any(|path| crate::swarm::topology::affects_neighbours(path)) { state.topology_changed.notify_one(); } // Fails only when nobody is subscribed. let _ = state.change_events.send(Arc::new(ChangeSet { paths })); } }