Skip to content

Commit cc1162e

Browse files
committed
Revert "chore: relax str to [u8] for thread ctx attrs"
This reverts commit 7dd922e. After discussion, the spec actually requires UTF8 bytes, so let's enforce that at the type system level.
1 parent 7dd922e commit cc1162e

1 file changed

Lines changed: 74 additions & 62 deletions

File tree

libdd-profiling/src/otel_thread_ctx.rs

Lines changed: 74 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
//!
4141
//! let trace_id = [0u8; 16];
4242
//! let span_id = [1u8; 8];
43-
//! let attrs: &[(u8, &[u8])] = &[(0, "GET"), (1, "/api/v1")];
43+
//! let attrs: &[(u8, &str)] = &[(0, "GET"), (1, "/api/v1")];
4444
//!
4545
//! // Publish a new context and save the previously attached one (if any).
4646
//! let ctx = ThreadContext::new(trace_id, span_id, attrs);
@@ -176,7 +176,7 @@ pub mod linux {
176176
trace_id: [u8; 16],
177177
span_id: [u8; 8],
178178
local_root_span_id: [u8; 8],
179-
attrs: &[(u8, &[u8])],
179+
attrs: &[(u8, &str)],
180180
) -> Self {
181181
const { assert!(size_of::<ThreadContextRecord>() == 640) }
182182

@@ -210,15 +210,28 @@ pub mod linux {
210210
/// recovery would require us to be able to rollback to the previous attributes which would
211211
/// hurt the happy path, or leave the record in a inconsistent state. Another possibility
212212
/// would be to error out and reset the record in that situation.
213-
fn set_attrs(&mut self, local_root_span_id: [u8; 8], attributes: &[(u8, &[u8])]) -> bool {
213+
fn set_attrs(&mut self, local_root_span_id: [u8; 8], attributes: &[(u8, &str)]) -> bool {
214214
let mut fully_encoded = true;
215215

216+
const { assert!(MAX_ATTRS_DATA_SIZE >= 18) }
217+
// The local root span id is provided as raw bytes (can be seen as a big-endian u64),
218+
// but readers will expect a string hex representation. We convert it to a fixed
219+
// 16-characters string in the usual lowercase hex format.
220+
//
221+
// There's currently no easy way to use Rust format capabilities to write directly in a
222+
// fixed-size array. Since the conversion is simple, we do it manually.
223+
const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
216224
self.attrs_data[0] = ROOT_SPAN_KEY_INDEX;
217-
self.attrs_data[1] = 8;
218-
self.attrs_data[2..10].copy_from_slice(local_root_span_id.as_slice());
219-
let mut offset = 10;
225+
self.attrs_data[1] = 16;
226+
for (i, &byte) in local_root_span_id.iter().enumerate() {
227+
self.attrs_data[2 + i * 2] = HEX_DIGITS[(byte >> 4) as usize];
228+
self.attrs_data[2 + i * 2 + 1] = HEX_DIGITS[(byte & 0xF) as usize];
229+
}
220230

221-
for &(key_index, val_bytes) in attributes {
231+
let mut offset = 18;
232+
233+
for &(key_index, val) in attributes {
234+
let val_bytes = val.as_bytes();
222235
let val_len = val_bytes.len();
223236
let val_len = if val_len > 255 {
224237
fully_encoded = false;
@@ -234,7 +247,7 @@ pub mod linux {
234247
}
235248

236249
self.attrs_data[offset] = key_index;
237-
// `val_len <= 255` from the check above
250+
// `val_len <= 255` thanks to the `min()`
238251
self.attrs_data[offset + 1] = val_len as u8;
239252
self.attrs_data[offset + 2..offset + 2 + val_len]
240253
.copy_from_slice(&val_bytes[..val_len]);
@@ -279,7 +292,7 @@ pub mod linux {
279292
trace_id: [u8; 16],
280293
span_id: [u8; 8],
281294
local_root_span_id: [u8; 8],
282-
attrs: &[(u8, &[u8])],
295+
attrs: &[(u8, &str)],
283296
) -> Self {
284297
Self::from(ThreadContextRecord::new(
285298
trace_id,
@@ -363,7 +376,7 @@ pub mod linux {
363376
trace_id: [u8; 16],
364377
span_id: [u8; 8],
365378
local_root_span_id: [u8; 8],
366-
attrs: &[(u8, &[u8])],
379+
attrs: &[(u8, &str)],
367380
) {
368381
let slot = get_tls_slot();
369382

@@ -471,34 +484,33 @@ pub mod linux {
471484
assert_eq!(record.trace_id, trace_id);
472485
assert_eq!(record.span_id, span_id);
473486
assert_eq!(record.valid.load(Ordering::Relaxed), 1);
474-
// 1 (key) + 1 (len) + 8 (root_span_id bytes) = 10
475-
assert_eq!(record.attrs_data_size, 10);
487+
// 1 (key) + 1 (len) + 16 (root_span_id hex chars) = 18
488+
assert_eq!(record.attrs_data_size, 18);
476489

477490
let _ = ThreadContext::detach();
478491
}
479492

480493
#[test]
481494
#[cfg_attr(miri, ignore)]
482495
fn attribute_encoding_basic() {
483-
let root_span_id = [0u8; 8];
484-
let attrs: &[(u8, &[u8])] = &[(1, b"GET"), (2, b"/api/v1")];
485-
ThreadContext::new([0u8; 16], [0u8; 8], root_span_id, attrs).attach();
496+
let attrs: &[(u8, &str)] = &[(1, "GET"), (2, "/api/v1")];
497+
ThreadContext::new([0u8; 16], [0u8; 8], [0u8; 8], attrs).attach();
486498

487499
let ptr = read_tls_context_ptr();
488500
assert!(!ptr.is_null());
489501
let record = unsafe { &*ptr };
490-
// 1+1+8 (root_span_id) + 1+1+3 (GET) + 1+1+7 (/api/v1)
491-
let expected_size: u16 = (2 + 8 + 2 + 3 + 2 + 7) as u16;
502+
// 1+1+16 (root_span_id hex) + 1+1+3 (GET) + 1+1+7 (/api/v1)
503+
let expected_size: u16 = (2 + 16 + 2 + 3 + 2 + 7) as u16;
492504
assert_eq!(record.attrs_data_size, expected_size);
493505
assert_eq!(record.attrs_data[0], 0);
494-
assert_eq!(record.attrs_data[1], 8);
495-
assert_eq!(&record.attrs_data[2..10], &root_span_id);
496-
assert_eq!(record.attrs_data[10], 1);
497-
assert_eq!(record.attrs_data[11], 3);
498-
assert_eq!(&record.attrs_data[12..15], b"GET");
499-
assert_eq!(record.attrs_data[15], 2);
500-
assert_eq!(record.attrs_data[16], 7);
501-
assert_eq!(&record.attrs_data[17..24], b"/api/v1");
506+
assert_eq!(record.attrs_data[1], 16);
507+
assert_eq!(&record.attrs_data[2..18], b"0000000000000000");
508+
assert_eq!(record.attrs_data[18], 1);
509+
assert_eq!(record.attrs_data[19], 3);
510+
assert_eq!(&record.attrs_data[20..23], b"GET");
511+
assert_eq!(record.attrs_data[23], 2);
512+
assert_eq!(record.attrs_data[24], 7);
513+
assert_eq!(&record.attrs_data[25..32], b"/api/v1");
502514

503515
let _ = ThreadContext::detach();
504516
}
@@ -508,31 +520,31 @@ pub mod linux {
508520
fn attribute_truncation_on_overflow() {
509521
// Build attributes whose combined encoded size exceeds MAX_ATTRS_DATA_SIZE.
510522
// Each max entry: 1 (key) + 1 (len) + 255 (val) = 257 bytes.
511-
// root_span_id: 1 (key) + 1 (len) + 8 (val) = 10 bytes.
512-
// Two such entries: 514 bytes, plus root_span_id: 524.
513-
// A third entry of 100 chars would need 102 bytes, bringing the total to 626 > 612, so
523+
// root_span_id: 1 (key) + 1 (len) + 16 (hex val) = 18 bytes.
524+
// Two such entries: 514 bytes, plus root_span_id: 532.
525+
// A third entry of 100 chars would need 102 bytes, bringing the total to 634 > 612, so
514526
// the third entry must be dropped.
515-
let val_a = b"a".repeat(255); // 257 bytes encoded
516-
let val_b = b"b".repeat(255); // 257 bytes encoded → 514 total
517-
let val_c = b"c".repeat(100); // 102 bytes encoded → 626 total: must be dropped
518-
519-
let attrs: &[(u8, &[u8])] = &[
520-
(1, val_a.as_slice()),
521-
(2, val_b.as_slice()),
522-
(3, val_c.as_slice()),
527+
let val_a = "a".repeat(255); // 257 bytes encoded
528+
let val_b = "b".repeat(255); // 257 bytes encoded → 514 total
529+
let val_c = "c".repeat(100); // 102 bytes encoded → 626 total: must be dropped
530+
531+
let attrs: &[(u8, &str)] = &[
532+
(1, val_a.as_str()),
533+
(2, val_b.as_str()),
534+
(3, val_c.as_str()),
523535
];
524536

525537
ThreadContext::new([0u8; 16], [0u8; 8], [0u8; 8], attrs).attach();
526538

527539
let ptr = read_tls_context_ptr();
528540
assert!(!ptr.is_null());
529541
let record = unsafe { &*ptr };
530-
// Only the first two entries fit (514 bytes + 10 bytes for root_span_id).
531-
assert_eq!(record.attrs_data_size, 524);
532-
assert_eq!(record.attrs_data[10], 1);
533-
assert_eq!(record.attrs_data[11], 255);
534-
assert_eq!(record.attrs_data[267], 2);
535-
assert_eq!(record.attrs_data[268], 255);
542+
// Only the first two entries fit (514 bytes + 18 bytes for root_span_id).
543+
assert_eq!(record.attrs_data_size, 532);
544+
assert_eq!(record.attrs_data[18], 1);
545+
assert_eq!(record.attrs_data[19], 255);
546+
assert_eq!(record.attrs_data[275], 2);
547+
assert_eq!(record.attrs_data[276], 255);
536548

537549
let _ = ThreadContext::detach();
538550
}
@@ -548,7 +560,7 @@ pub mod linux {
548560
let root_span_id2 = [0x79u8; 8];
549561

550562
// Updating before any context is attached should be equivalent to `attach()`
551-
ThreadContext::update(trace_id1, span_id1, root_span_id1, &[(0, b"v1")]);
563+
ThreadContext::update(trace_id1, span_id1, root_span_id1, &[(0, "v1")]);
552564

553565
let ptr_before = read_tls_context_ptr();
554566
assert!(!ptr_before.is_null());
@@ -557,13 +569,13 @@ pub mod linux {
557569
assert_eq!(record.span_id, span_id1);
558570
assert_eq!(record.valid.load(Ordering::Relaxed), 1);
559571
assert_eq!(record.attrs_data[0], 0);
560-
assert_eq!(record.attrs_data[1], 8);
561-
assert_eq!(&record.attrs_data[2..10], &root_span_id1);
562-
assert_eq!(record.attrs_data[10], 0);
563-
assert_eq!(record.attrs_data[11], 2);
564-
assert_eq!(&record.attrs_data[12..14], b"v1");
572+
assert_eq!(record.attrs_data[1], 16);
573+
assert_eq!(&record.attrs_data[2..18], b"7878787878787878");
574+
assert_eq!(record.attrs_data[18], 0);
575+
assert_eq!(record.attrs_data[19], 2);
576+
assert_eq!(&record.attrs_data[20..22], b"v1");
565577

566-
ThreadContext::update(trace_id2, span_id2, root_span_id2, &[(0, b"v2")]);
578+
ThreadContext::update(trace_id2, span_id2, root_span_id2, &[(0, "v2")]);
567579

568580
let ptr_after = read_tls_context_ptr();
569581
assert_eq!(
@@ -576,11 +588,11 @@ pub mod linux {
576588
assert_eq!(record.span_id, span_id2);
577589
assert_eq!(record.valid.load(Ordering::Relaxed), 1);
578590
assert_eq!(record.attrs_data[0], 0);
579-
assert_eq!(record.attrs_data[1], 8);
580-
assert_eq!(&record.attrs_data[2..10], &root_span_id2);
581-
assert_eq!(record.attrs_data[10], 0);
582-
assert_eq!(record.attrs_data[11], 2);
583-
assert_eq!(&record.attrs_data[12..14], b"v2");
591+
assert_eq!(record.attrs_data[1], 16);
592+
assert_eq!(&record.attrs_data[2..18], b"7979797979797979");
593+
assert_eq!(record.attrs_data[18], 0);
594+
assert_eq!(record.attrs_data[19], 2);
595+
assert_eq!(&record.attrs_data[20..22], b"v2");
584596

585597
let _ = ThreadContext::detach();
586598
assert!(read_tls_context_ptr().is_null());
@@ -603,17 +615,17 @@ pub mod linux {
603615
#[test]
604616
#[cfg_attr(miri, ignore)]
605617
fn long_value_capped_at_255_bytes() {
606-
let long_val = b"a".repeat(300);
607-
ThreadContext::new([0u8; 16], [0u8; 8], [0u8; 8], &[(0, long_val.as_slice())]).attach();
618+
let long_val = "a".repeat(300);
619+
ThreadContext::new([0u8; 16], [0u8; 8], [0u8; 8], &[(0, long_val.as_str())]).attach();
608620

609621
let ptr = read_tls_context_ptr();
610622
assert!(!ptr.is_null());
611623
let record = unsafe { &*ptr };
612-
// root_span_id occupies offset 0..10, then the attr entry starts at 10: key at [10],
613-
// len at [11]
614-
let val_len = record.attrs_data[2 + 8 + 1];
624+
// root_span_id occupies offset 0..18, then the attr entry starts at 18: key at [18],
625+
// len at [19]
626+
let val_len = record.attrs_data[2 + 16 + 1];
615627
assert_eq!(val_len, 255, "value must be capped at 255 bytes");
616-
assert_eq!(record.attrs_data_size, 2 + 8 + 2 + 255);
628+
assert_eq!(record.attrs_data_size, 2 + 16 + 2 + 255);
617629

618630
let _ = ThreadContext::detach();
619631
}
@@ -649,7 +661,7 @@ pub mod linux {
649661
let record = unsafe { &*ptr };
650662
assert_eq!(record.trace_id, spawned_trace_id);
651663
assert_eq!(record.span_id, spawned_span_id);
652-
assert_eq!(&record.attrs_data[2..10], &spawned_root_span_id);
664+
assert_eq!(&record.attrs_data[2..18], b"efefefefefefefef");
653665

654666
let _ = ThreadContext::detach();
655667
assert!(read_tls_context_ptr().is_null());
@@ -670,7 +682,7 @@ pub mod linux {
670682
let record = unsafe { &*ptr };
671683
assert_eq!(record.trace_id, main_trace_id);
672684
assert_eq!(record.span_id, main_span_id);
673-
assert_eq!(&record.attrs_data[2..10], &main_root_span_id);
685+
assert_eq!(&record.attrs_data[2..18], b"3333333333333333");
674686

675687
barrier.wait();
676688

0 commit comments

Comments
 (0)