Skip to main content

linera_views/views/
lazy_register_view.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::OnceLock;
5
6use allocative::Allocative;
7#[cfg(with_metrics)]
8use linera_base::prometheus_util::MeasureLatency as _;
9use serde::{de::DeserializeOwned, Serialize};
10
11use crate::{
12    batch::Batch,
13    common::{from_bytes_option_or_default, HasherOutput},
14    context::Context,
15    hashable_wrapper::WrappedHashableContainerView,
16    store::ReadableKeyValueStore,
17    views::{ClonableView, HashableView, Hasher, ReplaceContext, View},
18    ViewError,
19};
20
21#[cfg(with_metrics)]
22mod metrics {
23    use std::sync::LazyLock;
24
25    use linera_base::prometheus_util::{exponential_bucket_latencies, register_histogram_vec};
26    use prometheus::HistogramVec;
27
28    /// The runtime of hash computation
29    pub static LAZY_REGISTER_VIEW_HASH_RUNTIME: LazyLock<HistogramVec> = LazyLock::new(|| {
30        register_histogram_vec(
31            "lazy_register_view_hash_runtime",
32            "LazyRegisterView hash runtime",
33            &[],
34            exponential_bucket_latencies(5.0),
35        )
36    });
37}
38
39/// A view that supports modifying a single value of type `T`.
40/// Unlike [`crate::register_view::RegisterView`], the value is not loaded from storage until it is first accessed.
41#[derive(Debug, Allocative)]
42#[allocative(bound = "C, T: Allocative")]
43pub struct LazyRegisterView<C, T> {
44    /// Whether to clear storage before applying updates.
45    delete_storage_first: bool,
46    /// The view context.
47    #[allocative(skip)]
48    context: C,
49    /// The value persisted in storage, loaded lazily on first access.
50    /// `OnceLock` replaces both the `Mutex` and the `Option` that were
51    /// previously used: empty means not yet loaded, set means loaded.
52    #[allocative(skip)]
53    stored_value: OnceLock<Box<T>>,
54    /// Pending update not yet persisted to storage.
55    update: Option<Box<T>>,
56}
57
58impl<C, T, C2> ReplaceContext<C2> for LazyRegisterView<C, T>
59where
60    C: Context,
61    C2: Context,
62    T: Default + Send + Sync + Serialize + DeserializeOwned + Clone,
63{
64    type Target = LazyRegisterView<C2, T>;
65
66    async fn with_context(
67        &mut self,
68        ctx: impl FnOnce(&Self::Context) -> C2 + Clone,
69    ) -> Self::Target {
70        let stored_value = self.stored_value.clone();
71        LazyRegisterView {
72            delete_storage_first: self.delete_storage_first,
73            context: ctx(&self.context),
74            stored_value,
75            update: self.update.clone(),
76        }
77    }
78}
79
80impl<C, T> View for LazyRegisterView<C, T>
81where
82    C: Context,
83    T: Default + Send + Sync + Serialize + DeserializeOwned,
84{
85    const NUM_INIT_KEYS: usize = 0;
86
87    type Context = C;
88
89    fn context(&self) -> &C {
90        &self.context
91    }
92
93    fn pre_load(_context: &C) -> Result<Vec<Vec<u8>>, ViewError> {
94        Ok(vec![])
95    }
96
97    fn post_load(context: C, _values: &[Option<Vec<u8>>]) -> Result<Self, ViewError> {
98        Ok(Self {
99            delete_storage_first: false,
100            context,
101            stored_value: OnceLock::new(),
102            update: None,
103        })
104    }
105
106    fn rollback(&mut self) {
107        self.delete_storage_first = false;
108        self.update = None;
109    }
110
111    async fn has_pending_changes(&self) -> bool {
112        if self.delete_storage_first {
113            return true;
114        }
115        self.update.is_some()
116    }
117
118    fn pre_save(&self, batch: &mut Batch) -> Result<bool, ViewError> {
119        let mut delete_view = false;
120        if self.delete_storage_first {
121            batch.delete_key(self.context.base_key().bytes.clone());
122            delete_view = true;
123        } else if let Some(value) = &self.update {
124            let key = self.context.base_key().bytes.clone();
125            batch.put_key_value(key, value)?;
126        }
127        Ok(delete_view)
128    }
129
130    fn post_save(&mut self) {
131        if self.delete_storage_first {
132            self.stored_value = OnceLock::from(Box::<T>::default());
133        } else if let Some(value) = self.update.take() {
134            self.stored_value = OnceLock::from(value);
135        }
136        self.delete_storage_first = false;
137        self.update = None;
138    }
139
140    fn clear(&mut self) {
141        self.delete_storage_first = true;
142        self.update = Some(Box::default());
143    }
144}
145
146impl<C, T> ClonableView for LazyRegisterView<C, T>
147where
148    C: Context,
149    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
150{
151    fn clone_unchecked(&mut self) -> Result<Self, ViewError> {
152        let stored_value = self.stored_value.clone();
153        Ok(LazyRegisterView {
154            delete_storage_first: self.delete_storage_first,
155            context: self.context.clone(),
156            stored_value,
157            update: self.update.clone(),
158        })
159    }
160}
161
162impl<C, T> LazyRegisterView<C, T>
163where
164    C: Context,
165    T: Default + DeserializeOwned,
166{
167    /// Access the current value in the register.
168    /// ```rust
169    /// # tokio_test::block_on(async {
170    /// # use linera_views::context::MemoryContext;
171    /// # use linera_views::lazy_register_view::LazyRegisterView;
172    /// # use linera_views::views::View;
173    /// # let context = MemoryContext::new_for_testing(());
174    /// let register = LazyRegisterView::<_, u32>::load(context).await.unwrap();
175    /// let value = register.get().await.unwrap();
176    /// assert_eq!(*value, 0);
177    /// # })
178    /// ```
179    pub async fn get(&self) -> Result<&T, ViewError> {
180        if let Some(value) = &self.update {
181            return Ok(value);
182        }
183        if let Some(value) = self.stored_value.get() {
184            return Ok(value);
185        }
186        let key = self.context.base_key().bytes.clone();
187        let bytes = self.context.store().read_value_bytes(&key).await?;
188        let value = from_bytes_option_or_default(&bytes)?;
189        Ok(self.stored_value.get_or_init(|| Box::new(value)))
190    }
191
192    /// Sets the value in the register.
193    /// ```rust
194    /// # tokio_test::block_on(async {
195    /// # use linera_views::context::MemoryContext;
196    /// # use linera_views::lazy_register_view::LazyRegisterView;
197    /// # use linera_views::views::View;
198    /// # let context = MemoryContext::new_for_testing(());
199    /// let mut register = LazyRegisterView::load(context).await.unwrap();
200    /// register.set(5);
201    /// let value = register.get().await.unwrap();
202    /// assert_eq!(*value, 5);
203    /// # })
204    /// ```
205    pub fn set(&mut self, value: T) {
206        self.delete_storage_first = false;
207        self.update = Some(Box::new(value));
208    }
209
210    /// Obtains the extra data.
211    pub fn extra(&self) -> &C::Extra {
212        self.context.extra()
213    }
214
215    /// Drops the in-memory cache of the stored value. Subsequent `get()` calls
216    /// reload from storage. Pending unsaved updates are not affected.
217    pub fn evict(&mut self) {
218        self.stored_value = OnceLock::new();
219    }
220}
221
222impl<C, T> LazyRegisterView<C, T>
223where
224    C: Context,
225    T: Clone + Default + Serialize + DeserializeOwned,
226{
227    /// Obtains a mutable reference to the value in the register.
228    /// ```rust
229    /// # tokio_test::block_on(async {
230    /// # use linera_views::context::MemoryContext;
231    /// # use linera_views::lazy_register_view::LazyRegisterView;
232    /// # use linera_views::views::View;
233    /// # let context = MemoryContext::new_for_testing(());
234    /// let mut register: LazyRegisterView<_, u32> = LazyRegisterView::load(context).await.unwrap();
235    /// let value = register.get_mut().await.unwrap();
236    /// assert_eq!(*value, 0);
237    /// # })
238    /// ```
239    pub async fn get_mut(&mut self) -> Result<&mut T, ViewError> {
240        if self.update.is_none() {
241            let update = self.get().await?.clone();
242            self.update = Some(Box::new(update));
243        }
244        self.delete_storage_first = false;
245        Ok(self.update.as_mut().unwrap())
246    }
247
248    async fn compute_hash(&self) -> Result<<sha3::Sha3_256 as Hasher>::Output, ViewError> {
249        #[cfg(with_metrics)]
250        let _hash_latency = metrics::LAZY_REGISTER_VIEW_HASH_RUNTIME.measure_latency();
251        let mut hasher = sha3::Sha3_256::default();
252        hasher.update_with_bcs_bytes(self.get().await?)?;
253        Ok(hasher.finalize())
254    }
255}
256
257impl<C, T> HashableView for LazyRegisterView<C, T>
258where
259    C: Context,
260    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
261{
262    type Hasher = sha3::Sha3_256;
263
264    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
265        self.compute_hash().await
266    }
267
268    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
269        self.compute_hash().await
270    }
271}
272
273/// Type wrapping `LazyRegisterView` while memoizing the hash.
274pub type HashedLazyRegisterView<C, T> =
275    WrappedHashableContainerView<C, LazyRegisterView<C, T>, HasherOutput>;
276
277impl<C, T> WrappedHashableContainerView<C, LazyRegisterView<C, T>, HasherOutput>
278where
279    C: Context,
280    T: Default + Send + Sync + Serialize + DeserializeOwned,
281{
282    /// Drops the in-memory cache of the wrapped value without invalidating
283    /// the memoized hash (the logical content is unchanged).
284    pub fn evict(&mut self) {
285        self.inner_mut_preserve_hash().evict();
286    }
287}
288
289#[cfg(with_graphql)]
290mod graphql {
291    use std::borrow::Cow;
292
293    use super::LazyRegisterView;
294    use crate::context::Context;
295
296    impl<C, T> async_graphql::OutputType for LazyRegisterView<C, T>
297    where
298        C: Context,
299        T: async_graphql::OutputType + Default + Send + Sync + serde::de::DeserializeOwned,
300    {
301        fn type_name() -> Cow<'static, str> {
302            T::type_name()
303        }
304
305        fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
306            T::create_type_info(registry)
307        }
308
309        async fn resolve(
310            &self,
311            ctx: &async_graphql::ContextSelectionSet<'_>,
312            field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
313        ) -> async_graphql::ServerResult<async_graphql::Value> {
314            self.get()
315                .await
316                .map_err(|e| async_graphql::ServerError::new(e.to_string(), Some(field.pos)))?
317                .resolve(ctx, field)
318                .await
319        }
320    }
321}