diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index e2259823b7..5c1de248d6 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 5c7fb07bd8..a1c9bb7a91 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 394a536da0..53c825fd0e 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, /**/