From 736ab28003f47cd0b301de5b89021372f858001f Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 28 Aug 2026 04:27:11 +0000 Subject: [PATCH] fix(relay): never panic on multibyte request ids when naming worker threads Thread names sliced the first 8 bytes of the cloud-controlled request id. A multibyte UTF-8 character in those bytes panics on a char boundary; release is panic = "abort", so one bad frame killed the relay. Truncate by characters instead. Closes #17 Co-authored-by: duyet --- src/relay.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/relay.rs b/src/relay.rs index eb12c75..d063bb2 100644 --- a/src/relay.rs +++ b/src/relay.rs @@ -825,6 +825,12 @@ fn serve_connection( } } +/// First up-to-`max_chars` characters of `s`, safe on any input (the id is +/// cloud-controlled and only used for diagnostics). +fn short_id(s: &str, max_chars: usize) -> String { + s.chars().take(max_chars).collect() +} + /// Spawn a worker thread for one incoming request frame and register its /// cancel flag. `target` is 'static by construction: either one of the built-in /// probe constants or a leaked --target value (resolved once per process). @@ -837,7 +843,7 @@ fn spawn_request(state: &ConnState, frame: RequestFrame, target: &'static str) { .insert(frame.id.clone(), Arc::clone(&cancel)); let tx = state.tx.clone(); std::thread::Builder::new() - .name(format!("relay-req-{}", &frame.id[..frame.id.len().min(8)])) + .name(format!("relay-req-{}", short_id(&frame.id, 8))) .spawn(move || handle_request(&tx, &frame, target, &cancel)) .expect("spawn relay worker"); } @@ -1141,6 +1147,15 @@ mod tests { advertised_models("http://10.0.0.5:8000/v1", fetched) } + #[test] + fn short_id_is_char_boundary_safe_and_bounded() { + assert_eq!(short_id("abcdefghijklmnop", 8), "abcdefgh"); + // Multibyte inside the first 8 BYTES must not panic. + assert_eq!(short_id("héllo-world", 4), "héll"); + assert_eq!(short_id("🚀🚀🚀", 2), "🚀🚀"); + assert_eq!(short_id("", 8), ""); + } + #[test] fn utf8_flush_len_never_splits_a_codepoint() { // "héllo" where é is 2 bytes: split inside é keeps the partial byte.