#![allow(clippy::expect_used)]
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, Mutex, Once};
use std::time::Duration;
use axum::body::Body;
use axum::http::{Method, Request, StatusCode};
use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use futures::future::join_all;
use http_body_util::BodyExt;
use serde_json::{json, Value};
use tensor_wasm_api::{
build_router_with_config, AppState, AuthConfig, TenantConfig, HEADER_TRACE_ID,
};
use tower::ServiceExt;
use tracing::span;
use tracing::Subscriber;
use tracing_subscriber::layer::{Context, SubscriberExt};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::Layer;
const N_REQUESTS: usize = 64;
const SPANS_PER_REQUEST: usize = 4;
const COUNTED_SPAN_NAMES: &[&str] = &[
"http.request",
"http.invoke_function_async",
"async_invoke.job",
"invoke.run",
];
#[derive(Debug, Clone)]
struct Observed {
name: &'static str,
id: u64,
parent_id: Option<u64>,
}
#[derive(Clone)]
struct CountingLayer {
observed: Arc<Mutex<Vec<Observed>>>,
}
impl<S> Layer<S> for CountingLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
let name = attrs.metadata().name();
let static_name = COUNTED_SPAN_NAMES
.iter()
.copied()
.find(|n| *n == name)
.unwrap_or("__other__");
let parent_id = ctx.lookup_current().map(|s| s.id().into_u64());
let obs = Observed {
name: static_name,
id: id.into_u64(),
parent_id,
};
if let Ok(mut guard) = self.observed.lock() {
guard.push(obs);
}
}
}
fn install_counting_subscriber(observed: Arc<Mutex<Vec<Observed>>>) {
static INIT: Once = Once::new();
INIT.call_once(|| {
use opentelemetry::trace::TracerProvider as _;
let provider = opentelemetry_sdk::trace::TracerProvider::builder().build();
let tracer = provider.tracer("tensor-wasm-api-trace-concurrent-load-test");
opentelemetry::global::set_tracer_provider(provider);
let counting_layer = CountingLayer { observed };
let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
let subscriber = tracing_subscriber::registry()
.with(otel_layer)
.with(counting_layer);
let _ = subscriber.try_init();
});
}
async fn body_json(body: Body) -> Value {
let bytes = body.collect().await.expect("body").to_bytes().to_vec();
serde_json::from_slice(&bytes).expect("body is JSON")
}
fn router_with_state() -> (axum::Router, Arc<AppState>) {
let state = Arc::new(AppState::default());
let router = build_router_with_config(
Arc::clone(&state),
AuthConfig::default(),
TenantConfig::default(),
);
(router, state)
}
async fn deploy_noop_module(router: &axum::Router) -> String {
let wat = r#"(module (func (export "_start")))"#;
let wasm_bytes = wat::parse_str(wat).expect("wat");
let wasm_b64 = BASE64.encode(&wasm_bytes);
let deploy_req = Request::builder()
.method(Method::POST)
.uri("/functions")
.header("content-type", "application/json")
.body(Body::from(
serde_json::to_vec(&json!({ "name": "trace-load", "wasm_b64": wasm_b64 })).unwrap(),
))
.unwrap();
let resp = router.clone().oneshot(deploy_req).await.expect("deploy");
assert_eq!(resp.status(), StatusCode::OK);
body_json(resp.into_body())
.await
.get("id")
.and_then(Value::as_str)
.map(str::to_owned)
.expect("id")
}
async fn fire_invoke_async(router: axum::Router, function_id: String) -> axum::response::Response {
let req = Request::builder()
.method(Method::POST)
.uri(format!("/functions/{function_id}/invoke-async"))
.header("content-type", "application/json")
.body(Body::from(serde_json::to_vec(&json!({})).unwrap()))
.unwrap();
router.oneshot(req).await.expect("invoke-async")
}
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn concurrent_invoke_async_emits_exact_span_count_no_orphans_no_duplicate_trace_ids() {
let observed: Arc<Mutex<Vec<Observed>>> = Arc::new(Mutex::new(Vec::new()));
install_counting_subscriber(Arc::clone(&observed));
let baseline_len = observed.lock().expect("observed lock").len();
let (router, state) = router_with_state();
let function_id = deploy_noop_module(&router).await;
let pre_load_len = observed.lock().expect("observed lock").len();
let futures = (0..N_REQUESTS)
.map(|_| fire_invoke_async(router.clone(), function_id.clone()))
.collect::<Vec<_>>();
let responses = join_all(futures).await;
assert_eq!(
responses.len(),
N_REQUESTS,
"expected {N_REQUESTS} responses, got {}",
responses.len()
);
for (i, resp) in responses.iter().enumerate() {
assert_eq!(
resp.status(),
StatusCode::ACCEPTED,
"request #{i} did not return 202 Accepted (got {})",
resp.status()
);
}
let mut trace_ids: Vec<String> = Vec::new();
for resp in &responses {
if let Some(v) = resp.headers().get(HEADER_TRACE_ID) {
let s = v.to_str().expect("trace-id is ASCII").to_owned();
assert!(
!s.chars().all(|c| c == '0'),
"x-trace-id leaked the all-zero sentinel under load"
);
trace_ids.push(s);
}
}
let unique_trace_ids: HashSet<&String> = trace_ids.iter().collect();
assert_eq!(
unique_trace_ids.len(),
trace_ids.len(),
"x-trace-id collision under concurrent load: {} responses, {} unique ids",
trace_ids.len(),
unique_trace_ids.len(),
);
tokio::time::timeout(Duration::from_secs(10), async {
loop {
if state.metrics.jobs_active().get() == 0 {
return;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("jobs_active returns to zero within 10s of firing N invokes");
tokio::task::yield_now().await;
let snapshot: Vec<Observed> = observed.lock().expect("observed lock").clone();
let load_obs: Vec<&Observed> = snapshot.iter().skip(pre_load_len).collect();
let mut per_name: HashMap<&'static str, usize> = HashMap::new();
for obs in &load_obs {
*per_name.entry(obs.name).or_default() += 1;
}
for &name in COUNTED_SPAN_NAMES {
let got = per_name.get(name).copied().unwrap_or(0);
assert_eq!(
got, N_REQUESTS,
"span `{name}` fired {got} times for {N_REQUESTS} concurrent requests \
(expected exactly {N_REQUESTS}); \
count > N => double-instrument (likely a duplicate `.instrument()` wrap), \
count < N => `#[instrument]` dropped under contention",
);
}
let counted_total: usize = COUNTED_SPAN_NAMES
.iter()
.map(|n| per_name.get(n).copied().unwrap_or(0))
.sum();
assert_eq!(
counted_total,
SPANS_PER_REQUEST * N_REQUESTS,
"total W4.1 spans = {counted_total}, expected {SPANS_PER_REQUEST}*N = {}",
SPANS_PER_REQUEST * N_REQUESTS,
);
let all_observed_ids: HashSet<u64> = snapshot.iter().map(|o| o.id).collect();
let mut orphans: Vec<&Observed> = Vec::new();
for obs in &load_obs {
if let Some(pid) = obs.parent_id {
if !all_observed_ids.contains(&pid) {
orphans.push(obs);
}
}
}
assert!(
orphans.is_empty(),
"{} span(s) recorded a parent id we never observed — \
the `Instrument` wrap on `tokio::spawn` is parenting at a \
different context than the handler observed; W4.1 trace \
stitching is broken across the spawn boundary. First few: {:?}",
orphans.len(),
orphans.iter().take(5).collect::<Vec<_>>(),
);
assert!(
snapshot.len() > baseline_len,
"counting subscriber observed nothing across the load window \
(baseline={baseline_len}, after={})",
snapshot.len(),
);
}