Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
node: Some performance improvements for storage
Merged did:key:z6MksFqX...wzpT opened 1 year ago
  • Try to improve performance of repository open
  • node: Use inventory cache for checking missing
5 files changed +80 -14 8e541dcf 25c6660a
modified radicle-node/src/bounded.rs
@@ -159,7 +159,7 @@ impl<T, const N: usize> BoundedVec<T, N> {
        self.v
    }

-
    /// Calls [`Vec::Drain`].
+
    /// Calls [`std::vec::Drain`].
    pub fn drain<R: RangeBounds<usize>>(&mut self, range: R) -> std::vec::Drain<T> {
        self.v.drain(range)
    }
modified radicle-node/src/service.rs
@@ -1431,6 +1431,9 @@ where
                    }
                }

+
                let inventory = self.storage.inventory_ref();
+
                let mut missing = Vec::new();
+

                for id in message.inventory.as_slice() {
                    // TODO: Move this out (good luck with the borrow checker).
                    if let Some(sess) = self.sessions.get_mut(announcer) {
@@ -1448,22 +1451,18 @@ where
                        ) {
                            // Only if we do not have the repository locally do we fetch here.
                            // If we do have it, only fetch after receiving a ref announcement.
-
                            match self.storage.contains(id) {
-
                                Ok(true) => {
-
                                    // Do nothing.
-
                                }
-
                                Ok(false) => {
-
                                    debug!(target: "service", "Missing seeded inventory {id}; initiating fetch..");
-
                                    self.fetch(*id, *announcer, FETCH_TIMEOUT, None);
-
                                }
-
                                Err(e) => {
-
                                    error!(target: "service", "Error checking local inventory for {id}: {e}");
-
                                }
+
                            if !inventory.contains(id) {
+
                                missing.push(*id);
                            }
                        }
                    }
                }
+
                drop(inventory);

+
                for rid in missing {
+
                    debug!(target: "service", "Missing seeded inventory {rid}; initiating fetch..");
+
                    self.fetch(rid, *announcer, FETCH_TIMEOUT, None);
+
                }
                return Ok(relay);
            }
            AnnouncementMessage::Refs(message) => {
modified radicle/src/storage.rs
@@ -402,6 +402,9 @@ impl<V> Deref for Remote<V> {
/// Read-only operations on a storage instance.
pub trait ReadStorage {
    type Repository: ReadRepository;
+
    type InventoryRef<'a>: Deref<Target = Inventory>
+
    where
+
        Self: 'a;

    /// Get user info for this storage.
    fn info(&self) -> &UserInfo;
@@ -414,6 +417,9 @@ pub trait ReadStorage {
    /// Get the inventory of repositories hosted under this storage.
    /// This function should typically only return public repositories.
    fn inventory(&self) -> Result<Inventory, Error>;
+
    /// Return a reference to the inventory. Note that this function may hold a
+
    /// lock to the inventory.
+
    fn inventory_ref<'a, 's: 'a>(&'s self) -> Self::InventoryRef<'a>;
    /// Return all repositories (public and private).
    fn repositories(&self) -> Result<Vec<RepositoryInfo<Verified>>, Error>;
    /// Insert this repository into the inventory.
@@ -658,6 +664,7 @@ where
    S: ReadStorage + 'static,
{
    type Repository = S::Repository;
+
    type InventoryRef<'a> = S::InventoryRef<'a> where T: 'a;

    fn info(&self) -> &UserInfo {
        self.deref().info()
@@ -683,6 +690,13 @@ where
        self.deref().inventory()
    }

+
    fn inventory_ref<'a, 's: 'a>(&'s self) -> Self::InventoryRef<'a>
+
    where
+
        T: 'a,
+
    {
+
        self.deref().inventory_ref()
+
    }
+

    fn get(&self, rid: RepoId) -> Result<Option<Doc<Verified>>, RepositoryError> {
        self.deref().get(rid)
    }
modified radicle/src/storage/git.rs
@@ -5,7 +5,7 @@ pub mod transport;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
-
use std::sync::{Arc, Mutex};
+
use std::sync::{Arc, Mutex, MutexGuard};
use std::{fs, io};

use crypto::{Signer, Verified};
@@ -72,6 +72,23 @@ impl<'a> TryFrom<git2::Reference<'a>> for Ref {
    }
}

+
/// A reference to a locked inventory.
+
pub struct InventoryLock<'a> {
+
    guard: MutexGuard<'a, Option<BTreeSet<RepoId>>>,
+
}
+

+
impl<'a> Deref for InventoryLock<'a> {
+
    type Target = BTreeSet<RepoId>;
+

+
    #[track_caller]
+
    fn deref(&self) -> &Self::Target {
+
        match *self.guard {
+
            Some(ref cache) => cache,
+
            None => panic!("InventoryLock::deref: inventory cache was not initialized"),
+
        }
+
    }
+
}
+

#[derive(Debug, Clone)]
pub struct Storage {
    path: PathBuf,
@@ -82,6 +99,7 @@ pub struct Storage {

impl ReadStorage for Storage {
    type Repository = Repository;
+
    type InventoryRef<'a> = InventoryLock<'a>;

    fn info(&self) -> &UserInfo {
        &self.info
@@ -103,6 +121,15 @@ impl ReadStorage for Storage {
        Ok(false)
    }

+
    fn inventory_ref<'a, 's: 'a>(&'s self) -> InventoryLock<'a> {
+
        let guard = self
+
            .inventory
+
            .lock()
+
            .unwrap_or_else(|poisoned| poisoned.into_inner());
+

+
        InventoryLock { guard }
+
    }
+

    fn inventory(&self) -> Result<Inventory, Error> {
        let mut cache = self
            .inventory
@@ -374,7 +401,14 @@ pub enum Validation {
impl Repository {
    /// Open an existing repository.
    pub fn open<P: AsRef<Path>>(path: P, id: RepoId) -> Result<Self, RepositoryError> {
-
        let backend = git2::Repository::open_bare(path.as_ref())?;
+
        let backend = git2::Repository::open_ext(
+
            path.as_ref(),
+
            git2::RepositoryOpenFlags::empty()
+
                | git2::RepositoryOpenFlags::BARE
+
                | git2::RepositoryOpenFlags::NO_DOTGIT
+
                | git2::RepositoryOpenFlags::NO_SEARCH,
+
            &[] as &[&std::ffi::OsStr],
+
        )?;

        Ok(Self { id, backend })
    }
modified radicle/src/test/storage.rs
@@ -56,8 +56,21 @@ impl MockStorage {
    }
}

+
pub struct InventoryLock {
+
    inner: Inventory,
+
}
+

+
impl std::ops::Deref for InventoryLock {
+
    type Target = BTreeSet<RepoId>;
+

+
    fn deref(&self) -> &Self::Target {
+
        &self.inner
+
    }
+
}
+

impl ReadStorage for MockStorage {
    type Repository = MockRepository;
+
    type InventoryRef<'a> = InventoryLock;

    fn info(&self) -> &git::UserInfo {
        &self.info
@@ -79,6 +92,12 @@ impl ReadStorage for MockStorage {
        Ok(self.repos.keys().cloned().collect::<BTreeSet<_>>())
    }

+
    fn inventory_ref<'a, 's: 'a>(&'s self) -> Self::InventoryRef<'a> {
+
        InventoryLock {
+
            inner: self.inventory().unwrap(),
+
        }
+
    }
+

    fn insert(&self, _rid: RepoId) {}

    fn repository(&self, rid: RepoId) -> Result<Self::Repository, RepositoryError> {