summaryrefslogtreecommitdiffstats
path: root/src/swarm/members.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-24 03:51:10 +0200
committertv <tv@krebsco.de>2026-10-24 03:51:10 +0200
commit63abdf6208418d1f4c0ddc98a0e40ed4331c0481 (patch)
tree91bdbbc2e3e6c9c97bf7612eb018b5638ab469cf /src/swarm/members.rs
parenta1460b0a2861d5bb34ba25d409b234468f269e4e (diff)
add member registry
Diffstat (limited to 'src/swarm/members.rs')
-rw-r--r--src/swarm/members.rs67
1 files changed, 67 insertions, 0 deletions
diff --git a/src/swarm/members.rs b/src/swarm/members.rs
new file mode 100644
index 0000000..45c8aeb
--- /dev/null
+++ b/src/swarm/members.rs
@@ -0,0 +1,67 @@
+//! The member registry: every node that has joined the swarm, under
+//! `_continuum/members/<pk>`, replicated with the document (and carried into new
+//! epochs with the rest of `_continuum`).
+//!
+//! A pure registry: it decides nothing about admission (that's the swarm key). Each
+//! node registers itself once it is ready, and re-registers if its entry disappears
+//! while it is alive. An operator's forget (`DELETE /peers/{pk}`) removes the entry
+//! together with writing the forget record; a forgotten node that turns out to be alive
+//! cancels the record and registers again.
+
+use automerge::AutoCommit;
+use serde_json::json;
+use tracing::{info, warn};
+
+use crate::document::store::{self, StoreError};
+use crate::state::{AppState, unix_millis};
+
+const META_ROOT: &str = "_continuum";
+
+fn members_dir() -> Vec<String> {
+ vec![META_ROOT.to_owned(), "members".to_owned()]
+}
+
+pub(crate) fn member_path(pk: &str) -> Vec<String> {
+ let mut path = members_dir();
+ path.push(pk.to_owned());
+ path
+}
+
+/// Public keys of all registered members, sorted.
+pub(crate) fn members(doc: &AutoCommit) -> Vec<String> {
+ let mut members = store::keys_at(doc, &members_dir()).unwrap_or_default();
+ members.sort();
+ members
+}
+
+/// Registers this node if it is ready and its entry is missing.
+pub(crate) async fn ensure_registered(state: &AppState) {
+ if !state.is_ready().await {
+ return;
+ }
+ let own = member_path(state.local_id());
+ let registered = store::read_path(&state.doc.read().await.doc, &own)
+ .ok()
+ .flatten()
+ .is_some();
+ if registered {
+ return;
+ }
+
+ let entry = json!({ "since": unix_millis() });
+ match state
+ .mutate_doc(move |doc| store::write_path(doc, &own, &entry))
+ .await
+ {
+ Ok(()) => info!("Registered in the swarm's member list"),
+ Err(error) => warn!(error = %error, "Failed to register in the member list"),
+ }
+}
+
+/// Removes `pk`'s entry (an operator forgot it). Absent is fine.
+pub(crate) fn unregister(doc: &mut AutoCommit, pk: &str) -> Result<(), StoreError> {
+ match store::delete_path(doc, &member_path(pk), false) {
+ Err(StoreError::NotFound) => Ok(()),
+ result => result,
+ }
+}