Skip to main content

snix_castore/chunkstore/
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    chunkstore::{Chunk, ChunkStore},
12    composition::{CompositionContext, ServiceBuilder},
13};
14
15/// In-memory [ChunkStore] implementation.
16///
17/// Uses a `HashMap<B3Digest, Chunk>` behind an `Arc<RwLock<_>>` to allow cloning and concurrent access.
18#[derive(Default, Clone)]
19pub struct MemoryChunkStore {
20    instance_name: String,
21    db: Arc<RwLock<HashMap<B3Digest, Chunk>>>,
22}
23
24impl MemoryChunkStore {
25    /// Constructs a new in-memory [ChunkStore] with the given `instance_name`.
26    pub fn new(instance_name: String) -> Self {
27        Self {
28            instance_name,
29            db: Default::default(),
30        }
31    }
32}
33
34#[async_trait]
35impl ChunkStore for MemoryChunkStore {
36    #[instrument(skip_all, err, fields(chunk.digest=%digest, instance_name=%self.instance_name))]
37    async fn get(&self, digest: &B3Digest) -> Result<Option<Chunk>, super::Error> {
38        Ok(self.db.read().get(digest).cloned())
39    }
40
41    #[instrument(skip_all, err, fields(chunk.digest=%digest, instance_name=%self.instance_name))]
42    async fn has(&self, digest: &B3Digest) -> Result<bool, super::Error> {
43        Ok(self.db.read().contains_key(digest))
44    }
45
46    #[instrument(skip_all, err, fields(instance_name=%self.instance_name))]
47    async fn put(&self, chunk: Chunk) -> Result<B3Digest, super::Error> {
48        let digest: B3Digest = blake3::hash(chunk.as_ref()).into();
49        self.db.write().insert(digest, chunk);
50        Ok(digest)
51    }
52}
53
54/// Config for [MemoryChunkStore].
55#[derive(serde::Deserialize, Debug)]
56#[serde(deny_unknown_fields)]
57pub struct MemoryChunkStoreConfig {}
58
59impl TryFrom<url::Url> for MemoryChunkStoreConfig {
60    type Error = Box<dyn std::error::Error + Send + Sync>;
61    fn try_from(url: url::Url) -> Result<Self, Self::Error> {
62        // memory doesn't support authority or path in the URL.
63        if url.has_authority() || !url.path().is_empty() {
64            return Err("invalid url".into());
65        }
66        Ok(MemoryChunkStoreConfig {})
67    }
68}
69
70#[async_trait]
71impl ServiceBuilder for MemoryChunkStoreConfig {
72    type Output = dyn ChunkStore;
73    async fn build<'a>(
74        &'a self,
75        instance_name: &str,
76        _context: &CompositionContext,
77    ) -> Result<Arc<Self::Output>, Box<dyn std::error::Error + Send + Sync>> {
78        Ok(Arc::new(MemoryChunkStore::new(instance_name.to_string())))
79    }
80}