use std::cell::RefCell;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex, RwLock};
pub mod test_hooks {
use std::sync::atomic::{AtomicUsize, Ordering};
static LEGACY_STORES_CREATED: AtomicUsize = AtomicUsize::new(0);
static IMPLICIT_STORES_CREATED: AtomicUsize = AtomicUsize::new(0);
pub fn reset() {
LEGACY_STORES_CREATED.store(0, Ordering::Release);
IMPLICIT_STORES_CREATED.store(0, Ordering::Release);
}
pub fn legacy_stores_created() -> usize {
LEGACY_STORES_CREATED.load(Ordering::Acquire)
}
pub fn implicit_stores_created() -> usize {
IMPLICIT_STORES_CREATED.load(Ordering::Acquire)
}
pub(crate) fn record_legacy_store_created() {
LEGACY_STORES_CREATED.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn record_implicit_store_created() {
IMPLICIT_STORES_CREATED.fetch_add(1, Ordering::Relaxed);
}
}
pub struct PackStore {
objects_dir: PathBuf,
cache_epoch: AtomicU64,
catalog: RwLock<crate::pack::pack_cache::CatalogState>,
delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru,
midx: Mutex<crate::midx::midx_cache::State>,
repo_caches: RwLock<Option<Arc<crate::repo_caches::RepoCaches>>>,
}
const _: () = {
fn assert_send_sync<T: Send + Sync>() {}
let _ = assert_send_sync::<PackStore>;
};
thread_local! {
static CURRENT: RefCell<Option<Arc<PackStore>>> = const { RefCell::new(None) };
static ACTIVE_OBJECTS_DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
static LEGACY_BY_DIR: RefCell<HashMap<PathBuf, Arc<PackStore>>> = RefCell::new(HashMap::new());
static IMPLICIT: RefCell<Option<Arc<PackStore>>> = const { RefCell::new(None) };
}
impl PackStore {
#[must_use]
pub fn new(objects_dir: PathBuf) -> Self {
Self {
objects_dir,
cache_epoch: AtomicU64::new(0),
catalog: RwLock::new(crate::pack::pack_cache::CatalogState::default()),
delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru::new(),
midx: Mutex::new(crate::midx::midx_cache::State::default()),
repo_caches: RwLock::new(None),
}
}
pub(crate) fn set_repo_caches(&self, caches: Arc<crate::repo_caches::RepoCaches>) {
if let Ok(mut guard) = self.repo_caches.write() {
*guard = Some(caches);
}
}
fn repo_caches(&self) -> Option<Arc<crate::repo_caches::RepoCaches>> {
self.repo_caches
.read()
.ok()
.and_then(|g| g.as_ref().map(Arc::clone))
}
pub(crate) fn repo_caches_for_objects_dir(
objects_dir: &Path,
) -> Option<Arc<crate::repo_caches::RepoCaches>> {
if Self::has_context() {
let store = Self::active_for_reads();
if store.objects_dir() == objects_dir {
return store.repo_caches();
}
}
LEGACY_BY_DIR.with(|map| {
map.borrow()
.get(objects_dir)
.and_then(|store| store.repo_caches())
})
}
pub(crate) fn repo_caches_for_config_path(
config_path: &Path,
) -> Option<Arc<crate::repo_caches::RepoCaches>> {
let git_dir = config_path.parent()?;
Self::repo_caches_for_objects_dir(&git_dir.join("objects"))
}
#[must_use]
pub(crate) fn cache_epoch(&self) -> u64 {
self.cache_epoch.load(Ordering::Acquire)
}
pub(crate) fn bump_cache_epoch(&self) -> u64 {
self.cache_epoch.fetch_add(1, Ordering::Release) + 1
}
pub(crate) fn with_catalog<R>(
&self,
f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
) -> R {
f(&mut self.catalog.write().unwrap_or_else(|e| e.into_inner()))
}
pub(crate) fn with_catalog_read<R>(
&self,
f: impl FnOnce(&crate::pack::pack_cache::CatalogState) -> R,
) -> R {
f(&self.catalog.read().unwrap_or_else(|e| e.into_inner()))
}
pub(crate) fn try_with_catalog<R>(
&self,
f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
) -> Option<R> {
let mut guard = self.catalog.try_write().ok()?;
Some(f(&mut guard))
}
#[must_use]
pub(crate) fn delta_bases(&self) -> &crate::pack::pack_cache::ShardedDeltaBaseLru {
&self.delta_bases
}
pub(crate) fn active_for_reads() -> Arc<Self> {
if let Some(store) = CURRENT.with(|c| c.borrow().clone()) {
return store;
}
if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
return Self::legacy_for_objects_dir(&dir);
}
IMPLICIT.with(|i| {
if let Some(s) = i.borrow().as_ref() {
return Arc::clone(s);
}
test_hooks::record_implicit_store_created();
let s = Arc::new(Self::new(PathBuf::from("__grit_implicit_pack_store")));
*i.borrow_mut() = Some(Arc::clone(&s));
s
})
}
pub(crate) fn legacy_for_objects_dir(objects_dir: &Path) -> Arc<Self> {
LEGACY_BY_DIR.with(|map| {
let key = objects_dir.to_path_buf();
if let Some(s) = map.borrow().get(&key) {
return Arc::clone(s);
}
test_hooks::record_legacy_store_created();
let store = Arc::new(Self::new(key.clone()));
map.borrow_mut().insert(key, Arc::clone(&store));
store
})
}
pub(crate) fn legacy_for_pack_path(pack_path: &Path) -> Arc<Self> {
let objects_dir = pack_path.parent().and_then(|pack_dir| {
(pack_dir.file_name() == Some(std::ffi::OsStr::new("pack")))
.then(|| pack_dir.parent())
.flatten()
});
match objects_dir {
Some(dir) => Self::legacy_for_objects_dir(dir),
None => IMPLICIT.with(|i| {
if let Some(s) = i.borrow().as_ref() {
return Arc::clone(s);
}
test_hooks::record_implicit_store_created();
let s = Arc::new(Self::new(PathBuf::from("__grit_nonstandard_pack_cache")));
*i.borrow_mut() = Some(Arc::clone(&s));
s
}),
}
}
#[must_use]
pub fn objects_dir(&self) -> &Path {
&self.objects_dir
}
#[must_use]
pub fn has_context() -> bool {
CURRENT.with(|c| c.borrow().is_some())
}
#[must_use]
pub(crate) fn read_context_matches_objects_dir(objects_dir: &Path) -> bool {
Self::has_context()
&& ACTIVE_OBJECTS_DIR.with(|d| d.borrow().as_deref() == Some(objects_dir))
}
pub fn with_context<R>(store: Arc<Self>, f: impl FnOnce() -> R) -> R {
let _guard = PackReadContextGuard::install(store);
f()
}
pub fn invalidate_packs(&self) {
crate::pack::pack_cache::clear_catalog_on(&self.catalog);
self.delta_bases.clear();
let epoch = self.bump_cache_epoch();
crate::pack::pack_cache::clear_thread_local_after_invalidation(epoch);
}
pub fn invalidate_midx(&self) {
crate::midx::midx_cache::evict_pack_dir_on(&self.midx, &self.objects_dir.join("pack"));
}
pub fn invalidate_all(&self) {
self.invalidate_packs();
self.invalidate_midx();
}
pub fn configure_delta_base_from_config(&self, cfg: Option<&crate::config::ConfigSet>) {
crate::pack::pack_cache::configure_delta_base_on(&self.delta_bases, cfg);
}
pub(crate) fn with_pack<R>(
&self,
f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
) -> R {
self.with_catalog(f)
}
pub(crate) fn with_midx<R>(
&self,
f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
) -> R {
f(&mut self.midx.lock().unwrap_or_else(|e| e.into_inner()))
}
pub(crate) fn with_current_or_implicit<R>(
f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
) -> R {
Self::active_for_reads().with_pack(f)
}
pub(crate) fn with_current_or_implicit_midx<R>(
f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
) -> R {
if let Some(store) = CURRENT.with(|c| c.borrow().clone()) {
return store.with_midx(f);
}
if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
return Self::legacy_for_objects_dir(&dir).with_midx(f);
}
Self::active_for_reads().with_midx(f)
}
pub fn invalidate_all_legacy() {
LEGACY_BY_DIR.with(|map| {
for store in map.borrow().values() {
store.invalidate_all();
}
map.borrow_mut().clear();
});
IMPLICIT.with(|i| *i.borrow_mut() = None);
}
pub(crate) fn invalidate_legacy_for_objects_dir(objects_dir: &Path) {
LEGACY_BY_DIR.with(|map| {
if let Some(store) = map.borrow().get(objects_dir) {
store.invalidate_all();
}
});
}
}
pub(crate) struct PackReadContextGuard {
prev_store: Option<Arc<PackStore>>,
prev_dir: Option<PathBuf>,
}
impl PackReadContextGuard {
pub(crate) fn install(store: Arc<PackStore>) -> Self {
let prev_store = CURRENT.with(|c| c.borrow_mut().replace(Arc::clone(&store)));
let prev_dir =
ACTIVE_OBJECTS_DIR.with(|d| d.borrow_mut().replace(store.objects_dir().to_path_buf()));
crate::pack::pack_cache::warm_thread_local_from_store(&store);
Self {
prev_store,
prev_dir,
}
}
}
impl Drop for PackReadContextGuard {
fn drop(&mut self) {
crate::pack::clear_pack_read_info_cache();
ACTIVE_OBJECTS_DIR.with(|d| *d.borrow_mut() = self.prev_dir.take());
CURRENT.with(|c| *c.borrow_mut() = self.prev_store.take());
}
}