aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm/document_file.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/swarm/document_file.rs')
-rw-r--r--src/swarm/document_file.rs110
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;
+ }
+}