Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
httpd: Add a cache for `/tree`
xphoniex committed 2 years ago
commit 2e25250be44a687e820c41e6adf64dab98d53e88
parent 3fcadc505875ef57f77260511184db283b8d651b
7 files changed +64 -4
modified radicle-httpd/Cargo.toml
@@ -24,6 +24,7 @@ fastrand = { version = "1.9.0" }
flate2 = { version = "1" }
hyper = { version = "0.14.17", default-features = false }
lexopt = { version = "0.2.1" }
+
lru = { version = "0.10.0" }
nonempty = { version = "0.8.1", features = ["serialize"] }
radicle-surf = { version = "0.14.0", default-features = false, features = ["serde"] }
serde = { version = "1", features = ["derive"] }
modified radicle-httpd/src/api.rs
@@ -25,6 +25,9 @@ mod error;
mod json;
mod v1;

+
use crate::cache::Cache;
+
use crate::Options;
+

pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Identifier for sessions
@@ -34,13 +37,15 @@ type SessionId = String;
pub struct Context {
    profile: Arc<Profile>,
    sessions: Arc<RwLock<HashMap<SessionId, auth::Session>>>,
+
    cache: Option<Cache>,
}

impl Context {
-
    pub fn new(profile: Arc<Profile>) -> Self {
+
    pub fn new(profile: Arc<Profile>, options: &Options) -> Self {
        Self {
            profile,
            sessions: Default::default(),
+
            cache: options.cache.map(Cache::new),
        }
    }

modified radicle-httpd/src/api/v1/projects.rs
@@ -309,12 +309,24 @@ async fn tree_handler(
    State(ctx): State<Context>,
    Path((project, sha, path)): Path<(Id, Oid, String)>,
) -> impl IntoResponse {
+
    if let Some(ref cache) = ctx.cache {
+
        let cache = &mut cache.tree.lock().await;
+
        if let Some(response) = cache.get(&(project, sha, path.clone())) {
+
            return Ok::<_, Error>(Json(response.clone()));
+
        }
+
    }
+

    let storage = &ctx.profile.storage;
    let repo = Repository::open(paths::repository(storage, &project))?;
    let tree = repo.tree(sha, &path)?;
    let stats = repo.stats_from(&sha)?;
    let response = api::json::tree(&tree, &path, &stats);

+
    if let Some(cache) = ctx.cache {
+
        let cache = &mut cache.tree.lock().await;
+
        cache.put((project, sha, path.clone()), response.clone());
+
    }
+

    Ok::<_, Error>(Json(response))
}

added radicle-httpd/src/cache.rs
@@ -0,0 +1,22 @@
+
use std::num::NonZeroUsize;
+
use std::sync::Arc;
+

+
use lru::LruCache;
+
use tokio::sync::Mutex;
+

+
use radicle::prelude::Id;
+
use radicle_surf::Oid;
+

+
#[derive(Clone)]
+
pub struct Cache {
+
    pub tree: Arc<Mutex<LruCache<(Id, Oid, String), serde_json::Value>>>,
+
}
+

+
impl Cache {
+
    /// Creates a new cache of the given size.
+
    pub fn new(size: NonZeroUsize) -> Self {
+
        Cache {
+
            tree: Arc::new(Mutex::new(LruCache::new(size))),
+
        }
+
    }
+
}
modified radicle-httpd/src/lib.rs
@@ -4,6 +4,7 @@ pub mod error;

use std::collections::HashMap;
use std::net::SocketAddr;
+
use std::num::NonZeroUsize;
use std::process::Command;
use std::str;
use std::sync::Arc;
@@ -24,16 +25,21 @@ use tracing_extra::{tracing_middleware, ColoredStatus, Paint, RequestId, Tracing

mod api;
mod axum_extra;
+
mod cache;
mod git;
mod raw;
#[cfg(test)]
mod test;
mod tracing_extra;

+
/// Default cache HTTP size.
+
pub const DEFAULT_CACHE_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(100) };
+

#[derive(Debug, Clone)]
pub struct Options {
    pub aliases: HashMap<String, Id>,
    pub listen: SocketAddr,
+
    pub cache: Option<NonZeroUsize>,
}

/// Run the Server.
@@ -101,7 +107,7 @@ pub async fn run(options: Options) -> anyhow::Result<()> {
/// Create a router consisting of other sub-routers.
fn router(options: Options, profile: Profile) -> anyhow::Result<Router> {
    let profile = Arc::new(profile);
-
    let ctx = api::Context::new(profile.clone());
+
    let ctx = api::Context::new(profile.clone(), &options);

    let api_router = api::router(ctx);
    let git_router = git::router(profile.clone(), options.aliases);
@@ -132,6 +138,7 @@ mod routes {
            super::Options {
                aliases: HashMap::new(),
                listen: SocketAddr::from(([0, 0, 0, 0], 8080)),
+
                cache: None,
            },
            test::profile(tmp.path(), [0xff; 32]),
        )
modified radicle-httpd/src/main.rs
@@ -1,3 +1,4 @@
+
use std::num::NonZeroUsize;
use std::{collections::HashMap, process};

use radicle::prelude::Id;
@@ -51,6 +52,7 @@ fn parse_options() -> Result<httpd::Options, lexopt::Error> {
    let mut parser = lexopt::Parser::from_env();
    let mut listen = None;
    let mut aliases = HashMap::new();
+
    let mut cache = Some(httpd::DEFAULT_CACHE_SIZE);

    while let Some(arg) = parser.next()? {
        match arg {
@@ -64,8 +66,12 @@ fn parse_options() -> Result<httpd::Options, lexopt::Error> {

                aliases.insert(alias, id);
            }
+
            Long("cache") => {
+
                let size = parser.value()?.parse()?;
+
                cache = NonZeroUsize::new(size);
+
            }
            Long("help") | Short('h') => {
-
                println!("usage: radicle-httpd [--listen <addr>] [--alias <name> <rid>]..");
+
                println!("usage: radicle-httpd [--listen <addr>] [--alias <name> <rid>] [--cache <size>]..");
                process::exit(0);
            }
            _ => return Err(arg.unexpected()),
@@ -74,5 +80,6 @@ fn parse_options() -> Result<httpd::Options, lexopt::Error> {
    Ok(httpd::Options {
        aliases,
        listen: listen.unwrap_or_else(|| ([0, 0, 0, 0], 8080).into()),
+
        cache,
    })
}
modified radicle-httpd/src/test.rs
@@ -203,7 +203,13 @@ fn seed_with_signer<G: Signer>(dir: &Path, profile: radicle::Profile, signer: &G
        )
        .unwrap();

-
    Context::new(Arc::new(profile))
+
    let options = crate::Options {
+
        aliases: std::collections::HashMap::new(),
+
        listen: std::net::SocketAddr::from(([0, 0, 0, 0], 8080)),
+
        cache: Some(crate::DEFAULT_CACHE_SIZE),
+
    };
+

+
    Context::new(Arc::new(profile), &options)
}

/// Adds an authorized session to the Context::sessions HashMap.