Skip to main content

inc_complete/db/
mod.rs

1use std::collections::BTreeSet;
2use std::sync::Arc;
3use std::sync::atomic::{AtomicU32, Ordering};
4
5use crate::accumulate::{ACCUMULATED_COMPUTATION_ID, Accumulate, Accumulated};
6use crate::cell::CellData;
7use crate::storage::StorageFor;
8use crate::{Cell, Computation, Storage};
9
10pub mod debug_with_db;
11mod handle;
12mod serialize;
13mod tests;
14
15pub use handle::DbHandle;
16use parking_lot::Mutex;
17use rustc_hash::FxHashSet;
18
19const START_VERSION: u32 = 1;
20
21/// The central database object to manage and cache incremental computations.
22///
23/// To use this, a type implementing `Storage` is required to be provided.
24/// See the documentation for `impl_storage!`.
25pub struct Db<Storage> {
26    cells: dashmap::DashMap<Cell, CellData, rustc_hash::FxBuildHasher>,
27    version: AtomicU32,
28    next_cell: AtomicU32,
29    storage: Storage,
30
31    /// Lock used when acquiring new Cells to ensure the same data isn't assigned
32    /// multiple ids concurrently. Maps computation_id to each lock.
33    cell_locks: dashmap::DashMap<u32, Arc<Mutex<()>>, rustc_hash::FxBuildHasher>,
34}
35
36impl<Storage: Default> Db<Storage> {
37    /// Construct a new `Db` object using `Default::default()` for the initial storage.
38    pub fn new() -> Self {
39        Self::with_storage(Storage::default())
40    }
41}
42
43impl<S: Default> Default for Db<S> {
44    fn default() -> Self {
45        Self::new()
46    }
47}
48
49/// Abstracts over the `get` function provided by `Db<S>` and `DbHandle<S>` to avoid
50/// providing `get` and `get_db` variants for each function.
51pub trait DbGet<C: Computation> {
52    /// Run an incremental computation `C` and return its output.
53    /// If `C` is already cached, no computation will be performed.
54    fn get(&self, key: C) -> C::Output;
55}
56
57impl<S, C> DbGet<C> for Db<S>
58where
59    C: Computation,
60    S: Storage + StorageFor<C>,
61{
62    fn get(&self, key: C) -> C::Output {
63        self.get(key)
64    }
65}
66
67impl<S> Db<S> {
68    /// Construct a new `Db` object with the given initial storage.
69    pub fn with_storage(storage: S) -> Self {
70        Self {
71            cells: Default::default(),
72            version: AtomicU32::new(START_VERSION),
73            next_cell: AtomicU32::new(0),
74            cell_locks: Default::default(),
75            storage,
76        }
77    }
78
79    /// Retrieve an immutable reference to this `Db`'s storage
80    pub fn storage(&self) -> &S {
81        &self.storage
82    }
83
84    /// Retrieve a mutable reference to this `Db`'s storage.
85    ///
86    /// Note that any mutations made to the storage using this are _not_ tracked by the `Db`!
87    /// Using this incorrectly may break correctness!
88    pub fn storage_mut(&mut self) -> &mut S {
89        &mut self.storage
90    }
91}
92
93impl<S: Storage> Db<S> {
94    /// Return the corresponding Cell for a given computation, if it exists.
95    ///
96    /// This will not update any values.
97    fn get_cell<C: Computation>(&self, computation: &C) -> Option<Cell>
98    where
99        S: StorageFor<C>,
100    {
101        self.storage.get_cell_for_computation(computation)
102    }
103
104    pub(crate) fn get_or_insert_cell<C>(&self, input: C) -> Cell
105    where
106        C: Computation,
107        S: StorageFor<C>,
108    {
109        if let Some(cell) = self.get_cell(&input) {
110            return cell;
111        }
112
113        // Cell doesn't exist yet, we need to lock & create a unique Cell for this input
114        let computation_id = C::computation_id();
115        let lock = self.cell_locks.entry(computation_id).or_default().clone();
116        let _guard = lock.lock();
117
118        // Need to check get_cell again in case another thread created this Cell after
119        // our get_cell call but before we acquired the lock
120        if let Some(cell) = self.get_cell(&input) {
121            cell
122        } else {
123            // We just need a unique ID here, we don't care about ordering between
124            // threads, so we're using Ordering::Relaxed.
125            let cell_id = self.next_cell.fetch_add(1, Ordering::Relaxed);
126            let new_cell = Cell::new(cell_id);
127
128            self.cells.insert(new_cell, CellData::new(computation_id));
129            self.storage.insert_new_cell(new_cell, input);
130            new_cell
131        }
132    }
133
134    fn handle(&self, cell: Cell) -> DbHandle<'_, S> {
135        DbHandle::new(self, cell)
136    }
137
138    #[cfg(test)]
139    #[allow(unused)]
140    pub(crate) fn with_cell_data<C: Computation>(&self, input: &C, f: impl FnOnce(&CellData))
141    where
142        S: StorageFor<C>,
143    {
144        let cell = self
145            .get_cell(input)
146            .unwrap_or_else(|| panic!("unwrap_cell_value: Expected cell to exist"));
147
148        self.cells.get(&cell).map(|value| f(&value)).unwrap()
149    }
150
151    pub fn version(&self) -> u32 {
152        self.version.load(Ordering::SeqCst)
153    }
154
155    pub fn gc(&mut self, version: u32) {
156        let used_cells: std::collections::HashSet<Cell> = self
157            .cells
158            .iter()
159            .filter_map(|entry| {
160                if entry.value().last_verified_version >= version {
161                    Some(entry.key().clone())
162                } else {
163                    None
164                }
165            })
166            .collect();
167
168        self.storage.gc(&used_cells);
169    }
170}
171
172impl<S: Storage> Db<S> {
173    /// Updates an input with a new value
174    ///
175    /// This requires an exclusive reference to self to ensure that there are no currently
176    /// running queries. Updating an input while an incremental computation is occurring
177    /// can break soundness for dependency tracking.
178    ///
179    /// Panics if the given computation is not an input - ie. panics if it has at least 1 dependency.
180    pub fn update_input<C>(&mut self, input: C, new_value: C::Output)
181    where
182        C: Computation,
183        S: StorageFor<C>,
184    {
185        let cell_id = self.get_or_insert_cell(input);
186        assert!(
187            self.is_input(cell_id),
188            "`update_input` given a non-input value. Inputs must have 0 dependencies",
189        );
190
191        let changed = self.storage.update_output(cell_id, new_value);
192        let mut cell = self.cells.get_mut(&cell_id).unwrap();
193
194        if changed {
195            let version = self.version.fetch_add(1, Ordering::SeqCst) + 1;
196            cell.last_updated_version = version;
197            cell.last_verified_version = version;
198        } else {
199            cell.last_verified_version = self.version.load(Ordering::SeqCst);
200        }
201    }
202
203    fn is_input(&self, cell: Cell) -> bool {
204        self.with_cell(cell, |cell| cell.dependencies.is_empty())
205    }
206
207    /// True if a given computation is stale and needs to be re-computed.
208    /// Computations which have never been computed are also considered stale.
209    ///
210    /// Note that this may re-compute dependencies of the given computation.
211    pub fn is_stale<C: Computation>(&self, input: &C) -> bool
212    where
213        S: StorageFor<C>,
214    {
215        // If the cell doesn't exist, it is definitely stale
216        let Some(cell) = self.get_cell(input) else {
217            return true;
218        };
219        self.is_stale_cell(cell)
220    }
221
222    /// True if a given cell is stale and needs to be re-computed.
223    ///
224    /// Note that this may re-compute some input
225    fn is_stale_cell(&self, cell: Cell) -> bool {
226        let state = self.with_cell(cell, |data| {
227            (!self.storage.output_is_unset(cell, data.computation_id)).then(|| {
228                (
229                    data.computation_id,
230                    data.last_verified_version,
231                    data.dependencies.clone(),
232                )
233            })
234        });
235
236        let Some((computation_id, last_verified, dependencies)) = state else {
237            return true;
238        };
239
240        // Dependencies need to be iterated in the order they were computed.
241        // Otherwise we may re-run a computation which does not need to be re-run.
242        // In the worst case this could even lead to panics - see the div0 test.
243        dependencies.into_iter().any(|dependency_id| {
244            self.update_cell(dependency_id);
245            self.with_cell(dependency_id, |dependency| {
246                if computation_id == ACCUMULATED_COMPUTATION_ID {
247                    dependency.last_run_version > last_verified
248                } else {
249                    dependency.last_updated_version > last_verified
250                }
251            })
252        })
253    }
254
255    /// Similar to `update_input` but runs the compute function
256    /// instead of accepting a given value. This also will not update
257    /// `self.version`
258    fn run_compute_function(&self, cell_id: Cell) {
259        let computation_id = self.with_cell(cell_id, |data| data.computation_id);
260        self.storage.clear_accumulated_for_cell(cell_id);
261        let handle = self.handle(cell_id);
262        let changed = S::run_computation(&handle, cell_id, computation_id);
263
264        let version = self.version.load(Ordering::SeqCst);
265        let mut cell = self.cells.get_mut(&cell_id).unwrap();
266        cell.last_verified_version = version;
267        cell.last_run_version = version;
268
269        if changed {
270            cell.last_updated_version = version;
271        }
272    }
273
274    /// Trigger an update of the given cell, recursively checking and re-running any out of date
275    /// dependencies.
276    fn update_cell(&self, cell_id: Cell) {
277        let last_verified_version = self.with_cell(cell_id, |data| data.last_verified_version);
278        let version = self.version.load(Ordering::SeqCst);
279
280        if last_verified_version != version {
281            // if any dependency may have changed, update
282            if self.is_stale_cell(cell_id) {
283                let lock = self.with_cell(cell_id, |cell| cell.lock.clone());
284
285                match lock.try_lock() {
286                    Some(guard) => {
287                        self.run_compute_function(cell_id);
288                        drop(guard);
289                    }
290                    None => {
291                        // This computation is already being run in another thread.
292                        // Before blocking and waiting, since we have time, check for a cycle and
293                        // issue and panic if found.
294                        self.check_for_cycle(cell_id);
295
296                        // Block until it finishes and return the result
297                        drop(lock.lock());
298                    }
299                }
300            } else {
301                let mut cell = self.cells.get_mut(&cell_id).unwrap();
302                cell.last_verified_version = version;
303            }
304        }
305    }
306
307    /// Perform a DFS to check for a cycle, panicking if found
308    fn check_for_cycle(&self, starting_cell: Cell) {
309        let mut visited = FxHashSet::default();
310        let mut path = Vec::new();
311
312        // We're going to push actions to this stack. Most actions will be pushing
313        // a dependency cell to track as the next node in the graph, but some will be
314        // pop actions for popping the top node off the current path. If we encounter
315        // a node which is already in the current path, we have found a cycle.
316        let mut stack = Vec::new();
317        stack.push(Action::Traverse(starting_cell));
318
319        enum Action {
320            Traverse(Cell),
321            Pop(Cell),
322        }
323
324        while let Some(action) = stack.pop() {
325            match action {
326                // This assert_eq is never expected to fail
327                Action::Pop(expected) => assert_eq!(path.pop(), Some(expected)),
328                Action::Traverse(cell) => {
329                    if path.contains(&cell) {
330                        // Include the same cell twice so the cycle is more clear to users
331                        path.push(cell);
332                        self.cycle_error(&path);
333                    }
334
335                    if visited.insert(cell) {
336                        path.push(cell);
337                        stack.push(Action::Pop(cell));
338                        self.with_cell(cell, |cell| {
339                            for dependency in cell.dependencies.iter() {
340                                stack.push(Action::Traverse(*dependency));
341                            }
342                        });
343                    }
344                }
345            }
346        }
347    }
348
349    /// Issue an error with the given cycle
350    fn cycle_error(&self, cycle: &[Cell]) {
351        let mut error = String::new();
352        for (i, cell) in cycle.iter().enumerate() {
353            error += &format!(
354                "\n  {}. {}",
355                i + 1,
356                self.storage.input_debug_string(self, *cell)
357            );
358        }
359        panic!("inc-complete: Cycle Detected!\n\nCycle:{error}")
360    }
361
362    /// Retrieves the up to date value for the given computation, re-running any dependencies as
363    /// necessary.
364    ///
365    /// This function can panic if the dynamic type of the value returned by `compute.run(..)` is not `T`.
366    ///
367    /// Locking behavior: This function locks the cell corresponding to the given computation. This
368    /// can cause a deadlock if the computation recursively depends on itself.
369    pub fn get<C: Computation>(&self, compute: C) -> C::Output
370    where
371        S: StorageFor<C>,
372    {
373        let cell_id = self.get_or_insert_cell(compute);
374        self.get_with_cell::<C>(cell_id)
375    }
376
377    pub(crate) fn get_with_cell<Concrete: Computation>(&self, cell_id: Cell) -> Concrete::Output
378    where
379        S: StorageFor<Concrete>,
380    {
381        self.update_cell(cell_id);
382
383        self.storage
384            .get_output(cell_id)
385            .expect("cell result should have been computed already")
386    }
387
388    fn with_cell<R>(&self, cell: Cell, f: impl FnOnce(&CellData) -> R) -> R {
389        f(&self.cells.get(&cell).unwrap())
390    }
391
392    /// Retrieve each accumulated value of the given type after the given computation is run.
393    ///
394    /// This is most often used for operations like retrieving diagnostics or logs.
395    ///
396    /// Compared to [Db::get_accumulated_uncached], this version reuses the normal flow for
397    /// queries and thus saves accumulated values for each intermediate query. This involves
398    /// more synching and data duplication but can be beneficial if intermediate results
399    /// ever need to be reused, e.g. if you call [Db::get_accumulated] in a loop where each
400    /// call may share dependencies. If you already have a single query which emits all the
401    /// accumulated values you need, [Db::get_accumulated_uncached] is likely faster, but
402    /// requires a `&mut Db`.
403    pub fn get_accumulated<Item, C>(&self, compute: C) -> BTreeSet<Item>
404    where
405        S: StorageFor<C> + StorageFor<Accumulated<Item>>,
406        C: Computation,
407        Item: 'static,
408    {
409        let cell_id = self.get_or_insert_cell(compute);
410        self.update_cell(cell_id);
411        self.get(Accumulated::<Item>::new(cell_id))
412    }
413
414    /// Retrieve each accumulated value of the given type after the given computation is run.
415    ///
416    /// This is most often used for operations like retrieving diagnostics or logs.
417    ///
418    /// This is a faster version of [Db::get_accumulated] for some use-cases. This version tends to be
419    /// more efficient when you already have a single query which emits all the accumulated values
420    /// you need, while the original [Db::get_accumulated] is more efficient when you have many
421    /// smaller calls since it avoids duplicated work and is safe to call with only a [DbHandle].
422    pub fn get_accumulated_uncached<Item, C>(&mut self, compute: C) -> BTreeSet<Item>
423    where
424        S: StorageFor<C> + StorageFor<Accumulated<Item>> + Accumulate<Item>,
425        C: Computation,
426        Item: 'static + Ord,
427    {
428        let cell_id = self.get_or_insert_cell(compute);
429        self.update_cell(cell_id);
430
431        let mut items = BTreeSet::new();
432        let mut visited = BTreeSet::new();
433        let mut queue = vec![cell_id];
434
435        while let Some(cell) = queue.pop() {
436            if visited.insert(cell) {
437                self.with_cell(cell, |data| queue.extend_from_slice(&data.dependencies));
438                items.extend(self.storage().get_accumulated::<Vec<Item>>(cell));
439            }
440        }
441
442        items
443    }
444}