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 pub(crate) sparse_read_files: crate::pack_map::SparseReadFiles,
56 bounded_reads: bool,
57 cache_epoch: AtomicU64,
59 catalog: RwLock<crate::pack::pack_cache::CatalogState>,
60 delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru,
61 midx: Mutex<crate::midx::midx_cache::State>,
62 repo_caches: RwLock<Option<Arc<crate::repo_caches::RepoCaches>>>,
64}
65
66pub(crate) const BOUNDED_DELTA_BASE_BYTES: usize = 16 * 1024 * 1024;
68
69const _: () = {
70 fn assert_send_sync<T: Send + Sync>() {}
71 let _ = assert_send_sync::<PackStore>;
72};
73
74thread_local! {
76 static CURRENT: RefCell<Option<Arc<PackStore>>> = const { RefCell::new(None) };
77 static ACTIVE_OBJECTS_DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
78 static LEGACY_BY_DIR: RefCell<HashMap<PathBuf, Arc<PackStore>>> = RefCell::new(HashMap::new());
80 static IMPLICIT: RefCell<Option<Arc<PackStore>>> = const { RefCell::new(None) };
81}
82
83impl PackStore {
84 #[must_use]
86 pub fn new(objects_dir: PathBuf) -> Self {
87 Self {
88 objects_dir,
89 sparse_read_files: crate::pack_map::SparseReadFiles::default(),
90 bounded_reads: false,
91 cache_epoch: AtomicU64::new(0),
92 catalog: RwLock::new(crate::pack::pack_cache::CatalogState::default()),
93 delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru::new(),
94 midx: Mutex::new(crate::midx::midx_cache::State::default()),
95 repo_caches: RwLock::new(None),
96 }
97 }
98
99 pub(crate) fn for_bounded_reads(objects_dir: PathBuf) -> Self {
103 let mut store = Self::new(objects_dir);
104 store.bounded_reads = true;
105 store.delta_bases.set_byte_limit(BOUNDED_DELTA_BASE_BYTES);
106 store
107 }
108
109 pub(crate) fn bounded_reads(&self) -> bool {
110 self.bounded_reads
111 }
112
113 pub(crate) fn set_repo_caches(&self, caches: Arc<crate::repo_caches::RepoCaches>) {
115 if let Ok(mut guard) = self.repo_caches.write() {
116 *guard = Some(caches);
117 }
118 }
119
120 fn repo_caches(&self) -> Option<Arc<crate::repo_caches::RepoCaches>> {
121 self.repo_caches
122 .read()
123 .ok()
124 .and_then(|g| g.as_ref().map(Arc::clone))
125 }
126
127 pub(crate) fn repo_caches_for_objects_dir(
129 objects_dir: &Path,
130 ) -> Option<Arc<crate::repo_caches::RepoCaches>> {
131 if Self::has_context() {
132 let store = Self::active_for_reads();
133 if store.objects_dir() == objects_dir {
134 return store.repo_caches();
135 }
136 }
137 LEGACY_BY_DIR.with(|map| {
138 map.borrow()
139 .get(objects_dir)
140 .and_then(|store| store.repo_caches())
141 })
142 }
143
144 pub(crate) fn repo_caches_for_config_path(
146 config_path: &Path,
147 ) -> Option<Arc<crate::repo_caches::RepoCaches>> {
148 let git_dir = config_path.parent()?;
149 Self::repo_caches_for_objects_dir(&git_dir.join("objects"))
150 }
151
152 #[must_use]
154 pub(crate) fn cache_epoch(&self) -> u64 {
155 self.cache_epoch.load(Ordering::Acquire)
156 }
157
158 pub(crate) fn bump_cache_epoch(&self) -> u64 {
159 self.cache_epoch.fetch_add(1, Ordering::Release) + 1
160 }
161
162 pub(crate) fn with_catalog<R>(
164 &self,
165 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
166 ) -> R {
167 f(&mut self.catalog.write().unwrap_or_else(|e| e.into_inner()))
168 }
169
170 pub(crate) fn with_catalog_read<R>(
171 &self,
172 f: impl FnOnce(&crate::pack::pack_cache::CatalogState) -> R,
173 ) -> R {
174 f(&self.catalog.read().unwrap_or_else(|e| e.into_inner()))
175 }
176
177 pub(crate) fn try_with_catalog<R>(
179 &self,
180 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
181 ) -> Option<R> {
182 let mut guard = self.catalog.try_write().ok()?;
183 Some(f(&mut guard))
184 }
185
186 #[must_use]
188 pub(crate) fn delta_bases(&self) -> &crate::pack::pack_cache::ShardedDeltaBaseLru {
189 &self.delta_bases
190 }
191
192 pub(crate) fn active_for_reads() -> Arc<Self> {
194 if let Some(store) = CURRENT.with(|c| c.borrow().clone()) {
195 return store;
196 }
197 if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
198 return Self::legacy_for_objects_dir(&dir);
199 }
200 IMPLICIT.with(|i| {
201 if let Some(s) = i.borrow().as_ref() {
202 return Arc::clone(s);
203 }
204 test_hooks::record_implicit_store_created();
205 let s = Arc::new(Self::new(PathBuf::from("__grit_implicit_pack_store")));
206 *i.borrow_mut() = Some(Arc::clone(&s));
207 s
208 })
209 }
210
211 pub(crate) fn legacy_for_objects_dir(objects_dir: &Path) -> Arc<Self> {
213 LEGACY_BY_DIR.with(|map| {
214 let key = objects_dir.to_path_buf();
215 if let Some(s) = map.borrow().get(&key) {
216 return Arc::clone(s);
217 }
218 test_hooks::record_legacy_store_created();
219 let store = Arc::new(Self::new(key.clone()));
220 map.borrow_mut().insert(key, Arc::clone(&store));
221 store
222 })
223 }
224
225 pub(crate) fn legacy_for_pack_path(pack_path: &Path) -> Arc<Self> {
227 let objects_dir = pack_path.parent().and_then(|pack_dir| {
228 (pack_dir.file_name() == Some(std::ffi::OsStr::new("pack")))
229 .then(|| pack_dir.parent())
230 .flatten()
231 });
232 match objects_dir {
233 Some(dir) => Self::legacy_for_objects_dir(dir),
234 None => IMPLICIT.with(|i| {
235 if let Some(s) = i.borrow().as_ref() {
236 return Arc::clone(s);
237 }
238 test_hooks::record_implicit_store_created();
239 let s = Arc::new(Self::new(PathBuf::from("__grit_nonstandard_pack_cache")));
240 *i.borrow_mut() = Some(Arc::clone(&s));
241 s
242 }),
243 }
244 }
245
246 #[must_use]
248 pub fn objects_dir(&self) -> &Path {
249 &self.objects_dir
250 }
251
252 #[must_use]
254 pub fn has_context() -> bool {
255 CURRENT.with(|c| c.borrow().is_some())
256 }
257
258 #[must_use]
260 pub(crate) fn read_context_matches_objects_dir(objects_dir: &Path) -> bool {
261 Self::has_context()
262 && ACTIVE_OBJECTS_DIR.with(|d| d.borrow().as_deref() == Some(objects_dir))
263 }
264
265 pub fn with_context<R>(store: Arc<Self>, f: impl FnOnce() -> R) -> R {
270 if CURRENT.with(|current| {
271 current
272 .borrow()
273 .as_ref()
274 .is_some_and(|active| Arc::ptr_eq(active, &store))
275 }) {
276 return f();
277 }
278 let _guard = PackReadContextGuard::install(store);
279 f()
280 }
281
282 pub fn invalidate_packs(&self) {
284 crate::pack::pack_cache::clear_catalog_on(&self.catalog);
285 self.delta_bases.clear();
286 self.sparse_read_files.clear();
287 let epoch = self.bump_cache_epoch();
288 crate::pack::pack_cache::clear_thread_local_after_invalidation(epoch);
289 }
290
291 pub fn invalidate_midx(&self) {
293 crate::midx::midx_cache::evict_pack_dir_on(&self.midx, &self.objects_dir.join("pack"));
294 }
295
296 pub fn invalidate_all(&self) {
298 self.invalidate_packs();
299 self.invalidate_midx();
300 }
301
302 pub fn configure_delta_base_from_config(&self, cfg: Option<&crate::config::ConfigSet>) {
304 crate::pack::pack_cache::configure_delta_base_on(&self.delta_bases, cfg);
305 }
306
307 pub(crate) fn with_pack<R>(
308 &self,
309 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
310 ) -> R {
311 self.with_catalog(f)
312 }
313
314 pub(crate) fn with_midx<R>(
315 &self,
316 f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
317 ) -> R {
318 f(&mut self.midx.lock().unwrap_or_else(|e| e.into_inner()))
319 }
320
321 pub(crate) fn with_current_or_implicit<R>(
323 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
324 ) -> R {
325 Self::active_for_reads().with_pack(f)
326 }
327
328 pub(crate) fn with_current_or_implicit_midx<R>(
329 f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
330 ) -> R {
331 if let Some(store) = CURRENT.with(|c| c.borrow().clone()) {
332 return store.with_midx(f);
333 }
334 if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
335 return Self::legacy_for_objects_dir(&dir).with_midx(f);
336 }
337 Self::active_for_reads().with_midx(f)
338 }
339
340 pub fn invalidate_all_legacy() {
342 LEGACY_BY_DIR.with(|map| {
343 for store in map.borrow().values() {
344 store.invalidate_all();
345 }
346 map.borrow_mut().clear();
347 });
348 IMPLICIT.with(|i| *i.borrow_mut() = None);
349 }
350
351 pub(crate) fn invalidate_legacy_for_objects_dir(objects_dir: &Path) {
353 LEGACY_BY_DIR.with(|map| {
354 if let Some(store) = map.borrow().get(objects_dir) {
355 store.invalidate_all();
356 }
357 });
358 }
359}
360
361pub(crate) struct PackReadContextGuard {
363 prev_store: Option<Arc<PackStore>>,
364 prev_dir: Option<PathBuf>,
365}
366
367impl PackReadContextGuard {
368 pub(crate) fn install(store: Arc<PackStore>) -> Self {
369 let prev_store = CURRENT.with(|c| c.borrow_mut().replace(Arc::clone(&store)));
370 let prev_dir =
371 ACTIVE_OBJECTS_DIR.with(|d| d.borrow_mut().replace(store.objects_dir().to_path_buf()));
372 crate::pack::pack_cache::warm_thread_local_from_store(&store);
373 Self {
374 prev_store,
375 prev_dir,
376 }
377 }
378}
379
380impl Drop for PackReadContextGuard {
381 fn drop(&mut self) {
382 if CURRENT.with(|c| c.borrow().as_ref().is_some_and(|s| s.bounded_reads())) {
383 crate::pack::pack_cache::clear_thread_local_after_invalidation(0);
386 }
387 crate::pack::clear_pack_read_info_cache();
388 ACTIVE_OBJECTS_DIR.with(|d| *d.borrow_mut() = self.prev_dir.take());
389 CURRENT.with(|c| *c.borrow_mut() = self.prev_store.take());
390 }
391}