Commit: patch 9.2.0021: channel: connection timeout fails to fall back to IPv4

0 views
Skip to first unread message

Christian Brabandt

unread,
Feb 18, 2026, 4:46:42 PM (yesterday) Feb 18
to vim...@googlegroups.com
patch 9.2.0021: channel: connection timeout fails to fall back to IPv4

Commit: https://github.com/vim/vim/commit/dd989ec9ca3654584e2813f26a322f9de18406bf
Author: thinca <thi...@gmail.com>
Date: Wed Feb 18 21:34:57 2026 +0000

patch 9.2.0021: channel: connection timeout fails to fall back to IPv4

Problem: When ch_open() tries to connect to a hostname that resolves to
multiple addresses (e.g., both IPv6 and IPv4), it uses a
single waittime for all connection attempts. If the first IPv6
connection attempt times out, it consumes almost all of the
waittime, leaving insufficient time (often just 1ms) for the
IPv4 attempt to succeed. (reporter)
Solution: Implement a simplified version of Happy Eyeballs (RFC 8305) to
improve connection fallback behavior when IPv6 is unavailable
or slow (thinca).

Distribute the waittime across multiple addresses:
- First address: use up to 250ms (RFC 8305 Connection Attempt Delay) or
half of the total waittime, whichever is smaller
- Middle addresses: divide remaining time equally
- Last address: use all remaining time

This ensures that IPv4 fallback has sufficient time to succeed even when
IPv6 connection attempts fail or timeout.

closes: #19233

Co-Authored-By: Claude Sonnet 4.5 <nor...@anthropic.com>
Signed-off-by: thinca <thi...@gmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index e2259823b..5c1de248d 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -1,4 +1,4 @@
-*channel.txt* For Vim version 9.2. Last change: 2026 Feb 14
+*channel.txt* For Vim version 9.2. Last change: 2026 Feb 18


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -130,6 +130,11 @@ a Unix-domain socket path prefixed by "unix:". E.g. >
[2001:db8::1]:8765 " IPv6 + port
unix:/tmp/my-socket " Unix-domain socket path

+When a domain name resolves to multiple addresses (e.g., both IPv6 and IPv4),
+Vim tries each address in order. If a connection is slow or unreachable, it
+quickly falls back to the next address. This helps when IPv6 or IPv4 is
+unreachable on the network.
+
{options} is a dictionary with optional entries: *channel-open-options*

"mode" can be: *channel-mode*
diff --git a/src/channel.c b/src/channel.c
index 5c7fb07bd..a1c9bb7a9 100644
--- a/src/channel.c
+++ b/src/channel.c
@@ -935,13 +935,31 @@ channel_open(
return NULL;
}

+ // Count the number of addresses for timeout distribution
+ int addr_count = 0;
for (addr = res; addr != NULL; addr = addr->ai_next)
+ addr_count++;
+
+ // On Mac and Solaris a zero timeout almost never works. Waiting for
+ // one millisecond already helps a lot. Later Mac systems (using IPv6)
+ // need more time, 15 milliseconds appears to work well.
+ // Let's do it for all systems, because we don't know why this is
+ // needed.
+ if (waittime == 0)
+ waittime = 15;
+
+ int addr_index = 0;
+ for (addr = res; addr != NULL; addr = addr->ai_next, addr_index++)
{
const char *dst = hostname;
# ifdef HAVE_INET_NTOP
const void *src = NULL;
char buf[NUMBUFLEN];
# endif
+ int try_waittime;
+ int before_waittime;
+ int consumed;
+ int remaining_addrs;

if (addr->ai_family == AF_INET6)
{
@@ -974,18 +992,44 @@ channel_open(

ch_log(channel, "Trying to connect to %s port %d", dst, port);

- // On Mac and Solaris a zero timeout almost never works. Waiting for
- // one millisecond already helps a lot. Later Mac systems (using IPv6)
- // need more time, 15 milliseconds appears to work well.
- // Let's do it for all systems, because we don't know why this is
- // needed.
- if (waittime == 0)
- waittime = 15;
+ // Distribute the timeout across addresses for better fallback behavior.
+ // This implements a simplified version of Happy Eyeballs (RFC 8305).
+ if (addr->ai_next == NULL)
+ try_waittime = waittime;
+ else if (addr_index == 0)
+ {
+ if (waittime > 500)
+ try_waittime = 250;
+ else if (waittime > 30)
+ try_waittime = waittime / 2;
+ else
+ try_waittime = waittime;
+ }
+ else
+ {
+ remaining_addrs = addr_count - addr_index;
+ try_waittime = waittime / remaining_addrs;
+ }

+ before_waittime = try_waittime;
sd = channel_connect(channel, addr->ai_addr, (int)addr->ai_addrlen,
- &waittime);
+ &try_waittime);
+
+ // Update the overall waittime based on consumed time
+ consumed = before_waittime - try_waittime;
+ waittime -= consumed;
+ if (waittime < 0)
+ waittime = 0;
+
if (sd >= 0)
break;
+
+ // If we have no time left, stop trying
+ if (waittime <= 0 && addr->ai_next != NULL)
+ {
+ ch_log(channel, "Out of time, stopping connection attempts");
+ break;
+ }
}

freeaddrinfo(res);
diff --git a/src/version.c b/src/version.c
index 394a536da..53c825fd0 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 21,
/**/
20,
/**/
Reply all
Reply to author
Forward
0 new messages