mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
286c6ce27c
Instead of just falling back to making a TCP query to an upstream DNS server when the UDP query returns a truncated query, also start a TCP query in parallel with the UDP query after a given race timeout. This ensures that if the upstream DNS server does not reply over UDP (or if the response packet is blocked, or there's an error), we can still make queries if the server replies to TCP queries. This also adds a new package, util/race, to contain the logic required for racing two different functions and returning the first non-error answer. Updates tailscale/corp#14809 Signed-off-by: Andrew Dunham <andrew@du.nham.ca> Change-Id: I4311702016c1093b1beaa31b135da1def6d86316
90 lines
1.8 KiB
Go
90 lines
1.8 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package race
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestRaceSuccess1(t *testing.T) {
|
|
const want = "success"
|
|
rh := New[string](
|
|
10*time.Second,
|
|
func(context.Context) (string, error) {
|
|
return want, nil
|
|
}, func(context.Context) (string, error) {
|
|
t.Fatal("should not be called")
|
|
return "", nil
|
|
})
|
|
res, err := rh.Start(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res != want {
|
|
t.Errorf("got res=%q, want %q", res, want)
|
|
}
|
|
}
|
|
|
|
func TestRaceRetry(t *testing.T) {
|
|
const want = "fallback"
|
|
rh := New[string](
|
|
10*time.Second,
|
|
func(context.Context) (string, error) {
|
|
return "", errors.New("some error")
|
|
}, func(context.Context) (string, error) {
|
|
return want, nil
|
|
})
|
|
res, err := rh.Start(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res != want {
|
|
t.Errorf("got res=%q, want %q", res, want)
|
|
}
|
|
}
|
|
|
|
func TestRaceTimeout(t *testing.T) {
|
|
const want = "fallback"
|
|
rh := New[string](
|
|
100*time.Millisecond,
|
|
func(ctx context.Context) (string, error) {
|
|
// Block forever
|
|
<-ctx.Done()
|
|
return "", ctx.Err()
|
|
}, func(context.Context) (string, error) {
|
|
return want, nil
|
|
})
|
|
res, err := rh.Start(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res != want {
|
|
t.Errorf("got res=%q, want %q", res, want)
|
|
}
|
|
}
|
|
|
|
func TestRaceError(t *testing.T) {
|
|
err1 := errors.New("error 1")
|
|
err2 := errors.New("error 2")
|
|
|
|
rh := New[string](
|
|
100*time.Millisecond,
|
|
func(ctx context.Context) (string, error) {
|
|
return "", err1
|
|
}, func(context.Context) (string, error) {
|
|
return "", err2
|
|
})
|
|
|
|
_, err := rh.Start(context.Background())
|
|
if !errors.Is(err, err1) {
|
|
t.Errorf("wanted err to contain err1; got %v", err)
|
|
}
|
|
if !errors.Is(err, err2) {
|
|
t.Errorf("wanted err to contain err2; got %v", err)
|
|
}
|
|
}
|