aboutsummaryrefslogtreecommitdiffstats
path: root/src/swarm/changes.rs
blob: 6831081ff6b48e6efb0b1773e6466b1805115f8d (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
//! 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 }));
    }
}