1use std::{
8 any::{Any, TypeId},
9 cell::RefCell,
10 marker::PhantomData,
11 sync::Arc,
12};
13
14use im::HashMap as ImHashMap;
15use parking_lot::RwLock;
16use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
17
18use crate::{
19 execution_context::{with_execution_context, with_execution_context_mut},
20 read_dependencies::ReadDependencies,
21 runtime::{
22 RuntimePhase, compute_context_slot_key, current_phase,
23 current_replay_boundary_instance_key_from_scope, ensure_build_phase,
24 record_replay_boundary_invalidation_for_instance_key,
25 },
26 session::with_runtime_globals,
27};
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
30struct ContextSnapshotEntry {
31 slot: u32,
32 generation: u64,
33 key: SlotKey,
34}
35
36type ContextBindings = ImHashMap<TypeId, ContextSnapshotEntry>;
37
38#[derive(Clone, Debug, Default, Eq, PartialEq)]
39pub(crate) struct ContextMap(Option<Arc<ContextBindings>>);
40
41impl ContextMap {
42 fn new() -> Self {
43 Self(None)
44 }
45
46 fn from_bindings(bindings: ContextBindings) -> Self {
47 Self((!bindings.is_empty()).then(|| Arc::new(bindings)))
48 }
49
50 fn get(&self, type_id: &TypeId) -> Option<&ContextSnapshotEntry> {
51 self.0.as_deref()?.get(type_id)
52 }
53
54 fn update(&self, type_id: TypeId, entry: ContextSnapshotEntry) -> Self {
55 let mut bindings = self.0.as_deref().cloned().unwrap_or_default();
56 bindings.insert(type_id, entry);
57 Self::from_bindings(bindings)
58 }
59
60 #[cfg(test)]
61 fn is_empty(&self) -> bool {
62 self.0.is_none()
63 }
64
65 pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
66 match (&self.0, &other.0) {
67 (None, None) => true,
68 (Some(a), Some(b)) => Arc::ptr_eq(a, b),
69 _ => false,
70 }
71 }
72}
73
74#[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)]
75struct SlotKey {
76 instance_logic_id: u64,
77 slot_hash: u64,
78 type_id: TypeId,
79}
80
81struct SlotEntry {
82 key: SlotKey,
83 generation: u64,
84 value: Option<Arc<dyn Any + Send + Sync>>,
85 last_alive_epoch: u64,
86}
87
88#[derive(Default)]
89struct SlotTable {
90 entries: Vec<SlotEntry>,
91 free_list: Vec<u32>,
92 key_to_slot: HashMap<SlotKey, u32>,
93 slots_by_owner: HashMap<u64, HashSet<u32>>,
94 epoch: u64,
95}
96
97impl SlotTable {
98 fn index_slot(&mut self, key: SlotKey, slot: u32) {
99 self.key_to_slot.insert(key, slot);
100 self.slots_by_owner
101 .entry(key.instance_logic_id)
102 .or_default()
103 .insert(slot);
104 }
105
106 fn unindex_slot(&mut self, key: SlotKey, slot: u32) {
107 self.key_to_slot.remove(&key);
108 if let Some(slots) = self.slots_by_owner.get_mut(&key.instance_logic_id) {
109 slots.remove(&slot);
110 if slots.is_empty() {
111 self.slots_by_owner.remove(&key.instance_logic_id);
112 }
113 }
114 }
115
116 fn begin_epoch(&mut self) {
117 self.epoch = self.epoch.wrapping_add(1);
118 }
119}
120
121fn with_slot_table<R>(f: impl FnOnce(&SlotTable) -> R) -> R {
122 with_runtime_globals(|globals| f(&globals.context.slot_table.borrow()))
123}
124
125fn with_slot_table_mut<R>(f: impl FnOnce(&mut SlotTable) -> R) -> R {
126 with_runtime_globals(|globals| f(&mut globals.context.slot_table.borrow_mut()))
127}
128
129pub(crate) fn reset_context_slots() {
130 let removed = with_slot_table_mut(|table| {
131 let mut removed = Vec::new();
132 for entry in &mut table.entries {
133 if let Some(value) = entry.value.take() {
134 removed.push(value);
135 }
136 entry.generation = entry.generation.wrapping_add(1);
137 }
138 table.key_to_slot.clear();
139 table.slots_by_owner.clear();
140 table.free_list = (0..table.entries.len() as u32).collect();
141 removed
142 });
143 drop(removed);
144}
145
146#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
147struct ContextReadDependencyKey {
148 slot: u32,
149 generation: u64,
150}
151
152type ContextReadDependencyTracker = ReadDependencies<ContextReadDependencyKey, u64>;
153
154fn with_context_read_dependency_tracker<R>(
155 f: impl FnOnce(&ContextReadDependencyTracker) -> R,
156) -> R {
157 with_runtime_globals(|globals| f(&globals.context.read_dependency_tracker.borrow()))
158}
159
160fn with_context_read_dependency_tracker_mut<R>(
161 f: impl FnOnce(&mut ContextReadDependencyTracker) -> R,
162) -> R {
163 with_runtime_globals(|globals| f(&mut globals.context.read_dependency_tracker.borrow_mut()))
164}
165
166#[derive(Default)]
167pub(crate) struct ContextGlobals {
168 slot_table: RefCell<SlotTable>,
169 read_dependency_tracker: RefCell<ContextReadDependencyTracker>,
170}
171
172pub(crate) fn current_context_map() -> ContextMap {
173 with_execution_context(|context| context.context_map.clone())
174}
175
176fn resolve_snapshot_entry(
177 table: &SlotTable,
178 entry: ContextSnapshotEntry,
179) -> Option<ContextSnapshotEntry> {
180 let live_entry = table.entries.get(entry.slot as usize);
181 if let Some(live_entry) = live_entry
182 && live_entry.value.is_some()
183 && live_entry.key == entry.key
184 {
185 return Some(ContextSnapshotEntry {
186 slot: entry.slot,
187 generation: live_entry.generation,
188 key: entry.key,
189 });
190 }
191
192 let slot = table.key_to_slot.get(&entry.key).copied()?;
193 let live_entry = table.entries.get(slot as usize)?;
194 live_entry.value.as_ref()?;
195 Some(ContextSnapshotEntry {
196 slot,
197 generation: live_entry.generation,
198 key: entry.key,
199 })
200}
201
202pub(crate) fn normalize_context_snapshot(snapshot: &ContextMap) -> ContextMap {
203 let Some(bindings) = snapshot.0.as_deref() else {
204 return ContextMap::new();
205 };
206 let normalized = with_slot_table(|table| {
207 let mut updated = None;
208 for (&type_id, &entry) in bindings {
209 let resolved = resolve_snapshot_entry(table, entry);
210 if resolved == Some(entry) {
211 continue;
212 }
213 let next = updated.get_or_insert_with(|| bindings.clone());
214 if let Some(resolved) = resolved {
215 next.insert(type_id, resolved);
216 } else {
217 next.remove(&type_id);
218 }
219 }
220 updated
221 });
222 normalized.map_or_else(|| snapshot.clone(), ContextMap::from_bindings)
223}
224
225pub(crate) fn context_from_previous_snapshot_for_instance<T>(
226 instance_key: u64,
227) -> Option<Context<T>>
228where
229 T: Send + Sync + 'static,
230{
231 let map = crate::runtime::previous_component_context(instance_key)?;
232 map.get(&TypeId::of::<T>())
233 .copied()
234 .and_then(|entry| with_slot_table(|table| resolve_snapshot_entry(table, entry)))
235 .map(|entry| Context::new(entry.slot, entry.generation))
236}
237
238pub(crate) fn remove_context_read_dependencies(instance_keys: &HashSet<u64>) {
239 if instance_keys.is_empty() {
240 return;
241 }
242 with_context_read_dependency_tracker_mut(|tracker| tracker.remove_readers(instance_keys));
243}
244
245pub(crate) fn reset_context_read_dependencies() {
246 with_context_read_dependency_tracker_mut(|tracker| {
247 *tracker = ContextReadDependencyTracker::default();
248 });
249}
250
251pub(crate) fn with_context_map<R>(map: ContextMap, f: impl FnOnce() -> R) -> R {
252 struct RestoreContext(ContextMap);
253
254 impl Drop for RestoreContext {
255 fn drop(&mut self) {
256 with_execution_context_mut(|context| {
257 std::mem::swap(&mut context.context_map, &mut self.0);
258 });
259 }
260 }
261
262 let previous =
263 with_execution_context_mut(|context| std::mem::replace(&mut context.context_map, map));
264 let _restore = RestoreContext(previous);
265 f()
266}
267
268fn track_context_read_dependency(slot: u32, generation: u64) {
269 if !matches!(current_phase(), Some(RuntimePhase::Build)) {
270 return;
271 }
272 let Some(reader_instance_key) = current_replay_boundary_instance_key_from_scope() else {
273 return;
274 };
275
276 let key = ContextReadDependencyKey { slot, generation };
277 with_context_read_dependency_tracker_mut(|tracker| tracker.record(key, reader_instance_key));
278}
279
280fn context_read_subscribers(slot: u32, generation: u64) -> Vec<u64> {
281 let key = ContextReadDependencyKey { slot, generation };
282 with_context_read_dependency_tracker(|tracker| {
283 tracker
284 .readers(&key)
285 .map(Iterator::collect)
286 .unwrap_or_default()
287 })
288}
289
290pub(crate) fn begin_recompose_context_slot_epoch() {
291 with_slot_table_mut(SlotTable::begin_epoch);
293 with_execution_context_mut(|context| context.context_map = ContextMap::new());
294}
295
296pub(crate) fn recycle_recomposed_context_slots_for_instance_logic_ids(
297 instance_logic_ids: &HashSet<u64>,
298) {
299 if instance_logic_ids.is_empty() {
300 return;
301 }
302
303 let removed = with_slot_table_mut(|table| {
305 let epoch = table.epoch;
306 let mut freed: Vec<(u32, SlotKey)> = Vec::new();
307 let mut removed = Vec::new();
308 let slots: Vec<_> = instance_logic_ids
309 .iter()
310 .flat_map(|id| table.slots_by_owner.get(id).into_iter().flatten())
311 .copied()
312 .collect();
313 for slot in slots {
314 let entry = &mut table.entries[slot as usize];
315 if !instance_logic_ids.contains(&entry.key.instance_logic_id) {
316 continue;
317 }
318 if entry.value.is_none() {
319 continue;
320 }
321 if entry.last_alive_epoch == epoch {
322 continue;
323 }
324
325 freed.push((slot, entry.key));
326 removed.push(entry.value.take());
327 entry.generation = entry.generation.wrapping_add(1);
328 entry.last_alive_epoch = 0;
329 }
330
331 for (slot, key) in freed {
332 table.unindex_slot(key, slot);
333 table.free_list.push(slot);
334 }
335 removed
336 });
337 drop(removed);
339}
340
341pub(crate) fn live_context_slot_instance_logic_ids() -> HashSet<u64> {
342 with_slot_table(|table| table.slots_by_owner.keys().copied().collect())
343}
344
345pub(crate) fn drop_context_slots_for_instance_logic_ids(instance_logic_ids: &HashSet<u64>) {
346 if instance_logic_ids.is_empty() {
347 return;
348 }
349
350 let removed = with_slot_table_mut(|table| {
351 let mut freed: Vec<(u32, SlotKey)> = Vec::new();
352 let mut removed = Vec::new();
353 let slots: Vec<_> = instance_logic_ids
354 .iter()
355 .flat_map(|id| table.slots_by_owner.get(id).into_iter().flatten())
356 .copied()
357 .collect();
358 for slot in slots {
359 let entry = &mut table.entries[slot as usize];
360 if entry.value.is_none() {
361 continue;
362 }
363 if !instance_logic_ids.contains(&entry.key.instance_logic_id) {
364 continue;
365 }
366 freed.push((slot, entry.key));
367 removed.push(entry.value.take());
368 entry.generation = entry.generation.wrapping_add(1);
369 entry.last_alive_epoch = 0;
370 }
371
372 for (slot, key) in freed {
373 table.unindex_slot(key, slot);
374 table.free_list.push(slot);
375 }
376 removed
377 });
378 drop(removed);
379}
380
381pub struct Context<T> {
385 session: u128,
386 slot: u32,
387 generation: u64,
388 _marker: PhantomData<T>,
389}
390
391impl<T> Copy for Context<T> {}
392
393impl<T> Clone for Context<T> {
394 fn clone(&self) -> Self {
395 *self
396 }
397}
398
399impl<T> Context<T> {
400 fn new(slot: u32, generation: u64) -> Self {
401 Self {
402 session: crate::session::current_session_identity().expect("active context session"),
403 slot,
404 generation,
405 _marker: PhantomData,
406 }
407 }
408}
409
410impl<T> Context<T>
411where
412 T: Send + Sync + 'static,
413{
414 fn load_entry(&self) -> Arc<dyn Any + Send + Sync> {
415 assert_eq!(
416 crate::session::current_session_identity(),
417 Some(self.session),
418 "context belongs to a different runtime session"
419 );
420 with_slot_table(|table| {
421 let entry = table
422 .entries
423 .get(self.slot as usize)
424 .unwrap_or_else(|| panic!("Context points to freed slot: {}", self.slot));
425
426 if entry.generation != self.generation {
427 panic!(
428 "Context is stale (slot {}, generation {}, current generation {})",
429 self.slot, self.generation, entry.generation
430 );
431 }
432
433 if entry.key.type_id != TypeId::of::<T>() {
434 panic!(
435 "Context type mismatch for slot {}: expected {}, stored {:?}",
436 self.slot,
437 std::any::type_name::<T>(),
438 entry.key.type_id
439 );
440 }
441
442 entry
443 .value
444 .as_ref()
445 .unwrap_or_else(|| panic!("Context slot {} has been recycled", self.slot))
446 .clone()
447 })
448 }
449
450 fn load_lock(&self) -> Arc<RwLock<T>> {
451 self.load_entry()
452 .downcast::<RwLock<T>>()
453 .unwrap_or_else(|_| panic!("Context slot {} downcast failed", self.slot))
454 }
455
456 pub fn with<R>(&self, f: impl FnOnce(&T) -> R) -> R {
458 assert_eq!(
459 crate::session::current_session_identity(),
460 Some(self.session),
461 "context belongs to a different runtime session"
462 );
463 track_context_read_dependency(self.slot, self.generation);
464 let lock = self.load_lock();
465 let guard = lock.read();
466 f(&guard)
467 }
468
469 pub fn with_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
471 let lock = self.load_lock();
472 let result = {
473 let mut guard = lock.write();
474 f(&mut guard)
475 };
476 let subscribers = context_read_subscribers(self.slot, self.generation);
477 for instance_key in subscribers {
478 record_replay_boundary_invalidation_for_instance_key(instance_key);
479 }
480 result
481 }
482
483 pub fn get(&self) -> T
485 where
486 T: Clone,
487 {
488 self.with(Clone::clone)
489 }
490
491 pub fn set(&self, value: T) {
493 self.with_mut(|v| *v = value);
494 }
495
496 pub fn replace(&self, value: T) -> T {
498 self.with_mut(|v| std::mem::replace(v, value))
499 }
500}
501
502pub fn provide_context<T, I, F, R>(init: I, f: F) -> R
538where
539 T: Send + Sync + 'static,
540 I: FnOnce() -> T,
541 F: FnOnce() -> R,
542{
543 ensure_build_phase();
544
545 let (instance_logic_id, slot_hash) = compute_context_slot_key();
546 let type_id = TypeId::of::<T>();
547 let slot_key = SlotKey {
548 instance_logic_id,
549 slot_hash,
550 type_id,
551 };
552
553 let (slot, generation) = {
554 with_slot_table_mut(|table| {
555 let epoch = table.epoch;
556
557 if let Some(slot) = table.key_to_slot.get(&slot_key).copied() {
558 let entry = table
559 .entries
560 .get_mut(slot as usize)
561 .expect("context slot entry should exist");
562 entry.last_alive_epoch = epoch;
563
564 if entry.value.is_none() {
565 entry.value = Some(Arc::new(RwLock::new(init())));
566 entry.generation = entry.generation.wrapping_add(1);
567 }
568
569 let generation = entry.generation;
570 (slot, generation)
571 } else if let Some(slot) = table.free_list.pop() {
572 let entry = table
573 .entries
574 .get_mut(slot as usize)
575 .expect("context slot entry should exist");
576
577 entry.key = slot_key;
578 entry.value = Some(Arc::new(RwLock::new(init())));
579 entry.last_alive_epoch = epoch;
580
581 let generation = entry.generation;
582 table.index_slot(slot_key, slot);
583 (slot, generation)
584 } else {
585 let generation = 0;
586 let slot = table.entries.len() as u32;
587 table.entries.push(SlotEntry {
588 key: slot_key,
589 generation,
590 value: Some(Arc::new(RwLock::new(init()))),
591 last_alive_epoch: epoch,
592 });
593 table.index_slot(slot_key, slot);
594 (slot, generation)
595 }
596 })
597 };
598
599 let map = current_context_map().update(
600 type_id,
601 ContextSnapshotEntry {
602 slot,
603 generation,
604 key: slot_key,
605 },
606 );
607 with_context_map(map, f)
608}
609
610pub fn use_context<T>() -> Option<Context<T>>
614where
615 T: Send + Sync + 'static,
616{
617 ensure_build_phase();
618
619 with_execution_context(|context| {
620 context
621 .context_map
622 .get(&TypeId::of::<T>())
623 .copied()
624 .map(|entry| Context::new(entry.slot, entry.generation))
625 })
626}
627#[cfg(test)]
630mod tests {
631 use std::{any::TypeId, sync::Arc};
632
633 use parking_lot::RwLock;
634
635 use super::{
636 ContextMap, ContextSnapshotEntry, SlotEntry, SlotKey, SlotTable,
637 normalize_context_snapshot, with_slot_table_mut,
638 };
639 use crate::execution_context::{
640 reset_execution_context, with_execution_context, with_execution_context_mut,
641 };
642 use crate::runtime::{GroupPath, RuntimePhase, push_phase, with_replay_scope};
643
644 fn with_context_snapshot<R>(snapshot: &ContextMap, body: impl FnOnce() -> R) -> R {
645 with_replay_scope(
646 0,
647 GroupPath::default(),
648 None,
649 normalize_context_snapshot(snapshot),
650 body,
651 )
652 }
653
654 fn reset_test_state() {
655 with_slot_table_mut(|table| *table = SlotTable::default());
656 reset_execution_context();
657 }
658
659 #[test]
660 fn context_cleanup_drops_values_outside_table_borrow() {
661 let _runtime_session = crate::RuntimeSession::new();
662 let _runtime_scope = _runtime_session.enter();
663 struct ReadOnDrop;
664 impl Drop for ReadOnDrop {
665 fn drop(&mut self) {
666 super::with_slot_table(|table| {
667 assert!(table.entries.iter().all(|entry| entry.value.is_none()));
668 assert!(table.key_to_slot.is_empty());
669 });
670 }
671 }
672
673 for cleanup in 0..3 {
674 reset_test_state();
675 let key = SlotKey {
676 instance_logic_id: 42,
677 slot_hash: 7,
678 type_id: TypeId::of::<ReadOnDrop>(),
679 };
680 with_slot_table_mut(|table| {
681 table.entries.push(SlotEntry {
682 key,
683 generation: 0,
684 value: Some(Arc::new(ReadOnDrop)),
685 last_alive_epoch: 0,
686 });
687 table.index_slot(key, 0);
688 table.begin_epoch();
689 });
690 let instances = super::HashSet::from_iter([42]);
691 if cleanup == 2 {
692 crate::runtime::reset_slots();
693 } else if cleanup == 1 {
694 super::recycle_recomposed_context_slots_for_instance_logic_ids(&instances);
695 } else {
696 super::drop_context_slots_for_instance_logic_ids(&instances);
697 }
698 super::with_slot_table(|table| {
699 assert_eq!(table.free_list, [0]);
700 assert_eq!(table.entries[0].generation, 1);
701 });
702 }
703 }
704
705 #[test]
706 fn with_context_snapshot_restores_bindings_after_panic() {
707 let _runtime_session = crate::RuntimeSession::new();
708 let _runtime_scope = _runtime_session.enter();
709 reset_test_state();
710
711 let base_layer = ContextMap::new();
712 let parent_layer = base_layer.update(
713 TypeId::of::<u8>(),
714 ContextSnapshotEntry {
715 slot: 1,
716 generation: 2,
717 key: SlotKey {
718 instance_logic_id: 7,
719 slot_hash: 11,
720 type_id: TypeId::of::<u8>(),
721 },
722 },
723 );
724 with_execution_context_mut(|context| {
725 context.context_map = parent_layer.clone();
726 });
727
728 let snapshot = ContextMap::new().update(
729 TypeId::of::<u32>(),
730 ContextSnapshotEntry {
731 slot: 3,
732 generation: 4,
733 key: SlotKey {
734 instance_logic_id: 17,
735 slot_hash: 19,
736 type_id: TypeId::of::<u32>(),
737 },
738 },
739 );
740 let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
741 with_context_snapshot(&snapshot, || {
742 with_execution_context(|context| {
743 assert!(context.context_map.is_empty());
744 });
745 panic!("expected panic in context snapshot test");
746 });
747 }));
748 assert!(result.is_err());
749
750 with_execution_context(|context| {
751 assert_eq!(context.context_map, parent_layer);
752 });
753 }
754
755 #[test]
756 fn with_context_snapshot_shares_live_bindings() {
757 #[crate::tessera]
758 fn context_provider() {
759 super::provide_context(
760 || 42_u8,
761 || {
762 let snapshot = super::current_context_map();
763 with_context_snapshot(&snapshot, || {
764 assert!(snapshot.ptr_eq(&super::current_context_map()));
765 });
766 },
767 );
768 }
769
770 crate::testing::layout_test(|| context_provider()).run();
771 }
772
773 #[test]
774 fn with_context_snapshot_remaps_stale_generation() {
775 let _runtime_session = crate::RuntimeSession::new();
776 let _runtime_scope = _runtime_session.enter();
777 reset_test_state();
778
779 let key = SlotKey {
780 instance_logic_id: 31,
781 slot_hash: 37,
782 type_id: TypeId::of::<u8>(),
783 };
784 {
785 with_slot_table_mut(|table| {
786 *table = SlotTable::default();
787 table.entries.push(SlotEntry {
788 key,
789 generation: 2,
790 value: Some(Arc::new(RwLock::new(42_u8))),
791 last_alive_epoch: 1,
792 });
793 table.index_slot(key, 0);
794 });
795 }
796
797 let snapshot = ContextMap::new().update(
798 TypeId::of::<u8>(),
799 ContextSnapshotEntry {
800 slot: 0,
801 generation: 1,
802 key,
803 },
804 );
805
806 let _phase_guard = push_phase(RuntimePhase::Build);
807 with_context_snapshot(&snapshot, || {
808 let context = super::use_context::<u8>().expect("context should be remapped");
809 assert_eq!(context.get(), 42);
810 });
811 }
812
813 #[test]
814 fn with_context_snapshot_remaps_stale_slot_by_key() {
815 let _runtime_session = crate::RuntimeSession::new();
816 let _runtime_scope = _runtime_session.enter();
817 reset_test_state();
818
819 let key = SlotKey {
820 instance_logic_id: 41,
821 slot_hash: 43,
822 type_id: TypeId::of::<u16>(),
823 };
824 let old_key = SlotKey {
825 instance_logic_id: 47,
826 slot_hash: 53,
827 type_id: TypeId::of::<u16>(),
828 };
829 {
830 with_slot_table_mut(|table| {
831 *table = SlotTable::default();
832 table.entries.push(SlotEntry {
833 key: old_key,
834 generation: 9,
835 value: None,
836 last_alive_epoch: 0,
837 });
838 table.entries.push(SlotEntry {
839 key,
840 generation: 4,
841 value: Some(Arc::new(RwLock::new(7_u16))),
842 last_alive_epoch: 2,
843 });
844 table.index_slot(key, 1);
845 });
846 }
847
848 let snapshot = ContextMap::new().update(
849 TypeId::of::<u16>(),
850 ContextSnapshotEntry {
851 slot: 0,
852 generation: 3,
853 key,
854 },
855 );
856
857 let _phase_guard = push_phase(RuntimePhase::Build);
858 with_context_snapshot(&snapshot, || {
859 let context = super::use_context::<u16>().expect("context should be remapped by key");
860 assert_eq!(context.get(), 7);
861 });
862 }
863}