Skip to main content

PeerBoard

Struct PeerBoard 

Source
pub struct PeerBoard { /* private fields */ }
Expand description

The peers of one server process.

Implementations§

Source§

impl PeerBoard

Source

pub fn new() -> Self

An empty board.

Source

pub fn attach(&self, transport: Transport) -> PeerId

A session attached; it has no name until PeerBoard::identify.

use majordomus_cli::peers::{PeerBoard, Transport};
let board = PeerBoard::new();
let a = board.attach(Transport::Stdio);
let b = board.attach(Transport::Http);
assert_eq!((a.as_str(), b.as_str()), ("p1", "p2"));
board.detach(&a);
assert_eq!(board.list().len(), 1);
Source

pub fn identify(&self, id: &PeerId, client: ClientInfo)

The client behind a peer, from its initialize.

Source

pub fn touch(&self, id: &PeerId)

A message arrived from this peer.

Source

pub fn announce( &self, id: &PeerId, intent: &str, scope: Vec<String>, ) -> Option<Announced>

Record a peer’s unnamed claim, and answer with who else is on that ground.

This is the whole of what a session announcing about itself needs, and it is what announcing has always meant: the peer holds one claim and each announcement replaces it. A connection doing several things at once wants PeerBoard::announce_claim.

None when the id is not attached. The overlap is computed here, under the same lock as the write, so that two peers announcing at once cannot both be told they are alone.

use majordomus_cli::peers::{PeerBoard, Transport};
let board = PeerBoard::new();
let a = board.attach(Transport::Stdio);
let b = board.attach(Transport::Http);
board.announce(&a, "the ordering rule", vec!["apps/majordomus-cli/src".into()]);
let second = board
    .announce(&b, "the release model", vec!["apps/majordomus-cli/src/release".into()])
    .unwrap();
assert_eq!(second.overlaps.len(), 1, "a claim inside another claim is an overlap");
assert_eq!(second.overlaps[0].intent, "the ordering rule");

// announcing again, narrower: the peer is told what it stopped claiming
let again = board.announce(&b, "the release model", vec![]).unwrap();
assert_eq!(again.released, vec!["apps/majordomus-cli/src/release".to_string()]);
Source

pub fn announce_claim( &self, id: &PeerId, claim: Option<&str>, intent: &str, scope: Vec<String>, ) -> Option<Announced>

The same, naming which of the peer’s claims this is.

None is PeerBoard::announce: the peer’s one unnamed claim. Every other value is a claim of its own, held beside the rest until it is announced again under the same name.

use majordomus_cli::peers::{PeerBoard, Transport};
let board = PeerBoard::new();
let fleet = board.attach(Transport::Http);
board.announce(&fleet, "the whole mandate", vec!["docs".into()]);
let named = board
    .announce_claim(&fleet, Some("worker-a"), "the guard", vec![".claude".into()])
    .unwrap();
assert_eq!(named.peer.claims.len(), 2, "the unnamed claim still stands");
assert_eq!(named.peer.claims[1].name.as_deref(), Some("worker-a"));
Source

pub fn overlaps(&self) -> Vec<Overlap>

Every pair of claims that meet, each pair once, in peer order.

A peer holding several claims is asked about each of them, so two connections that collide on two different claims are reported twice, with the intent of each. That is the answer a reader wants: it is not “these two sessions overlap” but “these two pieces of work overlap”, and the intent says which.

Source

pub fn get(&self, id: &PeerId) -> Option<Peer>

One peer, as listed.

Source

pub fn detach(&self, id: &PeerId)

The session ended.

A peer that announced something is kept and marked departed rather than removed: what a session said it was working on is a fact about the repository, and it does not stop being true because a socket closed. A peer that never announced leaves nothing behind. The oldest departed slot past RETAINED is dropped.

use majordomus_cli::peers::{PeerBoard, Transport};
let board = PeerBoard::new();
let quiet = board.attach(Transport::Http);
let spoken = board.attach(Transport::Http);
board.announce(&spoken, "the ordering rule", vec!["apps".into()]);
board.detach(&quiet);
board.detach(&spoken);
assert_eq!(board.len(), 0, "neither is attached any more");
let listed = board.list();
assert_eq!(listed.len(), 1, "the one that said something is still on the board");
assert!(!listed[0].attached);
assert_eq!(listed[0].announcement.as_ref().unwrap().intent, "the ordering rule");
Source

pub fn list(&self) -> Vec<Peer>

Every peer, in attachment order.

Source

pub fn len(&self) -> usize

How many peers are attached. A departed peer whose announcement the board kept is listed but not counted: the count is what the lifecycle asks, and the lifecycle asks who is here now.

Source

pub fn is_empty(&self) -> bool

Nobody attached?

Source

pub fn summary(&self) -> String

The names of every peer, one line, for a log or the initialize instructions.

Trait Implementations§

Source§

impl Debug for PeerBoard

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for PeerBoard

Source§

fn default() -> PeerBoard

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more