1use std::{
8 any::{Any, TypeId},
9 cell::{Cell, OnceCell, RefCell},
10 marker::PhantomData,
11 rc::{Rc, Weak},
12 sync::Arc,
13};
14
15use rustc_hash::FxHashMap as HashMap;
16
17use crate::{execution_context::ExecutionContext, runtime::RuntimeGlobals};
18
19#[derive(Default)]
20struct Resources {
21 shared: HashMap<TypeId, Arc<dyn Any + Send + Sync>>,
22 local: HashMap<TypeId, Rc<dyn Any>>,
23 order: Vec<(TypeId, bool)>,
24}
25
26pub(crate) struct SessionState {
27 identity: u128,
28 root_claimed: Cell<bool>,
29 closing: Cell<bool>,
30 density: Cell<f64>,
31 execution_context: OnceCell<RefCell<ExecutionContext>>,
32 runtime_globals: OnceCell<Box<RuntimeGlobals>>,
33 resources: RefCell<Resources>,
34 previous: RefCell<SessionSelection>,
35}
36
37#[derive(Clone, Default)]
38struct SessionSelection {
39 state: Weak<SessionState>,
40 identity: u128,
41}
42
43impl SessionSelection {
44 fn new(state: &Rc<SessionState>) -> Self {
45 Self {
46 state: Rc::downgrade(state),
47 identity: state.identity,
48 }
49 }
50
51 fn selects(&self, state: &Rc<SessionState>) -> bool {
52 self.state.as_ptr() == Rc::as_ptr(state)
53 }
54}
55
56thread_local! {
57 static ACTIVE_SESSION: RefCell<SessionSelection> = const { RefCell::new(SessionSelection {
58 state: Weak::new(),
59 identity: 0,
60 }) };
61}
62
63fn active_session_state() -> Option<Rc<SessionState>> {
64 ACTIVE_SESSION
65 .try_with(|active| active.borrow().state.upgrade())
66 .ok()
67 .flatten()
68}
69
70#[derive(Clone)]
72pub struct RuntimeSession(Rc<SessionState>);
73
74#[derive(Clone)]
76pub struct WeakRuntimeSession(Weak<SessionState>);
77
78impl WeakRuntimeSession {
79 pub fn run<R>(&self, work: impl FnOnce() -> R) -> Option<R> {
81 self.0
82 .upgrade()
83 .filter(|owner| !owner.closing.get())
84 .map(|owner| RuntimeSession(owner).run(work))
85 }
86}
87
88#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
90pub struct RuntimeSessionId(u128);
91
92impl Default for RuntimeSession {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97
98impl RuntimeSession {
99 pub fn state_owner(&self) -> crate::StateOwner {
101 self.run(crate::StateOwner::new)
102 }
103
104 pub fn dispatcher(&self) -> crate::UiDispatcher {
106 self.run(crate::dispatcher::current)
107 }
108 pub fn downgrade(&self) -> WeakRuntimeSession {
110 WeakRuntimeSession(Rc::downgrade(&self.0))
111 }
112 pub fn resource<T: Default + Send + Sync + 'static>(&self) -> Arc<T> {
114 self.run(runtime_resource::<T>)
115 }
116
117 pub fn local_resource<T: Default + 'static>(&self) -> Rc<T> {
119 self.run(|| self.local_resource_in_session())
120 }
121
122 fn local_resource_in_session<T: Default + 'static>(&self) -> Rc<T> {
123 let key = TypeId::of::<T>();
124 if let Some(resource) = self.0.resources.borrow().local.get(&key).cloned() {
125 return Rc::downcast(resource).unwrap_or_else(|_| unreachable!());
126 }
127 let resource = Rc::new(T::default());
128 let stored = {
129 let mut resources = self.0.resources.borrow_mut();
130 if let Some(stored) = resources.local.get(&key) {
131 stored.clone()
132 } else {
133 resources.local.insert(key, resource.clone());
134 resources.order.push((key, false));
135 resource.clone()
136 }
137 };
138 Rc::downcast(stored).unwrap_or_else(|_| unreachable!())
139 }
140
141 pub fn identity(&self) -> RuntimeSessionId {
143 RuntimeSessionId(self.0.identity)
144 }
145 pub fn new() -> Self {
147 Self(Rc::new(SessionState {
148 identity: uuid::Uuid::new_v4().as_u128(),
149 root_claimed: Cell::new(false),
150 closing: Cell::new(false),
151 density: Cell::new(1.0),
152 execution_context: OnceCell::new(),
153 runtime_globals: OnceCell::new(),
154 resources: RefCell::new(Resources::default()),
155 previous: RefCell::default(),
156 }))
157 }
158
159 pub fn run<R>(&self, work: impl FnOnce() -> R) -> R {
161 let _guard = self.enter();
162 work()
163 }
164
165 pub fn enter(&self) -> RuntimeSessionGuard {
167 let previous = ACTIVE_SESSION.with(|active| active.replace(SessionSelection::new(&self.0)));
168 RuntimeSessionGuard {
169 previous,
170 _owner: self.clone(),
171 }
172 }
173
174 pub(crate) fn activate(&self) {
175 ACTIVE_SESSION.with(|active| {
176 let mut active = active.borrow_mut();
177 if !active.selects(&self.0) {
178 *self.0.previous.borrow_mut() =
179 std::mem::replace(&mut *active, SessionSelection::new(&self.0));
180 }
181 });
182 }
183
184 pub(crate) fn current() -> Option<Self> {
185 active_session_state().map(Self)
186 }
187
188 pub(crate) fn execution_context(&self) -> &RefCell<ExecutionContext> {
189 self.0.execution_context.get_or_init(RefCell::default)
190 }
191
192 pub(crate) fn runtime_globals(&self) -> &RuntimeGlobals {
193 self.0.runtime_globals.get_or_init(Box::default)
194 }
195
196 pub(crate) fn density(&self) -> f64 {
197 self.0.density.get()
198 }
199
200 pub(crate) fn set_density(&self, density: f64) {
201 self.0.density.set(density);
202 }
203
204 pub(crate) fn accepts_work(&self) -> bool {
205 !self.0.closing.get()
206 }
207
208 pub(crate) fn for_root() -> Self {
209 let session = Self::current()
210 .filter(|session| !session.0.root_claimed.get())
211 .unwrap_or_default();
212 session.0.root_claimed.set(true);
213 session
214 }
215}
216
217pub(crate) fn current_session_identity() -> Option<u128> {
218 ACTIVE_SESSION
219 .try_with(|active| {
220 let selected = active.borrow();
221 (selected.state.strong_count() != 0).then_some(selected.identity)
222 })
223 .ok()
224 .flatten()
225}
226
227pub(crate) fn with_active_execution_context<R>(
228 read: impl FnOnce(&RefCell<ExecutionContext>) -> R,
229) -> R {
230 let session = RuntimeSession::current().expect("a runtime session must be active");
231 read(session.execution_context())
232}
233
234pub(crate) fn with_runtime_globals<R>(read: impl FnOnce(&RuntimeGlobals) -> R) -> R {
235 try_with_runtime_globals(read).expect("a runtime session must be active")
236}
237
238pub(crate) fn try_with_runtime_globals<R>(
239 read: impl FnOnce(&RuntimeGlobals) -> R,
240) -> Result<R, ()> {
241 let session = RuntimeSession::current().ok_or(())?;
242 Ok(read(session.runtime_globals()))
243}
244
245impl Drop for RuntimeSession {
246 #[inline]
247 fn drop(&mut self) {
248 if Rc::strong_count(&self.0) == 1 {
249 self.shutdown();
250 }
251 }
252}
253
254impl RuntimeSession {
255 #[cold]
256 fn shutdown(&self) {
257 self.0.closing.set(true);
258 let previous = self.0.previous.borrow().clone();
259 let was_active = ACTIVE_SESSION
260 .try_with(|active| active.borrow().selects(&self.0))
261 .unwrap_or(false);
262 {
263 let _guard = self.enter();
264 crate::dispatcher::close();
265 crate::runtime::shutdown_runtime();
266 loop {
267 let removed = {
268 let mut resources = self.0.resources.borrow_mut();
269 resources.order.pop().map(|(key, shared)| {
270 if shared {
271 (resources.shared.remove(&key), None)
272 } else {
273 (None, resources.local.remove(&key))
274 }
275 })
276 };
277 let Some(removed) = removed else { break };
278 drop(removed);
279 }
280 }
281 if was_active {
282 let _ = ACTIVE_SESSION.try_with(|active| active.replace(previous));
283 }
284 }
285}
286
287pub struct RuntimeSessionGuard {
289 previous: SessionSelection,
290 _owner: RuntimeSession,
291}
292
293pub(crate) struct SessionLocal<T>(PhantomData<fn() -> T>);
294
295impl<T: Default + 'static> SessionLocal<T> {
296 pub(crate) const fn new() -> Self {
297 Self(PhantomData)
298 }
299
300 pub(crate) fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
301 self.try_with(read)
302 .expect("a runtime session must be active")
303 }
304
305 pub(crate) fn try_with<R>(&self, read: impl FnOnce(&T) -> R) -> Result<R, ()> {
306 let session = RuntimeSession::current().ok_or(())?;
307 let resource = session.local_resource_in_session::<T>();
308 Ok(read(&resource))
309 }
310}
311
312impl<T: Default + 'static> SessionLocal<RefCell<T>> {
313 pub(crate) fn with_borrow<R>(&self, read: impl FnOnce(&T) -> R) -> R {
314 self.with(|value| read(&value.borrow()))
315 }
316
317 pub(crate) fn with_borrow_mut<R>(&self, update: impl FnOnce(&mut T) -> R) -> R {
318 self.with(|value| update(&mut value.borrow_mut()))
319 }
320}
321
322impl<T: Copy + Default + 'static> SessionLocal<std::cell::Cell<T>> {
323 pub(crate) fn get(&self) -> T {
324 self.with(std::cell::Cell::get)
325 }
326 pub(crate) fn set(&self, value: T) {
327 self.with(|slot| slot.set(value));
328 }
329 pub(crate) fn replace(&self, value: T) -> T {
330 self.with(|slot| slot.replace(value))
331 }
332}
333
334impl Drop for RuntimeSessionGuard {
335 fn drop(&mut self) {
336 let _ =
337 ACTIVE_SESSION.try_with(|active| active.replace(std::mem::take(&mut self.previous)));
338 }
339}
340
341pub fn runtime_resource<T: Default + Send + Sync + 'static>() -> Arc<T> {
345 let Some(session) = RuntimeSession::current() else {
346 return Arc::new(T::default());
347 };
348 let key = TypeId::of::<T>();
349 if let Some(resource) = session.0.resources.borrow().shared.get(&key).cloned() {
350 return Arc::downcast(resource).unwrap_or_else(|_| unreachable!());
351 }
352 let resource = Arc::new(T::default());
353 let stored = {
354 let mut resources = session.0.resources.borrow_mut();
355 if let Some(stored) = resources.shared.get(&key) {
356 stored.clone()
357 } else {
358 resources.shared.insert(key, resource.clone());
359 resources.order.push((key, true));
360 resource.clone()
361 }
362 };
363 Arc::downcast(stored).unwrap_or_else(|_| unreachable!())
364}
365
366pub fn local_runtime_resource<T: Default + 'static>() -> Rc<T> {
371 let Some(session) = RuntimeSession::current() else {
372 return Rc::new(T::default());
373 };
374 session.local_resource_in_session()
375}
376
377#[cfg(all(test, feature = "testing"))]
378mod tests {
379 use std::{cell::Cell, panic::AssertUnwindSafe, rc::Rc};
380
381 use crate::{
382 State, remember,
383 testing::{TestProbe, layout_test, pump_layout_test_session, start_layout_test_session},
384 };
385
386 use super::{RuntimeSession, local_runtime_resource};
387
388 #[crate::tessera(crate)]
389 fn observed_state(output: TestProbe<Option<State<usize>>>) {
390 let state = remember(|| 7usize);
391 let _value = state.get();
392 output.set(Some(state));
393 }
394
395 #[test]
396 fn independent_sessions_reject_foreign_states_and_release_their_resources() {
397 let output = TestProbe::default();
398 let mut first = start_layout_test_session(layout_test({
399 let output = output.clone();
400 move || observed_state(output.clone())
401 }));
402 pump_layout_test_session(&mut first, 0);
403 let state = output.get().unwrap();
404 let resource = first.run(local_runtime_resource::<Cell<usize>>);
405 let weak = Rc::downgrade(&resource);
406 drop(resource);
407
408 let second = RuntimeSession::new();
409 second.run(|| {
410 assert!(!state.is_alive());
411 assert!(std::panic::catch_unwind(AssertUnwindSafe(|| state.set(12))).is_err());
412 assert_eq!(local_runtime_resource::<Cell<usize>>().get(), 0);
413 });
414 first.run(|| {
415 assert_eq!(state.get(), 7);
416 state.set(9);
417 });
418 pump_layout_test_session(&mut first, 1);
419 assert_eq!(
420 first
421 .work()
422 .count("observed_state", crate::WorkPhase::Build),
423 1
424 );
425 let _selected = second.enter();
426 drop(first);
427 assert!(weak.upgrade().is_none());
428 assert_eq!(
429 RuntimeSession::current().unwrap().identity(),
430 second.identity()
431 );
432 second.run(|| assert!(!state.is_alive()));
433 }
434
435 #[test]
436 fn session_selection_restores_density_and_rejects_an_expired_owner() {
437 let first = RuntimeSession::new();
438 first.activate();
439 crate::dp::set_scale_factor(2.0);
440 let second = RuntimeSession::new();
441 second.run(|| {
442 assert_eq!(crate::dp::scale_factor(), 1.0);
443 crate::dp::set_scale_factor(3.0);
444 });
445 assert_eq!(super::current_session_identity(), Some(first.identity().0));
446 assert_eq!(crate::dp::scale_factor(), 2.0);
447
448 let scope = second.enter();
449 drop(first);
450 assert_eq!(super::current_session_identity(), Some(second.identity().0));
451 assert_eq!(crate::dp::scale_factor(), 3.0);
452 drop(scope);
453
454 assert_eq!(super::current_session_identity(), None);
455 assert_eq!(crate::dp::scale_factor(), 1.0);
456 }
457
458 #[derive(Default)]
459 struct CleanupDependency(Cell<usize>);
460
461 #[derive(Default)]
462 struct DependentResource(std::cell::RefCell<Option<TestProbe<Vec<usize>>>>);
463
464 impl Drop for DependentResource {
465 fn drop(&mut self) {
466 if let Some(output) = self.0.get_mut().take() {
467 output.with_mut(|values| {
468 values.push(local_runtime_resource::<CleanupDependency>().0.get())
469 });
470 }
471 }
472 }
473
474 #[test]
475 fn session_resources_drop_after_dependents_with_owner_selected_and_registry_unborrowed() {
476 let session = RuntimeSession::new();
477 let output = TestProbe::default();
478 session.run(|| {
479 local_runtime_resource::<CleanupDependency>().0.set(42);
480 local_runtime_resource::<DependentResource>()
481 .0
482 .replace(Some(output.clone()));
483 });
484 drop(session);
485 assert_eq!(output.get(), [42]);
486 }
487
488 struct ObserveClockOnDrop(TestProbe<Vec<u64>>);
489
490 impl Drop for ObserveClockOnDrop {
491 fn drop(&mut self) {
492 self.0
493 .with_mut(|values| values.push(crate::current_frame_nanos()));
494 }
495 }
496
497 #[crate::tessera(crate)]
498 fn clock_probe(output: TestProbe<Vec<u64>>) {
499 let active = remember(|| true);
500 if active.get() {
501 let capture = ObserveClockOnDrop(output.clone());
502 crate::receive_frame_nanos(move |nanos| {
503 assert_eq!(crate::current_frame_nanos(), nanos);
504 let _ = &capture;
505 output.with_mut(|values| values.push(nanos));
506 active.set(false);
507 crate::FrameNanosControl::Stop
508 });
509 }
510 }
511
512 #[test]
513 fn frame_callbacks_and_stopped_captures_read_the_clock_without_borrowing_the_registry() {
514 let output = TestProbe::default();
515 let mut session = start_layout_test_session(layout_test({
516 let output = output.clone();
517 move || clock_probe(output.clone())
518 }));
519 pump_layout_test_session(&mut session, 0);
520 assert_eq!(session.frame_clock_diagnostics().pending_subscriptions, 1);
521 pump_layout_test_session(&mut session, 10);
522 assert_eq!(output.get(), [10, 10]);
523 assert_eq!(session.frame_clock_diagnostics().callbacks_executed, 1);
524 assert_eq!(session.frame_clock_diagnostics().pending_subscriptions, 0);
525 pump_layout_test_session(&mut session, 20);
526 assert_eq!(session.frame_clock_diagnostics().callbacks_executed, 0);
527 }
528
529 #[crate::tessera(crate)]
530 fn persistent_clock_probe(output: TestProbe<Vec<u64>>) {
531 let capture = ObserveClockOnDrop(output);
532 crate::receive_frame_nanos(move |_| {
533 let _ = &capture;
534 crate::FrameNanosControl::Continue
535 });
536 }
537
538 #[test]
539 fn shutdown_drops_clock_captures_while_the_owning_clock_remains_available() {
540 let output = TestProbe::default();
541 let mut session = start_layout_test_session(layout_test({
542 let output = output.clone();
543 move || persistent_clock_probe(output.clone())
544 }));
545 pump_layout_test_session(&mut session, 0);
546 pump_layout_test_session(&mut session, 17);
547 output.take();
548 drop(session);
549 assert_eq!(output.get(), [17]);
550 }
551
552 #[test]
553 fn conditional_state_writes_preserve_idle_frames_and_replay_changed_readers() {
554 let output = TestProbe::default();
555 let mut session = start_layout_test_session(layout_test({
556 let output = output.clone();
557 move || observed_state(output.clone())
558 }));
559 pump_layout_test_session(&mut session, 0);
560 let state = output.get().unwrap();
561 assert!(!state.set_if_changed(7));
562 assert!(!crate::runtime::has_pending_build_invalidations());
563 pump_layout_test_session(&mut session, 1);
564 assert_eq!(
565 session
566 .work()
567 .count("observed_state", crate::WorkPhase::Build),
568 0
569 );
570 assert!(state.set_if_changed(9));
571 assert!(crate::runtime::has_pending_build_invalidations());
572 pump_layout_test_session(&mut session, 2);
573 assert_eq!(state.get(), 9);
574 assert_eq!(
575 session
576 .work()
577 .count("observed_state", crate::WorkPhase::Build),
578 1
579 );
580 }
581
582 #[crate::tessera(crate)]
583 fn owned_state_reader(value: State<usize>, output: TestProbe<usize>) {
584 output.set(value.get());
585 }
586
587 #[test]
588 fn controller_owned_state_replays_readers_and_dispatches_worker_results_on_its_ui_session() {
589 let runtime = RuntimeSession::new();
590 let _scope = runtime.enter();
591 let owner = crate::StateOwner::new();
592 let value = owner.state_with_key("route", || 7usize);
593 assert!(
594 owner.state_with_key("route", || panic!("existing state was reinitialized")) == value
595 );
596 let output = TestProbe::new(0usize);
597 let mut session = start_layout_test_session(layout_test({
598 let output = output.clone();
599 move || owned_state_reader(value, output.clone())
600 }));
601 pump_layout_test_session(&mut session, 0);
602 assert_eq!(output.get(), 7);
603 let dispatcher = owner.dispatcher();
604 assert!(
605 std::thread::spawn(move || dispatcher.dispatch(move || value.set(11)))
606 .join()
607 .unwrap()
608 );
609 assert_eq!(output.get(), 7);
610 pump_layout_test_session(&mut session, 1);
611 assert_eq!(output.get(), 11);
612 assert_eq!(
613 session
614 .work()
615 .count("owned_state_reader", crate::WorkPhase::Build),
616 1
617 );
618 drop(owner);
619 assert!(!value.is_alive());
620 let next = crate::StateOwner::new();
621 let replacement = next.state_with_key("route", || 19usize);
622 assert_eq!(replacement.get(), 19);
623 assert!(!value.is_alive());
624 assert!(std::panic::catch_unwind(AssertUnwindSafe(|| value.set(99))).is_err());
625 assert_eq!(replacement.get(), 19);
626 }
627
628 struct StatefulModifier {
629 extra: bool,
630 output: TestProbe<Vec<State<usize>>>,
631 }
632
633 impl crate::BuildModifierNode for StatefulModifier {
634 fn materialize(
635 &self,
636 context: &mut crate::ModifierBuildContext,
637 ) -> Option<crate::Modifier> {
638 if self.extra {
639 let _ = context.state(|| false);
640 }
641 let state = context.state(|| 3usize);
642 let _ = state.get();
643 self.output.with_mut(|values| values.push(state));
644 Some(crate::Modifier::new())
645 }
646 }
647
648 #[crate::tessera(crate)]
649 fn modifier_owner_fixture(
650 visible: State<bool>,
651 extra: State<bool>,
652 output: TestProbe<Vec<State<usize>>>,
653 ) {
654 use crate::FocusModifierExt as _;
655 let requester = remember(crate::FocusRequester::new).get();
656 let mut modifier = crate::Modifier::new();
657 let extra = extra.get();
658 if extra {
659 modifier = modifier
660 .focus_requester(requester)
661 .on_layout_coordinates(|_| {});
662 }
663 if visible.get() {
664 modifier = modifier
665 .push_build(StatefulModifier {
666 extra,
667 output: output.clone(),
668 })
669 .push_build(StatefulModifier { extra, output });
670 }
671 crate::layout::layout().modifier(modifier);
672 }
673
674 #[test]
675 fn capability_state_survives_unrelated_modifiers_and_dies_with_its_attachment() {
676 let runtime = RuntimeSession::new();
677 let _scope = runtime.enter();
678 let owner = crate::StateOwner::new();
679 let visible = owner.state_with_key("visible", || true);
680 let extra = owner.state_with_key("extra", || false);
681 let output = TestProbe::default();
682 let unused = crate::Modifier::new().push_build(StatefulModifier {
683 extra: false,
684 output: output.clone(),
685 });
686 assert!(output.with(Vec::is_empty));
687 drop(unused);
688 let mut session = start_layout_test_session(layout_test({
689 let output = output.clone();
690 move || modifier_owner_fixture(visible, extra, output.clone())
691 }));
692 pump_layout_test_session(&mut session, 0);
693 let original = output.take();
694 assert_eq!(original.len(), 2);
695 assert!(original[0] != original[1]);
696 original[0].set(8);
697 for (frame, enabled) in [(1, false), (2, true), (3, false), (4, true)] {
698 extra.set(enabled);
699 pump_layout_test_session(&mut session, frame);
700 assert!(output.take() == original);
701 assert_eq!(original[0].get(), 8);
702 assert_eq!(original[1].get(), 3);
703 }
704 visible.set(false);
705 pump_layout_test_session(&mut session, 5);
706 assert!(original.iter().all(|state| !state.is_alive()));
707 assert!(output.with(Vec::is_empty));
708 visible.set(true);
709 pump_layout_test_session(&mut session, 6);
710 let replacement = output.take();
711 assert_eq!(replacement.len(), 2);
712 assert!(replacement[0] != original[0]);
713 assert_eq!(replacement[0].get(), 3);
714 }
715
716 struct ClockModifier(TestProbe<Vec<u64>>);
717
718 impl crate::BuildModifierNode for ClockModifier {
719 fn materialize(
720 &self,
721 context: &mut crate::ModifierBuildContext,
722 ) -> Option<crate::Modifier> {
723 let output = self.0.clone();
724 context.receive_frame_nanos(move |nanos| {
725 output.with_mut(|values| values.push(nanos));
726 crate::FrameNanosControl::Continue
727 });
728 Some(crate::Modifier::new())
729 }
730 }
731
732 #[crate::tessera(crate)]
733 fn clock_modifier_fixture(visible: State<bool>, output: TestProbe<Vec<u64>>) {
734 let modifier = if visible.get() {
735 crate::Modifier::new().push_build(ClockModifier(output))
736 } else {
737 crate::Modifier::new()
738 };
739 crate::layout::layout().modifier(modifier);
740 }
741
742 #[test]
743 fn removing_a_stateful_modifier_unsubscribes_its_clock_before_the_next_frame() {
744 let runtime = RuntimeSession::new();
745 let _scope = runtime.enter();
746 let owner = crate::StateOwner::new();
747 let visible = owner.state_with_key("visible", || true);
748 let output = TestProbe::default();
749 let mut session = start_layout_test_session(layout_test({
750 let output = output.clone();
751 move || clock_modifier_fixture(visible, output.clone())
752 }));
753 pump_layout_test_session(&mut session, 0);
754 assert_eq!(session.frame_clock_diagnostics().pending_subscriptions, 1);
755 pump_layout_test_session(&mut session, 1);
756 assert_eq!(output.get(), [1]);
757 assert_eq!(session.frame_clock_diagnostics().pending_subscriptions, 1);
758 visible.set(false);
759 pump_layout_test_session(&mut session, 2);
760 assert_eq!(output.get(), [1, 2]);
761 assert_eq!(session.frame_clock_diagnostics().pending_subscriptions, 0);
762 pump_layout_test_session(&mut session, 3);
763 assert_eq!(session.frame_clock_diagnostics().callbacks_executed, 0);
764 assert_eq!(output.get(), [1, 2]);
765 }
766
767 struct DropIntoState(State<usize>);
768
769 impl Drop for DropIntoState {
770 fn drop(&mut self) {
771 if self.0.is_alive() {
772 self.0.with_mut(|value| *value += 1);
773 }
774 }
775 }
776
777 #[test]
778 fn closing_an_owner_during_state_mutation_drops_its_taken_value_outside_the_slot_borrow() {
779 let runtime = RuntimeSession::new();
780 runtime.run(|| {
781 let observer = crate::StateOwner::new();
782 let observed = observer.state_with_key("drops", || 0usize);
783 let owner = crate::StateOwner::new();
784 let value = owner.state_with_key("resource", || DropIntoState(observed));
785 value.with_mut(|_| owner.close());
786 assert!(!value.is_alive());
787 assert_eq!(observed.get(), 1);
788 });
789 }
790
791 #[test]
792 fn closing_a_state_owner_discards_pending_results_before_they_can_touch_freed_state() {
793 let runtime = RuntimeSession::new();
794 let _scope = runtime.enter();
795 let owner = crate::StateOwner::new();
796 let value = owner.state_with_key("route", || 5usize);
797 let dispatcher = owner.dispatcher();
798 assert!(dispatcher.dispatch(move || value.set(9)));
799 owner.close();
800 assert!(!dispatcher.is_alive());
801 assert!(!dispatcher.dispatch(|| panic!("closed owner accepted a result")));
802 crate::dispatcher::flush();
803 assert!(!value.is_alive());
804 let next = crate::StateOwner::new();
805 assert_eq!(next.state_with_key("route", || 13usize).get(), 13);
806 }
807
808 #[derive(Default)]
809 struct DispatcherOnDrop(std::cell::RefCell<Option<TestProbe<Option<crate::UiDispatcher>>>>);
810
811 impl Drop for DispatcherOnDrop {
812 fn drop(&mut self) {
813 if let Some(output) = self.0.get_mut().take() {
814 output.set(Some(crate::dispatcher::current()));
815 }
816 }
817 }
818
819 #[test]
820 fn services_created_during_resource_cleanup_cannot_reopen_a_closed_session() {
821 let session = RuntimeSession::new();
822 let output = TestProbe::default();
823 let weak = session.downgrade();
824 session
825 .local_resource::<DispatcherOnDrop>()
826 .0
827 .replace(Some(output.clone()));
828 drop(session);
829 let dispatcher = output
830 .take()
831 .expect("resource destructor captured its session service");
832 assert!(!dispatcher.is_alive());
833 assert!(!dispatcher.dispatch(|| panic!("closed session accepted destructor work")));
834 assert!(weak.run(|| ()).is_none());
835 }
836
837 #[test]
838 fn session_shutdown_rejects_dispatched_work_and_closes_external_state_owners() {
839 let runtime = RuntimeSession::new();
840 let owner = runtime.state_owner();
841 let dispatcher = runtime.dispatcher();
842 let value = runtime.run(|| owner.state_with_key("value", || 7usize));
843 drop(runtime);
844 assert!(!owner.is_alive());
845 assert!(!dispatcher.is_alive());
846 assert!(!dispatcher.dispatch(|| panic!("shutdown work executed")));
847 let other = RuntimeSession::new();
848 other.run(|| assert!(!value.is_alive()));
849 }
850}