Skip to content

Commit 0ece2d5

Browse files
Implement pluggable cache backends
Co-authored-by: Christian Schilling <christian.schilling.de@gmail.com>
1 parent 2201f22 commit 0ece2d5

File tree

10 files changed

+420
-222
lines changed

10 files changed

+420
-222
lines changed

josh-core/src/cache.rs

Lines changed: 70 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
use super::*;
2+
3+
use crate::cache_sled::sled_open_josh_trees;
4+
use crate::cache_stack::CacheStack;
5+
26
use std::collections::HashMap;
37
use std::sync::{LazyLock, RwLock};
48

9+
pub(crate) const CACHE_VERSION: u64 = 24;
10+
11+
pub trait CacheBackend: Send + Sync {
12+
fn read(&self, filter: filter::Filter, from: git2::Oid) -> JoshResult<Option<git2::Oid>>;
13+
14+
fn write(&self, filter: filter::Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()>;
15+
}
16+
517
pub trait FilterHook {
618
fn filter_for_commit(&self, commit_oid: git2::Oid, arg: &str) -> JoshResult<filter::Filter>;
719
}
820

9-
const CACHE_VERSION: u64 = 24;
10-
1121
lazy_static! {
1222
static ref DB: std::sync::Mutex<Option<sled::Db>> = std::sync::Mutex::new(None);
1323
}
@@ -21,39 +31,40 @@ static POPULATE_MAP: LazyLock<RwLock<HashMap<(git2::Oid, git2::Oid), git2::Oid>>
2131
static GLOB_MAP: LazyLock<RwLock<HashMap<(git2::Oid, git2::Oid), git2::Oid>>> =
2232
LazyLock::new(Default::default);
2333

24-
pub fn load(path: &std::path::Path) -> JoshResult<()> {
25-
*DB.lock()? = Some(
26-
sled::Config::default()
27-
.path(path.join(format!("josh/{}/sled/", CACHE_VERSION)))
28-
.flush_every_ms(Some(200))
29-
.open()?,
30-
);
31-
Ok(())
34+
pub struct TransactionContext {
35+
path: std::path::PathBuf,
36+
cache: std::sync::Arc<CacheStack>,
3237
}
3338

34-
pub fn print_stats() {
35-
let d = DB.lock().unwrap();
36-
let db = d.as_ref().unwrap();
37-
db.flush().unwrap();
38-
log::debug!("Trees:");
39-
let mut v = vec![];
40-
for name in db.tree_names() {
41-
let name = String::from_utf8(name.to_vec()).unwrap();
42-
let t = db.open_tree(&name).unwrap();
43-
if !t.is_empty() {
44-
let name = if let Ok(filter) = filter::parse(&name) {
45-
filter::pretty(filter, 4)
46-
} else {
47-
name.clone()
48-
};
49-
v.push((t.len(), name));
39+
impl TransactionContext {
40+
pub fn from_env(cache: std::sync::Arc<CacheStack>) -> JoshResult<Self> {
41+
let repo = git2::Repository::open_from_env()?;
42+
let path = repo.path().to_owned();
43+
44+
Ok(Self { path, cache })
45+
}
46+
47+
pub fn new(path: impl AsRef<std::path::Path>, cache: std::sync::Arc<CacheStack>) -> Self {
48+
Self {
49+
path: path.as_ref().to_path_buf(),
50+
cache,
5051
}
5152
}
5253

53-
v.sort();
54+
pub fn open(&self, ref_prefix: Option<&str>) -> JoshResult<Transaction> {
55+
if !self.path.exists() {
56+
return Err(josh_error("path does not exist"));
57+
}
5458

55-
for (len, name) in v.iter() {
56-
println!("[{}] {}", len, name);
59+
Ok(Transaction::new(
60+
git2::Repository::open_ext(
61+
&self.path,
62+
git2::RepositoryOpenFlags::NO_SEARCH,
63+
&[] as &[&std::ffi::OsStr],
64+
)?,
65+
self.cache.clone(),
66+
ref_prefix,
67+
))
5768
}
5869
}
5970

@@ -64,7 +75,7 @@ struct Transaction2 {
6475
subtract_map: HashMap<(git2::Oid, git2::Oid), git2::Oid>,
6576
overlay_map: HashMap<(git2::Oid, git2::Oid), git2::Oid>,
6677
unapply_map: HashMap<git2::Oid, HashMap<git2::Oid, git2::Oid>>,
67-
sled_trees: HashMap<git2::Oid, sled::Tree>,
78+
cache: std::sync::Arc<CacheStack>,
6879
path_tree: sled::Tree,
6980
invert_tree: sled::Tree,
7081
trigram_index_tree: sled::Tree,
@@ -81,67 +92,24 @@ pub struct Transaction {
8192
}
8293

8394
impl Transaction {
84-
pub fn open(path: &std::path::Path, ref_prefix: Option<&str>) -> JoshResult<Transaction> {
85-
if !path.exists() {
86-
return Err(josh_error("path does not exist"));
87-
}
88-
Ok(Transaction::new(
89-
git2::Repository::open_ext(
90-
path,
91-
git2::RepositoryOpenFlags::NO_SEARCH,
92-
&[] as &[&std::ffi::OsStr],
93-
)?,
94-
ref_prefix,
95-
))
96-
}
97-
98-
pub fn open_from_env(load_cache: bool) -> JoshResult<Transaction> {
99-
let repo = git2::Repository::open_from_env()?;
100-
let path = repo.path().to_owned();
101-
if load_cache {
102-
load(&path)?
103-
};
104-
105-
Ok(Transaction::new(repo, None))
106-
}
95+
fn new(
96+
repo: git2::Repository,
97+
cache: std::sync::Arc<CacheStack>,
98+
ref_prefix: Option<&str>,
99+
) -> Transaction {
100+
log::debug!("new transaction");
107101

108-
pub fn status(&self, _msg: &str) {
109-
/* let mut t2 = self.t2.borrow_mut(); */
110-
/* write!(t2.out, "{}", msg).ok(); */
111-
/* t2.out.flush().ok(); */
112-
}
102+
let (path_tree, invert_tree, trigram_index_tree) =
103+
sled_open_josh_trees().expect("failed to open transaction");
113104

114-
fn new(repo: git2::Repository, ref_prefix: Option<&str>) -> Transaction {
115-
log::debug!("new transaction");
116-
let path_tree = DB
117-
.lock()
118-
.unwrap()
119-
.as_ref()
120-
.unwrap()
121-
.open_tree("_paths")
122-
.unwrap();
123-
let invert_tree = DB
124-
.lock()
125-
.unwrap()
126-
.as_ref()
127-
.unwrap()
128-
.open_tree("_invert")
129-
.unwrap();
130-
let trigram_index_tree = DB
131-
.lock()
132-
.unwrap()
133-
.as_ref()
134-
.unwrap()
135-
.open_tree("_trigram_index")
136-
.unwrap();
137105
Transaction {
138106
t2: std::cell::RefCell::new(Transaction2 {
139107
commit_map: HashMap::new(),
140108
apply_map: HashMap::new(),
141109
subtract_map: HashMap::new(),
142110
overlay_map: HashMap::new(),
143111
unapply_map: HashMap::new(),
144-
sled_trees: HashMap::new(),
112+
cache,
145113
path_tree,
146114
invert_tree,
147115
trigram_index_tree,
@@ -156,7 +124,12 @@ impl Transaction {
156124
}
157125

158126
pub fn try_clone(&self) -> JoshResult<Transaction> {
159-
Transaction::open(self.repo.path(), Some(&self.ref_prefix))
127+
let context = TransactionContext {
128+
cache: self.t2.borrow().cache.clone(),
129+
path: self.repo.path().to_owned(),
130+
};
131+
132+
context.open(Some(&self.ref_prefix))
160133
}
161134

162135
pub fn repo(&self) -> &git2::Repository {
@@ -348,34 +321,12 @@ impl Transaction {
348321
// random extra commits (probability 1/256) to avoid long searches for filters that reduce
349322
// the history length by a very large factor.
350323
if store || from.as_bytes()[0] == 0 {
351-
let t = t2.sled_trees.entry(filter.id()).or_insert_with(|| {
352-
DB.lock()
353-
.unwrap()
354-
.as_ref()
355-
.unwrap()
356-
.open_tree(filter::spec(filter))
357-
.unwrap()
358-
});
359-
360-
t.insert(from.as_bytes(), to.as_bytes()).unwrap();
324+
t2.cache
325+
.write_all(filter, from, to)
326+
.expect("Failed to write cache");
361327
}
362328
}
363329

364-
#[allow(clippy::len_without_is_empty)]
365-
pub fn len(&self, filter: filter::Filter) -> usize {
366-
let mut t2 = self.t2.borrow_mut();
367-
let t = t2.sled_trees.entry(filter.id()).or_insert_with(|| {
368-
DB.lock()
369-
.unwrap()
370-
.as_ref()
371-
.unwrap()
372-
.open_tree(filter::spec(filter))
373-
.unwrap()
374-
});
375-
376-
t.len()
377-
}
378-
379330
pub fn get_missing(&self) -> Vec<(filter::Filter, git2::Oid)> {
380331
let mut missing = self.t2.borrow().missing.clone();
381332
missing.sort_by_key(|(f, i)| (filter::nesting(*f), *f, *i));
@@ -404,25 +355,25 @@ impl Transaction {
404355
if filter == filter::nop() {
405356
return Some(from);
406357
}
407-
let mut t2 = self.t2.borrow_mut();
358+
let t2 = self.t2.borrow_mut();
408359
if let Some(m) = t2.commit_map.get(&filter.id()) {
409360
if let Some(oid) = m.get(&from).cloned() {
410361
return Some(oid);
411362
}
412363
}
413-
let t = t2.sled_trees.entry(filter.id()).or_insert_with(|| {
414-
DB.lock()
415-
.unwrap()
416-
.as_ref()
417-
.unwrap()
418-
.open_tree(filter::spec(filter))
419-
.unwrap()
420-
});
421-
if let Some(oid) = t.get(from.as_bytes()).unwrap() {
422-
let oid = git2::Oid::from_bytes(&oid).unwrap();
364+
365+
let oid = t2
366+
.cache
367+
.read_propagate(filter, from)
368+
.expect("Failed to read from cache backend");
369+
370+
let oid = if let Some(oid) = oid { Some(oid) } else { None };
371+
372+
if let Some(oid) = oid {
423373
if oid == git2::Oid::zero() {
424374
return Some(oid);
425375
}
376+
426377
if self.repo.odb().unwrap().exists(oid) {
427378
// Only report an object as cached if it exists in the object database.
428379
// This forces a rebuild in case the object was garbage collected.

josh-core/src/cache_sled.rs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
use std::sync::LazyLock;
2+
3+
use crate::cache::{CACHE_VERSION, CacheBackend};
4+
use crate::filter::Filter;
5+
use crate::{JoshResult, filter, josh_error};
6+
7+
static DB: LazyLock<std::sync::Mutex<Option<sled::Db>>> = LazyLock::new(Default::default);
8+
9+
pub fn sled_print_stats() -> JoshResult<()> {
10+
let db = DB.lock()?;
11+
let db = match db.as_ref() {
12+
Some(db) => db,
13+
None => return Err(josh_error("cache not initialized")),
14+
};
15+
16+
db.flush()?;
17+
log::debug!("Trees:");
18+
19+
let mut v = vec![];
20+
for name in db.tree_names() {
21+
let name = String::from_utf8(name.to_vec())?;
22+
let t = db.open_tree(&name)?;
23+
24+
if !t.is_empty() {
25+
let name = if let Ok(filter) = filter::parse(&name) {
26+
filter::pretty(filter, 4)
27+
} else {
28+
name.clone()
29+
};
30+
v.push((t.len(), name));
31+
}
32+
}
33+
34+
v.sort();
35+
36+
for (len, name) in v.iter() {
37+
println!("[{}] {}", len, name);
38+
}
39+
40+
Ok(())
41+
}
42+
43+
pub fn sled_open_josh_trees() -> JoshResult<(sled::Tree, sled::Tree, sled::Tree)> {
44+
let db = DB.lock()?;
45+
let db = match db.as_ref() {
46+
Some(db) => db,
47+
None => return Err(josh_error("cache not initialized")),
48+
};
49+
50+
let path_tree = db.open_tree("_paths")?;
51+
let invert_tree = db.open_tree("_invert")?;
52+
let trigram_index_tree = db.open_tree("_trigram_index")?;
53+
54+
Ok((path_tree, invert_tree, trigram_index_tree))
55+
}
56+
57+
pub fn sled_load(path: &std::path::Path) -> JoshResult<()> {
58+
let db = sled::Config::default()
59+
.path(path.join(format!("josh/{}/sled/", CACHE_VERSION)))
60+
.flush_every_ms(Some(200))
61+
.open()?;
62+
63+
*DB.lock()? = Some(db);
64+
65+
Ok(())
66+
}
67+
68+
#[derive(Default)]
69+
pub struct SledCacheBackend {
70+
trees: std::sync::Mutex<std::collections::HashMap<git2::Oid, sled::Tree>>,
71+
}
72+
73+
fn insert_sled_tree(filter: Filter) -> sled::Tree {
74+
DB.lock()
75+
.unwrap()
76+
.as_ref()
77+
.expect("Sled DB not initialized")
78+
.open_tree(filter::spec(filter))
79+
.expect("Failed to insert Sled tree")
80+
}
81+
82+
impl CacheBackend for SledCacheBackend {
83+
fn read(&self, filter: Filter, from: git2::Oid) -> JoshResult<Option<git2::Oid>> {
84+
let mut trees = self.trees.lock()?;
85+
let tree = trees
86+
.entry(filter.id())
87+
.or_insert_with(|| insert_sled_tree(filter));
88+
89+
if let Some(oid) = tree.get(from.as_bytes())? {
90+
let oid = git2::Oid::from_bytes(&oid)?;
91+
Ok(Some(oid))
92+
} else {
93+
Ok(None)
94+
}
95+
}
96+
97+
fn write(&self, filter: Filter, from: git2::Oid, to: git2::Oid) -> JoshResult<()> {
98+
let mut trees = self.trees.lock()?;
99+
let tree = trees
100+
.entry(filter.id())
101+
.or_insert_with(|| insert_sled_tree(filter));
102+
103+
tree.insert(from.as_bytes(), to.as_bytes())?;
104+
Ok(())
105+
}
106+
}

0 commit comments

Comments
 (0)