From 8d509e920a42656749ff78f7c88d2b7218b1d3f5 Mon Sep 17 00:00:00 2001 From: HoneyryderChuck Date: Wed, 9 Jul 2025 13:27:44 +0100 Subject: [PATCH] native dns: do not write query to buffer if waiting on a previous reply in a normal situation this would not happen, as events on a resolver would be dealt one at a time, but in a fiber-scheduler environment like async, the first query could be buffered in the 1st fiber switch, the second could then be enqueued on 2nd, then flush buffer and fiber switch on read, and a third query would then enter a corrupt state where, due to the buffer having been flushed on the 2nd fiber, write the 3rd query before receiving the second one, and messing up the pending query bookkeeping, making the fiber then block (due to DNS query never returning back) and expose that as an empty interest registration from some other connection on the selector this is fixed by using @timer as a proxy for knowing that a given DNS query has been flushed but is still waiting on a response (or an eventual retry) --- lib/httpx/resolver/native.rb | 8 +++++++- sig/resolver/native.rbs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/httpx/resolver/native.rb b/lib/httpx/resolver/native.rb index cbdac28a..dc6dcacd 100644 --- a/lib/httpx/resolver/native.rb +++ b/lib/httpx/resolver/native.rb @@ -42,6 +42,7 @@ module HTTPX @read_buffer = "".b @write_buffer = Buffer.new(@resolver_options[:packet_size]) @state = :idle + @timer = nil end def close @@ -153,6 +154,8 @@ module HTTPX @timer = @current_selector.after(timeout) do next unless @connections.include?(connection) + @timer = nil + do_retry(h, connection, timeout) end end @@ -270,6 +273,8 @@ module HTTPX def parse(buffer) @timer.cancel + @timer = nil + code, result = Resolver.decode_dns_answer(buffer) case code @@ -383,7 +388,8 @@ module HTTPX raise Error, "no URI to resolve" unless connection - return unless @write_buffer.empty? + # do not buffer query if previous is still in the buffer or awaiting reply/retry + return unless @write_buffer.empty? && @timer.nil? hostname ||= @queries.key(connection) diff --git a/sig/resolver/native.rbs b/sig/resolver/native.rbs index 458b4935..fb2b49a5 100644 --- a/sig/resolver/native.rbs +++ b/sig/resolver/native.rbs @@ -23,6 +23,7 @@ module HTTPX @large_packet: Buffer? @io: UDP | TCP @name: String? + @timer: Timers::Timer? attr_reader state: Symbol