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