node: Re-think inventory update code

Instead of re-loading the full inventory on `rad init`, we simply add
the new repository to the in-memory cache.

We also keep track of whether a fetch was a full clone, to know when
to update our routing table.
This commit is contained in:
cloudhead 2024-03-20 17:50:27 +01:00
parent 0e880e12e6
commit 2bcb03b021
No known key found for this signature in database
15 changed files with 116 additions and 56 deletions

View File

@ -367,7 +367,7 @@ fn sync(
let events = node.subscribe(time::Duration::from_secs(3))?; let events = node.subscribe(time::Duration::from_secs(3))?;
let sessions = node.sessions()?; let sessions = node.sessions()?;
node.sync_inventory()?; node.update_inventory(rid)?;
spinner.message("Announcing.."); spinner.message("Announcing..");
if !sessions.iter().any(|s| s.is_connected()) { if !sessions.iter().any(|s| s.is_connected()) {

View File

@ -531,7 +531,6 @@ pub fn announce_inventory(mut node: Node) -> anyhow::Result<()> {
let peers = node.sessions()?.iter().filter(|s| s.is_connected()).count(); let peers = node.sessions()?.iter().filter(|s| s.is_connected()).count();
let spinner = term::spinner(format!("Announcing inventory to {peers} peers..")); let spinner = term::spinner(format!("Announcing inventory to {peers} peers.."));
node.sync_inventory()?;
node.announce_inventory()?; node.announce_inventory()?;
spinner.finish(); spinner.finish();

View File

@ -1,4 +1,4 @@
use std::ops; use std::{collections::BTreeSet, ops};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
@ -189,6 +189,22 @@ impl<T, const N: usize> TryFrom<Vec<T>> for BoundedVec<T, N> {
} }
} }
impl<T, const N: usize> TryFrom<BTreeSet<T>> for BoundedVec<T, N> {
type Error = Error;
fn try_from(value: BTreeSet<T>) -> Result<Self, Self::Error> {
if value.len() > N {
return Err(Error::InvalidSize {
expected: N,
actual: value.len(),
});
}
Ok(BoundedVec {
v: value.into_iter().collect(),
})
}
}
impl<T, const N: usize> From<BoundedVec<T, N>> for Vec<T> { impl<T, const N: usize> From<BoundedVec<T, N>> for Vec<T> {
fn from(value: BoundedVec<T, N>) -> Self { fn from(value: BoundedVec<T, N>) -> Self {
value.v value.v

View File

@ -168,7 +168,7 @@ where
} }
CommandResult::ok().to_writer(writer).ok(); CommandResult::ok().to_writer(writer).ok();
} }
Command::SyncInventory => match handle.sync_inventory() { Command::UpdateInventory { rid } => match handle.update_inventory(rid) {
Ok(result) => { Ok(result) => {
CommandResult::updated(result).to_writer(writer)?; CommandResult::updated(result).to_writer(writer)?;
} }

View File

@ -243,9 +243,9 @@ impl radicle::node::Handle for Handle {
.map_err(Error::from) .map_err(Error::from)
} }
fn sync_inventory(&mut self) -> Result<bool, Error> { fn update_inventory(&mut self, rid: RepoId) -> Result<bool, Error> {
let (sender, receiver) = chan::bounded(1); let (sender, receiver) = chan::bounded(1);
self.command(service::Command::SyncInventory(sender))?; self.command(service::Command::UpdateInventory(rid, sender))?;
receiver.recv().map_err(Error::from) receiver.recv().map_err(Error::from)
} }

View File

@ -10,7 +10,7 @@ pub mod message;
pub mod session; pub mod session;
use std::collections::hash_map::Entry; use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{BTreeSet, HashMap, VecDeque};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::sync::Arc; use std::sync::Arc;
use std::{fmt, net, time}; use std::{fmt, net, time};
@ -31,7 +31,7 @@ use radicle::node::seed;
use radicle::node::seed::Store as _; use radicle::node::seed::Store as _;
use radicle::node::{ConnectOptions, Penalty, Severity}; use radicle::node::{ConnectOptions, Penalty, Severity};
use radicle::storage::refs::SignedRefsUpdate; use radicle::storage::refs::SignedRefsUpdate;
use radicle::storage::RepositoryError; use radicle::storage::{Inventory, RepositoryError};
use crate::crypto; use crate::crypto;
use crate::crypto::{Signer, Verified}; use crate::crypto::{Signer, Verified};
@ -172,8 +172,8 @@ pub enum Command {
AnnounceRefs(RepoId, chan::Sender<RefsAt>), AnnounceRefs(RepoId, chan::Sender<RefsAt>),
/// Announce local repositories to peers. /// Announce local repositories to peers.
AnnounceInventory, AnnounceInventory,
/// Announce local inventory to peers. /// Update local inventory.
SyncInventory(chan::Sender<bool>), UpdateInventory(RepoId, chan::Sender<bool>),
/// Connect to node with the given address. /// Connect to node with the given address.
Connect(NodeId, Address, ConnectOptions), Connect(NodeId, Address, ConnectOptions),
/// Disconnect from node. /// Disconnect from node.
@ -203,7 +203,7 @@ impl fmt::Debug for Command {
match self { match self {
Self::AnnounceRefs(id, _) => write!(f, "AnnounceRefs({id})"), Self::AnnounceRefs(id, _) => write!(f, "AnnounceRefs({id})"),
Self::AnnounceInventory => write!(f, "AnnounceInventory"), Self::AnnounceInventory => write!(f, "AnnounceInventory"),
Self::SyncInventory(_) => write!(f, "SyncInventory(..)"), Self::UpdateInventory(rid, _) => write!(f, "UpdateInventory({rid})"),
Self::Connect(id, addr, opts) => write!(f, "Connect({id}, {addr}, {opts:?})"), Self::Connect(id, addr, opts) => write!(f, "Connect({id}, {addr}, {opts:?})"),
Self::Disconnect(id) => write!(f, "Disconnect({id})"), Self::Disconnect(id) => write!(f, "Disconnect({id})"),
Self::Config(_) => write!(f, "Config"), Self::Config(_) => write!(f, "Config"),
@ -788,7 +788,8 @@ where
error!(target: "service", "Error announcing inventory: {err}"); error!(target: "service", "Error announcing inventory: {err}");
} }
} }
Command::SyncInventory(resp) => { Command::UpdateInventory(rid, resp) => {
self.storage.insert(rid);
let synced = self let synced = self
.sync_inventory() .sync_inventory()
.expect("Service::command: error syncing inventory"); .expect("Service::command: error syncing inventory");
@ -938,6 +939,7 @@ where
Ok(fetch::FetchResult { Ok(fetch::FetchResult {
updated, updated,
namespaces, namespaces,
clone,
}) => { }) => {
debug!(target: "service", "Fetched {rid} from {remote} successfully"); debug!(target: "service", "Fetched {rid} from {remote} successfully");
@ -953,6 +955,7 @@ where
FetchResult::Success { FetchResult::Success {
updated, updated,
namespaces, namespaces,
clone,
} }
} }
Err(err) => { Err(err) => {
@ -999,6 +1002,7 @@ where
FetchResult::Success { FetchResult::Success {
updated, updated,
namespaces, namespaces,
..
} if !updated.is_empty() => { } if !updated.is_empty() => {
if let Err(e) = self.announce_refs(rid, namespaces.iter().cloned()) { if let Err(e) = self.announce_refs(rid, namespaces.iter().cloned()) {
error!(target: "service", "Failed to announce new refs: {e}"); error!(target: "service", "Failed to announce new refs: {e}");
@ -1007,14 +1011,12 @@ where
_ => debug!(target: "service", "Nothing to announce, no refs were updated.."), _ => debug!(target: "service", "Nothing to announce, no refs were updated.."),
} }
} }
// TODO: Since this fetch could be either a full clone
// or simply a ref update, we need to either announce // Announce our new inventory if this fetch was a clone.
// new inventory, or new refs. Right now, we announce if let FetchResult::Success { clone: true, .. } = result {
// both in some cases. self.storage.insert(rid);
// self.sync_and_announce();
// Announce the newly fetched repository to the }
// network, if necessary.
self.sync_and_announce();
// We can now try to dequeue another fetch. // We can now try to dequeue another fetch.
self.dequeue_fetch(); self.dequeue_fetch();
@ -1311,7 +1313,11 @@ where
inventory: message.inventory.to_vec(), inventory: message.inventory.to_vec(),
timestamp: message.timestamp, timestamp: message.timestamp,
}); });
match self.sync_routing(&message.inventory, *announcer, message.timestamp) { match self.sync_routing(
message.inventory.iter().cloned(),
*announcer,
message.timestamp,
) {
Ok(synced) => { Ok(synced) => {
if synced.is_empty() { if synced.is_empty() {
trace!(target: "service", "No routes updated by inventory announcement from {announcer}"); trace!(target: "service", "No routes updated by inventory announcement from {announcer}");
@ -1723,7 +1729,7 @@ where
// Other than crashing the node completely, there's nothing we can do // Other than crashing the node completely, there's nothing we can do
// here besides returning an empty inventory and logging an error. // here besides returning an empty inventory and logging an error.
error!(target: "service", "Error getting local inventory for initial messages: {e}"); error!(target: "service", "Error getting local inventory for initial messages: {e}");
vec![] Default::default()
} }
}; };
@ -1765,10 +1771,8 @@ where
/// Update our routing table with our local node's inventory. /// Update our routing table with our local node's inventory.
fn sync_inventory(&mut self) -> Result<SyncedRouting, Error> { fn sync_inventory(&mut self) -> Result<SyncedRouting, Error> {
self.storage.refresh()?; // Refresh storage inventory cache.
let inventory = self.storage.inventory()?; let inventory = self.storage.inventory()?;
let result = self.sync_routing(&inventory, self.node_id(), self.time())?; let result = self.sync_routing(inventory, self.node_id(), self.time())?;
Ok(result) Ok(result)
} }
@ -1778,14 +1782,18 @@ where
/// given inventory. /// given inventory.
fn sync_routing( fn sync_routing(
&mut self, &mut self,
inventory: &[RepoId], inventory: impl IntoIterator<Item = RepoId>,
from: NodeId, from: NodeId,
timestamp: Timestamp, timestamp: Timestamp,
) -> Result<SyncedRouting, Error> { ) -> Result<SyncedRouting, Error> {
let mut synced = SyncedRouting::default(); let mut synced = SyncedRouting::default();
let included: HashSet<&RepoId> = HashSet::from_iter(inventory); let included = inventory.into_iter().collect::<BTreeSet<_>>();
for (rid, result) in self.db.routing_mut().insert(inventory, from, timestamp)? { for (rid, result) in self
.db
.routing_mut()
.insert(included.iter(), from, timestamp)?
{
match result { match result {
InsertResult::SeedAdded => { InsertResult::SeedAdded => {
info!(target: "service", "Routing table updated for {rid} with seed {from}"); info!(target: "service", "Routing table updated for {rid} with seed {from}");
@ -2014,7 +2022,7 @@ where
//////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////
/// Announce our inventory to all connected peers. /// Announce our inventory to all connected peers.
fn announce_inventory(&mut self, inventory: Vec<RepoId>) -> Result<(), storage::Error> { fn announce_inventory(&mut self, inventory: Inventory) -> Result<(), storage::Error> {
let time = if self.clock > self.last_announce { let time = if self.clock > self.last_announce {
self.clock.as_millis() self.clock.as_millis()
} else if self.last_announce - self.clock < LocalDuration::from_secs(1) { } else if self.last_announce - self.clock < LocalDuration::from_secs(1) {

View File

@ -23,10 +23,12 @@ pub fn node(config: &Config, timestamp: Timestamp) -> NodeAnnouncement {
} }
} }
pub fn inventory(timestamp: Timestamp, inventory: Vec<RepoId>) -> InventoryAnnouncement { pub fn inventory(
type Inventory = BoundedVec<RepoId, INVENTORY_LIMIT>; timestamp: Timestamp,
inventory: impl IntoIterator<Item = RepoId>,
if inventory.len() > Inventory::max() { ) -> InventoryAnnouncement {
let inventory = inventory.into_iter().collect::<Vec<_>>();
if inventory.len() > INVENTORY_LIMIT {
error!( error!(
target: "service", target: "service",
"inventory announcement limit ({}) exceeded, other nodes will see only some of your projects", "inventory announcement limit ({}) exceeded, other nodes will see only some of your projects",

View File

@ -61,6 +61,7 @@ impl radicle::node::Handle for Handle {
Ok(FetchResult::Success { Ok(FetchResult::Success {
updated: vec![], updated: vec![],
namespaces: HashSet::new(), namespaces: HashSet::new(),
clone: false,
}) })
} }
@ -100,7 +101,7 @@ impl radicle::node::Handle for Handle {
Ok(()) Ok(())
} }
fn sync_inventory(&mut self) -> Result<bool, Self::Error> { fn update_inventory(&mut self, _rid: RepoId) -> Result<bool, Self::Error> {
unimplemented!() unimplemented!()
} }

View File

@ -655,6 +655,7 @@ impl<S: WriteStorage + 'static, G: Signer> Simulation<S, G> {
Namespaces::Followed(hs) => hs, Namespaces::Followed(hs) => hs,
Namespaces::All => HashSet::new(), Namespaces::All => HashSet::new(),
}, },
clone: true,
})), })),
), ),
}, },

View File

@ -674,7 +674,12 @@ fn test_refs_announcement_relay() {
}, },
) )
}; };
let bob_inv = bob.storage().inventory().unwrap(); let bob_inv = bob
.storage()
.inventory()
.unwrap()
.into_iter()
.collect::<Vec<_>>();
alice.seed(&bob_inv[0], policy::Scope::All).unwrap(); alice.seed(&bob_inv[0], policy::Scope::All).unwrap();
alice.seed(&bob_inv[1], policy::Scope::All).unwrap(); alice.seed(&bob_inv[1], policy::Scope::All).unwrap();
@ -740,13 +745,13 @@ fn test_refs_announcement_fetch_trusted_no_inventory() {
) )
}; };
let bob_inv = bob.storage().inventory().unwrap(); let bob_inv = bob.storage().inventory().unwrap();
let rid = bob_inv[0]; let rid = bob_inv.first().unwrap();
alice.seed(&rid, policy::Scope::Followed).unwrap(); alice.seed(&rid, policy::Scope::Followed).unwrap();
alice.connect_to(&bob); alice.connect_to(&bob);
// Alice receives Bob's refs. // Alice receives Bob's refs.
alice.receive(bob.id(), bob.refs_announcement(rid)); alice.receive(bob.id(), bob.refs_announcement(*rid));
// Alice fetches Bob's refs as this is a new repo. // Alice fetches Bob's refs as this is a new repo.
assert_matches!(alice.outbox().next(), Some(Io::Fetch { .. })); assert_matches!(alice.outbox().next(), Some(Io::Fetch { .. }));
@ -1426,6 +1431,7 @@ fn test_queued_fetch_from_ann_same_rid() {
oid, oid,
}], }],
namespaces: [carol.id()].into_iter().collect(), namespaces: [carol.id()].into_iter().collect(),
clone: false,
}), }),
); );
// Now the 1st fetch is done, but the 2nd and 3rd fetches are redundant. // Now the 1st fetch is done, but the 2nd and 3rd fetches are redundant.
@ -1616,7 +1622,7 @@ fn test_push_and_pull() {
// We now expect Eve to fetch Alice's project from Alice. // We now expect Eve to fetch Alice's project from Alice.
// Then we expect Bob to fetch Alice's project from Eve. // Then we expect Bob to fetch Alice's project from Eve.
alice.elapse(LocalDuration::from_secs(1)); // Make sure our announcement is fresh. alice.elapse(LocalDuration::from_secs(1)); // Make sure our announcement is fresh.
alice.command(service::Command::SyncInventory(send)); alice.command(service::Command::UpdateInventory(proj_id, send));
sim.run_while([&mut alice, &mut bob, &mut eve], |s| !s.is_settled()); sim.run_while([&mut alice, &mut bob, &mut eve], |s| !s.is_settled());

View File

@ -22,6 +22,8 @@ pub struct FetchResult {
pub updated: Vec<RefUpdate>, pub updated: Vec<RefUpdate>,
/// The set of remote namespaces that were updated. /// The set of remote namespaces that were updated.
pub namespaces: HashSet<PublicKey>, pub namespaces: HashSet<PublicKey>,
/// The fetch was a full clone.
pub clone: bool,
} }
pub enum Handle { pub enum Handle {
@ -69,12 +71,12 @@ impl Handle {
remote: PublicKey, remote: PublicKey,
refs_at: Option<Vec<SignedRefsUpdate>>, refs_at: Option<Vec<SignedRefsUpdate>>,
) -> Result<FetchResult, error::Fetch> { ) -> Result<FetchResult, error::Fetch> {
let (result, notifs) = match self { let (result, clone, notifs) = match self {
Self::Clone { mut handle, tmp } => { Self::Clone { mut handle, tmp } => {
log::debug!(target: "worker", "{} cloning from {remote}", handle.local()); log::debug!(target: "worker", "{} cloning from {remote}", handle.local());
let result = radicle_fetch::clone(&mut handle, limit, remote)?; let result = radicle_fetch::clone(&mut handle, limit, remote)?;
mv(tmp, storage, &rid)?; mv(tmp, storage, &rid)?;
(result, None) (result, true, None)
} }
Self::Pull { Self::Pull {
mut handle, mut handle,
@ -82,7 +84,7 @@ impl Handle {
} => { } => {
log::debug!(target: "worker", "{} pulling from {remote}", handle.local()); log::debug!(target: "worker", "{} pulling from {remote}", handle.local());
let result = radicle_fetch::pull(&mut handle, limit, remote, refs_at)?; let result = radicle_fetch::pull(&mut handle, limit, remote, refs_at)?;
(result, Some(notifications)) (result, false, Some(notifications))
} }
}; };
@ -125,6 +127,7 @@ impl Handle {
Ok(FetchResult { Ok(FetchResult {
updated: applied.updated, updated: applied.updated,
namespaces: remotes.into_iter().collect(), namespaces: remotes.into_iter().collect(),
clone,
}) })
} }
} }

View File

@ -429,8 +429,8 @@ pub enum Command {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
AnnounceInventory, AnnounceInventory,
/// Sync local inventory with node. /// Update node's inventory.
SyncInventory, UpdateInventory { rid: RepoId },
/// Get the current node condiguration. /// Get the current node condiguration.
Config, Config,
@ -654,6 +654,7 @@ pub enum FetchResult {
Success { Success {
updated: Vec<RefUpdate>, updated: Vec<RefUpdate>,
namespaces: HashSet<NodeId>, namespaces: HashSet<NodeId>,
clone: bool,
}, },
// TODO: Create enum for reason. // TODO: Create enum for reason.
Failed { Failed {
@ -671,6 +672,7 @@ impl FetchResult {
Self::Success { Self::Success {
updated, updated,
namespaces, namespaces,
..
} => Some((updated, namespaces)), } => Some((updated, namespaces)),
_ => None, _ => None,
} }
@ -685,12 +687,13 @@ impl FetchResult {
} }
} }
impl<S: ToString> From<Result<(Vec<RefUpdate>, HashSet<NodeId>), S>> for FetchResult { impl<S: ToString> From<Result<(Vec<RefUpdate>, HashSet<NodeId>, bool), S>> for FetchResult {
fn from(value: Result<(Vec<RefUpdate>, HashSet<NodeId>), S>) -> Self { fn from(value: Result<(Vec<RefUpdate>, HashSet<NodeId>, bool), S>) -> Self {
match value { match value {
Ok((updated, namespaces)) => Self::Success { Ok((updated, namespaces, clone)) => Self::Success {
updated, updated,
namespaces, namespaces,
clone,
}, },
Err(err) => Self::Failed { Err(err) => Self::Failed {
reason: err.to_string(), reason: err.to_string(),
@ -725,6 +728,7 @@ impl FetchResults {
if let FetchResult::Success { if let FetchResult::Success {
updated, updated,
namespaces, namespaces,
..
} = r } = r
{ {
Some((nid, updated.as_slice(), namespaces.clone())) Some((nid, updated.as_slice(), namespaces.clone()))
@ -849,8 +853,8 @@ pub trait Handle: Clone + Sync + Send {
fn announce_refs(&mut self, id: RepoId) -> Result<RefsAt, Self::Error>; fn announce_refs(&mut self, id: RepoId) -> Result<RefsAt, Self::Error>;
/// Announce local inventory. /// Announce local inventory.
fn announce_inventory(&mut self) -> Result<(), Self::Error>; fn announce_inventory(&mut self) -> Result<(), Self::Error>;
/// Notify the service that our inventory was updated. /// Notify the service that our inventory was updated with the given repository.
fn sync_inventory(&mut self) -> Result<bool, Self::Error>; fn update_inventory(&mut self, rid: RepoId) -> Result<bool, Self::Error>;
/// Ask the service to shutdown. /// Ask the service to shutdown.
fn shutdown(self) -> Result<(), Self::Error>; fn shutdown(self) -> Result<(), Self::Error>;
/// Query the peer session state. /// Query the peer session state.
@ -1107,8 +1111,8 @@ impl Handle for Node {
Ok(()) Ok(())
} }
fn sync_inventory(&mut self) -> Result<bool, Error> { fn update_inventory(&mut self, rid: RepoId) -> Result<bool, Error> {
let mut line = self.call::<Success>(Command::SyncInventory, DEFAULT_TIMEOUT)?; let mut line = self.call::<Success>(Command::UpdateInventory { rid }, DEFAULT_TIMEOUT)?;
let response = line.next().ok_or(Error::EmptyResponse {})??; let response = line.next().ok_or(Error::EmptyResponse {})??;
Ok(response.updated) Ok(response.updated)

View File

@ -1,7 +1,7 @@
pub mod git; pub mod git;
pub mod refs; pub mod refs;
use std::collections::{hash_map, HashSet}; use std::collections::{hash_map, BTreeSet, HashSet};
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::{fmt, io}; use std::{fmt, io};
@ -28,7 +28,7 @@ use self::git::UserInfo;
use self::refs::SignedRefs; use self::refs::SignedRefs;
pub type BranchName = git::RefString; pub type BranchName = git::RefString;
pub type Inventory = Vec<RepoId>; pub type Inventory = BTreeSet<RepoId>;
/// Describes one or more namespaces. /// Describes one or more namespaces.
#[derive(Default, Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
@ -374,6 +374,8 @@ pub trait ReadStorage {
/// Get the inventory of repositories hosted under this storage. /// Get the inventory of repositories hosted under this storage.
/// This function should typically only return public repositories. /// This function should typically only return public repositories.
fn inventory(&self) -> Result<Inventory, Error>; fn inventory(&self) -> Result<Inventory, Error>;
/// Insert this repository into the inventory.
fn insert(&self, rid: RepoId);
/// Refresh storage inventory. /// Refresh storage inventory.
fn refresh(&self) -> Result<(), Error>; fn refresh(&self) -> Result<(), Error>;
/// Open or create a read-only repository. /// Open or create a read-only repository.
@ -626,6 +628,10 @@ where
self.deref().path_of(rid) self.deref().path_of(rid)
} }
fn insert(&self, rid: RepoId) {
self.deref().insert(rid)
}
fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError> { fn contains(&self, rid: &RepoId) -> Result<bool, RepositoryError> {
self.deref().contains(rid) self.deref().contains(rid)
} }

View File

@ -89,7 +89,7 @@ impl<'a> TryFrom<git2::Reference<'a>> for Ref {
pub struct Storage { pub struct Storage {
path: PathBuf, path: PathBuf,
info: UserInfo, info: UserInfo,
inventory: Arc<Mutex<Vec<RepoId>>>, inventory: Arc<Mutex<BTreeSet<RepoId>>>,
} }
impl ReadStorage for Storage { impl ReadStorage for Storage {
@ -125,6 +125,18 @@ impl ReadStorage for Storage {
} }
} }
fn insert(&self, rid: RepoId) {
match self.inventory.lock() {
Ok(mut locked) => {
locked.insert(rid);
}
Err(poisoned) => {
let mut inv = poisoned.into_inner();
inv.insert(rid);
}
}
}
fn refresh(&self) -> Result<(), Error> { fn refresh(&self) -> Result<(), Error> {
let repos = self.repositories()?; let repos = self.repositories()?;
let rids = repos let rids = repos
@ -189,7 +201,7 @@ impl Storage {
let storage = Self { let storage = Self {
path, path,
info, info,
inventory: Arc::new(Mutex::new(vec![])), inventory: Arc::new(Mutex::new(BTreeSet::new())),
}; };
storage.refresh()?; storage.refresh()?;

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::{BTreeSet, HashMap};
use std::convert::Infallible; use std::convert::Infallible;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -76,9 +76,11 @@ impl ReadStorage for MockStorage {
} }
fn inventory(&self) -> Result<Inventory, Error> { fn inventory(&self) -> Result<Inventory, Error> {
Ok(self.repos.keys().cloned().collect::<Vec<_>>()) Ok(self.repos.keys().cloned().collect::<BTreeSet<_>>())
} }
fn insert(&self, _rid: RepoId) {}
fn refresh(&self) -> Result<(), Error> { fn refresh(&self) -> Result<(), Error> {
Ok(()) Ok(())
} }