@@ -460,6 +460,15 @@ impl ContextStack {
460
460
// The empty context is always at the bottom of the [`ContextStack`]
461
461
// and cannot be popped, and the overflow position is invalid, so do
462
462
// nothing.
463
+ otel_warn ! (
464
+ name: "Context.OutOfOrderDrop" ,
465
+ position = pos,
466
+ message = if pos == ContextStack :: BASE_POS {
467
+ "Attempted to pop the base context which is not allowed"
468
+ } else {
469
+ "Attempted to pop the overflow position which is not allowed"
470
+ }
471
+ ) ;
463
472
return ;
464
473
}
465
474
let len: u16 = self . stack . len ( ) as u16 ;
@@ -479,6 +488,12 @@ impl ContextStack {
479
488
// This is an out of order pop.
480
489
if pos >= len {
481
490
// This is an invalid id, ignore it.
491
+ otel_warn ! (
492
+ name: "Context.PopOutOfBounds" ,
493
+ position = pos,
494
+ stack_length = len,
495
+ message = "Attempted to pop beyond the end of the context stack"
496
+ ) ;
482
497
return ;
483
498
}
484
499
// Clear out the entry at the given id.
@@ -505,6 +520,8 @@ impl Default for ContextStack {
505
520
#[ cfg( test) ]
506
521
mod tests {
507
522
use super :: * ;
523
+ use std:: time:: Duration ;
524
+ use tokio:: time:: sleep;
508
525
509
526
#[ derive( Debug , PartialEq ) ]
510
527
struct ValueA ( u64 ) ;
@@ -653,20 +670,231 @@ mod tests {
653
670
for _ in 0 ..16 {
654
671
let cx_guard = Context :: current ( ) . with_value ( ValueA ( 1 ) ) . attach ( ) ;
655
672
assert_eq ! ( cx_guard. cx_pos, ContextStack :: MAX_POS ) ;
656
- assert_eq ! ( Context :: current( ) . get( ) , Some ( & ValueA ( 2 ) ) ) ;
673
+ assert_eq ! ( Context :: current( ) . get:: < ValueA > ( ) , Some ( & ValueA ( 2 ) ) ) ;
657
674
assert_eq ! ( Context :: current( ) . get( ) , Some ( & ValueB ( stack_max_pos - 2 ) ) ) ;
658
675
guards. push ( cx_guard) ;
659
676
}
660
677
}
661
678
679
+ /// Tests that a new ContextStack is created with the correct initial capacity.
680
+ #[ test]
681
+ fn test_initial_capacity ( ) {
682
+ let stack = ContextStack :: default ( ) ;
683
+ assert_eq ! ( stack. stack. capacity( ) , ContextStack :: INITIAL_CAPACITY ) ;
684
+ }
685
+
686
+ /// Tests that map_current_cx correctly accesses the current context.
687
+ #[ test]
688
+ fn test_map_current_cx ( ) {
689
+ let mut stack = ContextStack :: default ( ) ;
690
+ let test_value = ValueA ( 42 ) ;
691
+ stack. current_cx = Context :: new ( ) . with_value ( test_value) ;
692
+
693
+ let result = stack. map_current_cx ( |cx| {
694
+ assert_eq ! ( cx. get:: <ValueA >( ) , Some ( & ValueA ( 42 ) ) ) ;
695
+ true
696
+ } ) ;
697
+ assert ! ( result) ;
698
+ }
699
+
700
+ /// Tests popping contexts in non-sequential order.
701
+ #[ test]
702
+ fn test_pop_id_out_of_order ( ) {
703
+ let mut stack = ContextStack :: default ( ) ;
704
+
705
+ // Push three contexts
706
+ let cx1 = Context :: new ( ) . with_value ( ValueA ( 1 ) ) ;
707
+ let cx2 = Context :: new ( ) . with_value ( ValueA ( 2 ) ) ;
708
+ let cx3 = Context :: new ( ) . with_value ( ValueA ( 3 ) ) ;
709
+
710
+ let id1 = stack. push ( cx1) ;
711
+ let id2 = stack. push ( cx2) ;
712
+ let id3 = stack. push ( cx3) ;
713
+
714
+ // Pop middle context first - should not affect current context
715
+ stack. pop_id ( id2) ;
716
+ assert_eq ! ( stack. current_cx. get:: <ValueA >( ) , Some ( & ValueA ( 3 ) ) ) ;
717
+ assert_eq ! ( stack. stack. len( ) , 3 ) ; // Length unchanged for middle pops
718
+
719
+ // Pop last context - should restore previous valid context
720
+ stack. pop_id ( id3) ;
721
+ assert_eq ! ( stack. current_cx. get:: <ValueA >( ) , Some ( & ValueA ( 1 ) ) ) ;
722
+ assert_eq ! ( stack. stack. len( ) , 1 ) ;
723
+
724
+ // Pop first context - should restore to empty state
725
+ stack. pop_id ( id1) ;
726
+ assert_eq ! ( stack. current_cx. get:: <ValueA >( ) , None ) ;
727
+ assert_eq ! ( stack. stack. len( ) , 0 ) ;
728
+ }
729
+
730
+ /// Tests edge cases in context stack operations. IRL these should log
731
+ /// warnings, and definitely not panic.
662
732
#[ test]
663
- fn context_stack_pop_id ( ) {
664
- // This is to get full line coverage of the `pop_id` function.
665
- // In real life the `Drop`` implementation of `ContextGuard` ensures that
666
- // the ids are valid and inside the bounds.
733
+ fn test_pop_id_edge_cases ( ) {
667
734
let mut stack = ContextStack :: default ( ) ;
735
+
736
+ // Test popping BASE_POS - should be no-op
668
737
stack. pop_id ( ContextStack :: BASE_POS ) ;
738
+ assert_eq ! ( stack. stack. len( ) , 0 ) ;
739
+
740
+ // Test popping MAX_POS - should be no-op
669
741
stack. pop_id ( ContextStack :: MAX_POS ) ;
670
- stack. pop_id ( 4711 ) ;
742
+ assert_eq ! ( stack. stack. len( ) , 0 ) ;
743
+
744
+ // Test popping invalid position - should be no-op
745
+ stack. pop_id ( 1000 ) ;
746
+ assert_eq ! ( stack. stack. len( ) , 0 ) ;
747
+
748
+ // Test popping from empty stack - should be safe
749
+ stack. pop_id ( 1 ) ;
750
+ assert_eq ! ( stack. stack. len( ) , 0 ) ;
751
+ }
752
+
753
+ /// Tests stack behavior when reaching maximum capacity.
754
+ /// Once we push beyond this point, we should end up with a context
755
+ /// that points _somewhere_, but mutating it should not affect the current
756
+ /// active context.
757
+ #[ test]
758
+ fn test_push_overflow ( ) {
759
+ let mut stack = ContextStack :: default ( ) ;
760
+ let max_pos = ContextStack :: MAX_POS as usize ;
761
+
762
+ // Fill stack up to max position
763
+ for i in 0 ..max_pos {
764
+ let cx = Context :: new ( ) . with_value ( ValueA ( i as u64 ) ) ;
765
+ let id = stack. push ( cx) ;
766
+ assert_eq ! ( id, ( i + 1 ) as u16 ) ;
767
+ }
768
+
769
+ // Try to push beyond capacity
770
+ let cx = Context :: new ( ) . with_value ( ValueA ( max_pos as u64 ) ) ;
771
+ let id = stack. push ( cx) ;
772
+ assert_eq ! ( id, ContextStack :: MAX_POS ) ;
773
+
774
+ // Verify current context remains unchanged after overflow
775
+ assert_eq ! (
776
+ stack. current_cx. get:: <ValueA >( ) ,
777
+ Some ( & ValueA ( ( max_pos - 2 ) as u64 ) )
778
+ ) ;
779
+ }
780
+
781
+ /// Tests that:
782
+ /// 1. Parent context values are properly propagated to async operations
783
+ /// 2. Values added during async operations do not affect parent context
784
+ #[ tokio:: test]
785
+ async fn test_async_context_propagation ( ) {
786
+ // A nested async operation we'll use to test propagation
787
+ async fn nested_operation ( ) {
788
+ // Verify we can see the parent context's value
789
+ assert_eq ! (
790
+ Context :: current( ) . get:: <ValueA >( ) ,
791
+ Some ( & ValueA ( 42 ) ) ,
792
+ "Parent context value should be available in async operation"
793
+ ) ;
794
+
795
+ // Create new context
796
+ let cx_with_both = Context :: current ( )
797
+ . with_value ( ValueA ( 43 ) ) // override ValueA
798
+ . with_value ( ValueB ( 24 ) ) ; // Add new ValueB
799
+
800
+ // Run nested async operation with both values
801
+ async {
802
+ // Verify both values are available
803
+ assert_eq ! (
804
+ Context :: current( ) . get:: <ValueA >( ) ,
805
+ Some ( & ValueA ( 43 ) ) ,
806
+ "Parent value should still be available after adding new value"
807
+ ) ;
808
+ assert_eq ! (
809
+ Context :: current( ) . get:: <ValueB >( ) ,
810
+ Some ( & ValueB ( 24 ) ) ,
811
+ "New value should be available in async operation"
812
+ ) ;
813
+
814
+ // Do some async work to simulate real-world scenario
815
+ sleep ( Duration :: from_millis ( 10 ) ) . await ;
816
+
817
+ // Values should still be available after async work
818
+ assert_eq ! (
819
+ Context :: current( ) . get:: <ValueA >( ) ,
820
+ Some ( & ValueA ( 43 ) ) ,
821
+ "Parent value should persist across await points"
822
+ ) ;
823
+ assert_eq ! (
824
+ Context :: current( ) . get:: <ValueB >( ) ,
825
+ Some ( & ValueB ( 24 ) ) ,
826
+ "New value should persist across await points"
827
+ ) ;
828
+ }
829
+ . with_context ( cx_with_both)
830
+ . await ;
831
+ }
832
+
833
+ // Set up initial context with ValueA
834
+ let parent_cx = Context :: new ( ) . with_value ( ValueA ( 42 ) ) ;
835
+
836
+ // Create and run async operation with the parent context explicitly propagated
837
+ nested_operation ( ) . with_context ( parent_cx. clone ( ) ) . await ;
838
+
839
+ // After async operation completes:
840
+ // 1. Parent context should be unchanged
841
+ assert_eq ! (
842
+ parent_cx. get:: <ValueA >( ) ,
843
+ Some ( & ValueA ( 42 ) ) ,
844
+ "Parent context should be unchanged"
845
+ ) ;
846
+ assert_eq ! (
847
+ parent_cx. get:: <ValueB >( ) ,
848
+ None ,
849
+ "Parent context should not see values added in async operation"
850
+ ) ;
851
+
852
+ // 2. Current context should be back to default
853
+ assert_eq ! (
854
+ Context :: current( ) . get:: <ValueA >( ) ,
855
+ None ,
856
+ "Current context should be back to default"
857
+ ) ;
858
+ assert_eq ! (
859
+ Context :: current( ) . get:: <ValueB >( ) ,
860
+ None ,
861
+ "Current context should not have async operation's values"
862
+ ) ;
863
+ }
864
+
865
+ ///
866
+ /// Tests that unnatural parent->child relationships in nested async
867
+ /// operations behave properly.
868
+ ///
869
+ #[ tokio:: test]
870
+ async fn test_out_of_order_context_detachment_futures ( ) {
871
+ // This function returns a future, but doesn't await it
872
+ // It will complete before the future that it creates.
873
+ async fn create_a_future ( ) -> impl std:: future:: Future < Output = ( ) > {
874
+ // Create a future that will do some work, referencing our current
875
+ // context, but don't await it.
876
+ async {
877
+ assert_eq ! ( Context :: current( ) . get:: <ValueA >( ) , Some ( & ValueA ( 42 ) ) ) ;
878
+
879
+ // Longer work
880
+ sleep ( Duration :: from_millis ( 50 ) ) . await ;
881
+ }
882
+ . with_context ( Context :: current ( ) )
883
+ }
884
+
885
+ // Create our base context
886
+ let parent_cx = Context :: new ( ) . with_value ( ValueA ( 42 ) ) ;
887
+
888
+ // await our nested function, which will create and detach a context
889
+ let future = create_a_future ( ) . with_context ( parent_cx) . await ;
890
+
891
+ // Execute the future. The future that created it is long gone, but this shouldn't
892
+ // cause issues.
893
+ let _a = future. await ;
894
+
895
+ // Nothing terrible (e.g., panics!) should happen, and we should definitely not have any
896
+ // values attached to our current context that were set in the nested operations.
897
+ assert_eq ! ( Context :: current( ) . get:: <ValueA >( ) , None ) ;
898
+ assert_eq ! ( Context :: current( ) . get:: <ValueB >( ) , None ) ;
671
899
}
672
900
}
0 commit comments