@@ -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