1use std::cell::RefCell;
11use std::collections::HashMap;
12use std::path::{Path, PathBuf};
13use std::sync::atomic::{AtomicU64, Ordering};
14use std::sync::{Arc, Mutex, RwLock};
15
16pub mod test_hooks {
18 use std::sync::atomic::{AtomicUsize, Ordering};
19
20 static LEGACY_STORES_CREATED: AtomicUsize = AtomicUsize::new(0);
21 static IMPLICIT_STORES_CREATED: AtomicUsize = AtomicUsize::new(0);
22
23 pub fn reset() {
25 LEGACY_STORES_CREATED.store(0, Ordering::Release);
26 IMPLICIT_STORES_CREATED.store(0, Ordering::Release);
27 }
28
29 pub fn legacy_stores_created() -> usize {
31 LEGACY_STORES_CREATED.load(Ordering::Acquire)
32 }
33
34 pub fn implicit_stores_created() -> usize {
36 IMPLICIT_STORES_CREATED.load(Ordering::Acquire)
37 }
38
39 pub(crate) fn record_legacy_store_created() {
40 LEGACY_STORES_CREATED.fetch_add(1, Ordering::Relaxed);
41 }
42
43 pub(crate) fn record_implicit_store_created() {
44 IMPLICIT_STORES_CREATED.fetch_add(1, Ordering::Relaxed);
45 }
46}
47
48pub struct PackStore {
54 objects_dir: PathBuf,
55 cache_epoch: AtomicU64,
57 catalog: RwLock<crate::pack::pack_cache::CatalogState>,
58 delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru,
59 midx: Mutex<crate::midx::midx_cache::State>,
60 repo_caches: RwLock<Option<Arc<crate::repo_caches::RepoCaches>>>,
62}
63
64const _: () = {
65 fn assert_send_sync<T: Send + Sync>() {}
66 let _ = assert_send_sync::<PackStore>;
67};
68
69thread_local! {
71 static CURRENT: RefCell<Option<Arc<PackStore>>> = const { RefCell::new(None) };
72 static ACTIVE_OBJECTS_DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
73 static LEGACY_BY_DIR: RefCell<HashMap<PathBuf, Arc<PackStore>>> = RefCell::new(HashMap::new());
75 static IMPLICIT: RefCell<Option<Arc<PackStore>>> = const { RefCell::new(None) };
76}
77
78impl PackStore {
79 #[must_use]
81 pub fn new(objects_dir: PathBuf) -> Self {
82 Self {
83 objects_dir,
84 cache_epoch: AtomicU64::new(0),
85 catalog: RwLock::new(crate::pack::pack_cache::CatalogState::default()),
86 delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru::new(),
87 midx: Mutex::new(crate::midx::midx_cache::State::default()),
88 repo_caches: RwLock::new(None),
89 }
90 }
91
92 pub(crate) fn set_repo_caches(&self, caches: Arc<crate::repo_caches::RepoCaches>) {
94 if let Ok(mut guard) = self.repo_caches.write() {
95 *guard = Some(caches);
96 }
97 }
98
99 fn repo_caches(&self) -> Option<Arc<crate::repo_caches::RepoCaches>> {
100 self.repo_caches
101 .read()
102 .ok()
103 .and_then(|g| g.as_ref().map(Arc::clone))
104 }
105
106 pub(crate) fn repo_caches_for_objects_dir(
108 objects_dir: &Path,
109 ) -> Option<Arc<crate::repo_caches::RepoCaches>> {
110 if Self::has_context() {
111 let store = Self::active_for_reads();
112 if store.objects_dir() == objects_dir {
113 return store.repo_caches();
114 }
115 }
116 LEGACY_BY_DIR.with(|map| {
117 map.borrow()
118 .get(objects_dir)
119 .and_then(|store| store.repo_caches())
120 })
121 }
122
123 pub(crate) fn repo_caches_for_config_path(
125 config_path: &Path,
126 ) -> Option<Arc<crate::repo_caches::RepoCaches>> {
127 let git_dir = config_path.parent()?;
128 Self::repo_caches_for_objects_dir(&git_dir.join("objects"))
129 }
130
131 #[must_use]
133 pub(crate) fn cache_epoch(&self) -> u64 {
134 self.cache_epoch.load(Ordering::Acquire)
135 }
136
137 pub(crate) fn bump_cache_epoch(&self) -> u64 {
138 self.cache_epoch.fetch_add(1, Ordering::Release) + 1
139 }
140
141 pub(crate) fn with_catalog<R>(
143 &self,
144 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
145 ) -> R {
146 f(&mut self.catalog.write().unwrap_or_else(|e| e.into_inner()))
147 }
148
149 pub(crate) fn with_catalog_read<R>(
150 &self,
151 f: impl FnOnce(&crate::pack::pack_cache::CatalogState) -> R,
152 ) -> R {
153 f(&self.catalog.read().unwrap_or_else(|e| e.into_inner()))
154 }
155
156 pub(crate) fn try_with_catalog<R>(
158 &self,
159 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
160 ) -> Option<R> {
161 let mut guard = self.catalog.try_write().ok()?;
162 Some(f(&mut guard))
163 }
164
165 #[must_use]
167 pub(crate) fn delta_bases(&self) -> &crate::pack::pack_cache::ShardedDeltaBaseLru {
168 &self.delta_bases
169 }
170
171 pub(crate) fn active_for_reads() -> Arc<Self> {
173 if let Some(store) = CURRENT.with(|c| c.borrow().clone()) {
174 return store;
175 }
176 if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
177 return Self::legacy_for_objects_dir(&dir);
178 }
179 IMPLICIT.with(|i| {
180 if let Some(s) = i.borrow().as_ref() {
181 return Arc::clone(s);
182 }
183 test_hooks::record_implicit_store_created();
184 let s = Arc::new(Self::new(PathBuf::from("__grit_implicit_pack_store")));
185 *i.borrow_mut() = Some(Arc::clone(&s));
186 s
187 })
188 }
189
190 pub(crate) fn legacy_for_objects_dir(objects_dir: &Path) -> Arc<Self> {
192 LEGACY_BY_DIR.with(|map| {
193 let key = objects_dir.to_path_buf();
194 if let Some(s) = map.borrow().get(&key) {
195 return Arc::clone(s);
196 }
197 test_hooks::record_legacy_store_created();
198 let store = Arc::new(Self::new(key.clone()));
199 map.borrow_mut().insert(key, Arc::clone(&store));
200 store
201 })
202 }
203
204 pub(crate) fn legacy_for_pack_path(pack_path: &Path) -> Arc<Self> {
206 let objects_dir = pack_path.parent().and_then(|pack_dir| {
207 (pack_dir.file_name() == Some(std::ffi::OsStr::new("pack")))
208 .then(|| pack_dir.parent())
209 .flatten()
210 });
211 match objects_dir {
212 Some(dir) => Self::legacy_for_objects_dir(dir),
213 None => IMPLICIT.with(|i| {
214 if let Some(s) = i.borrow().as_ref() {
215 return Arc::clone(s);
216 }
217 test_hooks::record_implicit_store_created();
218 let s = Arc::new(Self::new(PathBuf::from("__grit_nonstandard_pack_cache")));
219 *i.borrow_mut() = Some(Arc::clone(&s));
220 s
221 }),
222 }
223 }
224
225 #[must_use]
227 pub fn objects_dir(&self) -> &Path {
228 &self.objects_dir
229 }
230
231 #[must_use]
233 pub fn has_context() -> bool {
234 CURRENT.with(|c| c.borrow().is_some())
235 }
236
237 #[must_use]
239 pub(crate) fn read_context_matches_objects_dir(objects_dir: &Path) -> bool {
240 Self::has_context()
241 && ACTIVE_OBJECTS_DIR.with(|d| d.borrow().as_deref() == Some(objects_dir))
242 }
243
244 pub fn with_context<R>(store: Arc<Self>, f: impl FnOnce() -> R) -> R {
246 let _guard = PackReadContextGuard::install(store);
247 f()
248 }
249
250 pub fn invalidate_packs(&self) {
252 crate::pack::pack_cache::clear_catalog_on(&self.catalog);
253 self.delta_bases.clear();
254 let epoch = self.bump_cache_epoch();
255 crate::pack::pack_cache::clear_thread_local_after_invalidation(epoch);
256 }
257
258 pub fn invalidate_midx(&self) {
260 crate::midx::midx_cache::evict_pack_dir_on(&self.midx, &self.objects_dir.join("pack"));
261 }
262
263 pub fn invalidate_all(&self) {
265 self.invalidate_packs();
266 self.invalidate_midx();
267 }
268
269 pub fn configure_delta_base_from_config(&self, cfg: Option<&crate::config::ConfigSet>) {
271 crate::pack::pack_cache::configure_delta_base_on(&self.delta_bases, cfg);
272 }
273
274 pub(crate) fn with_pack<R>(
275 &self,
276 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
277 ) -> R {
278 self.with_catalog(f)
279 }
280
281 pub(crate) fn with_midx<R>(
282 &self,
283 f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
284 ) -> R {
285 f(&mut self.midx.lock().unwrap_or_else(|e| e.into_inner()))
286 }
287
288 pub(crate) fn with_current_or_implicit<R>(
290 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
291 ) -> R {
292 Self::active_for_reads().with_pack(f)
293 }
294
295 pub(crate) fn with_current_or_implicit_midx<R>(
296 f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
297 ) -> R {
298 if let Some(store) = CURRENT.with(|c| c.borrow().clone()) {
299 return store.with_midx(f);
300 }
301 if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
302 return Self::legacy_for_objects_dir(&dir).with_midx(f);
303 }
304 Self::active_for_reads().with_midx(f)
305 }
306
307 pub fn invalidate_all_legacy() {
309 LEGACY_BY_DIR.with(|map| {
310 for store in map.borrow().values() {
311 store.invalidate_all();
312 }
313 map.borrow_mut().clear();
314 });
315 IMPLICIT.with(|i| *i.borrow_mut() = None);
316 }
317
318 pub(crate) fn invalidate_legacy_for_objects_dir(objects_dir: &Path) {
320 LEGACY_BY_DIR.with(|map| {
321 if let Some(store) = map.borrow().get(objects_dir) {
322 store.invalidate_all();
323 }
324 });
325 }
326}
327
328pub(crate) struct PackReadContextGuard {
330 prev_store: Option<Arc<PackStore>>,
331 prev_dir: Option<PathBuf>,
332}
333
334impl PackReadContextGuard {
335 pub(crate) fn install(store: Arc<PackStore>) -> Self {
336 let prev_store = CURRENT.with(|c| c.borrow_mut().replace(Arc::clone(&store)));
337 let prev_dir =
338 ACTIVE_OBJECTS_DIR.with(|d| d.borrow_mut().replace(store.objects_dir().to_path_buf()));
339 crate::pack::pack_cache::warm_thread_local_from_store(&store);
340 Self {
341 prev_store,
342 prev_dir,
343 }
344 }
345}
346
347impl Drop for PackReadContextGuard {
348 fn drop(&mut self) {
349 crate::pack::clear_pack_read_info_cache();
350 ACTIVE_OBJECTS_DIR.with(|d| *d.borrow_mut() = self.prev_dir.take());
351 CURRENT.with(|c| *c.borrow_mut() = self.prev_store.take());
352 }
353}