Skip to main content

inc_complete/db/
handle.rs

1use std::collections::BTreeSet;
2
3use crate::{
4    Cell, Computation, Db, Storage,
5    accumulate::{Accumulate, Accumulated},
6    storage::StorageFor,
7};
8
9use super::DbGet;
10
11/// A handle to the database during some operation.
12///
13/// This wraps calls to the Db so that any `get` calls
14/// will be automatically registered as dependencies of
15/// the current operation.
16pub struct DbHandle<'db, S> {
17    db: &'db Db<S>,
18    current_operation: Cell,
19}
20
21impl<'db, S> DbHandle<'db, S> {
22    pub(crate) fn new(db: &'db Db<S>, current_operation: Cell) -> Self {
23        // We're re-running a cell so remove any past dependencies
24        let mut cell = db.cells.get_mut(&current_operation).unwrap();
25
26        cell.dependencies.clear();
27        cell.dependency_set.clear();
28        cell.input_dependencies.clear();
29
30        Self {
31            db,
32            current_operation,
33        }
34    }
35
36    /// Retrieve an immutable reference to this `Db`'s storage
37    ///
38    /// Note that any mutations made to the storage using this are _not_ tracked by the database!
39    /// Using this incorrectly may break correctness!
40    pub fn storage(&self) -> &S {
41        self.db.storage()
42    }
43}
44
45impl<S: Storage> DbHandle<'_, S> {
46    /// Locking behavior: This function locks the cell corresponding to the given computation. This
47    /// can cause a deadlock if the computation recursively depends on itself.
48    pub fn get<C: Computation>(&self, compute: C) -> C::Output
49    where
50        S: StorageFor<C>,
51    {
52        // Register the dependency
53        let dependency = self.db.get_or_insert_cell(compute);
54        self.update_and_register_dependency::<C>(dependency);
55
56        // Fetch the current value of the dependency, running it if out of date
57        self.db.get_with_cell(dependency)
58    }
59
60    /// Registers the given cell as a dependency, running it and updating any required metadata
61    fn update_and_register_dependency<C: Computation>(&self, dependency: Cell) {
62        self.update_and_register_dependency_inner(dependency, C::IS_INPUT);
63    }
64
65    fn update_and_register_dependency_inner(&self, dependency: Cell, is_input: bool) {
66        let mut cell = self.db.cells.get_mut(&self.current_operation).unwrap();
67
68        // Storing dependency_set separately takes a hit to memory usage but is worth
69        // it for extra runtime performance on this check
70        let newly_registered = cell.dependency_set.insert(dependency);
71        if newly_registered {
72            cell.dependencies.push(dependency);
73            if is_input {
74                cell.input_dependencies.insert(dependency);
75            }
76        }
77        drop(cell);
78
79        // Run the computation to update its dependencies before we query them afterward
80        self.db.update_cell(dependency);
81
82        if !newly_registered {
83            return;
84        }
85
86        let dependency = self.db.cells.get(&dependency).unwrap();
87        let dependency_inputs = dependency.input_dependencies.clone();
88        drop(dependency);
89
90        // TODO: Is this check necessary? It is meant as an optimization to avoid unnecessarily acquiring
91        // `cell` but in practice the vast majority of computations will have at least 1 input dependency.
92        if !dependency_inputs.is_empty() {
93            let mut cell = self.db.cells.get_mut(&self.current_operation).unwrap();
94            cell.input_dependencies.extend(dependency_inputs);
95        }
96    }
97
98    /// Accumulate an item in the current computation. This item can be retrieved along
99    /// with all other accumulated items in this computation and its dependencies via
100    /// a call to `get_accumulated`.
101    ///
102    /// This is most often used for operations like pushing diagnostics or logs.
103    pub fn accumulate<Item>(&self, item: Item)
104    where
105        S: Accumulate<Item>,
106    {
107        self.storage().accumulate(self.current_operation, item);
108    }
109
110    /// Retrieve an accumulated value in a container of the user's choice.
111    /// This will return all the accumulated items after the given computation.
112    ///
113    /// This is most often used for operations like retrieving diagnostics or logs.
114    pub fn get_accumulated<Item, C>(&self, compute: C) -> BTreeSet<Item>
115    where
116        C: Computation,
117        Item: 'static + Ord,
118        S: StorageFor<Accumulated<Item>> + StorageFor<C> + Accumulate<Item>,
119    {
120        let dependency = self.db.get_or_insert_cell(compute);
121        self.get_accumulated_with_cell::<Item>(dependency)
122    }
123
124    /// Retrieve an accumulated value in a container of the user's choice.
125    /// This will return all the accumulated items after the given computation.
126    ///
127    /// This is the implementation of the publically accessible `db.get(Accumulated::<Item>(MyComputation))`.
128    ///
129    /// This is most often used for operations like retrieving diagnostics or logs.
130    pub(crate) fn get_accumulated_with_cell<Item>(&self, cell_id: Cell) -> BTreeSet<Item>
131    where
132        Item: 'static + Ord,
133        S: StorageFor<Accumulated<Item>> + Accumulate<Item>,
134    {
135        self.update_and_register_dependency_inner(cell_id, false);
136        let dependencies = self.db.with_cell(cell_id, |cell| cell.dependencies.clone());
137
138        // Collect `Accumulator` results from each dependency. This should also ensure we
139        // rerun this if any dependency changes, even if `cell_id` is updated such that it
140        // uses different dependencies but its output remains the same.
141        let computation_id = Accumulated::<Item>::computation_id();
142        let mut result: BTreeSet<Item> = dependencies
143            .into_iter()
144            // Filter out `Accumulated<Item>` cells from the dep list — they exist for staleness
145            // tracking only and must not be traversed for value collection, or we'd get duplicates.
146            .filter(|&dep| self.db.with_cell(dep, |cell| cell.computation_id) != computation_id)
147            .flat_map(|dependency| self.get(Accumulated::<Item>::new(dependency)))
148            .collect();
149
150        result.extend(self.storage().get_accumulated::<Vec<Item>>(cell_id));
151        result
152    }
153}
154
155impl<'db, S, C> DbGet<C> for DbHandle<'db, S>
156where
157    C: Computation,
158    S: Storage + StorageFor<C>,
159{
160    fn get(&self, key: C) -> C::Output {
161        self.get(key)
162    }
163}