From b5fedf363ac686e6bfb33880f35315f14d616418 Mon Sep 17 00:00:00 2001 From: bee Date: Thu, 1 Oct 2026 18:15:05 +0100 Subject: [PATCH] Retain the existing timer backend for short scan deadlines --- Cargo.lock | 71 +++++++++++++++++++++++++++++++++++++---- Cargo.toml | 2 +- docs/tokio-migration.md | 15 +++++---- src/scanner/mod.rs | 5 ++- src/scanner/timeout.rs | 2 +- 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b207147..5971ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -103,6 +103,24 @@ version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + [[package]] name = "async-trait" version = "0.1.86" @@ -288,6 +306,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb474a9c3219a8254ead020421ecf1b90427f29b55f6aae9a2471fa62c126ef" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -521,6 +548,16 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.31" @@ -544,12 +581,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" -[[package]] -name = "futures-timer" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" - [[package]] name = "futures-util" version = "0.3.31" @@ -645,6 +676,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + [[package]] name = "hickory-proto" version = "0.24.4" @@ -1120,6 +1157,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1189,6 +1232,20 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "portable-atomic" version = "1.11.0" @@ -1453,6 +1510,7 @@ dependencies = [ "ansi_term", "anstream", "anyhow", + "async-io", "cidr-utils", "clap", "colored", @@ -1461,7 +1519,6 @@ dependencies = [ "dirs", "env_logger", "futures", - "futures-timer", "gcd", "hickory-resolver", "itertools 0.15.0", diff --git a/Cargo.toml b/Cargo.toml index 693b996..7716aba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ clap = { version = "4.6.1", features = ["derive", "wrap_help"] } colored = "3.1.1" tokio = { version = "1.53.1", features = ["rt", "net", "time"] } futures = "0.3" -futures-timer = "3.0.4" +async-io = "2.4.0" rlimit = "0.11.0" log = "0.4.32" env_logger = "0.11.10" diff --git a/docs/tokio-migration.md b/docs/tokio-migration.md index 5050b1d..d449d45 100644 --- a/docs/tokio-migration.md +++ b/docs/tokio-migration.md @@ -15,7 +15,8 @@ runtime. Script execution runs after the runtime has been dropped. Port ordering exclusions, retries, UDP payloads, closed-port reporting and CLI options retain their existing implementations. -Timeouts and port intervals use the runtime-independent `futures-timer` crate. +Timeouts and port intervals retain `async_io::Timer`, the timer underneath +async-std, independently of the Tokio socket runtime. An expired timer maps to `std::io::ErrorKind::TimedOut`. Socket errors retain their original kind, so TCP refusal reporting and UDP timeout retries continue to distinguish replies from failures. Successful TCP streams are converted to @@ -23,8 +24,8 @@ standard streams, shut down in both directions, and dropped before another attempt is polled. Using Tokio's timers for short waits added approximately one millisecond per -interval in the first benchmark. `futures-timer` uses a shared helper thread and -OS waits, avoiding that extra rounding while keeping the async runtime responsive. +interval in the first benchmark. Retaining the existing timer avoids that extra +rounding while keeping the async runtime responsive. Dropping a scan cancels its timers; runtime shutdown never has to wait for a sleeping blocking task. Immediately ready I/O operations avoid timer allocation, and the time spent polling a pending operation is charged to its original timeout. @@ -71,10 +72,10 @@ owns its own runtime and performs blocking lookups. The direct Tokio dependency enables only `rt`, `net`, and `time`; `test-util` is enabled for deterministic tests with a paused clock. Tokio was already present through Hickory. The existing `futures` dependency continues to provide -`FuturesUnordered`, `StreamExt` and future polling. The new `futures-timer` 3.0.4 -dependency has no transitive dependencies on native targets; its purpose is to -preserve short timeout and interval performance. `async-std` is absent from the -resolved dependency tree. +`FuturesUnordered`, `StreamExt` and future polling. `async-io` becomes a direct +dependency solely to retain the existing timer implementation and its short-wait +performance; socket I/O uses Tokio. `async-std` and its executor are absent from +the resolved dependency tree. Regression tests cover successful operations (including zero-byte responses), preservation of I/O errors, timeout cancellation, immediate results at a zero diff --git a/src/scanner/mod.rs b/src/scanner/mod.rs index 50280cb..614a90b 100644 --- a/src/scanner/mod.rs +++ b/src/scanner/mod.rs @@ -165,7 +165,10 @@ impl Scanner { // Tokio's millisecond rounding adds about 1 ms to each short interval. // This cancellable delay keeps the requested spacing without blocking // the async runtime or accumulating that rounding on every port. - self.run_with_delay(futures_timer::Delay::new).await + self.run_with_delay(|interval| async move { + async_io::Timer::after(interval).await; + }) + .await } /// Supplying the delay lets tests verify spacing with a virtual clock. diff --git a/src/scanner/timeout.rs b/src/scanner/timeout.rs index ef83e62..3736bd8 100644 --- a/src/scanner/timeout.rs +++ b/src/scanner/timeout.rs @@ -20,7 +20,7 @@ pub(super) async fn io_timeout( // Charge socket setup to the original deadline. The timer uses OS waits // without rounding each short timeout up to Tokio's next millisecond tick. - let delay = futures_timer::Delay::new(duration.saturating_sub(started.elapsed())); + let delay = async_io::Timer::after(duration.saturating_sub(started.elapsed())); match select(operation, delay).await { Either::Left((result, _)) => result, Either::Right(_) => Err(io::Error::new(io::ErrorKind::TimedOut, "future timed out")),