node: Always check refs status before fetching

We were only checking it on dequeue.

Also simplifies some of the logic by centralizing the check for fetching
our own refs.
This commit is contained in:
cloudhead 2024-04-04 18:10:49 +02:00
parent 36808234a6
commit f2fe0242e1
No known key found for this signature in database
2 changed files with 66 additions and 54 deletions

View File

@ -830,17 +830,32 @@ where
} }
} }
/// Initiate an outgoing fetch for some repository, based on /// Initiate an outgoing fetch for some repository, based on another node's announcement.
/// another node's announcement. /// Returns `true` if the fetch was initiated and `false` if it was skipped.
fn fetch_refs_at( fn fetch_refs_at(
&mut self, &mut self,
rid: RepoId, rid: RepoId,
from: NodeId, from: NodeId,
refs: NonEmpty<RefsAt>, refs: NonEmpty<RefsAt>,
scope: Scope,
timeout: time::Duration, timeout: time::Duration,
channel: Option<chan::Sender<FetchResult>>, channel: Option<chan::Sender<FetchResult>>,
) { ) -> bool {
self._fetch(rid, from, refs.into(), timeout, channel) match self.refs_status_of(rid, refs, &scope) {
Ok(status) => {
if status.want.is_empty() {
debug!(target: "service", "Skipping fetch for {rid}, all refs are already in storage");
} else {
self._fetch(rid, from, status.want, timeout, channel);
return true;
}
}
Err(e) => {
error!(target: "service", "Error getting the refs status of {rid}: {e}");
}
}
// We didn't try to fetch anything.
false
} }
/// Initiate an outgoing fetch for some repository. /// Initiate an outgoing fetch for some repository.
@ -942,6 +957,7 @@ where
return Err(TryFetchError::SessionCapacityReached); return Err(TryFetchError::SessionCapacityReached);
} }
let remotes = refs_at.len();
let fetching = fetching.or_insert(FetchState { let fetching = fetching.or_insert(FetchState {
from, from,
refs_at: refs_at.clone(), refs_at: refs_at.clone(),
@ -952,7 +968,10 @@ where
self.outbox self.outbox
.fetch(session, rid, namespaces, refs_at, timeout); .fetch(session, rid, namespaces, refs_at, timeout);
debug!(target: "service", "Fetch initiated for {rid} with {}..", session.id); debug!(
target: "service",
"Fetch initiated for {rid} with {} ({remotes} remote(s))..", session.id
);
Ok(fetching) Ok(fetching)
} }
@ -1077,19 +1096,12 @@ where
.seed_policy(&rid) .seed_policy(&rid)
.expect("Service::dequeue_fetch: error accessing repo seeding configuration"); .expect("Service::dequeue_fetch: error accessing repo seeding configuration");
match self.refs_status_of(rid, refs_at, &repo_entry.scope) { if let Some(refs) = NonEmpty::from_vec(refs_at) {
Ok(status) => { if self.fetch_refs_at(rid, from, refs, repo_entry.scope, FETCH_TIMEOUT, channel) {
if let Some(refs) = NonEmpty::from_vec(status.fresh) { break;
self.fetch_refs_at(rid, from, refs, FETCH_TIMEOUT, channel);
return;
} else {
trace!(target: "service", "Skipping dequeued fetch for {rid}, all refs are already in local storage");
}
}
Err(e) => {
error!(target: "service", "Error getting the refs status of {rid}: {e}");
return;
} }
} else {
self.fetch(rid, from, FETCH_TIMEOUT, channel);
} }
} }
} }
@ -1475,15 +1487,15 @@ where
}; };
// Finally, if there's anything to fetch, we fetch it from the remote. // Finally, if there's anything to fetch, we fetch it from the remote.
if let Some(refs) = NonEmpty::from_vec( if let Some(refs) = NonEmpty::from_vec(message.refs.to_vec()) {
message self.fetch_refs_at(
.refs message.rid,
.iter() remote.id,
.filter(|r| r.remote != self.node_id()) // Don't fetch our own refs. refs,
.cloned() repo_entry.scope,
.collect(), FETCH_TIMEOUT,
) { None,
self.fetch_refs_at(message.rid, remote.id, refs, FETCH_TIMEOUT, None); );
} else { } else {
debug!(target: "service", "Skipping fetch, no remote refs in announcement for {}", message.rid); debug!(target: "service", "Skipping fetch, no remote refs in announcement for {}", message.rid);
} }
@ -1685,31 +1697,30 @@ where
fn refs_status_of( fn refs_status_of(
&self, &self,
rid: RepoId, rid: RepoId,
refs: Vec<RefsAt>, refs: NonEmpty<RefsAt>,
scope: &policy::Scope, scope: &policy::Scope,
) -> Result<RefsStatus, Error> { ) -> Result<RefsStatus, Error> {
let mut refs = RefsStatus::new(rid, refs, self.db.refs())?; let mut refs = RefsStatus::new(rid, refs, self.db.refs())?;
// First, check the freshness. // Check that there's something we want.
if refs.fresh.is_empty() { if refs.want.is_empty() {
return Ok(refs); return Ok(refs);
} }
// Second, check the scope. // Check scope.
match scope { let mut refs = match scope {
policy::Scope::All => Ok(refs), policy::Scope::All => refs,
policy::Scope::Followed => { policy::Scope::Followed => match self.policies.namespaces_for(&self.storage, &rid) {
match self.policies.namespaces_for(&self.storage, &rid) { Ok(Namespaces::All) => refs,
Ok(Namespaces::All) => Ok(refs), Ok(Namespaces::Followed(followed)) => {
Ok(Namespaces::Followed(mut followed)) => { refs.want.retain(|r| followed.contains(&r.remote));
// Get the set of followed nodes except self. refs
followed.remove(self.nid());
refs.fresh.retain(|r| followed.contains(&r.remote));
Ok(refs)
}
Err(e) => Err(e.into()),
} }
} Err(e) => return Err(e.into()),
} },
};
// Remove our own remote, we don't want to fetch that.
refs.want.retain(|r| r.remote != self.node_id());
Ok(refs)
} }
/// Set of initial messages to send to a peer. /// Set of initial messages to send to a peer.

