Skip to main content

snix_castore/blobstore/
memory.rs

1//! In-memory implementation, using a `HashMap`.
2
3use hashbrown::HashMap;
4use parking_lot::RwLock;
5use std::sync::Arc;
6use tonic::async_trait;
7use tracing::instrument;
8
9use crate::{
10    B3Digest,
11    blobstore::{BlobMeta, BlobStore},
12    composition::{CompositionContext, ServiceBuilder},
13};
14
15/// In-memory [BlobStore] implementation.
16///
17/// Uses a `HashMap<B3Digest, BlobMeta>` behind an `Arc<RwLock<_>>` to allow cloning and concurrent access.
18#[derive(Default, Clone)]
19pub struct MemoryBlobStore {
20    instance_name: String,
21    db: Arc<RwLock<HashMap<B3Digest, BlobMeta>>>,
22}
23
24#[async_trait]
25impl BlobStore for MemoryBlobStore {
26    #[instrument(skip_all, err, fields(blob.digest=%digest, instance_name=%self.instance_name))]
27    async fn get(&self, digest: &B3Digest) -> Result<Option<BlobMeta>, super::Error> {
28        Ok(self.db.read().get(digest).cloned())
29    }
30
31    #[instrument(skip_all, err, fields(blob.digest=%digest, instance_name=%self.instance_name))]
32    async fn has(&self, digest: &B3Digest) -> Result<bool, super::Error> {
33        Ok(self.db.read().contains_key(digest))
34    }
35    #[instrument(skip_all, err, fields(blob.digest=%digest, instance_name=%self.instance_name))]
36    async fn put(&self, digest: &B3Digest, blob_meta: BlobMeta) -> Result<(), super::Error> {
37        match self.db.write().entry(*digest) {
38            hashbrown::hash_map::Entry::Occupied(_) => {}
39            hashbrown::hash_map::Entry::Vacant(vacant_entry) => {
40                vacant_entry.insert_entry(blob_meta);
41            }
42        }
43
44        Ok(())
45    }
46}
47
48/// Config for [MemoryBlobStore].
49#[derive(serde::Deserialize, Debug)]
50#[serde(deny_unknown_fields)]
51pub struct MemoryBlobStoreConfig {}
52
53impl TryFrom<url::Url> for MemoryBlobStoreConfig {
54    type Error = Box<dyn std::error::Error + Send + Sync>;
55    fn try_from(url: url::Url) -> Result<Self, Self::Error> {
56        // memory doesn't support authority or path in the URL.
57        if url.has_authority() || !url.path().is_empty() {
58            return Err("invalid url".into());
59        }
60        Ok(MemoryBlobStoreConfig {})
61    }
62}
63
64#[async_trait]
65impl ServiceBuilder for MemoryBlobStoreConfig {
66    type Output = dyn BlobStore;
67    async fn build<'a>(
68        &'a self,
69        instance_name: &str,
70        _context: &CompositionContext,
71    ) -> Result<Arc<Self::Output>, Box<dyn std::error::Error + Send + Sync>> {
72        Ok(Arc::new(MemoryBlobStore {
73            instance_name: instance_name.to_string(),
74            db: Default::default(),
75        }))
76    }
77}