don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(knot): cache open repository handles

Requires feature "repository-cache" to be enabled.

Signed-off-by: tjh <did:plc:65gha4t3avpfpzmvpbwovss7>

+82 -37
+4
crates/knot/Cargo.toml
··· 49 49 [dev-dependencies] 50 50 http-body-util = "0.1.3" 51 51 52 + [features] 53 + default = [] 54 + repository-cache = [] 55 + 52 56 [[bin]] 53 57 name = "gordian-knot" 54 58 path = "src/main.rs"
-10
crates/knot/src/model.rs
··· 8 8 use core::ops; 9 9 use std::sync::Arc; 10 10 11 - use atproto::did::Did; 12 11 use axum::{ 13 12 extract::{FromRef, FromRequestParts}, 14 13 http::request::Parts, ··· 22 23 }; 23 24 24 25 pub use knot_state::KnotState; 25 - 26 - #[derive(Debug, Clone)] 27 - pub struct RepositoryKey { 28 - /// Repository owner's Did. 29 - pub owner: Box<Did>, 30 - 31 - /// Repository record key. 32 - pub rkey: Box<str>, 33 - } 34 26 35 27 #[derive(Debug, Clone)] 36 28 #[repr(transparent)]
+28 -24
crates/knot/src/model/knot_state.rs
··· 20 20 authorization::AuthorizationClaimsStore, 21 21 database::{DataStore, DataStoreError}, 22 22 }, 23 - types::repository_path::RepositoryPath, 23 + types::{repository_key::RepositoryKey, repository_path::RepositoryPath}, 24 24 }; 25 25 26 - use super::{RepositoryKey, config::KnotConfiguration}; 26 + use super::config::KnotConfiguration; 27 27 28 28 #[derive(Debug)] 29 29 pub struct KnotState { ··· 55 55 /// 56 56 /// Maps (handle, name), (handle, rkey), or (did, name) to (did, rkey). 57 57 repo_cache: RwLock<HashMap<RepositoryPath, RepositoryKey>>, 58 + 59 + #[cfg(feature = "repository-cache")] 60 + repo_handle_cache: Mutex<HashMap<RepositoryKey, gix::ThreadSafeRepository>>, 58 61 } 59 62 60 63 impl KnotState { ··· 74 71 75 72 let inner = Arc::new(Self { 76 73 config, 77 - jwt_claims: Default::default(), 78 - repo_cache: Default::default(), 79 74 public_http, 80 75 resolver, 81 76 jetstream, 82 77 store: database, 83 78 pool, 79 + jwt_claims: Default::default(), 80 + repo_cache: Default::default(), 81 + #[cfg(feature = "repository-cache")] 82 + repo_handle_cache: Default::default(), 84 83 }); 85 84 86 85 let state = Arc::clone(&inner); ··· 297 292 Ok(resolved_repo_path) 298 293 } 299 294 300 - pub async fn open_repository_direct( 295 + pub fn open_repository_direct( 301 296 &self, 302 - repository_path: &RepositoryPath, 297 + repository_key: &RepositoryKey, 303 298 ) -> Result<gix::ThreadSafeRepository, RepositoryOpenError> { 304 299 use gix::open::Options; 305 300 301 + #[cfg(feature = "repository-cache")] 302 + let mut guard = self.repo_handle_cache.lock().unwrap(); 303 + #[cfg(feature = "repository-cache")] 304 + if let Some(repository) = guard.get(repository_key) { 305 + return Ok(repository.clone()); 306 + } 307 + 306 308 let path = self 307 309 .repository_path() 308 - .join(repository_path.owner()) 309 - .join(repository_path.name()); 310 + .join(repository_key.owner_str()) 311 + .join(repository_key.rkey()); 310 312 311 313 let repository = Options::default() 312 314 .strict_config(true) 313 315 .open_path_as_is(true) 314 316 .open(path)?; 315 317 318 + let local = repository.to_thread_local(); 319 + assert!(local.is_bare()); 320 + 321 + #[cfg(feature = "repository-cache")] 322 + guard.insert(repository_key.clone(), repository.clone()); 316 323 Ok(repository) 317 324 } 318 325 ··· 336 319 R::Error: core::error::Error, 337 320 RepositoryOpenError: From<R::Error>, 338 321 { 339 - use gix::open::Options; 340 - 341 322 let repo_path = repo.try_into()?; 342 - let resolved = self.resolve_repo_path(&repo_path).await?; 343 - let path = self 344 - .repository_path() 345 - .join(resolved.owner.as_str()) 346 - .join(resolved.rkey.as_ref()); 323 + let repository_key = self.resolve_repo_path(&repo_path).await?; 324 + let repository = self.open_repository_direct(&repository_key)?; 347 325 348 - let repository = Options::default() 349 - .strict_config(true) 350 - .open_path_as_is(true) 351 - .open(path)?; 352 - 353 - let local = repository.to_thread_local(); 354 - assert!(local.is_bare()); 355 - 356 - Ok(local) 326 + Ok(repository.to_thread_local()) 357 327 } 358 328 359 329 pub async fn can_push(&self, repo: &RepositoryKey, did: &Did) -> bool {
-1
crates/knot/src/model/repository.rs
··· 375 375 let knot = Knot::from_ref(state); 376 376 let repository = knot 377 377 .open_repository_direct(&repository_path) 378 - .await 379 378 .map_err(errors::RepoNotFound)? 380 379 .to_thread_local(); 381 380
+1 -1
crates/knot/src/services/rbac.rs
··· 1 1 use atproto::Did; 2 2 use futures_util::{FutureExt, future::BoxFuture}; 3 3 4 - use crate::model::{KnotState, RepositoryKey}; 4 + use crate::{model::KnotState, types::repository_key::RepositoryKey}; 5 5 6 6 pub trait Policy<Subject, Resource, Action, Context>: Send + Sync { 7 7 /// Evaluates whether access should be granted.
+1
crates/knot/src/types.rs
··· 1 + pub mod repository_key; 1 2 pub mod repository_path; 2 3 pub mod sh;
+47
crates/knot/src/types/repository_key.rs
··· 1 + use core::fmt; 2 + 3 + use atproto::Did; 4 + use serde::Deserialize; 5 + 6 + use super::repository_path::{Error, validate}; 7 + 8 + #[derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize)] 9 + pub struct RepositoryKey { 10 + /// Repository owner's Did. 11 + pub owner: Box<Did>, 12 + 13 + /// Repository record key. 14 + pub rkey: Box<str>, 15 + } 16 + 17 + impl RepositoryKey { 18 + pub fn owner_str(&self) -> &str { 19 + &self.owner 20 + } 21 + 22 + pub fn rkey(&self) -> &str { 23 + &self.rkey 24 + } 25 + } 26 + 27 + impl fmt::Display for RepositoryKey { 28 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 29 + write!(f, "{}/{}", self.owner, self.rkey) 30 + } 31 + } 32 + 33 + impl std::str::FromStr for RepositoryKey { 34 + type Err = Error; 35 + fn from_str(s: &str) -> Result<Self, Self::Err> { 36 + let (owner, name) = s.split_once('/').ok_or(Error::Format)?; 37 + 38 + let owner: Box<Did> = owner.parse().map_err(|_| Error::Format)?; 39 + validate(owner.as_str())?; 40 + validate(name)?; 41 + 42 + Ok(Self { 43 + owner, 44 + rkey: name.into(), 45 + }) 46 + } 47 + }
+1 -1
crates/knot/src/types/repository_path.rs
··· 84 84 } 85 85 } 86 86 87 - fn validate(name: &str) -> Result<(), Error> { 87 + pub(crate) fn validate(name: &str) -> Result<(), Error> { 88 88 static FORBIDDEN_SUBSTRINGS: &[&str] = &[".."]; 89 89 static FORBIDDEN_PREFIXES: &[&str] = &[".", "-"]; 90 90 static ACCEPTED_SYMBOLS: &[char] = &['.', '-', '_', ':'];