aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm/changes.rs
blob: 8565cb6dd14857139e8dc9a17ca0516646f85963 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! 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, and to the peer worker when the
//! forget records did.

use std::sync::Arc;

use tracing::{info, warn};

use crate::document::changes;
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();
    // The replica's heads (sorted, as of its last refresh: every change refreshes them).
    let mut last_heads = state.doc.read().await.heads().to_vec();

    info!("Starting change publisher");

    while changed.changed().await.is_ok() {
        let before = last_heads.clone();

        let doc = Arc::clone(&state.doc);
        let (heads, paths, conflicts) = tokio::task::spawn_blocking(move || {
            let replica = doc.blocking_read();
            let heads = replica.heads().to_vec();
            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 !changes::missing(&replica.doc, &before).is_empty() {
                return (heads, vec![Vec::new()], Vec::new());
            }

            let mut paths = Vec::new();
            let mut conflicts = Vec::new();
            for changed in store::changed(&replica.doc, &before, &heads, InLists::Index) {
                if changed.conflict {
                    conflicts.push(changed.path.clone());
                }
                paths.push(changed.path);
            }
            (heads, paths, conflicts)
        })
        .await
        .expect("change publisher task panicked");

        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();
        }
        if paths
            .iter()
            .any(|path| crate::swarm::peer::affects_forget_records(path))
        {
            state
                .forget_records_changed
                .store(true, std::sync::atomic::Ordering::Release);
            state.peers_due.now();
        }

        // Fails only when nobody is subscribed.
        let _ = state.change_events.send(Arc::new(ChangeSet { paths }));
    }
}