1use std::cell::RefCell;
14use std::collections::HashMap;
15use std::path::{Path, PathBuf};
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::{Arc, Mutex, RwLock};
18
19#[cfg(any(test, feature = "test-support"))]
20pub(crate) use crate::test_support::pack_store::{
21 record_implicit_store_created, record_legacy_store_created,
22};
23
24#[cfg(not(any(test, feature = "test-support")))]
25pub(crate) fn record_legacy_store_created() {}
26
27#[cfg(not(any(test, feature = "test-support")))]
28pub(crate) fn record_implicit_store_created() {}
29
30pub struct PackStore {
36 objects_dir: PathBuf,
37 id: u64,
40 lifetime: Arc<()>,
42 pub(crate) sparse_read_files: crate::pack_map::SparseReadFiles,
43 bounded_reads: bool,
44 cache_epoch: AtomicU64,
46 catalog: RwLock<crate::pack::pack_cache::CatalogState>,
47 delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru,
48 midx: Mutex<crate::midx::midx_cache::State>,
49 repo_caches: RwLock<Option<Arc<crate::repo_caches::RepoCaches>>>,
51}
52
53pub(crate) const BOUNDED_DELTA_BASE_BYTES: usize = 16 * 1024 * 1024;
55
56const _: () = {
57 fn assert_send_sync<T: Send + Sync>() {}
58 let _ = assert_send_sync::<PackStore>;
59};
60
61struct CurrentPackStore(Option<Arc<PackStore>>);
63
64impl Drop for CurrentPackStore {
65 fn drop(&mut self) {
66 crate::pack::pack_cache::set_skip_store_tls_release(true);
67 self.0.take();
68 }
69}
70
71struct LegacyStoresByDir(HashMap<PathBuf, Arc<PackStore>>);
72
73impl Drop for LegacyStoresByDir {
74 fn drop(&mut self) {
75 crate::pack::pack_cache::set_skip_store_tls_release(true);
76 self.0.clear();
77 }
78}
79
80struct ImplicitPackStore(Option<Arc<PackStore>>);
81
82impl Drop for ImplicitPackStore {
83 fn drop(&mut self) {
84 crate::pack::pack_cache::set_skip_store_tls_release(true);
85 self.0.take();
86 }
87}
88
89thread_local! {
91 static CURRENT: RefCell<CurrentPackStore> = const { RefCell::new(CurrentPackStore(None)) };
92 static ACTIVE_OBJECTS_DIR: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
93 static LEGACY_BY_DIR: RefCell<LegacyStoresByDir> =
95 RefCell::new(LegacyStoresByDir(HashMap::new()));
96 static IMPLICIT: RefCell<ImplicitPackStore> = const { RefCell::new(ImplicitPackStore(None)) };
97}
98
99impl PackStore {
100 #[must_use]
102 pub fn new(objects_dir: PathBuf) -> Self {
103 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
104 Self {
105 objects_dir,
106 id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
107 lifetime: Arc::new(()),
108 sparse_read_files: crate::pack_map::SparseReadFiles::default(),
109 bounded_reads: false,
110 cache_epoch: AtomicU64::new(0),
111 catalog: RwLock::new(crate::pack::pack_cache::CatalogState::default()),
112 delta_bases: crate::pack::pack_cache::ShardedDeltaBaseLru::new(),
113 midx: Mutex::new(crate::midx::midx_cache::State::default()),
114 repo_caches: RwLock::new(None),
115 }
116 }
117
118 pub(crate) fn for_bounded_reads(objects_dir: PathBuf) -> Self {
122 let mut store = Self::new(objects_dir);
123 store.bounded_reads = true;
124 store.delta_bases.set_byte_limit(BOUNDED_DELTA_BASE_BYTES);
125 store
126 }
127
128 pub(crate) fn bounded_reads(&self) -> bool {
129 self.bounded_reads
130 }
131
132 pub(crate) fn set_repo_caches(&self, caches: Arc<crate::repo_caches::RepoCaches>) {
134 if let Ok(mut guard) = self.repo_caches.write() {
135 *guard = Some(caches);
136 }
137 }
138
139 fn repo_caches(&self) -> Option<Arc<crate::repo_caches::RepoCaches>> {
140 self.repo_caches
141 .read()
142 .ok()
143 .and_then(|g| g.as_ref().map(Arc::clone))
144 }
145
146 pub(crate) fn repo_caches_for_objects_dir(
148 objects_dir: &Path,
149 ) -> Option<Arc<crate::repo_caches::RepoCaches>> {
150 if Self::has_context() {
151 let store = Self::active_for_reads();
152 if store.objects_dir() == objects_dir {
153 return store.repo_caches();
154 }
155 }
156 LEGACY_BY_DIR.with(|map| {
157 map.borrow()
158 .0
159 .get(objects_dir)
160 .and_then(|store| store.repo_caches())
161 })
162 }
163
164 pub(crate) fn repo_caches_for_config_path(
166 config_path: &Path,
167 ) -> Option<Arc<crate::repo_caches::RepoCaches>> {
168 let git_dir = config_path.parent()?;
169 Self::repo_caches_for_objects_dir(&git_dir.join("objects"))
170 }
171
172 pub(crate) fn id(&self) -> u64 {
174 self.id
175 }
176
177 pub(crate) fn lifetime_weak(&self) -> std::sync::Weak<()> {
178 Arc::downgrade(&self.lifetime)
179 }
180
181 #[must_use]
183 pub(crate) fn cache_epoch(&self) -> u64 {
184 self.cache_epoch.load(Ordering::Acquire)
185 }
186
187 pub(crate) fn bump_cache_epoch(&self) -> u64 {
188 self.cache_epoch.fetch_add(1, Ordering::Release) + 1
189 }
190
191 pub(crate) fn with_catalog<R>(
193 &self,
194 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
195 ) -> R {
196 f(&mut self.catalog.write().unwrap_or_else(|e| e.into_inner()))
197 }
198
199 pub(crate) fn with_catalog_read<R>(
200 &self,
201 f: impl FnOnce(&crate::pack::pack_cache::CatalogState) -> R,
202 ) -> R {
203 f(&self.catalog.read().unwrap_or_else(|e| e.into_inner()))
204 }
205
206 pub(crate) fn try_with_catalog<R>(
208 &self,
209 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
210 ) -> Option<R> {
211 let mut guard = self.catalog.try_write().ok()?;
212 Some(f(&mut guard))
213 }
214
215 #[must_use]
217 pub(crate) fn delta_bases(&self) -> &crate::pack::pack_cache::ShardedDeltaBaseLru {
218 &self.delta_bases
219 }
220
221 pub(crate) fn active_for_reads() -> Arc<Self> {
223 if let Some(store) = CURRENT.with(|c| c.borrow().0.clone()) {
224 return store;
225 }
226 if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
227 return Self::legacy_for_objects_dir(&dir);
228 }
229 IMPLICIT.with(|i| {
230 if let Some(s) = i.borrow().0.as_ref() {
231 return Arc::clone(s);
232 }
233 record_implicit_store_created();
234 let s = Arc::new(Self::new(PathBuf::from("__grit_implicit_pack_store")));
235 i.borrow_mut().0 = Some(Arc::clone(&s));
236 s
237 })
238 }
239
240 pub(crate) fn legacy_for_objects_dir(objects_dir: &Path) -> Arc<Self> {
242 LEGACY_BY_DIR.with(|map| {
243 let key = objects_dir.to_path_buf();
244 if let Some(s) = map.borrow().0.get(&key) {
245 return Arc::clone(s);
246 }
247 record_legacy_store_created();
248 let store = Arc::new(Self::new(key.clone()));
249 map.borrow_mut().0.insert(key, Arc::clone(&store));
250 store
251 })
252 }
253
254 pub(crate) fn legacy_for_pack_path(pack_path: &Path) -> Arc<Self> {
256 let objects_dir = pack_path.parent().and_then(|pack_dir| {
257 (pack_dir.file_name() == Some(std::ffi::OsStr::new("pack")))
258 .then(|| pack_dir.parent())
259 .flatten()
260 });
261 match objects_dir {
262 Some(dir) => Self::legacy_for_objects_dir(dir),
263 None => IMPLICIT.with(|i| {
264 if let Some(s) = i.borrow().0.as_ref() {
265 return Arc::clone(s);
266 }
267 record_implicit_store_created();
268 let s = Arc::new(Self::new(PathBuf::from("__grit_nonstandard_pack_cache")));
269 i.borrow_mut().0 = Some(Arc::clone(&s));
270 s
271 }),
272 }
273 }
274
275 #[must_use]
277 pub fn objects_dir(&self) -> &Path {
278 &self.objects_dir
279 }
280
281 #[must_use]
283 pub fn has_context() -> bool {
284 CURRENT.with(|c| c.borrow().0.is_some())
285 }
286
287 #[must_use]
289 pub(crate) fn read_context_matches_objects_dir(objects_dir: &Path) -> bool {
290 Self::has_context()
291 && ACTIVE_OBJECTS_DIR.with(|d| d.borrow().as_deref() == Some(objects_dir))
292 }
293
294 pub fn with_context<R>(store: Arc<Self>, f: impl FnOnce() -> R) -> R {
299 if CURRENT.with(|current| {
300 current
301 .borrow()
302 .0
303 .as_ref()
304 .is_some_and(|active| Arc::ptr_eq(active, &store))
305 }) {
306 return f();
307 }
308 let _guard = PackReadContextGuard::install(store);
309 f()
310 }
311
312 pub fn invalidate_packs(&self) {
314 crate::pack::pack_cache::clear_catalog_on(&self.catalog);
315 self.delta_bases.clear();
316 self.sparse_read_files.clear();
317 let epoch = self.bump_cache_epoch();
318 crate::pack::pack_cache::drop_store_tls_snapshot(self.id());
319 crate::pack::pack_cache::clear_thread_local_after_invalidation(epoch);
320 }
321
322 pub fn invalidate_midx(&self) {
324 crate::midx::midx_cache::evict_pack_dir_on(&self.midx, &self.objects_dir.join("pack"));
325 }
326
327 pub fn invalidate_all(&self) {
329 self.invalidate_packs();
330 self.invalidate_midx();
331 }
332
333 pub fn configure_delta_base_from_config(&self, cfg: Option<&crate::config::ConfigSet>) {
335 crate::pack::pack_cache::configure_delta_base_on(&self.delta_bases, cfg);
336 }
337
338 pub(crate) fn with_pack<R>(
339 &self,
340 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
341 ) -> R {
342 self.with_catalog(f)
343 }
344
345 pub(crate) fn with_midx<R>(
346 &self,
347 f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
348 ) -> R {
349 f(&mut self.midx.lock().unwrap_or_else(|e| e.into_inner()))
350 }
351
352 pub(crate) fn with_current_or_implicit<R>(
354 f: impl FnOnce(&mut crate::pack::pack_cache::CatalogState) -> R,
355 ) -> R {
356 Self::active_for_reads().with_pack(f)
357 }
358
359 pub(crate) fn with_current_or_implicit_midx<R>(
360 f: impl FnOnce(&mut crate::midx::midx_cache::State) -> R,
361 ) -> R {
362 if let Some(store) = CURRENT.with(|c| c.borrow().0.clone()) {
363 return store.with_midx(f);
364 }
365 if let Some(dir) = ACTIVE_OBJECTS_DIR.with(|d| d.borrow().clone()) {
366 return Self::legacy_for_objects_dir(&dir).with_midx(f);
367 }
368 Self::active_for_reads().with_midx(f)
369 }
370
371 pub fn invalidate_all_legacy() {
373 LEGACY_BY_DIR.with(|map| {
374 for store in map.borrow().0.values() {
375 store.invalidate_all();
376 }
377 map.borrow_mut().0.clear();
378 });
379 IMPLICIT.with(|i| i.borrow_mut().0 = None);
380 }
381
382 pub(crate) fn invalidate_legacy_for_objects_dir(objects_dir: &Path) {
384 LEGACY_BY_DIR.with(|map| {
385 if let Some(store) = map.borrow().0.get(objects_dir) {
386 store.invalidate_all();
387 }
388 });
389 }
390}
391
392impl Drop for PackStore {
393 fn drop(&mut self) {
394 crate::pack::pack_cache::release_store_thread_local(self.id);
395 }
396}
397
398pub(crate) struct PackReadContextGuard {
400 prev_store: Option<Arc<PackStore>>,
401 prev_dir: Option<PathBuf>,
402}
403
404impl PackReadContextGuard {
405 pub(crate) fn install(store: Arc<PackStore>) -> Self {
406 let prev_store = CURRENT.with(|c| c.borrow_mut().0.replace(Arc::clone(&store)));
407 let prev_dir =
408 ACTIVE_OBJECTS_DIR.with(|d| d.borrow_mut().replace(store.objects_dir().to_path_buf()));
409 crate::pack::pack_cache::warm_thread_local_from_store(&store);
410 Self {
411 prev_store,
412 prev_dir,
413 }
414 }
415}
416
417impl Drop for PackReadContextGuard {
418 fn drop(&mut self) {
419 crate::pack::pack_cache::clear_alternate_pack_read_store();
420 let bounded = CURRENT.with(|c| c.borrow().0.as_ref().is_some_and(|s| s.bounded_reads()));
421 if bounded {
422 crate::pack::pack_cache::clear_thread_local_after_invalidation(0);
425 crate::pack::clear_pack_read_info_cache();
426 }
427 ACTIVE_OBJECTS_DIR.with(|d| *d.borrow_mut() = self.prev_dir.take());
428 let prev_store = self.prev_store.take();
429 CURRENT.with(|c| c.borrow_mut().0 = prev_store.clone());
430 if bounded {
431 return;
432 }
433 if let Some(store) = prev_store {
434 crate::pack::pack_cache::switch_tls_for_store(&store);
435 }
436 }
437}