1use std::{
2 collections::{HashMap, VecDeque},
3 iter::once,
4 ops::Deref,
5};
6
7use futures::{
8 Stream, StreamExt,
9 stream::{FuturesUnordered, unfold},
10};
11use ruma::{EventId, OwnedEventId};
12use tuwunel_core::{
13 Result, implement, is_equal_to,
14 matrix::{Event, event_id::RandomState, pdu::AuthEvents},
15 smallvec::SmallVec,
16 utils::{
17 BoolExt,
18 math::expect_into,
19 stream::{IterStream, automatic_width},
20 },
21};
22
23struct Global<Fut: Future + Send> {
24 subgraph: Subgraph,
25 todo: Todo<Fut>,
26 locals: Locals,
27 waiters: Waiters,
28 ready: Ready,
29 deferred: Deferred,
30 parked: usize,
31}
32
33struct Context<'a> {
34 subgraph: &'a mut Subgraph,
35 waiters: &'a mut Waiters,
36 ready: &'a mut Ready,
37 parked: &'a mut usize,
38 outputs: &'a mut Path,
39}
40
41#[derive(Debug, Default)]
42struct Local {
43 path: Path,
44 stack: Stack,
45 marked: usize,
46}
47
48#[derive(Debug)]
49struct Wake {
50 event_id: OwnedEventId,
51 locals: Waiting,
52 result: Resolution,
53}
54
55#[derive(Debug)]
56enum Evaluation {
57 Continue,
58 Fetch(OwnedEventId),
59 Park,
60}
61
62#[derive(Clone, Copy, Debug)]
63enum Resolution {
64 Dead,
65 Subgraph,
66}
67
68#[derive(Clone, Copy, Debug)]
69enum Substate {
70 Conflicted,
71 Pending(LocalId),
74 Dead,
75 Subgraph,
76}
77
78type Todo<Fut> = FuturesUnordered<Fut>;
79type Subgraph = HashMap<OwnedEventId, Substate, RandomState>;
80type Locals = Vec<Local>;
81type LocalId = u32;
82type Waiters = HashMap<OwnedEventId, Waiting, RandomState>;
83type Waiting = SmallVec<[usize; WAITING_INLINE]>;
84type Ready = Vec<Wake>;
85type Deferred = VecDeque<(usize, OwnedEventId)>;
86type Path = SmallVec<[OwnedEventId; PATH_INLINE]>;
87type Stack = SmallVec<[Frame; STACK_INLINE]>;
88type Frame = AuthEvents;
89
90const PATH_INLINE: usize = 4;
91const STACK_INLINE: usize = 4;
92const WAITING_INLINE: usize = 1;
93const CAPACITY_MULTIPLIER: usize = 4;
94
95#[tracing::instrument(
96 name = "subgraph_dfs",
97 level = "debug",
98 skip_all,
99 fields(
100 starting_events = %conflicted_set.len(),
101 )
102)]
103pub(super) fn conflicted_subgraph_dfs<Fetch, Fut, Pdu>(
104 conflicted_set: &Vec<&OwnedEventId>,
105 fetch: &Fetch,
106) -> impl Stream<Item = OwnedEventId> + Send
107where
108 Fetch: Fn(OwnedEventId) -> Fut + Sync,
109 Fut: Future<Output = Result<Pdu>> + Send,
110 Pdu: Event,
111{
112 let initial_capacity = conflicted_set
113 .len()
114 .saturating_mul(CAPACITY_MULTIPLIER);
115
116 let seeds = || conflicted_set.iter().map(Deref::deref).cloned();
117
118 let mut subgraph = Subgraph::with_capacity_and_hasher(initial_capacity, RandomState);
121
122 subgraph.extend(seeds().map(|event_id| (event_id, Substate::Conflicted)));
123
124 let state = Global {
125 subgraph,
126 todo: Todo::new(),
127 locals: Locals::with_capacity(conflicted_set.len()),
128 waiters: Waiters::with_hasher(RandomState),
129 ready: Ready::new(),
130 deferred: Deferred::new(),
131 parked: 0,
132 };
133
134 unfold((seeds(), state), async |(mut inputs, mut state)| {
135 let width = automatic_width();
136
137 debug_assert!(
138 state.todo.len() <= width,
139 "Excessive in-flight conflicted-subgraph fetches"
140 );
141
142 while state.todo.len() < width {
143 if let Some((id, event_id)) = state.deferred.pop_front() {
144 state.todo.push(fetch_auth(id, event_id, fetch));
145 continue;
146 }
147
148 let Some(seed) = inputs.next() else {
149 break;
150 };
151
152 let id = state.locals.len();
153
154 state.locals.push(Local::default());
155 state.todo.push(fetch_auth(id, seed, fetch));
156 }
157
158 let Some((id, event_id, event)) = state.todo.next().await else {
159 debug_assert!(state.waiters.is_empty(), "Unresolved conflicted-subgraph waiters");
160 debug_assert!(state.ready.is_empty(), "Undrained conflicted-subgraph wakes");
161 debug_assert!(state.deferred.is_empty(), "Deferred conflicted-subgraph fetches");
162 debug_assert_eq!(state.parked, 0, "Parked conflicted-subgraph walkers");
163 return None;
164 };
165
166 while state.todo.len() < width
167 && let Some((deferred_id, deferred_event_id)) = state.deferred.pop_front()
168 {
169 state
170 .todo
171 .push(fetch_auth(deferred_id, deferred_event_id, fetch));
172 }
173
174 let mut outputs = Path::new();
175
176 if let Some(next_id) = process_fetch(&mut state, id, event_id, event, &mut outputs) {
177 if state.todo.len() < width {
178 state.todo.push(fetch_auth(id, next_id, fetch));
179 } else {
180 state.deferred.push_back((id, next_id));
181 }
182 }
183
184 while let Some(Wake { event_id, locals, result }) = state.ready.pop() {
185 for id in locals {
186 if let Some(next_id) = resume(&mut state, id, &event_id, result, &mut outputs) {
187 if state.todo.len() < width {
188 state.todo.push(fetch_auth(id, next_id, fetch));
189 } else {
190 state.deferred.push_back((id, next_id));
191 }
192 }
193 }
194 }
195
196 Some((outputs.into_iter().stream(), (inputs, state)))
197 })
198 .flatten()
199}
200
201fn fetch_auth<Fetch, Fut, Pdu>(
202 id: usize,
203 event_id: OwnedEventId,
204 fetch: &Fetch,
205) -> impl Future<Output = (usize, OwnedEventId, Result<Pdu>)> + Send
206where
207 Fetch: Fn(OwnedEventId) -> Fut,
208 Fut: Future<Output = Result<Pdu>> + Send,
209{
210 let fut = fetch(event_id.clone());
211
212 async move { (id, event_id, fut.await) }
213}
214
215fn process_fetch<Fut, Pdu>(
216 state: &mut Global<Fut>,
217 id: usize,
218 event_id: OwnedEventId,
219 event: Result<Pdu>,
220 outputs: &mut Path,
221) -> Option<OwnedEventId>
222where
223 Fut: Future + Send,
224 Pdu: Event,
225{
226 match event {
227 | Ok(event) => {
228 let local = &mut state.locals[id];
229
230 local.path.push(event_id);
231 local
232 .stack
233 .push(event.auth_events_into().into_iter().collect());
234 },
235 | Err(_) => {
236 let Global { subgraph, waiters, ready, parked, .. } = state;
237 let mut context = Context {
238 subgraph,
239 waiters,
240 ready,
241 parked,
242 outputs,
243 };
244
245 complete_pending(&mut context, event_id, Resolution::Dead);
246 },
247 }
248
249 advance(state, id, outputs)
250}
251
252fn resume<Fut: Future + Send>(
253 state: &mut Global<Fut>,
254 id: usize,
255 event_id: &EventId,
256 result: Resolution,
257 outputs: &mut Path,
258) -> Option<OwnedEventId> {
259 if matches!(result, Resolution::Subgraph) {
260 let Global {
261 subgraph, locals, waiters, ready, parked, ..
262 } = state;
263
264 let mut context = Context {
265 subgraph,
266 waiters,
267 ready,
268 parked,
269 outputs,
270 };
271
272 locals[id].insert_path(&mut context, event_id);
273 }
274
275 advance(state, id, outputs)
276}
277
278fn advance<Fut: Future + Send>(
279 state: &mut Global<Fut>,
280 id: usize,
281 outputs: &mut Path,
282) -> Option<OwnedEventId> {
283 let Global {
284 subgraph, locals, waiters, ready, parked, ..
285 } = state;
286
287 let local = &mut locals[id];
288 let mut context = Context {
289 subgraph,
290 waiters,
291 ready,
292 parked,
293 outputs,
294 };
295
296 while let Some(event_id) = local.pop(&mut context) {
297 match local.eval(id, &mut context, event_id) {
298 | Evaluation::Continue => {},
299 | Evaluation::Fetch(event_id) => return Some(event_id),
300 | Evaluation::Park => return None,
301 }
302 }
303
304 if local.stack.is_empty() {
305 *local = Local::default();
306 }
307
308 None
309}
310
311#[implement(Local)]
312fn pop(&mut self, context: &mut Context<'_>) -> Option<OwnedEventId> {
313 while self.stack.last().is_some_and(Frame::is_empty) {
314 self.stack.pop();
315
316 if let Some(event_id) = self.path.pop() {
317 complete_pending(context, event_id, Resolution::Dead);
318 }
319 }
320
321 self.marked = self.marked.min(self.path.len());
322 self.stack.last_mut().and_then(Frame::pop)
323}
324
325#[implement(Local)]
326#[tracing::instrument(
327 name = "descent",
328 level = "trace",
329 skip_all,
330 fields(
331 s = ?context
332 .subgraph
333 .values()
334 .fold((0_u64, 0_u64, 0_u64, 0_u64), |(pending, dead, conflicted, subgraph), state| {
335 match state {
336 | Substate::Pending(_) =>
337 (pending.saturating_add(1), dead, conflicted, subgraph),
338 | Substate::Dead => (pending, dead.saturating_add(1), conflicted, subgraph),
339 | Substate::Conflicted => {
340 (pending, dead, conflicted.saturating_add(1), subgraph)
341 },
342 | Substate::Subgraph => {
343 (pending, dead, conflicted, subgraph.saturating_add(1))
344 },
345 }
346 }),
347
348 %event_id,
349 path = self.path.len(),
350 stack = self.stack.iter().flatten().count(),
351 )
352)]
353fn eval(&mut self, id: usize, context: &mut Context<'_>, event_id: OwnedEventId) -> Evaluation {
354 match context.subgraph.get(&event_id).copied() {
355 | Some(Substate::Subgraph) => {
356 self.insert_path(context, &event_id);
357 Evaluation::Continue
358 },
359 | Some(Substate::Dead) => Evaluation::Continue,
360 | Some(Substate::Pending(owner)) => {
361 if expect_into::<usize, _>(owner) == id {
362 return Evaluation::Continue;
363 }
364
365 context
366 .waiters
367 .entry(event_id)
368 .or_default()
369 .push(id);
370
371 *context.parked = context.parked.saturating_add(1);
372 Evaluation::Park
373 },
374 | Some(Substate::Conflicted) => {
375 self.insert_path(context, &event_id);
376
377 self.path
378 .first()
379 .is_some_and(is_equal_to!(&event_id))
380 .is_false()
381 .then_some(event_id)
382 .map_or(Evaluation::Continue, Evaluation::Fetch)
383 },
384 | None => {
385 context
386 .subgraph
387 .insert(event_id.clone(), Substate::Pending(expect_into(id)));
388
389 Evaluation::Fetch(event_id)
390 },
391 }
392}
393
394#[implement(Local)]
395fn insert_path(&mut self, context: &mut Context<'_>, event_id: &EventId) {
396 let Context {
397 subgraph,
398 waiters,
399 ready,
400 parked,
401 outputs,
402 } = context;
403
404 let inserted = self.path[self.marked..]
405 .iter()
406 .map(AsRef::as_ref)
407 .chain(once(event_id))
408 .filter(|event_id| insert_path_filter(subgraph, waiters, ready, parked, event_id))
409 .map(ToOwned::to_owned);
410
411 outputs.extend(inserted);
412 self.marked = self.path.len();
413}
414
415fn insert_path_filter(
416 subgraph: &mut Subgraph,
417 waiters: &mut Waiters,
418 ready: &mut Ready,
419 parked: &mut usize,
420 event_id: &EventId,
421) -> bool {
422 let Some(state) = subgraph.get_mut(event_id) else {
423 subgraph.insert(event_id.to_owned(), Substate::Subgraph);
424 return true;
425 };
426
427 if matches!(*state, Substate::Subgraph) {
428 return false;
429 }
430
431 let pending = matches!(*state, Substate::Pending(_));
432
433 debug_assert!(
434 !matches!(*state, Substate::Dead),
435 "Dead node inserted into conflicted subgraph"
436 );
437
438 *state = Substate::Subgraph;
439
440 if pending
441 && !waiters.is_empty()
442 && let Some(locals) = waiters.remove(event_id)
443 {
444 debug_assert!(*parked >= locals.len(), "Invalid parked walker count");
445 *parked = parked.saturating_sub(locals.len());
446 ready.push(Wake {
447 event_id: event_id.to_owned(),
448 locals,
449 result: Resolution::Subgraph,
450 });
451 }
452
453 true
454}
455
456fn complete_pending(context: &mut Context<'_>, event_id: OwnedEventId, result: Resolution) {
457 let Some(state) = context.subgraph.get_mut(&event_id) else {
458 return;
459 };
460
461 if !matches!(*state, Substate::Pending(_)) {
462 return;
463 }
464
465 *state = match result {
466 | Resolution::Dead => Substate::Dead,
467 | Resolution::Subgraph => Substate::Subgraph,
468 };
469
470 if context.waiters.is_empty() {
471 return;
472 }
473
474 let Some(locals) = context.waiters.remove(&event_id) else {
475 return;
476 };
477
478 debug_assert!(*context.parked >= locals.len(), "Invalid parked walker count");
479 *context.parked = context.parked.saturating_sub(locals.len());
480 context
481 .ready
482 .push(Wake { event_id, locals, result });
483}