|
| 1 | +//! Regression test for GitHub issue #1: |
| 2 | +//! `measure_many` hangs with `current_thread` tokio runtime. |
| 3 | +//! |
| 4 | +//! The bug was a race condition where the background receive task would |
| 5 | +//! block on socket recv() before processing subscription messages. In a |
| 6 | +//! single-threaded runtime, this caused ICMP replies to be dropped because |
| 7 | +//! subscribers weren't registered yet. |
| 8 | +//! |
| 9 | +//! This test requires network access and the ability to send ICMP packets |
| 10 | +//! to localhost. |
| 11 | +
|
| 12 | +use std::{net::IpAddr, time::Duration}; |
| 13 | + |
| 14 | +use futures_util::StreamExt; |
| 15 | +use massping::DualstackPinger; |
| 16 | +use tokio::time; |
| 17 | + |
| 18 | +/// Test that pinging localhost works with `current_thread` runtime. |
| 19 | +/// |
| 20 | +/// This is a regression test for issue #1 where `measure_many` would hang |
| 21 | +/// indefinitely on single-threaded runtimes due to a race condition between |
| 22 | +/// subscription registration and ICMP reply processing. |
| 23 | +#[tokio::test(flavor = "current_thread")] |
| 24 | +async fn ping_localhost_current_thread() { |
| 25 | + let localhost: IpAddr = "127.0.0.1".parse().unwrap(); |
| 26 | + |
| 27 | + let pinger = DualstackPinger::new().expect("failed to create pinger"); |
| 28 | + let mut stream = pinger.measure_many([localhost].into_iter()); |
| 29 | + |
| 30 | + // With the bug, this would hang forever. With the fix, localhost should |
| 31 | + // respond within milliseconds. We use a generous 5 second timeout to |
| 32 | + // account for slow CI environments. |
| 33 | + let result = time::timeout(Duration::from_secs(5), stream.next()).await; |
| 34 | + |
| 35 | + match result { |
| 36 | + Ok(Some((addr, rtt))) => { |
| 37 | + assert_eq!(addr, localhost); |
| 38 | + // Localhost RTT should be very fast (sub-millisecond typically) |
| 39 | + assert!(rtt < Duration::from_secs(1), "RTT too high: {rtt:?}"); |
| 40 | + } |
| 41 | + Ok(None) => { |
| 42 | + panic!("stream ended unexpectedly"); |
| 43 | + } |
| 44 | + Err(_) => { |
| 45 | + panic!( |
| 46 | + "timeout waiting for ping response - \ |
| 47 | + this indicates the current_thread runtime bug (issue #1) has regressed" |
| 48 | + ); |
| 49 | + } |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +/// Test that pinging localhost works with `multi_thread` runtime. |
| 54 | +/// |
| 55 | +/// This serves as a baseline - if this test passes but `current_thread` fails, |
| 56 | +/// it confirms the issue is specific to single-threaded runtimes. |
| 57 | +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] |
| 58 | +async fn ping_localhost_multi_thread() { |
| 59 | + let localhost: IpAddr = "127.0.0.1".parse().unwrap(); |
| 60 | + |
| 61 | + let pinger = DualstackPinger::new().expect("failed to create pinger"); |
| 62 | + let mut stream = pinger.measure_many([localhost].into_iter()); |
| 63 | + |
| 64 | + let result = time::timeout(Duration::from_secs(5), stream.next()).await; |
| 65 | + |
| 66 | + match result { |
| 67 | + Ok(Some((addr, rtt))) => { |
| 68 | + assert_eq!(addr, localhost); |
| 69 | + assert!(rtt < Duration::from_secs(1), "RTT too high: {rtt:?}"); |
| 70 | + } |
| 71 | + Ok(None) => { |
| 72 | + panic!("stream ended unexpectedly"); |
| 73 | + } |
| 74 | + Err(_) => { |
| 75 | + panic!("timeout waiting for ping response"); |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +/// Test pinging multiple times sequentially with `current_thread` runtime. |
| 81 | +/// |
| 82 | +/// This tests that multiple sequential ping operations work correctly, |
| 83 | +/// ensuring the fix handles repeated use of the pinger. |
| 84 | +#[tokio::test(flavor = "current_thread")] |
| 85 | +async fn ping_sequential_current_thread() { |
| 86 | + let localhost: IpAddr = "127.0.0.1".parse().unwrap(); |
| 87 | + |
| 88 | + let pinger = DualstackPinger::new().expect("failed to create pinger"); |
| 89 | + |
| 90 | + // Perform multiple sequential pings |
| 91 | + for i in 0..3 { |
| 92 | + let mut stream = pinger.measure_many([localhost].into_iter()); |
| 93 | + |
| 94 | + let result = time::timeout(Duration::from_secs(5), stream.next()).await; |
| 95 | + |
| 96 | + match result { |
| 97 | + Ok(Some((addr, rtt))) => { |
| 98 | + assert_eq!(addr, localhost); |
| 99 | + assert!( |
| 100 | + rtt < Duration::from_secs(1), |
| 101 | + "RTT too high on ping {i}: {rtt:?}" |
| 102 | + ); |
| 103 | + } |
| 104 | + Ok(None) => { |
| 105 | + panic!("stream ended unexpectedly on ping {i}"); |
| 106 | + } |
| 107 | + Err(_) => { |
| 108 | + panic!( |
| 109 | + "timeout on ping {i} - \ |
| 110 | + current_thread runtime bug may have regressed" |
| 111 | + ); |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | +} |
0 commit comments