Skip to content

Commit 1957ae3

Browse files
Lewis-Eclaude
andcommitted
Add integration test for concurrent TCP + named pipe transports
Boots one mini agent with both a non-zero dd_apm_receiver_port and a named pipe configured, sends a service-tagged trace through each, and asserts both service-name needles appear in the mock backend's request bodies. Then fires shutdown_tx and asserts the final stats flush arrives — exercising the watch-channel fan-out to both accept loops, the drain of each transport's in-flight handlers, and the supervisor's sequenced final flusher signal. Covers the dropped receiver_port=0 override in config.rs and the serve() supervisor's responsibility to keep both accept loops alive. create_test_trace_payload now takes Option<&str> so the dual-transport test can distinguish payloads by service name without a second helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4df57c7 commit 1957ae3

3 files changed

Lines changed: 132 additions & 17 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/target
22
/.idea
3+
/.vscode
34
/CLAUDE.md
45
/AGENTS.md

crates/datadog-trace-agent/tests/common/helpers.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,18 @@ use libdd_trace_utils::test_utils::create_test_json_span;
1010
use std::time::{Duration, UNIX_EPOCH};
1111
use tokio::time::timeout;
1212

13-
/// Create a simple test trace payload as msgpack bytes
14-
pub fn create_test_trace_payload() -> Vec<u8> {
13+
/// Create a simple test trace payload as msgpack bytes. Pass `Some(name)` to
14+
/// override the default service ("test-service"); dual-transport tests use
15+
/// distinct service names to distinguish payloads that flow through
16+
/// different listeners but share a single flusher pipeline.
17+
pub fn create_test_trace_payload(service: Option<&str>) -> Vec<u8> {
1518
let start = UNIX_EPOCH.elapsed().unwrap().as_nanos() as i64;
16-
let json_span = create_test_json_span(11, 222, 0, start, false);
17-
rmp_serde::to_vec(&vec![vec![json_span]]).expect("Failed to serialize test trace")
19+
let mut span = create_test_json_span(11, 222, 0, start, false);
20+
if let Some(name) = service {
21+
span["service"] = serde_json::Value::String(name.into());
22+
span["meta"]["service"] = serde_json::Value::String(name.into());
23+
}
24+
rmp_serde::to_vec(&vec![vec![span]]).expect("Failed to serialize test trace")
1825
}
1926

2027
/// Send an HTTP request over TCP and return the response

crates/datadog-trace-agent/tests/integration_test.rs

Lines changed: 120 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ async fn test_mini_agent_tcp_handles_requests() {
179179

180180
// Start the mini agent
181181
let agent_handle = tokio::spawn(async move {
182-
let (_shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
182+
let (_shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
183183
let _ = mini_agent.start_mini_agent(shutdown_rx, None).await;
184184
});
185185

@@ -240,7 +240,7 @@ async fn test_mini_agent_tcp_handles_requests() {
240240
);
241241

242242
// Test /v0.4/traces endpoint with real trace data
243-
let trace_payload = create_test_trace_payload();
243+
let trace_payload = create_test_trace_payload(None);
244244
let trace_response =
245245
send_tcp_request(test_port, "/v0.4/traces", "POST", Some(trace_payload), &[])
246246
.await
@@ -279,7 +279,7 @@ async fn test_mini_agent_named_pipe_handles_requests() {
279279

280280
// Start the mini agent
281281
let agent_handle = tokio::spawn(async move {
282-
let (_shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
282+
let (_shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
283283
let _ = mini_agent.start_mini_agent(shutdown_rx, None).await;
284284
});
285285

@@ -322,7 +322,7 @@ async fn test_mini_agent_named_pipe_handles_requests() {
322322
);
323323

324324
// Test /v0.4/traces endpoint with real trace data
325-
let trace_payload = create_test_trace_payload();
325+
let trace_payload = create_test_trace_payload(None);
326326
let trace_response =
327327
send_named_pipe_request(&pipe_path, "/v0.4/traces", "POST", Some(trace_payload))
328328
.await
@@ -353,7 +353,7 @@ async fn test_mini_agent_tcp_with_real_flushers() {
353353
let (mini_agent, stats_concentrator_service_handle) =
354354
create_mini_agent_with_real_flushers(config);
355355

356-
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
356+
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
357357
let agent_handle = tokio::spawn(async move {
358358
let _ = mini_agent
359359
.start_mini_agent(shutdown_rx, Some(stats_concentrator_service_handle))
@@ -377,7 +377,7 @@ async fn test_mini_agent_tcp_with_real_flushers() {
377377
);
378378

379379
// Send trace data
380-
let trace_payload = create_test_trace_payload();
380+
let trace_payload = create_test_trace_payload(None);
381381
let trace_response =
382382
send_tcp_request(test_port, "/v0.4/traces", "POST", Some(trace_payload), &[])
383383
.await
@@ -389,7 +389,7 @@ async fn test_mini_agent_tcp_with_real_flushers() {
389389
verify_trace_request(&mock_server);
390390

391391
// Trigger shutdown to force flush in progress concentrator buckets
392-
let _ = shutdown_tx.send(());
392+
let _ = shutdown_tx.send(true);
393393
let _ = agent_handle.await;
394394
verify_stats_request(&mock_server); // Stats generator should generate stats from trace payload
395395
}
@@ -410,7 +410,7 @@ async fn test_concentrator_task_death_shuts_down_mini_agent() {
410410
create_mini_agent_with_real_flushers(config);
411411
let abort_handle = stats_concentrator_service_handle.abort_handle();
412412

413-
let (_shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
413+
let (_shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
414414
let agent_handle = tokio::spawn(async move {
415415
mini_agent
416416
.start_mini_agent(shutdown_rx, Some(stats_concentrator_service_handle))
@@ -463,7 +463,7 @@ async fn test_mini_agent_tcp_with_real_flushers_and_tracer_computed_stats() {
463463
create_mini_agent_with_real_flushers(config);
464464

465465
let agent_handle = tokio::spawn(async move {
466-
let (_shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
466+
let (_shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
467467
let _ = mini_agent.start_mini_agent(shutdown_rx, None).await;
468468
});
469469

@@ -484,7 +484,7 @@ async fn test_mini_agent_tcp_with_real_flushers_and_tracer_computed_stats() {
484484
);
485485

486486
// Send trace data
487-
let trace_payload = create_test_trace_payload();
487+
let trace_payload = create_test_trace_payload(None);
488488
let trace_response = send_tcp_request(
489489
test_port,
490490
"/v0.4/traces",
@@ -524,7 +524,7 @@ async fn test_mini_agent_named_pipe_with_real_flushers() {
524524
let (mini_agent, _stats_concentrator_service_handle) =
525525
create_mini_agent_with_real_flushers(config);
526526

527-
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
527+
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
528528
let agent_handle = tokio::spawn(async move {
529529
let _ = mini_agent.start_mini_agent(shutdown_rx, None).await;
530530
});
@@ -546,7 +546,7 @@ async fn test_mini_agent_named_pipe_with_real_flushers() {
546546
);
547547

548548
// Send trace data via named pipe
549-
let trace_payload = create_test_trace_payload();
549+
let trace_payload = create_test_trace_payload(None);
550550
let trace_response =
551551
send_named_pipe_request(pipe_name, "/v0.4/traces", "POST", Some(trace_payload))
552552
.await
@@ -558,7 +558,114 @@ async fn test_mini_agent_named_pipe_with_real_flushers() {
558558
verify_trace_request(&mock_server);
559559

560560
// Trigger shutdown to force flush in progress concentrator buckets
561-
let _ = shutdown_tx.send(());
561+
let _ = shutdown_tx.send(true);
562+
let _ = agent_handle.await;
563+
verify_stats_request(&mock_server);
564+
}
565+
566+
#[cfg(all(test, windows, feature = "windows-pipes"))]
567+
#[tokio::test]
568+
#[serial]
569+
async fn test_mini_agent_dual_transport_with_real_flushers() {
570+
let mock_server = MockServer::start().await;
571+
tokio::time::sleep(Duration::from_millis(50)).await;
572+
573+
let pipe_name = r"\\.\pipe\dd_trace_dual_transport_test";
574+
let tcp_port: u16 = 8130;
575+
576+
let mut config = create_tcp_test_config(tcp_port);
577+
configure_mock_endpoints(&mut config, &mock_server.url());
578+
config.dd_apm_windows_pipe_name = Some(pipe_name.to_string());
579+
// Both transports are deliberately set on the same agent: a non-zero TCP
580+
// port AND a pipe name. They must come up concurrently.
581+
config.agent_stats_computation_enabled = true;
582+
let config = Arc::new(config);
583+
584+
let (mini_agent, stats_concentrator_service_handle) =
585+
create_mini_agent_with_real_flushers(config);
586+
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
587+
let agent_handle = tokio::spawn(async move {
588+
let _ = mini_agent
589+
.start_mini_agent(shutdown_rx, Some(stats_concentrator_service_handle))
590+
.await;
591+
});
592+
593+
// Readiness on each transport, sequentially (TCP then pipe).
594+
let mut tcp_ready = false;
595+
for _ in 0..20 {
596+
tokio::time::sleep(Duration::from_millis(50)).await;
597+
if let Ok(response) = send_tcp_request(tcp_port, "/info", "GET", None, &[]).await
598+
&& response.status().is_success()
599+
{
600+
tcp_ready = true;
601+
break;
602+
}
603+
}
604+
assert!(
605+
tcp_ready,
606+
"TCP listener did not bind on port {tcp_port} when pipe is also configured \
607+
— config may be overriding receiver_port to 0 when a pipe name is set"
608+
);
609+
610+
let mut pipe_ready = false;
611+
for _ in 0..20 {
612+
tokio::time::sleep(Duration::from_millis(50)).await;
613+
if let Ok(response) = send_named_pipe_request(pipe_name, "/info", "GET", None).await
614+
&& response.status().is_success()
615+
{
616+
pipe_ready = true;
617+
break;
618+
}
619+
}
620+
assert!(
621+
pipe_ready,
622+
"Named pipe listener did not come up at {pipe_name} when TCP is also configured"
623+
);
624+
625+
// One trace per transport, distinguishable by service name.
626+
let tcp_payload = create_test_trace_payload(Some("dual-tcp-svc"));
627+
let pipe_payload = create_test_trace_payload(Some("dual-pipe-svc"));
628+
629+
let tcp_response = send_tcp_request(tcp_port, "/v0.4/traces", "POST", Some(tcp_payload), &[])
630+
.await
631+
.expect("Failed to send /v0.4/traces request over TCP");
632+
assert_eq!(tcp_response.status(), StatusCode::OK);
633+
634+
let pipe_response =
635+
send_named_pipe_request(pipe_name, "/v0.4/traces", "POST", Some(pipe_payload))
636+
.await
637+
.expect("Failed to send /v0.4/traces request over named pipe");
638+
assert_eq!(pipe_response.status(), StatusCode::OK);
639+
640+
tokio::time::sleep(FLUSH_WAIT_DURATION).await;
641+
642+
// Both payloads must reach the same backend through the shared flusher
643+
// pipeline. The flusher may batch them into one POST or two; either is
644+
// fine, what matters is that both service-name needles show up.
645+
let trace_reqs = mock_server.get_requests_for_path("/api/v0.2/traces");
646+
assert!(
647+
!trace_reqs.is_empty(),
648+
"no trace POST reached backend; expected traces from both transports"
649+
);
650+
let mut all_bytes = Vec::new();
651+
for req in &trace_reqs {
652+
assert_eq!(req.method, "POST");
653+
all_bytes.extend_from_slice(&req.body);
654+
}
655+
assert!(
656+
all_bytes.windows(12).any(|w| w == b"dual-tcp-svc"),
657+
"TCP-side trace did not reach backend"
658+
);
659+
assert!(
660+
all_bytes.windows(13).any(|w| w == b"dual-pipe-svc"),
661+
"pipe-side trace did not reach backend"
662+
);
663+
664+
// Trigger graceful shutdown. The watch fan-out must reach BOTH accept
665+
// loops; each drains its in-flight handlers; the supervisor then
666+
// signals the stats flusher to do its final emit. If fan-out skipped
667+
// a transport, agent_handle would hang or stats wouldn't arrive.
668+
let _ = shutdown_tx.send(true);
562669
let _ = agent_handle.await;
563670
verify_stats_request(&mock_server);
564671
}

0 commit comments

Comments
 (0)