View File

@ -1,5 +1,6 @@
use std::{fmt, io, mem}; use std::{fmt, io, mem};
use nonempty::NonEmpty;
use radicle::git; use radicle::git;
use radicle::storage::refs::RefsAt; use radicle::storage::refs::RefsAt;
@ -167,14 +168,14 @@ pub struct RefsAnnouncement {
#[derive(Default)] #[derive(Default)]
pub struct RefsStatus { pub struct RefsStatus {
/// The `rad/sigrefs` was missing or it's ahead of the local /// The `rad/sigrefs` was missing or it's ahead of the local
/// `rad/sigrefs`. /// `rad/sigrefs`. We want it.
pub fresh: Vec<RefsAt>, pub want: Vec<RefsAt>,
/// The `rad/sigrefs` has been seen before. /// The `rad/sigrefs` has been seen before. We already have it.
pub stale: Vec<RefsAt>, pub have: Vec<RefsAt>,
} }
impl RefsStatus { impl RefsStatus {
/// Get the set of `fresh` and `stale` `RefsAt`'s for the given /// Get the set of `want` and `have` `RefsAt`'s for the given
/// announcement. /// announcement.
/// ///
/// Nb. We use the refs database as a cache for quick lookups. This does *not* check /// Nb. We use the refs database as a cache for quick lookups. This does *not* check
@ -183,7 +184,7 @@ impl RefsStatus {
/// and old refs announcements will be discarded due to their lower timestamps. /// and old refs announcements will be discarded due to their lower timestamps.
pub fn new<D: node::refs::Store>( pub fn new<D: node::refs::Store>(
rid: RepoId, rid: RepoId,
refs: Vec<RefsAt>, refs: NonEmpty<RefsAt>,
db: &D, db: &D,
) -> Result<RefsStatus, storage::Error> { ) -> Result<RefsStatus, storage::Error> {
let mut status = RefsStatus::default(); let mut status = RefsStatus::default();
@ -202,13 +203,13 @@ impl RefsStatus {
match db.get(repo, &theirs.remote, &storage::refs::SIGREFS_BRANCH) { match db.get(repo, &theirs.remote, &storage::refs::SIGREFS_BRANCH) {
Ok(Some((ours, _))) => { Ok(Some((ours, _))) => {
if theirs.at != ours { if theirs.at != ours {
self.fresh.push(theirs); self.want.push(theirs);
} else { } else {
self.stale.push(theirs); self.have.push(theirs);
} }
} }
Ok(None) => { Ok(None) => {
self.fresh.push(theirs); self.want.push(theirs);
} }
Err(e) => { Err(e) => {
log::warn!( log::warn!(