Happy Eyeballs Rev 2: The DNS-ening

Instead of simply trying a V6 connection and following it with a plain V4 connection, this method relies on DNS to make some smarter decisions on resolution. This improves performance and reliability for users (and servers) without IPv6 connectivity at all.

- Switch to a DNS-driven strategy for Happy Eyeballs
- Add a Zipper Merge and CancellableDelay utility to the corups
- Remove state from HappyEyeballsCallback as it largely went unused
- Fix a couple small implementation bugs
- Suppress innocent (unhandled) exceptions.
This commit is contained in:
Kaz Wolfe 2023-04-26 23:58:46 -07:00
parent 66f955c87c
commit 76190d7e59
No known key found for this signature in database
GPG key ID: 258813F53A16EBB4
4 changed files with 136 additions and 68 deletions

View file

@ -13,7 +13,7 @@ public static class AsyncUtils
{
/// <summary>
/// Race a set of tasks, returning either the first to succeed or an aggregate of all exceptions. This helper does
/// not perform any automatic cancellation of losing tasks.
/// not perform any automatic cancellation of losing tasks, nor does it handle exceptions of losing tasks.
/// </summary>
/// <remarks>Derived from <a href="https://stackoverflow.com/a/37529395">this StackOverflow post</a>.</remarks>
/// <param name="tasks">A list of tasks to race.</param>
@ -29,7 +29,7 @@ public static class AsyncUtils
{
task.ContinueWith(t =>
{
if (task.IsCompletedSuccessfully)
if (t.IsCompletedSuccessfully)
{
tcs.TrySetResult(t.Result);
}
@ -42,4 +42,19 @@ public static class AsyncUtils
return tcs.Task;
}
/// <summary>
/// Provide a <see cref="Task.Delay(int, CancellationToken)"/> that won't throw an exception when it's canceled.
/// </summary>
/// <inheritdoc cref="Task.Delay(int, CancellationToken)"/>
public static async Task CancellableDelay(int millisecondsDelay, CancellationToken cancellationToken)
{
try
{
await Task.Delay(millisecondsDelay, cancellationToken);
}
catch (TaskCanceledException)
{
}
}
}

View file

@ -556,6 +556,56 @@ public static class Util
Process.Start(process);
}
/// <summary>
/// Perform a "zipper merge" (A, 1, B, 2, C, 3) of multiple enumerables, allowing for lists to end early.
/// </summary>
/// <param name="sources">A set of enumerable sources to combine.</param>
/// <typeparam name="TSource">The resulting type of the merged list to return.</typeparam>
/// <returns>A new enumerable, consisting of the final merge of all lists.</returns>
public static IEnumerable<TSource> ZipperMerge<TSource>(params IEnumerable<TSource>[] sources)
{
// Borrowed from https://codereview.stackexchange.com/a/263451, thank you!
var enumerators = new IEnumerator<TSource>[sources.Length];
try
{
for (var i = 0; i < sources.Length; i++)
{
enumerators[i] = sources[i].GetEnumerator();
}
var hasNext = new bool[enumerators.Length];
bool MoveNext()
{
var anyHasNext = false;
for (var i = 0; i < enumerators.Length; i++)
{
anyHasNext |= hasNext[i] = enumerators[i].MoveNext();
}
return anyHasNext;
}
while (MoveNext())
{
for (var i = 0; i < enumerators.Length; i++)
{
if (hasNext[i])
{
yield return enumerators[i].Current;
}
}
}
}
finally
{
foreach (var enumerator in enumerators)
{
enumerator?.Dispose();
}
}
}
/// <summary>
/// Dispose this object.
/// </summary>