diff options
Diffstat (limited to 'src/swarm/document_file.rs')
| -rw-r--r-- | src/swarm/document_file.rs | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/src/swarm/document_file.rs b/src/swarm/document_file.rs new file mode 100644 index 0000000..e9deca0 --- /dev/null +++ b/src/swarm/document_file.rs @@ -0,0 +1,110 @@ +//! The worker that keeps the document file (`document::file`) up to date: it looks at +//! the node (ready, the document's epoch and heads, this node's actors), and the file +//! says what is due. While a ready node rejoins, the file follows the document before +//! the rejoin, which takes the node's writes meanwhile (`state::StaleView`): a restart +//! then resumes on it, and rejoins again. + +use std::sync::Arc; + +use crate::document::Document; +use crate::document::file::Due; +use crate::document::replica::Epoch; +use crate::locks::RwLockExt; +use crate::state::AppState; + +/// Keeps the document file up to date. Runs for as long as the node does. +pub(crate) async fn run(state: AppState) { + let Some(file) = state.document_file.clone() else { + return; + }; + let mut tick = tokio::time::interval(file.append_every()); + tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); + loop { + tick.tick().await; + flush(&state).await; + } +} + +/// Writes what is due now (`DocumentFile::due`). Also called at shutdown. +pub(crate) async fn flush(state: &AppState) { + let Some(file) = state.document_file.clone() else { + return; + }; + if state.remake.is_rejoining() { + if let Some((epoch, stale)) = state.stale_document() { + flush_before_rejoin(&file, epoch, stale).await; + } + return; + } + if !state.is_ready().await { + return; + } + let own = state.remake.own_actors(); + let (epoch, heads) = { + let replica = state.doc.read().await; + (replica.epoch, replica.heads().to_vec()) + }; + match file.due(epoch, own.len(), &heads) { + Due::Rewrite(reason) => { + let (epoch, copy) = { + let replica = state.doc.read().await; + (replica.epoch, replica.doc.clone()) + }; + file.rewrite(epoch, copy, own, reason).await; + } + Due::Append { epoch, since } => { + let (heads, increment) = { + let replica = state.doc.read().await; + // Switched since: the next flush rewrites the file instead. + if replica.epoch != epoch { + return; + } + let heads = replica.heads().to_vec(); + (heads, crate::document::save_since(&replica.doc, &since)) + }; + file.append(since, heads, increment).await; + } + Due::Nothing => {} + } +} + +/// [`flush`] of the document before a rejoin (`epoch`'s), while it takes this node's +/// writes. Only appends: a rewrite (another epoch, or the increments outgrew the full +/// save) waits for the end of the rejoin, so the file keeps the actors it had. +async fn flush_before_rejoin( + file: &Arc<crate::document::file::DocumentFile>, + epoch: Epoch, + stale: Arc<std::sync::RwLock<Document>>, +) { + let heads = { + let stale = Arc::clone(&stale); + // Sorted, as the live document's (`Replica::heads`) the file compares with. + tokio::task::spawn_blocking(move || { + let mut heads = stale.read_or_recover().get_heads(); + heads.sort_unstable(); + heads + }) + .await + .expect("document read task panicked") + }; + let Some(actors) = file.actors() else { + return; + }; + if let Due::Append { + epoch: written, + since, + } = file.due(epoch, actors, &heads) + && written == epoch + { + let since_then = since.clone(); + let (heads, increment) = tokio::task::spawn_blocking(move || { + let doc = stale.read_or_recover(); + let mut heads = doc.get_heads(); + heads.sort_unstable(); + (heads, crate::document::save_since(&doc, &since_then)) + }) + .await + .expect("document read task panicked"); + file.append(since, heads, increment).await; + } +} |
