1use core::cell::{Cell, RefCell};
78use core::future::Future;
79use core::marker::PhantomData;
80use core::pin::Pin;
81use core::task::{Context as TaskContext, Poll};
82use std::sync::Arc;
83
84use crate::record::{Field, same_output_key};
85use crate::value::{OwnedField, Value};
86
87pub const MAX_CONTEXT_SLOTS: usize = 16;
90
91enum Entry {
92 Field { id: u64, field: OwnedField },
93 Snapshot { id: u64, fields: Arc<[OwnedField]> },
94}
95
96impl Entry {
97 const fn id(&self) -> u64 {
98 match self {
99 Self::Field { id, .. } | Self::Snapshot { id, .. } => *id,
100 }
101 }
102}
103
104enum Pending {
105 Push(Entry),
106 Remove(u64),
107 Clear,
108}
109
110struct ThreadContext {
111 entries: RefCell<Vec<Entry>>,
112 pending: RefCell<Vec<Pending>>,
114 has_pending: Cell<bool>,
115 next_id: Cell<u64>,
116}
117
118impl ThreadContext {
119 const fn new() -> Self {
120 Self {
121 entries: RefCell::new(Vec::new()),
122 pending: RefCell::new(Vec::new()),
123 has_pending: Cell::new(false),
124 next_id: Cell::new(1),
125 }
126 }
127
128 fn next_id(&self) -> u64 {
129 let id = self.next_id.get();
130 self.next_id.set(id.wrapping_add(1).max(1));
131 id
132 }
133
134 fn apply_or_defer(&self, op: Pending) {
137 match self.entries.try_borrow_mut() {
138 Ok(mut entries) => apply(&mut entries, op),
139 Err(_) => {
140 if let Ok(mut pending) = self.pending.try_borrow_mut() {
141 pending.push(op);
142 self.has_pending.set(true);
143 }
144 }
145 }
146 }
147
148 fn apply_pending(&self) {
149 if !self.has_pending.get() {
150 return;
151 }
152 let Ok(mut entries) = self.entries.try_borrow_mut() else {
153 return;
155 };
156 let Ok(mut pending) = self.pending.try_borrow_mut() else {
157 return;
158 };
159 for op in pending.drain(..) {
160 apply(&mut entries, op);
161 }
162 self.has_pending.set(false);
163 }
164}
165
166fn apply(entries: &mut Vec<Entry>, op: Pending) {
167 match op {
168 Pending::Push(entry) => {
169 if entries.len() >= MAX_CONTEXT_SLOTS {
170 drop(entries.remove(0));
171 }
172 entries.push(entry);
173 }
174 Pending::Remove(id) => {
175 if let Some(pos) = entries.iter().rposition(|e| e.id() == id) {
176 drop(entries.remove(pos));
177 }
178 }
179 Pending::Clear => entries.clear(),
180 }
181}
182
183thread_local! {
184 static CONTEXT: ThreadContext = const { ThreadContext::new() };
185}
186
187#[must_use = "context is removed when this guard is dropped; bind it to a variable"]
202#[derive(Debug)]
203pub struct ContextGuard {
204 id: u64,
207 _not_send: PhantomData<*const ()>,
208}
209
210impl ContextGuard {
211 const fn new(id: u64) -> Self {
212 Self {
213 id,
214 _not_send: PhantomData,
215 }
216 }
217}
218
219impl Drop for ContextGuard {
220 fn drop(&mut self) {
221 if self.id == 0 {
222 return;
223 }
224 let id = self.id;
225 let _tls_may_be_gone = CONTEXT.try_with(|ctx| ctx.apply_or_defer(Pending::Remove(id)));
228 }
229}
230
231fn push(make: impl FnOnce(u64) -> Entry) -> ContextGuard {
232 CONTEXT
233 .try_with(|ctx| {
234 let id = ctx.next_id();
235 ctx.apply_or_defer(Pending::Push(make(id)));
236 ContextGuard::new(id)
237 })
238 .unwrap_or(ContextGuard::new(0))
239}
240
241pub fn with_field<'a, V: Into<Value<'a>>>(key: &str, value: V) -> ContextGuard {
259 let field = OwnedField::new(key, value.into());
260 push(move |id| Entry::Field { id, field })
261}
262
263pub fn with_trace_id(id: &str) -> ContextGuard {
265 with_field("trace_id", Value::Str(id))
266}
267
268pub fn with_request_id(id: &str) -> ContextGuard {
270 with_field("request_id", Value::Str(id))
271}
272
273pub fn with_snapshot<R>(f: impl FnOnce(&[Field<'_>]) -> R) -> R {
293 if CONTEXT.try_with(|_| ()).is_err() {
298 return f(&[]);
299 }
300 CONTEXT.with(|ctx| {
301 let out = match ctx.entries.try_borrow() {
302 Ok(entries) => {
303 let mut buf = [EMPTY; MAX_CONTEXT_SLOTS];
304 let len = materialize(&entries, &mut buf);
305 f(&buf[..len])
306 }
307 Err(_) => f(&[]),
310 };
311 ctx.apply_pending();
312 out
313 })
314}
315
316const EMPTY: Field<'static> = Field {
317 key: "",
318 value: Value::Null,
319};
320
321fn materialize<'e>(entries: &'e [Entry], buf: &mut [Field<'e>; MAX_CONTEXT_SLOTS]) -> usize {
324 let mut len = 0;
325 'entries: for entry in entries.iter().rev() {
326 let fields: &[OwnedField] = match entry {
327 Entry::Field { field, .. } => core::slice::from_ref(field),
328 Entry::Snapshot { fields, .. } => fields,
329 };
330 for owned in fields.iter().rev() {
331 if len == MAX_CONTEXT_SLOTS {
332 break 'entries;
333 }
334 if buf[..len]
335 .iter()
336 .any(|f| same_output_key(f.key, &owned.key))
337 {
338 continue;
339 }
340 buf[len] = owned.as_field();
341 len += 1;
342 }
343 }
344 buf[..len].reverse();
345 len
346}
347
348pub fn clear() {
354 let _tls_may_be_gone = CONTEXT.try_with(|ctx| ctx.apply_or_defer(Pending::Clear));
355}
356
357#[must_use]
360pub fn len() -> usize {
361 with_snapshot(|fields| fields.len())
362}
363
364#[must_use]
383pub fn capture() -> ContextSnapshot {
384 with_snapshot(ContextSnapshot::from_fields)
385}
386
387#[derive(Clone, Default)]
396pub struct ContextSnapshot {
397 fields: Option<Arc<[OwnedField]>>,
398}
399
400impl ContextSnapshot {
401 #[must_use]
415 pub fn from_fields(fields: &[Field<'_>]) -> Self {
416 if fields.is_empty() {
417 return Self::default();
418 }
419 let mut owned: Vec<OwnedField> = Vec::with_capacity(fields.len());
420 for field in fields {
421 if let Some(existing) = owned
422 .iter_mut()
423 .find(|o| same_output_key(&o.key, field.key))
424 {
425 *existing = OwnedField::new(field.key, field.value);
426 } else {
427 owned.push(OwnedField::new(field.key, field.value));
428 }
429 }
430 Self {
431 fields: Some(owned.into()),
432 }
433 }
434
435 #[must_use]
437 pub fn len(&self) -> usize {
438 self.fields.as_deref().map_or(0, <[OwnedField]>::len)
439 }
440
441 #[must_use]
443 pub fn is_empty(&self) -> bool {
444 self.len() == 0
445 }
446
447 pub fn iter(&self) -> impl Iterator<Item = Field<'_>> {
449 self.fields
450 .as_deref()
451 .unwrap_or(&[])
452 .iter()
453 .map(OwnedField::as_field)
454 }
455
456 pub fn attach(&self) -> ContextGuard {
459 match &self.fields {
460 Some(fields) => {
461 let fields = Arc::clone(fields);
462 push(move |id| Entry::Snapshot { id, fields })
463 }
464 None => ContextGuard::new(0),
465 }
466 }
467
468 pub fn scope<R>(&self, f: impl FnOnce() -> R) -> R {
470 let _guard = self.attach();
471 f()
472 }
473
474 pub fn wrap<F: Future>(&self, future: F) -> WithContext<F> {
519 WithContext {
520 future: Box::pin(future),
521 snapshot: self.clone(),
522 held: Vec::new(),
523 held_ids: Vec::new(),
524 }
525 }
526}
527
528impl core::fmt::Debug for ContextSnapshot {
529 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
530 f.debug_map()
531 .entries(self.iter().map(|field| (field.key, field.value)))
532 .finish()
533 }
534}
535
536#[must_use = "futures do nothing unless polled"]
539pub struct WithContext<F> {
540 future: Pin<Box<F>>,
541 snapshot: ContextSnapshot,
542 held: Vec<Entry>,
545 held_ids: Vec<u64>,
547}
548
549impl<F: Future> Future for WithContext<F> {
550 type Output = F::Output;
551
552 fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
553 let this = &mut *self;
554 let first_new_id = CONTEXT.try_with(|ctx| ctx.next_id.get()).unwrap_or(0);
556 let snapshot_guard = this.snapshot.attach();
557 reinstall(&mut this.held, &mut this.held_ids);
558 let poll = this.future.as_mut().poll(cx);
559 if poll.is_pending() && first_new_id != 0 {
560 take_task_entries(
561 first_new_id,
562 snapshot_guard.id,
563 &this.held_ids,
564 &mut this.held,
565 );
566 }
567 drop(snapshot_guard);
568 poll
569 }
570}
571
572fn reinstall(held: &mut Vec<Entry>, held_ids: &mut Vec<u64>) {
575 held_ids.clear();
576 if held.is_empty() {
577 return;
578 }
579 let _tls_may_be_gone = CONTEXT.try_with(|ctx| {
580 for entry in held.drain(..) {
581 held_ids.push(entry.id());
582 ctx.apply_or_defer(Pending::Push(entry));
583 }
584 });
585}
586
587fn take_task_entries(
590 first_new_id: u64,
591 snapshot_id: u64,
592 reinstalled: &[u64],
593 held: &mut Vec<Entry>,
594) {
595 let _tls_may_be_gone = CONTEXT.try_with(|ctx| {
596 let Ok(mut entries) = ctx.entries.try_borrow_mut() else {
599 return;
600 };
601 let mut index = 0;
602 while index < entries.len() {
603 let id = entries[index].id();
604 let owned_by_task =
605 (id >= first_new_id && id != snapshot_id) || reinstalled.contains(&id);
606 if owned_by_task {
607 held.push(entries.remove(index));
608 } else {
609 index += 1;
610 }
611 }
612 });
613}
614
615impl<F> core::fmt::Debug for WithContext<F> {
616 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
617 f.debug_struct("WithContext")
618 .field("snapshot", &self.snapshot)
619 .finish_non_exhaustive()
620 }
621}
622
623#[cfg(test)]
624mod tests {
625 use super::*;
626
627 fn keys() -> Vec<String> {
628 with_snapshot(|f| f.iter().map(|f| f.key.to_owned()).collect())
629 }
630
631 #[test]
632 fn test_context_is_isolated_per_thread() {
633 clear();
634 let _g = with_field("k", 1_u64);
635 assert_eq!(len(), 1);
636 std::thread::spawn(|| assert_eq!(len(), 0)).join().unwrap();
637 clear();
638 }
639
640 #[test]
641 fn test_context_guard_removes_on_drop() {
642 clear();
643 {
644 let _g = with_field("k", 1_u64);
645 assert_eq!(len(), 1);
646 }
647 assert_eq!(len(), 0);
648 }
649
650 #[test]
651 fn test_context_nested_scopes_layer_and_unwind() {
652 clear();
653 let outer = with_field("outer", 1_u64);
654 {
655 let _inner = with_field("inner", 2_u64);
656 assert_eq!(keys(), ["outer", "inner"]);
657 }
658 assert_eq!(keys(), ["outer"]);
659 drop(outer);
660 assert_eq!(len(), 0);
661 }
662
663 #[test]
664 fn test_context_out_of_order_drop() {
665 clear();
666 let a = with_field("a", 1_u64);
667 let b = with_field("b", 2_u64);
668 drop(a);
669 assert_eq!(keys(), ["b"]);
670 drop(b);
671 assert_eq!(len(), 0);
672 }
673
674 #[test]
675 fn test_context_inner_scope_shadows_outer_key() {
676 clear();
678 let _a = with_field("user", "outer");
679 let b = with_field("user", "inner");
680 with_snapshot(|f| {
681 assert_eq!(f.len(), 1);
682 assert_eq!(f[0].value, Value::Str("inner"));
683 });
684 drop(b);
685 with_snapshot(|f| assert_eq!(f[0].value, Value::Str("outer")));
686 clear();
687 }
688
689 #[test]
690 fn test_context_slot_overflow_evicts_oldest() {
691 clear();
692 let mut guards = Vec::new();
693 for i in 0..(MAX_CONTEXT_SLOTS + 5) {
694 guards.push(with_field(&format!("k{i}"), i as u64));
695 }
696 with_snapshot(|f| {
697 assert_eq!(f.len(), MAX_CONTEXT_SLOTS);
698 assert_eq!(f[0].key, "k5", "oldest entries are evicted");
699 assert_eq!(f[MAX_CONTEXT_SLOTS - 1].key, "k20");
700 });
701 drop(guards);
702 assert_eq!(len(), 0);
703 }
704
705 #[test]
706 fn test_context_trace_and_request_id_helpers() {
707 clear();
708 let _t = with_trace_id("abc");
709 let _r = with_request_id("xyz");
710 with_snapshot(|f| {
711 assert_eq!(f[0].key, "trace_id");
712 assert_eq!(f[0].value, Value::Str("abc"));
713 assert_eq!(f[1].key, "request_id");
714 assert_eq!(f[1].value, Value::Str("xyz"));
715 });
716 clear();
717 }
718
719 #[test]
720 fn test_context_with_field_inside_snapshot_is_deferred_not_panicking() {
721 clear();
723 let guard = with_snapshot(|_| with_field("x", 1_u32));
724 assert_eq!(keys(), ["x"], "applied after the snapshot ended");
725 drop(guard);
726 assert_eq!(len(), 0);
727 }
728
729 #[test]
730 fn test_context_guard_dropped_inside_snapshot_is_removed_afterwards() {
731 clear();
733 let guard = with_field("k", 1_u32);
734 with_snapshot(|f| {
735 assert_eq!(f.len(), 1);
736 drop(guard);
737 });
738 assert_eq!(len(), 0, "removal must not leak");
739 }
740
741 #[test]
742 fn test_context_nested_snapshots_see_outer_context() {
743 clear();
744 let _g = with_field("k", 1_u32);
745 with_snapshot(|outer| {
746 assert_eq!(outer.len(), 1);
747 with_snapshot(|inner| assert_eq!(inner.len(), 1));
748 let _late = with_field("late", 2_u32);
749 });
750 assert_eq!(keys(), ["k"], "the late guard was pushed and removed");
751 clear();
752 }
753
754 #[test]
755 fn test_context_clear_inside_snapshot_is_deferred() {
756 clear();
757 let _g = with_field("k", 1_u32);
758 with_snapshot(|_| clear());
759 assert_eq!(len(), 0);
760 }
761
762 #[test]
763 fn test_context_guard_drop_during_tls_teardown_does_not_abort() {
764 thread_local! {
767 static HOLD: RefCell<Option<ContextGuard>> = const { RefCell::new(None) };
768 }
769 for order in 0..2 {
770 std::thread::spawn(move || {
771 if order == 0 {
772 HOLD.with(|h| drop(h.borrow_mut().take()));
773 let g = with_field("k", 1_u32);
774 HOLD.with(|h| *h.borrow_mut() = Some(g));
775 } else {
776 let g = with_field("k", 1_u32);
777 HOLD.with(|h| *h.borrow_mut() = Some(g));
778 }
779 })
780 .join()
781 .expect("thread must exit cleanly");
782 }
783 }
784
785 #[test]
786 fn test_snapshot_capture_attach_scope() {
787 clear();
788 let snapshot = {
789 let _t = with_trace_id("t-1");
790 let _u = with_field("user", "u-1");
791 capture()
792 };
793 assert_eq!(len(), 0);
794 assert_eq!(snapshot.len(), 2);
795 assert!(!snapshot.is_empty());
796 let seen = snapshot.scope(keys);
797 assert_eq!(seen, ["trace_id", "user"]);
798 assert_eq!(len(), 0);
799 let handle = {
800 let snapshot = snapshot.clone();
801 std::thread::spawn(move || snapshot.scope(keys))
802 };
803 assert_eq!(handle.join().unwrap(), ["trace_id", "user"]);
804 let debug = format!("{snapshot:?}");
805 assert!(debug.contains("trace_id"), "{debug}");
806 }
807
808 #[test]
809 fn test_snapshot_shadowing_with_thread_fields() {
810 clear();
811 let snapshot = ContextSnapshot::from_fields(&[
812 Field::new("tenant", "a"),
813 Field::new("tenant", "b"),
814 Field::new("region", "eu"),
815 ]);
816 assert_eq!(snapshot.len(), 2);
817 let _early = with_field("region", "us");
818 let _attached = snapshot.attach();
819 let _late = with_field("tenant", "c");
820 with_snapshot(|f| {
821 let pairs: Vec<(&str, Value<'_>)> = f.iter().map(|f| (f.key, f.value)).collect();
822 assert_eq!(
823 pairs,
824 [("region", Value::Str("eu")), ("tenant", Value::Str("c"))]
825 );
826 });
827 clear();
828 }
829
830 #[test]
831 fn test_empty_snapshot_attach_is_noop() {
832 clear();
833 let empty = capture();
834 assert!(empty.is_empty());
835 assert_eq!(empty.iter().count(), 0);
836 let _g = empty.attach();
837 assert_eq!(len(), 0);
838 assert_eq!(ContextSnapshot::from_fields(&[]).len(), 0);
839 }
840
841 #[test]
842 fn test_with_context_future_installs_snapshot_per_poll() {
843 use std::task::Waker;
844
845 struct YieldThenLen(bool);
847 impl Future for YieldThenLen {
848 type Output = usize;
849 fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<usize> {
850 if self.0 {
851 Poll::Ready(len())
852 } else {
853 self.0 = true;
854 cx.waker().wake_by_ref();
855 Poll::Pending
856 }
857 }
858 }
859
860 clear();
861 let snapshot = ContextSnapshot::from_fields(&[Field::new("req", "r1")]);
862 let mut fut = snapshot.wrap(YieldThenLen(false));
863 assert!(format!("{fut:?}").starts_with("WithContext"));
864 let waker = Waker::noop().clone();
865 let mut cx = TaskContext::from_waker(&waker);
866 assert!(Pin::new(&mut fut).poll(&mut cx).is_pending());
867 assert_eq!(len(), 0, "nothing leaks between polls");
868 let out = std::thread::spawn(move || {
870 let waker = Waker::noop().clone();
871 let mut cx = TaskContext::from_waker(&waker);
872 Pin::new(&mut fut).poll(&mut cx)
873 })
874 .join()
875 .unwrap();
876 assert_eq!(out, Poll::Ready(1));
877 }
878
879 struct YieldOnce(bool);
881 impl Future for YieldOnce {
882 type Output = ();
883 fn poll(mut self: Pin<&mut Self>, _: &mut TaskContext<'_>) -> Poll<()> {
884 if self.0 {
885 Poll::Ready(())
886 } else {
887 self.0 = true;
888 Poll::Pending
889 }
890 }
891 }
892
893 fn join_on_this_thread<'a>(
896 a: Pin<&'a mut dyn Future<Output = ()>>,
897 b: Pin<&'a mut dyn Future<Output = ()>>,
898 ) {
899 let mut cx = TaskContext::from_waker(std::task::Waker::noop());
900 let (mut a, mut b) = (Some(a), Some(b));
901 while a.is_some() || b.is_some() {
902 for slot in [&mut a, &mut b] {
903 if let Some(fut) = slot.as_mut() {
904 if fut.as_mut().poll(&mut cx).is_ready() {
905 *slot = None;
906 }
907 }
908 }
909 }
910 }
911
912 #[test]
913 fn test_wrapped_future_keeps_held_context_from_sibling_futures() {
914 clear();
917 let seen_by_b = std::rc::Rc::new(RefCell::new(Vec::new()));
918 let seen_by_a = std::rc::Rc::new(RefCell::new(Vec::new()));
919 let (log_b, log_a) = (seen_by_b.clone(), seen_by_a.clone());
920 let task = ContextSnapshot::from_fields(&[Field::new("req", "r1")]);
921 let a = core::pin::pin!(task.wrap(async move {
922 let _tenant = with_field("tenant", "A");
923 YieldOnce(false).await;
924 log_a.borrow_mut().push(keys());
925 {
926 let _nested = ContextSnapshot::from_fields(&[Field::new("n", 1_u8)]).attach();
927 YieldOnce(false).await;
928 log_a.borrow_mut().push(keys());
929 }
930 YieldOnce(false).await;
931 log_a.borrow_mut().push(keys());
932 }));
933 let b = core::pin::pin!(async move {
934 for _ in 0..4 {
935 log_b.borrow_mut().push(keys());
936 YieldOnce(false).await;
937 }
938 });
939 join_on_this_thread(a, b);
940 for seen in seen_by_b.borrow().iter() {
941 assert!(seen.is_empty(), "B saw A's context: {seen:?}");
942 }
943 assert_eq!(
944 *seen_by_a.borrow(),
945 [
946 vec!["req", "tenant"],
947 vec!["req", "tenant", "n"],
948 vec!["req", "tenant"],
949 ]
950 );
951 assert_eq!(len(), 0, "nothing is left on the thread");
952 }
953
954 #[test]
955 fn test_wrapped_future_dropped_while_suspended_leaves_nothing() {
956 clear();
957 let mut cx = TaskContext::from_waker(std::task::Waker::noop());
958 let mut task = Box::pin(ContextSnapshot::default().wrap(async {
959 let _held = with_field("held", 1_u8);
960 YieldOnce(false).await;
961 }));
962 assert!(task.as_mut().poll(&mut cx).is_pending());
963 assert_eq!(len(), 0);
964 drop(task);
965 assert_eq!(len(), 0);
966 let _outer = with_field("outer", 1_u8);
967 assert_eq!(keys(), ["outer"]);
968 clear();
969 }
970
971 #[test]
972 fn test_snapshot_is_send_and_sync() {
973 fn assert_send_sync<T: Send + Sync>() {}
974 assert_send_sync::<ContextSnapshot>();
975 assert_send_sync::<WithContext<core::future::Ready<()>>>();
976 }
977}