1use std::collections::HashMap;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicUsize, Ordering};
13
14use fsqlite_error::{FrankenError, Result};
15use fsqlite_types::Region;
16use fsqlite_types::cx::{self, Cx};
17use tracing::debug;
18
19use crate::quiescence::{ChildRegionQuiescence, RegionQuiescenceSnapshot};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum RegionKind {
26 DbRoot,
28 WriteCoordinator,
30 SymbolStore,
32 Replication,
34 CheckpointGc,
36 Observability,
38 PerConnection,
40 PerTransaction,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum RegionState {
47 Open,
49 Closing,
51 Closed,
53}
54
55type Finalizer = Box<dyn FnOnce() + Send>;
57
58type SharedCounter = Arc<AtomicUsize>;
60
61fn new_counter() -> SharedCounter {
62 Arc::new(AtomicUsize::new(0))
63}
64
65struct RegionNode {
69 kind: RegionKind,
70 state: RegionState,
71 cx: Cx<cx::FullCaps>,
72 parent: Option<Region>,
73 children: Vec<Region>,
74 finalizers: Vec<Finalizer>,
75 active_tasks: SharedCounter,
76 active_obligations: SharedCounter,
77}
78
79pub struct TaskHandle {
86 counter: SharedCounter,
87 region: Region,
88}
89
90impl TaskHandle {
91 #[must_use]
93 pub const fn region(&self) -> Region {
94 self.region
95 }
96}
97
98impl Drop for TaskHandle {
99 fn drop(&mut self) {
100 self.counter.fetch_sub(1, Ordering::AcqRel);
101 }
102}
103
104pub struct ObligationHandle {
109 counter: SharedCounter,
110 region: Region,
111}
112
113impl ObligationHandle {
114 #[must_use]
116 pub const fn region(&self) -> Region {
117 self.region
118 }
119
120 pub fn resolve(self) {
124 }
126}
127
128impl Drop for ObligationHandle {
129 fn drop(&mut self) {
130 self.counter.fetch_sub(1, Ordering::AcqRel);
131 }
132}
133
134pub struct RegionTree {
142 nodes: HashMap<Region, RegionNode>,
143 next_id: u32,
144 root: Option<Region>,
145}
146
147impl Default for RegionTree {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153impl RegionTree {
154 #[must_use]
156 pub fn new() -> Self {
157 Self {
158 nodes: HashMap::new(),
159 next_id: 0,
160 root: None,
161 }
162 }
163
164 pub fn create_root(&mut self, kind: RegionKind, cx: Cx<cx::FullCaps>) -> Result<Region> {
168 if self.root.is_some() {
169 return Err(FrankenError::Internal(
170 "root region already exists".to_owned(),
171 ));
172 }
173 let id = self.alloc_id();
174 self.nodes.insert(
175 id,
176 RegionNode {
177 kind,
178 state: RegionState::Open,
179 cx,
180 parent: None,
181 children: Vec::new(),
182 finalizers: Vec::new(),
183 active_tasks: new_counter(),
184 active_obligations: new_counter(),
185 },
186 );
187 self.root = Some(id);
188 debug!(region = id.get(), kind = ?kind, "region created (root)");
189 Ok(id)
190 }
191
192 pub fn create_child(
194 &mut self,
195 parent: Region,
196 kind: RegionKind,
197 cx: Cx<cx::FullCaps>,
198 ) -> Result<Region> {
199 let parent_state = self.nodes.get(&parent).map(|n| n.state).ok_or_else(|| {
200 FrankenError::Internal(format!("parent region {} not found", parent.get()))
201 })?;
202 if parent_state != RegionState::Open {
203 return Err(FrankenError::Busy);
204 }
205 let id = self.alloc_id();
206 self.nodes.insert(
207 id,
208 RegionNode {
209 kind,
210 state: RegionState::Open,
211 cx,
212 parent: Some(parent),
213 children: Vec::new(),
214 finalizers: Vec::new(),
215 active_tasks: new_counter(),
216 active_obligations: new_counter(),
217 },
218 );
219 if let Some(parent_node) = self.nodes.get_mut(&parent) {
220 parent_node.children.push(id);
221 }
222 debug!(region = id.get(), parent = parent.get(), kind = ?kind, "region created (child)");
223 Ok(id)
224 }
225
226 #[must_use]
230 pub fn root(&self) -> Option<Region> {
231 self.root
232 }
233
234 #[must_use]
236 pub fn kind(&self, id: Region) -> Option<RegionKind> {
237 self.nodes.get(&id).map(|n| n.kind)
238 }
239
240 #[must_use]
242 pub fn state(&self, id: Region) -> Option<RegionState> {
243 self.nodes.get(&id).map(|n| n.state)
244 }
245
246 #[must_use]
248 pub fn parent(&self, id: Region) -> Option<Option<Region>> {
249 self.nodes.get(&id).map(|n| n.parent)
250 }
251
252 #[must_use]
254 pub fn children(&self, id: Region) -> Option<&[Region]> {
255 self.nodes.get(&id).map(|n| n.children.as_slice())
256 }
257
258 #[must_use]
260 pub fn cx(&self, id: Region) -> Option<Cx<cx::FullCaps>> {
261 self.nodes.get(&id).map(|n| n.cx.clone())
262 }
263
264 #[must_use]
266 pub fn active_tasks(&self, id: Region) -> usize {
267 self.nodes
268 .get(&id)
269 .map_or(0, |n| n.active_tasks.load(Ordering::Acquire))
270 }
271
272 #[must_use]
274 pub fn active_obligations(&self, id: Region) -> usize {
275 self.nodes
276 .get(&id)
277 .map_or(0, |n| n.active_obligations.load(Ordering::Acquire))
278 }
279
280 pub fn register_task(&self, id: Region) -> Result<TaskHandle> {
287 let node = self
288 .nodes
289 .get(&id)
290 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?;
291 if node.state != RegionState::Open {
292 return Err(FrankenError::Busy);
293 }
294 node.active_tasks.fetch_add(1, Ordering::AcqRel);
295 debug!(region = id.get(), "task registered");
296 Ok(TaskHandle {
297 counter: Arc::clone(&node.active_tasks),
298 region: id,
299 })
300 }
301
302 pub fn register_obligation(&self, id: Region) -> Result<ObligationHandle> {
308 let node = self
309 .nodes
310 .get(&id)
311 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?;
312 if node.state == RegionState::Closed {
313 return Err(FrankenError::Busy);
314 }
315 node.active_obligations.fetch_add(1, Ordering::AcqRel);
316 debug!(region = id.get(), "obligation registered");
317 Ok(ObligationHandle {
318 counter: Arc::clone(&node.active_obligations),
319 region: id,
320 })
321 }
322
323 pub fn register_finalizer(
325 &mut self,
326 id: Region,
327 finalizer: impl FnOnce() + Send + 'static,
328 ) -> Result<()> {
329 let node = self
330 .nodes
331 .get_mut(&id)
332 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?;
333 if node.state != RegionState::Open {
334 return Err(FrankenError::Busy);
335 }
336 node.finalizers.push(Box::new(finalizer));
337 Ok(())
338 }
339
340 pub fn begin_close(&mut self, id: Region) -> Result<()> {
350 let children = self
351 .nodes
352 .get(&id)
353 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?
354 .children
355 .clone();
356
357 let node = self
359 .nodes
360 .get_mut(&id)
361 .expect("region confirmed present above");
362 if node.state == RegionState::Closed {
363 return Ok(());
364 }
365 node.cx.cancel();
366 node.state = RegionState::Closing;
367 debug!(region = id.get(), kind = ?node.kind, "region closing");
368
369 for child in children {
371 if self.state(child) == Some(RegionState::Open) {
372 self.begin_close(child)?;
373 }
374 }
375 Ok(())
376 }
377
378 #[must_use]
385 pub fn is_quiescent(&self, id: Region) -> bool {
386 self.quiescence_snapshot(id)
387 .is_some_and(|snapshot| snapshot.is_quiescent())
388 }
389
390 #[must_use]
392 pub fn quiescence_snapshot(&self, id: Region) -> Option<RegionQuiescenceSnapshot> {
393 let node = self.nodes.get(&id)?;
394 let non_closed_children = node
395 .children
396 .iter()
397 .filter_map(|child| {
398 let state = self.state(*child);
399 if state == Some(RegionState::Closed) {
400 None
401 } else {
402 Some(ChildRegionQuiescence {
403 region: *child,
404 state,
405 })
406 }
407 })
408 .collect();
409
410 Some(RegionQuiescenceSnapshot {
411 region: id,
412 state: node.state,
413 active_tasks: node.active_tasks.load(Ordering::Acquire),
414 active_obligations: node.active_obligations.load(Ordering::Acquire),
415 non_closed_children,
416 })
417 }
418
419 pub fn complete_close(&mut self, id: Region) -> Result<()> {
423 let state = self
424 .nodes
425 .get(&id)
426 .map(|n| n.state)
427 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?;
428 if state == RegionState::Closed {
429 return Ok(());
430 }
431 if state != RegionState::Closing {
432 return Err(FrankenError::Internal(
433 "region must be in Closing state before complete_close".to_owned(),
434 ));
435 }
436 let snapshot = self
437 .quiescence_snapshot(id)
438 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?;
439 if !snapshot.is_quiescent() {
440 return Err(FrankenError::Internal(format!(
441 "region not quiescent; children_open={} active_tasks={} active_obligations={}",
442 snapshot.non_closed_children.len(),
443 snapshot.active_tasks,
444 snapshot.active_obligations
445 )));
446 }
447 let node = self
448 .nodes
449 .get_mut(&id)
450 .ok_or_else(|| FrankenError::Internal(format!("region {} not found", id.get())))?;
451 let finalizers = std::mem::take(&mut node.finalizers);
452 for f in finalizers {
453 f();
454 }
455 node.state = RegionState::Closed;
456 debug!(region = id.get(), kind = ?node.kind, "region closed");
457 Ok(())
458 }
459
460 pub fn close_and_drain(&mut self, id: Region) -> Result<()> {
466 self.begin_close(id)?;
467 self.drain_subtree(id)
468 }
469
470 fn drain_subtree(&mut self, id: Region) -> Result<()> {
473 let children = self
474 .nodes
475 .get(&id)
476 .map(|n| n.children.clone())
477 .unwrap_or_default();
478 for child in children {
479 self.drain_subtree(child)?;
480 }
481 while self.active_tasks(id) > 0 || self.active_obligations(id) > 0 {
482 std::hint::spin_loop();
483 }
484 self.complete_close(id)
485 }
486
487 fn alloc_id(&mut self) -> Region {
488 let id = Region::new(self.next_id);
489 self.next_id = self.next_id.checked_add(1).expect("region id overflow");
490 id
491 }
492}
493
494#[cfg(test)]
497mod tests {
498 use std::sync::Arc;
499 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
500
501 use super::*;
502
503 const BEAD_ID: &str = "bd-3go.9";
504
505 #[test]
506 fn test_region_tree_structure() {
507 let mut tree = RegionTree::new();
508 let root = tree
509 .create_root(RegionKind::DbRoot, Cx::new())
510 .expect("root creation");
511 let wc = tree
512 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
513 .expect("wc");
514 let ss = tree
515 .create_child(root, RegionKind::SymbolStore, Cx::new())
516 .expect("ss");
517 let repl = tree
518 .create_child(root, RegionKind::Replication, Cx::new())
519 .expect("repl");
520 let gc = tree
521 .create_child(root, RegionKind::CheckpointGc, Cx::new())
522 .expect("gc");
523 let obs = tree
524 .create_child(root, RegionKind::Observability, Cx::new())
525 .expect("obs");
526
527 assert_eq!(
529 tree.root(),
530 Some(root),
531 "bead_id={BEAD_ID} case=root_exists"
532 );
533 assert_eq!(tree.kind(root), Some(RegionKind::DbRoot));
534
535 let children = tree.children(root).expect("root has children");
537 assert_eq!(
538 children.len(),
539 5,
540 "bead_id={BEAD_ID} case=root_has_5_service_children"
541 );
542 assert_eq!(children, &[wc, ss, repl, gc, obs]);
543
544 assert_eq!(tree.kind(wc), Some(RegionKind::WriteCoordinator));
546 assert_eq!(tree.kind(ss), Some(RegionKind::SymbolStore));
547 assert_eq!(tree.kind(repl), Some(RegionKind::Replication));
548 assert_eq!(tree.kind(gc), Some(RegionKind::CheckpointGc));
549 assert_eq!(tree.kind(obs), Some(RegionKind::Observability));
550
551 for &child in children {
552 assert_eq!(
553 tree.parent(child),
554 Some(Some(root)),
555 "bead_id={BEAD_ID} case=child_parent_is_root region={}",
556 child.get()
557 );
558 }
559 }
560
561 #[test]
562 fn test_region_quiescence_all_children_complete() {
563 let mut tree = RegionTree::new();
564 let root = tree
565 .create_root(RegionKind::DbRoot, Cx::new())
566 .expect("root");
567 let region = tree
568 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
569 .expect("wc");
570
571 let tasks: Vec<TaskHandle> = (0..5)
573 .map(|_| tree.register_task(region).expect("register task"))
574 .collect();
575
576 assert_eq!(
577 tree.active_tasks(region),
578 5,
579 "bead_id={BEAD_ID} case=5_tasks_registered"
580 );
581
582 tree.begin_close(region).expect("begin close");
584 assert_eq!(tree.state(region), Some(RegionState::Closing));
585 assert!(
586 !tree.is_quiescent(region),
587 "bead_id={BEAD_ID} case=not_quiescent_with_active_tasks"
588 );
589
590 for (i, task) in tasks.into_iter().enumerate() {
592 drop(task);
593 if i < 4 {
594 assert!(
595 !tree.is_quiescent(region),
596 "bead_id={BEAD_ID} case=not_quiescent_after_{}_completions",
597 i + 1
598 );
599 }
600 }
601
602 assert!(
603 tree.is_quiescent(region),
604 "bead_id={BEAD_ID} case=quiescent_after_all_tasks_complete"
605 );
606 tree.complete_close(region).expect("complete close");
607 assert_eq!(tree.state(region), Some(RegionState::Closed));
608 }
609
610 #[test]
611 fn test_region_quiescence_finalizers_run() {
612 let mut tree = RegionTree::new();
613 let root = tree
614 .create_root(RegionKind::DbRoot, Cx::new())
615 .expect("root");
616 let region = tree
617 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
618 .expect("wc");
619
620 let flags: Vec<Arc<AtomicBool>> =
622 (0..3).map(|_| Arc::new(AtomicBool::new(false))).collect();
623 let tasks: Vec<TaskHandle> = (0..3)
624 .map(|_| tree.register_task(region).expect("register task"))
625 .collect();
626 for flag in &flags {
627 let f = Arc::clone(flag);
628 tree.register_finalizer(region, move || {
629 f.store(true, Ordering::Release);
630 })
631 .expect("register finalizer");
632 }
633
634 tree.begin_close(region).expect("begin close");
636 drop(tasks);
637
638 for (i, flag) in flags.iter().enumerate() {
640 assert!(
641 !flag.load(Ordering::Acquire),
642 "bead_id={BEAD_ID} case=finalizer_{i}_not_run_before_complete_close"
643 );
644 }
645
646 tree.complete_close(region).expect("complete close");
648 for (i, flag) in flags.iter().enumerate() {
649 assert!(
650 flag.load(Ordering::Acquire),
651 "bead_id={BEAD_ID} case=finalizer_{i}_ran_after_complete_close"
652 );
653 }
654 }
655
656 #[test]
657 fn test_region_quiescence_obligations_resolved() {
658 let mut tree = RegionTree::new();
659 let root = tree
660 .create_root(RegionKind::DbRoot, Cx::new())
661 .expect("root");
662 let region = tree
663 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
664 .expect("wc");
665
666 let obligations: Vec<ObligationHandle> = (0..3)
667 .map(|_| {
668 tree.register_obligation(region)
669 .expect("register obligation")
670 })
671 .collect();
672
673 tree.begin_close(region).expect("begin close");
674 assert!(
675 !tree.is_quiescent(region),
676 "bead_id={BEAD_ID} case=not_quiescent_with_pending_obligations"
677 );
678
679 for (i, obligation) in obligations.into_iter().enumerate() {
681 obligation.resolve();
682 if i < 2 {
683 assert!(
684 !tree.is_quiescent(region),
685 "bead_id={BEAD_ID} case=not_quiescent_after_{}_resolutions",
686 i + 1
687 );
688 }
689 }
690
691 assert!(
692 tree.is_quiescent(region),
693 "bead_id={BEAD_ID} case=quiescent_after_all_obligations_resolved"
694 );
695 tree.complete_close(region).expect("complete close");
696 }
697
698 #[test]
699 fn test_quiescence_snapshot_reports_precise_blockers() {
700 let mut tree = RegionTree::new();
701 let root = tree
702 .create_root(RegionKind::DbRoot, Cx::new())
703 .expect("root");
704 let parent = tree
705 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
706 .expect("parent");
707 let child = tree
708 .create_child(parent, RegionKind::PerConnection, Cx::new())
709 .expect("child");
710 let _task = tree.register_task(parent).expect("task");
711 let _obligation = tree.register_obligation(parent).expect("obligation");
712
713 tree.begin_close(parent).expect("begin close");
714 let snapshot = tree
715 .quiescence_snapshot(parent)
716 .expect("snapshot for existing region");
717 assert_eq!(snapshot.region, parent);
718 assert_eq!(snapshot.state, RegionState::Closing);
719 assert_eq!(snapshot.active_tasks, 1);
720 assert_eq!(snapshot.active_obligations, 1);
721 assert_eq!(snapshot.non_closed_children.len(), 1);
722 assert_eq!(snapshot.non_closed_children[0].region, child);
723 assert_eq!(
724 snapshot.non_closed_children[0].state,
725 Some(RegionState::Closing)
726 );
727 assert_eq!(snapshot.blocker_count(), 3);
728 assert!(!snapshot.is_quiescent());
729 }
730
731 #[test]
732 fn test_complete_close_error_includes_quiescence_counts() {
733 let mut tree = RegionTree::new();
734 let root = tree
735 .create_root(RegionKind::DbRoot, Cx::new())
736 .expect("root");
737 let child = tree
738 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
739 .expect("child");
740 let _task = tree.register_task(child).expect("task");
741
742 tree.begin_close(child).expect("begin close");
743 let err = tree
744 .complete_close(child)
745 .expect_err("active task must block close");
746 let FrankenError::Internal(message) = err else {
747 panic!("expected internal error for non-quiescent close");
748 };
749 assert!(
750 message.contains("children_open=0"),
751 "bead_id={BEAD_ID} case=quiescence_error_children_count message={message}"
752 );
753 assert!(
754 message.contains("active_tasks=1"),
755 "bead_id={BEAD_ID} case=quiescence_error_task_count message={message}"
756 );
757 assert!(
758 message.contains("active_obligations=0"),
759 "bead_id={BEAD_ID} case=quiescence_error_obligation_count message={message}"
760 );
761 }
762
763 #[test]
764 fn test_no_detached_tasks() {
765 let tree = RegionTree::new();
766 let result = tree.register_task(Region::new(999));
768 assert!(
769 result.is_err(),
770 "bead_id={BEAD_ID} case=detached_task_rejected"
771 );
772 }
773
774 #[test]
775 fn test_complete_close_requires_closing_state() {
776 let mut tree = RegionTree::new();
777 let root = tree
778 .create_root(RegionKind::DbRoot, Cx::new())
779 .expect("root");
780 let child = tree
781 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
782 .expect("child");
783
784 let err = tree
785 .complete_close(child)
786 .expect_err("must require begin_close");
787 assert!(
788 matches!(err, FrankenError::Internal(_)),
789 "bead_id={BEAD_ID} case=complete_close_requires_closing_state got {err:?}"
790 );
791 assert_eq!(
792 tree.state(child),
793 Some(RegionState::Open),
794 "bead_id={BEAD_ID} case=child_state_unchanged_when_close_rejected"
795 );
796 }
797
798 #[test]
799 fn test_database_close_awaits_quiescence() {
800 let mut tree = RegionTree::new();
801 let root = tree
802 .create_root(RegionKind::DbRoot, Cx::new())
803 .expect("root");
804
805 let wc = tree
807 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
808 .expect("wc");
809 let gc = tree
810 .create_child(root, RegionKind::CheckpointGc, Cx::new())
811 .expect("gc");
812
813 let wc_task = tree.register_task(wc).expect("wc task");
814 let gc_task = tree.register_task(gc).expect("gc task");
815
816 let finalized = Arc::new(AtomicBool::new(false));
817 {
818 let flag = Arc::clone(&finalized);
819 tree.register_finalizer(root, move || {
820 flag.store(true, Ordering::Release);
821 })
822 .expect("root finalizer");
823 }
824
825 tree.begin_close(root).expect("begin close root");
827 assert_eq!(tree.state(wc), Some(RegionState::Closing));
828 assert_eq!(tree.state(gc), Some(RegionState::Closing));
829
830 assert!(
832 !tree.is_quiescent(root),
833 "bead_id={BEAD_ID} case=root_not_quiescent_with_active_children"
834 );
835
836 drop(wc_task);
838 assert!(
839 !tree.is_quiescent(root),
840 "bead_id={BEAD_ID} case=root_not_quiescent_gc_still_active"
841 );
842 drop(gc_task);
843
844 assert!(tree.is_quiescent(wc));
846 assert!(tree.is_quiescent(gc));
847 tree.complete_close(wc).expect("close wc");
848 tree.complete_close(gc).expect("close gc");
849
850 assert!(
852 tree.is_quiescent(root),
853 "bead_id={BEAD_ID} case=root_quiescent_after_children_closed"
854 );
855 tree.complete_close(root).expect("close root");
856
857 assert!(
858 finalized.load(Ordering::Acquire),
859 "bead_id={BEAD_ID} case=root_finalizer_ran"
860 );
861 assert_eq!(tree.state(root), Some(RegionState::Closed));
862 }
863
864 #[test]
865 fn test_per_connection_region_child_of_root() {
866 let mut tree = RegionTree::new();
867 let root = tree
868 .create_root(RegionKind::DbRoot, Cx::new())
869 .expect("root");
870 let conn = tree
871 .create_child(root, RegionKind::PerConnection, Cx::new())
872 .expect("conn");
873
874 assert_eq!(
875 tree.parent(conn),
876 Some(Some(root)),
877 "bead_id={BEAD_ID} case=connection_is_child_of_root"
878 );
879 assert_eq!(tree.kind(conn), Some(RegionKind::PerConnection));
880
881 let conn_cx = tree.cx(conn).expect("conn cx");
883 tree.begin_close(root).expect("begin close root");
884 assert!(
885 conn_cx.is_cancel_requested(),
886 "bead_id={BEAD_ID} case=root_close_cancels_connection"
887 );
888 assert_eq!(tree.state(conn), Some(RegionState::Closing));
889
890 tree.complete_close(conn).expect("close conn");
891 tree.complete_close(root).expect("close root");
892 }
893
894 #[test]
895 #[allow(clippy::too_many_lines)]
896 fn test_e2e_structured_concurrency_shutdown() {
897 let mut tree = RegionTree::new();
898 let root = tree
899 .create_root(RegionKind::DbRoot, Cx::new())
900 .expect("root");
901
902 let wc = tree
904 .create_child(root, RegionKind::WriteCoordinator, Cx::new())
905 .expect("wc");
906 let ss = tree
907 .create_child(root, RegionKind::SymbolStore, Cx::new())
908 .expect("ss");
909 let repl = tree
910 .create_child(root, RegionKind::Replication, Cx::new())
911 .expect("repl");
912 let gc = tree
913 .create_child(root, RegionKind::CheckpointGc, Cx::new())
914 .expect("gc");
915 let obs = tree
916 .create_child(root, RegionKind::Observability, Cx::new())
917 .expect("obs");
918
919 let conns: Vec<Region> = (0..3)
921 .map(|_| {
922 tree.create_child(root, RegionKind::PerConnection, Cx::new())
923 .expect("conn")
924 })
925 .collect();
926
927 let mut txn_tasks = Vec::new();
928 for &conn in &conns {
929 let txn = tree
930 .create_child(conn, RegionKind::PerTransaction, Cx::new())
931 .expect("txn");
932 txn_tasks.push(tree.register_task(txn).expect("txn task"));
933 }
934
935 let service_tasks = vec![
937 tree.register_task(wc).expect("wc task"),
938 tree.register_task(ss).expect("ss task"),
939 tree.register_task(repl).expect("repl task"),
940 tree.register_task(gc).expect("gc task"),
941 tree.register_task(obs).expect("obs task"),
942 ];
943
944 let finalized_count = Arc::new(AtomicUsize::new(0));
946 for _ in 0..3 {
947 let counter = Arc::clone(&finalized_count);
948 tree.register_finalizer(root, move || {
949 counter.fetch_add(1, Ordering::AcqRel);
950 })
951 .expect("root finalizer");
952 }
953
954 tree.begin_close(root).expect("begin close root");
956
957 assert_eq!(tree.state(root), Some(RegionState::Closing));
959 for &conn in &conns {
960 assert_eq!(tree.state(conn), Some(RegionState::Closing));
961 }
962
963 assert!(
965 !tree.is_quiescent(root),
966 "bead_id={BEAD_ID} case=e2e_root_not_quiescent_initially"
967 );
968
969 drop(txn_tasks);
971 drop(service_tasks);
972
973 for &conn in &conns {
975 let txn_children = tree.children(conn).expect("conn children").to_vec();
976 for txn in txn_children {
977 tree.complete_close(txn).expect("close txn");
978 }
979 }
980 for &conn in &conns {
981 tree.complete_close(conn).expect("close conn");
982 }
983 for &svc in &[wc, ss, repl, gc, obs] {
984 tree.complete_close(svc).expect("close svc");
985 }
986
987 assert!(
989 tree.is_quiescent(root),
990 "bead_id={BEAD_ID} case=e2e_root_quiescent"
991 );
992 tree.complete_close(root).expect("close root");
993
994 assert_eq!(
995 finalized_count.load(Ordering::Acquire),
996 3,
997 "bead_id={BEAD_ID} case=e2e_all_finalizers_ran"
998 );
999 assert_eq!(
1000 tree.state(root),
1001 Some(RegionState::Closed),
1002 "bead_id={BEAD_ID} case=e2e_root_closed"
1003 );
1004
1005 assert_eq!(tree.active_tasks(root), 0);
1007 for &conn in &conns {
1008 assert_eq!(tree.active_tasks(conn), 0);
1009 }
1010 }
1011
1012 #[test]
1013 fn test_close_and_drain_threaded() {
1014 use std::sync::Mutex;
1015 use std::thread;
1016 use std::time::Duration;
1017
1018 let tree = Arc::new(Mutex::new(RegionTree::new()));
1019 let root = {
1020 let mut t = tree.lock().unwrap_or_else(|e| e.into_inner());
1021 t.create_root(RegionKind::DbRoot, Cx::new()).expect("root")
1022 };
1023 let wc = {
1024 let mut t = tree.lock().unwrap_or_else(|e| e.into_inner());
1025 t.create_child(root, RegionKind::WriteCoordinator, Cx::new())
1026 .expect("wc")
1027 };
1028
1029 let task1 = tree
1031 .lock()
1032 .unwrap_or_else(|e| e.into_inner())
1033 .register_task(wc)
1034 .expect("t1");
1035 let task2 = tree
1036 .lock()
1037 .unwrap_or_else(|e| e.into_inner())
1038 .register_task(wc)
1039 .expect("t2");
1040
1041 let completed = Arc::new(AtomicBool::new(false));
1042 let flag = Arc::clone(&completed);
1043
1044 let t1 = thread::spawn(move || {
1046 thread::sleep(Duration::from_millis(20));
1047 drop(task1);
1048 });
1049 let t2 = thread::spawn(move || {
1050 thread::sleep(Duration::from_millis(30));
1051 drop(task2);
1052 });
1053
1054 {
1056 let mut t = tree.lock().unwrap_or_else(|e| e.into_inner());
1057 t.close_and_drain(root).expect("close_and_drain");
1058 }
1059 flag.store(true, Ordering::Release);
1060
1061 t1.join().expect("t1 join");
1062 t2.join().expect("t2 join");
1063
1064 assert!(
1065 completed.load(Ordering::Acquire),
1066 "bead_id={BEAD_ID} case=threaded_close_completed"
1067 );
1068 assert_eq!(
1069 tree.lock().unwrap_or_else(|e| e.into_inner()).state(root),
1070 Some(RegionState::Closed),
1071 "bead_id={BEAD_ID} case=threaded_root_closed"
1072 );
1073 }
1074}