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