solana_accounts_db/
account_storage.rs

1//! Manage the map of slot -> append vec
2
3use {
4    crate::accounts_db::{AccountStorageEntry, AccountsFileId},
5    dashmap::DashMap,
6    solana_clock::Slot,
7    solana_nohash_hasher::BuildNoHashHasher,
8    std::sync::Arc,
9};
10
11pub mod meta;
12
13#[derive(Clone, Debug)]
14pub struct AccountStorageReference {
15    /// the single storage for a given slot
16    pub storage: Arc<AccountStorageEntry>,
17    /// id can be read from 'storage', but it is an atomic read.
18    /// id will never change while a storage is held, so we store it separately here for faster runtime lookup in 'get_account_storage_entry'
19    pub id: AccountsFileId,
20}
21
22pub type AccountStorageMap = DashMap<Slot, AccountStorageReference, BuildNoHashHasher<Slot>>;
23
24#[derive(Default, Debug)]
25pub struct AccountStorage {
26    /// map from Slot -> the single append vec for the slot
27    map: AccountStorageMap,
28    /// while shrink is operating on a slot, there can be 2 append vecs active for that slot
29    /// Once the index has been updated to only refer to the new append vec, the single entry for the slot in 'map' can be updated.
30    /// Entries in 'shrink_in_progress_map' can be found by 'get_account_storage_entry'
31    shrink_in_progress_map: DashMap<Slot, Arc<AccountStorageEntry>, BuildNoHashHasher<Slot>>,
32}
33
34impl AccountStorage {
35    /// Return the append vec in 'slot' and with id='store_id'.
36    /// can look in 'map' and 'shrink_in_progress_map' to find the specified append vec
37    /// when shrinking begins, shrinking_in_progress is called.
38    /// This fn looks in 'map' first, then in 'shrink_in_progress_map', then in 'map' again because
39    /// 'shrinking_in_progress' first inserts the new append vec into 'shrink_in_progress_map'
40    /// Then, when 'shrink_in_progress' is dropped,
41    /// the old append vec is replaced in 'map' with the new append vec
42    /// then the new append vec is dropped from 'shrink_in_progress_map'.
43    /// So, it is possible for a race with this fn and dropping 'shrink_in_progress'.
44    /// Callers to this function have 2 choices:
45    /// 1. hold the account index read lock for the pubkey so that the account index entry cannot be changed prior to or during this call. (scans do this)
46    /// 2. expect to be ready to start over and read the index again if this function returns None
47    ///
48    /// Operations like shrinking or write cache flushing may have updated the index between when the caller read the index and called this function to
49    /// load from the append vec specified in the index.
50    ///
51    /// In practice, this fn will return the entry from the map in the very first lookup unless a shrink is in progress.
52    /// The third lookup will only be called if a requesting thread exactly interposes itself between the 2 map manipulations in the drop of 'shrink_in_progress'.
53    pub(crate) fn get_account_storage_entry(
54        &self,
55        slot: Slot,
56        store_id: AccountsFileId,
57    ) -> Option<Arc<AccountStorageEntry>> {
58        let lookup_in_map = || {
59            self.map
60                .get(&slot)
61                .and_then(|r| (r.id == store_id).then_some(Arc::clone(&r.storage)))
62        };
63
64        lookup_in_map()
65            .or_else(|| {
66                self.shrink_in_progress_map.get(&slot).and_then(|entry| {
67                    (entry.value().id() == store_id).then(|| Arc::clone(entry.value()))
68                })
69            })
70            .or_else(lookup_in_map)
71    }
72
73    /// returns true if shrink in progress is NOT active
74    pub(crate) fn no_shrink_in_progress(&self) -> bool {
75        self.shrink_in_progress_map.is_empty()
76    }
77
78    /// return the append vec for 'slot' if it exists
79    /// This is only ever called when shrink is not possibly running and there is a max of 1 append vec per slot.
80    pub fn get_slot_storage_entry(&self, slot: Slot) -> Option<Arc<AccountStorageEntry>> {
81        assert!(
82            self.no_shrink_in_progress(),
83            "self.no_shrink_in_progress(): {slot}"
84        );
85        self.get_slot_storage_entry_shrinking_in_progress_ok(slot)
86    }
87
88    pub(crate) fn replace_storage_with_equivalent(
89        &self,
90        slot: Slot,
91        storage: Arc<AccountStorageEntry>,
92    ) {
93        assert_eq!(storage.slot(), slot);
94        if let Some(mut existing_storage) = self.map.get_mut(&slot) {
95            assert_eq!(slot, existing_storage.value().storage.slot());
96            existing_storage.value_mut().storage = storage;
97        }
98    }
99
100    /// return the append vec for 'slot' if it exists
101    pub(crate) fn get_slot_storage_entry_shrinking_in_progress_ok(
102        &self,
103        slot: Slot,
104    ) -> Option<Arc<AccountStorageEntry>> {
105        self.map.get(&slot).map(|entry| Arc::clone(&entry.storage))
106    }
107
108    pub(crate) fn all_slots(&self) -> Vec<Slot> {
109        assert!(self.no_shrink_in_progress());
110        self.map.iter().map(|iter_item| *iter_item.key()).collect()
111    }
112
113    /// returns true if there is no entry for 'slot'
114    #[cfg(test)]
115    pub(crate) fn is_empty_entry(&self, slot: Slot) -> bool {
116        assert!(
117            self.no_shrink_in_progress(),
118            "self.no_shrink_in_progress(): {slot}"
119        );
120        self.map.get(&slot).is_none()
121    }
122
123    /// initialize the storage map to 'all_storages'
124    pub fn initialize(&mut self, all_storages: AccountStorageMap) {
125        assert!(self.map.is_empty());
126        assert!(self.no_shrink_in_progress());
127        self.map.extend(all_storages)
128    }
129
130    /// remove the append vec at 'slot'
131    /// returns the current contents
132    pub(crate) fn remove(
133        &self,
134        slot: &Slot,
135        shrink_can_be_active: bool,
136    ) -> Option<Arc<AccountStorageEntry>> {
137        assert!(shrink_can_be_active || self.shrink_in_progress_map.is_empty());
138        self.map.remove(slot).map(|(_, entry)| entry.storage)
139    }
140
141    /// iterate through all (slot, append-vec)
142    pub(crate) fn iter(&self) -> AccountStorageIter<'_> {
143        assert!(self.no_shrink_in_progress());
144        AccountStorageIter::new(self)
145    }
146
147    pub(crate) fn insert(&self, slot: Slot, store: Arc<AccountStorageEntry>) {
148        assert!(
149            self.no_shrink_in_progress(),
150            "self.no_shrink_in_progress(): {slot}"
151        );
152        assert!(self
153            .map
154            .insert(
155                slot,
156                AccountStorageReference {
157                    id: store.id(),
158                    storage: store,
159                }
160            )
161            .is_none());
162    }
163
164    /// called when shrinking begins on a slot and append vec.
165    /// When 'ShrinkInProgress' is dropped by caller, the old store will be replaced with 'new_store' in the storage map.
166    /// Fails if there are no existing stores at the slot.
167    /// 'new_store' will be replacing the current store at 'slot' in 'map'
168    /// So, insert 'new_store' into 'shrink_in_progress_map'.
169    /// This allows tx processing loads to find the items in 'shrink_in_progress_map' after the index is updated and item is now located in 'new_store'.
170    pub(crate) fn shrinking_in_progress(
171        &self,
172        slot: Slot,
173        new_store: Arc<AccountStorageEntry>,
174    ) -> ShrinkInProgress<'_> {
175        let shrinking_store = Arc::clone(
176            &self
177                .map
178                .get(&slot)
179                .expect("no pre-existing storage for shrinking slot")
180                .value()
181                .storage,
182        );
183
184        // insert 'new_store' into 'shrink_in_progress_map'
185        assert!(
186            self.shrink_in_progress_map
187                .insert(slot, Arc::clone(&new_store))
188                .is_none(),
189            "duplicate call"
190        );
191
192        ShrinkInProgress {
193            storage: self,
194            slot,
195            new_store,
196            old_store: shrinking_store,
197        }
198    }
199
200    #[cfg(test)]
201    pub(crate) fn len(&self) -> usize {
202        self.map.len()
203    }
204
205    /// Returns the (slot, storage) tuples where `predicate` returns `true`
206    ///
207    /// This function is useful when not all storages are desired,
208    /// as storages are only Arc::cloned if they pass the predicate.
209    ///
210    /// # Panics
211    ///
212    /// Panics if `shrink` is in progress.
213    pub fn get_if(
214        &self,
215        predicate: impl Fn(&Slot, &AccountStorageEntry) -> bool,
216    ) -> Box<[(Slot, Arc<AccountStorageEntry>)]> {
217        assert!(self.no_shrink_in_progress());
218        self.map
219            .iter()
220            .filter_map(|entry| {
221                let slot = entry.key();
222                let storage = &entry.value().storage;
223                predicate(slot, storage).then(|| (*slot, Arc::clone(storage)))
224            })
225            .collect()
226    }
227}
228
229/// iterate contents of AccountStorage without exposing internals
230pub struct AccountStorageIter<'a> {
231    iter: dashmap::iter::Iter<'a, Slot, AccountStorageReference, BuildNoHashHasher<Slot>>,
232}
233
234impl<'a> AccountStorageIter<'a> {
235    pub fn new(storage: &'a AccountStorage) -> Self {
236        Self {
237            iter: storage.map.iter(),
238        }
239    }
240}
241
242impl Iterator for AccountStorageIter<'_> {
243    type Item = (Slot, Arc<AccountStorageEntry>);
244
245    fn next(&mut self) -> Option<Self::Item> {
246        if let Some(entry) = self.iter.next() {
247            let slot = entry.key();
248            let store = entry.value();
249            return Some((*slot, Arc::clone(&store.storage)));
250        }
251        None
252    }
253}
254
255/// exists while there is a shrink in progress
256/// keeps track of the 'new_store' being created and the 'old_store' being replaced.
257#[derive(Debug)]
258pub struct ShrinkInProgress<'a> {
259    storage: &'a AccountStorage,
260    /// old store which will be shrunk and replaced
261    old_store: Arc<AccountStorageEntry>,
262    /// newly shrunk store with a subset of contents from 'old_store'
263    new_store: Arc<AccountStorageEntry>,
264    slot: Slot,
265}
266
267/// called when the shrink is no longer in progress. This means we can release the old append vec and update the map of slot -> append vec
268impl Drop for ShrinkInProgress<'_> {
269    fn drop(&mut self) {
270        assert_eq!(
271            self.storage
272                .map
273                .insert(
274                    self.slot,
275                    AccountStorageReference {
276                        storage: Arc::clone(&self.new_store),
277                        id: self.new_store.id()
278                    }
279                )
280                .map(|store| store.id),
281            Some(self.old_store.id())
282        );
283
284        // The new store can be removed from 'shrink_in_progress_map'
285        assert!(self
286            .storage
287            .shrink_in_progress_map
288            .remove(&self.slot)
289            .is_some());
290    }
291}
292
293impl ShrinkInProgress<'_> {
294    pub fn new_storage(&self) -> &Arc<AccountStorageEntry> {
295        &self.new_store
296    }
297    pub(crate) fn old_storage(&self) -> &Arc<AccountStorageEntry> {
298        &self.old_store
299    }
300}
301
302#[cfg_attr(feature = "frozen-abi", derive(AbiExample, AbiEnumVisitor))]
303#[derive(Debug, Eq, PartialEq, Copy, Clone, Deserialize, Serialize)]
304pub enum AccountStorageStatus {
305    Available = 0,
306    Full = 1,
307    Candidate = 2,
308}
309
310impl Default for AccountStorageStatus {
311    fn default() -> Self {
312        Self::Available
313    }
314}
315
316#[cfg(test)]
317pub(crate) mod tests {
318    use {
319        super::*,
320        crate::accounts_file::AccountsFileProvider,
321        std::{iter, path::Path},
322    };
323
324    #[test]
325    fn test_shrink_in_progress() {
326        // test that we check in order map then shrink_in_progress_map
327        let storage = AccountStorage::default();
328        let slot = 0;
329        let id = 0;
330        // empty everything
331        assert!(storage.get_account_storage_entry(slot, id).is_none());
332
333        // add a map store
334        let common_store_path = Path::new("");
335        let store_file_size = 4000;
336        let store_file_size2 = store_file_size * 2;
337        // 2 append vecs with same id, but different sizes
338        let entry = Arc::new(AccountStorageEntry::new(
339            common_store_path,
340            slot,
341            id,
342            store_file_size,
343            AccountsFileProvider::AppendVec,
344        ));
345        let entry2 = Arc::new(AccountStorageEntry::new(
346            common_store_path,
347            slot,
348            id,
349            store_file_size2,
350            AccountsFileProvider::AppendVec,
351        ));
352        storage
353            .map
354            .insert(slot, AccountStorageReference { id, storage: entry });
355
356        // look in map
357        assert_eq!(
358            store_file_size,
359            storage
360                .get_account_storage_entry(slot, id)
361                .map(|entry| entry.accounts.capacity())
362                .unwrap_or_default()
363        );
364
365        // look in shrink_in_progress_map
366        storage.shrink_in_progress_map.insert(slot, entry2);
367
368        // look in map
369        assert_eq!(
370            store_file_size,
371            storage
372                .get_account_storage_entry(slot, id)
373                .map(|entry| entry.accounts.capacity())
374                .unwrap_or_default()
375        );
376
377        // remove from map
378        storage.map.remove(&slot).unwrap();
379
380        // look in shrink_in_progress_map
381        assert_eq!(
382            store_file_size2,
383            storage
384                .get_account_storage_entry(slot, id)
385                .map(|entry| entry.accounts.capacity())
386                .unwrap_or_default()
387        );
388    }
389
390    impl AccountStorage {
391        fn get_test_storage_with_id(&self, id: AccountsFileId) -> Arc<AccountStorageEntry> {
392            let slot = 0;
393            // add a map store
394            let common_store_path = Path::new("");
395            let store_file_size = 4000;
396            Arc::new(AccountStorageEntry::new(
397                common_store_path,
398                slot,
399                id,
400                store_file_size,
401                AccountsFileProvider::AppendVec,
402            ))
403        }
404        fn get_test_storage(&self) -> Arc<AccountStorageEntry> {
405            self.get_test_storage_with_id(0)
406        }
407    }
408
409    #[test]
410    #[should_panic(expected = "self.no_shrink_in_progress()")]
411    fn test_get_slot_storage_entry_fail() {
412        let storage = AccountStorage::default();
413        storage
414            .shrink_in_progress_map
415            .insert(0, storage.get_test_storage());
416        storage.get_slot_storage_entry(0);
417    }
418
419    #[test]
420    #[should_panic(expected = "self.no_shrink_in_progress()")]
421    fn test_all_slots_fail() {
422        let storage = AccountStorage::default();
423        storage
424            .shrink_in_progress_map
425            .insert(0, storage.get_test_storage());
426        storage.all_slots();
427    }
428
429    #[test]
430    #[should_panic(expected = "self.no_shrink_in_progress()")]
431    fn test_initialize_fail() {
432        let mut storage = AccountStorage::default();
433        storage
434            .shrink_in_progress_map
435            .insert(0, storage.get_test_storage());
436        storage.initialize(AccountStorageMap::default());
437    }
438
439    #[test]
440    #[should_panic(expected = "shrink_can_be_active || self.shrink_in_progress_map.is_empty()")]
441    fn test_remove_fail() {
442        let storage = AccountStorage::default();
443        storage
444            .shrink_in_progress_map
445            .insert(0, storage.get_test_storage());
446        storage.remove(&0, false);
447    }
448
449    #[test]
450    #[should_panic(expected = "self.no_shrink_in_progress()")]
451    fn test_iter_fail() {
452        let storage = AccountStorage::default();
453        storage
454            .shrink_in_progress_map
455            .insert(0, storage.get_test_storage());
456        storage.iter();
457    }
458
459    #[test]
460    #[should_panic(expected = "self.no_shrink_in_progress()")]
461    fn test_insert_fail() {
462        let storage = AccountStorage::default();
463        let sample = storage.get_test_storage();
464        storage.shrink_in_progress_map.insert(0, sample.clone());
465        storage.insert(0, sample);
466    }
467
468    #[test]
469    #[should_panic(expected = "duplicate call")]
470    fn test_shrinking_in_progress_fail3() {
471        // already entry in shrink_in_progress_map
472        let storage = AccountStorage::default();
473        let sample = storage.get_test_storage();
474        storage.map.insert(
475            0,
476            AccountStorageReference {
477                id: 0,
478                storage: sample.clone(),
479            },
480        );
481        storage.shrink_in_progress_map.insert(0, sample.clone());
482        storage.shrinking_in_progress(0, sample);
483    }
484
485    #[test]
486    #[should_panic(expected = "duplicate call")]
487    fn test_shrinking_in_progress_fail4() {
488        // already called 'shrink_in_progress' on this slot and it is still active
489        let storage = AccountStorage::default();
490        let sample_to_shrink = storage.get_test_storage();
491        let sample = storage.get_test_storage();
492        storage.map.insert(
493            0,
494            AccountStorageReference {
495                id: 0,
496                storage: sample_to_shrink,
497            },
498        );
499        let _shrinking_in_progress = storage.shrinking_in_progress(0, sample.clone());
500        storage.shrinking_in_progress(0, sample);
501    }
502
503    #[test]
504    fn test_shrinking_in_progress_second_call() {
505        // already called 'shrink_in_progress' on this slot, but it finished, so we succeed
506        // verify data structures during and after shrink and then with subsequent shrink call
507        let storage = AccountStorage::default();
508        let slot = 0;
509        let id_to_shrink = 1;
510        let id_shrunk = 0;
511        let sample_to_shrink = storage.get_test_storage_with_id(id_to_shrink);
512        let sample = storage.get_test_storage();
513        storage.map.insert(
514            slot,
515            AccountStorageReference {
516                id: id_to_shrink,
517                storage: sample_to_shrink,
518            },
519        );
520        let shrinking_in_progress = storage.shrinking_in_progress(slot, sample.clone());
521        assert!(storage.map.contains_key(&slot));
522        assert_eq!(id_to_shrink, storage.map.get(&slot).unwrap().storage.id());
523        assert_eq!(
524            (slot, id_shrunk),
525            storage
526                .shrink_in_progress_map
527                .iter()
528                .next()
529                .map(|r| (*r.key(), r.value().id()))
530                .unwrap()
531        );
532        drop(shrinking_in_progress);
533        assert!(storage.map.contains_key(&slot));
534        assert_eq!(id_shrunk, storage.map.get(&slot).unwrap().storage.id());
535        assert!(storage.shrink_in_progress_map.is_empty());
536        storage.shrinking_in_progress(slot, sample);
537    }
538
539    #[test]
540    #[should_panic(expected = "no pre-existing storage for shrinking slot")]
541    fn test_shrinking_in_progress_fail1() {
542        // nothing in slot currently
543        let storage = AccountStorage::default();
544        let sample = storage.get_test_storage();
545        storage.shrinking_in_progress(0, sample);
546    }
547
548    #[test]
549    #[should_panic(expected = "no pre-existing storage for shrinking slot")]
550    fn test_shrinking_in_progress_fail2() {
551        // nothing in slot currently, but there is an empty map entry
552        let storage = AccountStorage::default();
553        let sample = storage.get_test_storage();
554        storage.shrinking_in_progress(0, sample);
555    }
556
557    #[test]
558    fn test_missing() {
559        // already called 'shrink_in_progress' on this slot, but it finished, so we succeed
560        // verify data structures during and after shrink and then with subsequent shrink call
561        let storage = AccountStorage::default();
562        let sample = storage.get_test_storage();
563        let id = sample.id();
564        let missing_id = 9999;
565        let slot = sample.slot();
566        // id is missing since not in maps at all
567        assert!(storage.get_account_storage_entry(slot, id).is_none());
568        // missing should always be missing
569        assert!(storage
570            .get_account_storage_entry(slot, missing_id)
571            .is_none());
572        storage.map.insert(
573            slot,
574            AccountStorageReference {
575                id,
576                storage: sample.clone(),
577            },
578        );
579        // id is found in map
580        assert!(storage.get_account_storage_entry(slot, id).is_some());
581        assert!(storage
582            .get_account_storage_entry(slot, missing_id)
583            .is_none());
584        storage
585            .shrink_in_progress_map
586            .insert(slot, Arc::clone(&sample));
587        // id is found in map
588        assert!(storage
589            .get_account_storage_entry(slot, missing_id)
590            .is_none());
591        assert!(storage.get_account_storage_entry(slot, id).is_some());
592        storage.map.remove(&slot);
593        // id is found in shrink_in_progress_map
594        assert!(storage
595            .get_account_storage_entry(slot, missing_id)
596            .is_none());
597        assert!(storage.get_account_storage_entry(slot, id).is_some());
598    }
599
600    #[test]
601    fn test_get_if() {
602        let storage = AccountStorage::default();
603        assert!(storage.get_if(|_, _| true).is_empty());
604
605        // add some entries
606        let ids = [123, 456, 789];
607        for id in ids {
608            let slot = id as Slot;
609            let entry = AccountStorageEntry::new(
610                Path::new(""),
611                slot,
612                id,
613                5000,
614                AccountsFileProvider::AppendVec,
615            );
616            storage.map.insert(
617                slot,
618                AccountStorageReference {
619                    id,
620                    storage: entry.into(),
621                },
622            );
623        }
624
625        // look 'em up
626        for id in ids {
627            let found = storage.get_if(|slot, _| *slot == id as Slot);
628            assert!(found
629                .iter()
630                .map(|(slot, _)| *slot)
631                .eq(iter::once(id as Slot)));
632        }
633
634        assert!(storage.get_if(|_, _| false).is_empty());
635        assert_eq!(storage.get_if(|_, _| true).len(), ids.len());
636    }
637
638    #[test]
639    #[should_panic(expected = "self.no_shrink_in_progress()")]
640    fn test_get_if_fail() {
641        let storage = AccountStorage::default();
642        storage
643            .shrink_in_progress_map
644            .insert(0, storage.get_test_storage());
645        storage.get_if(|_, _| true);
646    }
647}