|
1 | 1 | use crate::cycle::{CycleRecoveryStrategy, IterationStamp}; |
2 | | -use crate::database::RawDatabase; |
3 | 2 | use crate::function::eviction::EvictionPolicy; |
4 | | -use crate::function::execute::{QueryState, QueryStateImpl, execute_erased}; |
| 3 | +use crate::function::execute::{QueryState, QueryStateImpl}; |
5 | 4 | use crate::function::memo::{ErasedMemo, Memo}; |
6 | | -use crate::function::sync::{ClaimResult, SyncTable}; |
| 5 | +use crate::function::sync::ClaimResult; |
7 | 6 | use crate::function::{Configuration, IngredientImpl, Reentrancy}; |
8 | 7 | use crate::zalsa::{MemoIngredientIndex, Zalsa}; |
9 | 8 | use crate::zalsa_local::{QueryRevisions, ZalsaLocal}; |
@@ -112,134 +111,132 @@ where |
112 | 111 | memo_ingredient_index: MemoIngredientIndex, |
113 | 112 | ) -> Option<&'db Memo<'db, C>> { |
114 | 113 | let database_key_index = self.database_key_index(id); |
115 | | - let mut state = QueryStateImpl::new(self, db); |
116 | | - let memo = fetch_cold_erased( |
117 | | - &mut state, |
118 | | - &self.sync_table, |
119 | | - zalsa, |
120 | | - zalsa_local, |
121 | | - db.into(), |
122 | | - database_key_index, |
123 | | - memo_ingredient_index, |
124 | | - C::CYCLE_STRATEGY, |
125 | | - )?; |
126 | | - // SAFETY: `state` and all memos it returns belong to `IngredientImpl<C>`. |
127 | | - Some(unsafe { memo.downcast::<C>() }) |
128 | | - } |
129 | | -} |
| 114 | + // Try to claim this query: if someone else has claimed it already, go back and start again. |
| 115 | + let claim_guard = match self |
| 116 | + .sync_table |
| 117 | + .try_claim(zalsa, zalsa_local, id, Reentrancy::Allow) |
| 118 | + { |
| 119 | + ClaimResult::Claimed(guard) => guard, |
| 120 | + ClaimResult::Running(blocked_on) => { |
| 121 | + let _ = blocked_on.block_on(zalsa); |
| 122 | + return None; |
| 123 | + } |
| 124 | + ClaimResult::Cycle { .. } => { |
| 125 | + return Some(self.fetch_cold_cycle( |
| 126 | + db, |
| 127 | + zalsa, |
| 128 | + zalsa_local, |
| 129 | + database_key_index, |
| 130 | + memo_ingredient_index, |
| 131 | + )); |
| 132 | + } |
| 133 | + }; |
130 | 134 |
|
131 | | -#[allow(clippy::too_many_arguments)] |
132 | | -fn fetch_cold_erased<'db>( |
133 | | - state: &mut dyn QueryState<'db>, |
134 | | - sync_table: &'db SyncTable, |
135 | | - zalsa: &'db Zalsa, |
136 | | - zalsa_local: &'db ZalsaLocal, |
137 | | - db: RawDatabase<'db>, |
138 | | - database_key_index: DatabaseKeyIndex, |
139 | | - memo_ingredient_index: MemoIngredientIndex, |
140 | | - strategy: CycleRecoveryStrategy, |
141 | | -) -> Option<ErasedMemo<'db>> { |
142 | | - let id = database_key_index.key_index(); |
| 135 | + // Now that we've claimed the item, check again to see if there's a hot value. |
| 136 | + let opt_old_memo = self.get_memo_from_table_for(zalsa, id, memo_ingredient_index); |
143 | 137 |
|
144 | | - // Try to claim this query: if someone else has claimed it already, go back and start again. |
145 | | - let claim_guard = match sync_table.try_claim(zalsa, zalsa_local, id, Reentrancy::Allow) { |
146 | | - ClaimResult::Claimed(guard) => guard, |
147 | | - ClaimResult::Running(blocked_on) => { |
148 | | - let _ = blocked_on.block_on(zalsa); |
149 | | - return None; |
150 | | - } |
151 | | - ClaimResult::Cycle { .. } => { |
152 | | - return Some(fetch_cold_cycle_erased( |
153 | | - state, |
154 | | - zalsa, |
155 | | - zalsa_local, |
156 | | - database_key_index, |
157 | | - memo_ingredient_index, |
158 | | - strategy, |
159 | | - )); |
| 138 | + if let Some(old_memo) = opt_old_memo { |
| 139 | + if old_memo.value.is_some() |
| 140 | + && old_memo |
| 141 | + .header |
| 142 | + .verify_memo(db.into(), &claim_guard, C::CYCLE_STRATEGY, true) |
| 143 | + { |
| 144 | + // SAFETY: The memo is present in the memo table, and we verified that it is valid |
| 145 | + // for the current revision. |
| 146 | + return unsafe { Some(self.extend_memo_lifetime(old_memo)) }; |
| 147 | + } |
160 | 148 | } |
161 | | - }; |
162 | 149 |
|
163 | | - // Now that we've claimed the item, check again to see if there's a hot value. |
164 | | - let opt_old_memo = state.get_memo(zalsa, id, memo_ingredient_index); |
| 150 | + self.execute(db, claim_guard, opt_old_memo, memo_ingredient_index) |
| 151 | + } |
165 | 152 |
|
166 | | - if let Some(old_memo) = opt_old_memo { |
167 | | - if old_memo.value_is_some && old_memo.verify_memo(db, &claim_guard, strategy, true) { |
168 | | - return Some(old_memo); |
| 153 | + #[cold] |
| 154 | + #[inline(never)] |
| 155 | + fn fetch_cold_cycle<'db>( |
| 156 | + &'db self, |
| 157 | + db: &'db C::DbView, |
| 158 | + zalsa: &'db Zalsa, |
| 159 | + zalsa_local: &'db ZalsaLocal, |
| 160 | + database_key_index: DatabaseKeyIndex, |
| 161 | + memo_ingredient_index: MemoIngredientIndex, |
| 162 | + ) -> &'db Memo<'db, C> { |
| 163 | + match C::CYCLE_STRATEGY { |
| 164 | + CycleRecoveryStrategy::Panic => fetch_cold_cycle_panic(zalsa_local, database_key_index), |
| 165 | + CycleRecoveryStrategy::FallbackImmediate | CycleRecoveryStrategy::Fixpoint => { |
| 166 | + let mut state = QueryStateImpl::new(self, db); |
| 167 | + let memo = fetch_cold_cycle_recoverable_erased( |
| 168 | + &mut state, |
| 169 | + zalsa, |
| 170 | + database_key_index, |
| 171 | + memo_ingredient_index, |
| 172 | + ); |
| 173 | + // SAFETY: `state` and all memos it returns belong to `IngredientImpl<C>`. |
| 174 | + unsafe { memo.downcast::<C>() } |
| 175 | + } |
169 | 176 | } |
170 | 177 | } |
| 178 | +} |
171 | 179 |
|
172 | | - execute_erased( |
173 | | - state, |
174 | | - claim_guard, |
175 | | - opt_old_memo, |
176 | | - memo_ingredient_index, |
177 | | - strategy, |
178 | | - ) |
| 180 | +#[cold] |
| 181 | +fn fetch_cold_cycle_panic(zalsa_local: &ZalsaLocal, database_key_index: DatabaseKeyIndex) -> ! { |
| 182 | + // SAFETY: We do not access the query stack reentrantly. |
| 183 | + unsafe { |
| 184 | + zalsa_local.with_query_stack_unchecked(|stack| { |
| 185 | + panic!( |
| 186 | + "dependency graph cycle when querying {database_key_index:#?}, \ |
| 187 | + set cycle_fn/cycle_initial to fixpoint iterate.\n\ |
| 188 | + Query stack:\n{stack:#?}", |
| 189 | + ); |
| 190 | + }) |
| 191 | + } |
179 | 192 | } |
180 | 193 |
|
181 | 194 | #[cold] |
182 | | -fn fetch_cold_cycle_erased<'db>( |
| 195 | +fn fetch_cold_cycle_recoverable_erased<'db>( |
183 | 196 | state: &mut dyn QueryState<'db>, |
184 | 197 | zalsa: &'db Zalsa, |
185 | | - zalsa_local: &'db ZalsaLocal, |
186 | 198 | database_key_index: DatabaseKeyIndex, |
187 | 199 | memo_ingredient_index: MemoIngredientIndex, |
188 | | - strategy: CycleRecoveryStrategy, |
189 | 200 | ) -> ErasedMemo<'db> { |
190 | 201 | let id = database_key_index.key_index(); |
191 | 202 |
|
192 | | - match strategy { |
193 | | - // SAFETY: We do not access the query stack reentrantly. |
194 | | - CycleRecoveryStrategy::Panic => unsafe { |
195 | | - zalsa_local.with_query_stack_unchecked(|stack| { |
196 | | - panic!( |
197 | | - "dependency graph cycle when querying {database_key_index:#?}, \ |
198 | | - set cycle_fn/cycle_initial to fixpoint iterate.\n\ |
199 | | - Query stack:\n{stack:#?}", |
200 | | - ); |
201 | | - }) |
202 | | - }, |
203 | | - CycleRecoveryStrategy::Fixpoint | CycleRecoveryStrategy::FallbackImmediate => { |
204 | | - let cancellation_count = zalsa.runtime().cancellation_count(); |
205 | | - // Don't validate provisional memos here: an existing value should be reused. |
206 | | - let current_memo = state |
207 | | - .get_memo(zalsa, id, memo_ingredient_index) |
208 | | - .filter(|memo| { |
209 | | - memo.verified_at.load() == zalsa.current_revision() |
210 | | - && memo.value_is_some |
211 | | - && memo.revisions.iteration().cancellation_count() == cancellation_count |
212 | | - }); |
213 | | - |
214 | | - // Ideally, any current provisional value could be reused. Reusing a value that was |
215 | | - // not a cycle head in the last iteration would require inserting itself as a head, |
216 | | - // which in turn requires cloning the value or making the cycle-head list concurrent. |
217 | | - if let Some(memo) = current_memo |
218 | | - .filter(|memo| memo.revisions.cycle_heads().contains(&database_key_index)) |
219 | | - { |
220 | | - memo.revisions |
221 | | - .cycle_heads() |
222 | | - .remove_all_except(database_key_index); |
223 | | - |
224 | | - crate::tracing::debug!( |
225 | | - "hit cycle at {database_key_index:#?}, \ |
226 | | - returning last provisional value: {:#?}", |
227 | | - memo.revisions |
228 | | - ); |
229 | | - return memo; |
230 | | - } |
231 | | - |
232 | | - crate::tracing::debug!( |
233 | | - "hit cycle at {database_key_index:#?}, \ |
234 | | - inserting and returning fixpoint initial value" |
235 | | - ); |
236 | | - |
237 | | - let iteration = current_memo |
238 | | - .map(|memo| memo.revisions.iteration()) |
239 | | - .unwrap_or_else(|| IterationStamp::initial(cancellation_count)); |
240 | | - let revisions = QueryRevisions::fixpoint_initial(database_key_index, iteration); |
241 | | - state.use_fallback(zalsa, id); |
242 | | - state.insert_memo(zalsa, id, revisions, memo_ingredient_index) |
243 | | - } |
| 203 | + let cancellation_count = zalsa.runtime().cancellation_count(); |
| 204 | + // Don't validate provisional memos here: an existing value should be reused. |
| 205 | + let current_memo = state |
| 206 | + .get_memo(zalsa, id, memo_ingredient_index) |
| 207 | + .filter(|memo| { |
| 208 | + memo.verified_at.load() == zalsa.current_revision() |
| 209 | + && memo.value_is_some |
| 210 | + && memo.revisions.iteration().cancellation_count() == cancellation_count |
| 211 | + }); |
| 212 | + |
| 213 | + // Ideally, any current provisional value could be reused. Reusing a value that was not a |
| 214 | + // cycle head in the last iteration would require inserting itself as a head, which in turn |
| 215 | + // requires cloning the value or making the cycle-head list concurrent. |
| 216 | + if let Some(memo) = |
| 217 | + current_memo.filter(|memo| memo.revisions.cycle_heads().contains(&database_key_index)) |
| 218 | + { |
| 219 | + memo.revisions |
| 220 | + .cycle_heads() |
| 221 | + .remove_all_except(database_key_index); |
| 222 | + |
| 223 | + crate::tracing::debug!( |
| 224 | + "hit cycle at {database_key_index:#?}, \ |
| 225 | + returning last provisional value: {:#?}", |
| 226 | + memo.revisions |
| 227 | + ); |
| 228 | + return memo; |
244 | 229 | } |
| 230 | + |
| 231 | + crate::tracing::debug!( |
| 232 | + "hit cycle at {database_key_index:#?}, \ |
| 233 | + inserting and returning fixpoint initial value" |
| 234 | + ); |
| 235 | + |
| 236 | + let iteration = current_memo |
| 237 | + .map(|memo| memo.revisions.iteration()) |
| 238 | + .unwrap_or_else(|| IterationStamp::initial(cancellation_count)); |
| 239 | + let revisions = QueryRevisions::fixpoint_initial(database_key_index, iteration); |
| 240 | + state.use_fallback(zalsa, id); |
| 241 | + state.insert_memo(zalsa, id, revisions, memo_ingredient_index) |
245 | 242 | } |
0 commit comments