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.clone()
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
216impl<C, T> LazyRegisterView<C, T>
217where
218    C: Context,
219    T: Clone + Default + Serialize + DeserializeOwned,
220{
221    /// Obtains a mutable reference to the value in the register.
222    /// ```rust
223    /// # tokio_test::block_on(async {
224    /// # use linera_views::context::MemoryContext;
225    /// # use linera_views::lazy_register_view::LazyRegisterView;
226    /// # use linera_views::views::View;
227    /// # let context = MemoryContext::new_for_testing(());
228    /// let mut register: LazyRegisterView<_, u32> = LazyRegisterView::load(context).await.unwrap();
229    /// let value = register.get_mut().await.unwrap();
230    /// assert_eq!(*value, 0);
231    /// # })
232    /// ```
233    pub async fn get_mut(&mut self) -> Result<&mut T, ViewError> {
234        if self.update.is_none() {
235            let update = self.get().await?.clone();
236            self.update = Some(Box::new(update));
237        }
238        self.delete_storage_first = false;
239        Ok(self.update.as_mut().unwrap())
240    }
241
242    async fn compute_hash(&self) -> Result<<sha3::Sha3_256 as Hasher>::Output, ViewError> {
243        #[cfg(with_metrics)]
244        let _hash_latency = metrics::LAZY_REGISTER_VIEW_HASH_RUNTIME.measure_latency();
245        let mut hasher = sha3::Sha3_256::default();
246        hasher.update_with_bcs_bytes(self.get().await?)?;
247        Ok(hasher.finalize())
248    }
249}
250
251impl<C, T> HashableView for LazyRegisterView<C, T>
252where
253    C: Context,
254    T: Clone + Default + Send + Sync + Serialize + DeserializeOwned,
255{
256    type Hasher = sha3::Sha3_256;
257
258    async fn hash_mut(&mut self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
259        self.compute_hash().await
260    }
261
262    async fn hash(&self) -> Result<<Self::Hasher as Hasher>::Output, ViewError> {
263        self.compute_hash().await
264    }
265}
266
267/// Type wrapping `LazyRegisterView` while memoizing the hash.
268pub type HashedLazyRegisterView<C, T> =
269    WrappedHashableContainerView<C, LazyRegisterView<C, T>, HasherOutput>;
270
271#[cfg(with_graphql)]
272mod graphql {
273    use std::borrow::Cow;
274
275    use super::LazyRegisterView;
276    use crate::context::Context;
277
278    impl<C, T> async_graphql::OutputType for LazyRegisterView<C, T>
279    where
280        C: Context,
281        T: async_graphql::OutputType + Default + Send + Sync + serde::de::DeserializeOwned,
282    {
283        fn type_name() -> Cow<'static, str> {
284            T::type_name()
285        }
286
287        fn create_type_info(registry: &mut async_graphql::registry::Registry) -> String {
288            T::create_type_info(registry)
289        }
290
291        async fn resolve(
292            &self,
293            ctx: &async_graphql::ContextSelectionSet<'_>,
294            field: &async_graphql::Positioned<async_graphql::parser::types::Field>,
295        ) -> async_graphql::ServerResult<async_graphql::Value> {
296            self.get()
297                .await
298                .map_err(|e| async_graphql::ServerError::new(e.to_string(), Some(field.pos)))?
299                .resolve(ctx, field)
300                .await
301        }
302    }
303}