p3: tavily parse errors degrade; retry DNS on search

A 200 with a bad Tavily body is tavily: error, not an empty success
that would cache for the hour. Upstream clients stay in the source
list if DNS is down at boot and retry the pin on Search; /healthz
lists them as unresolved until a pin lands.
This commit is contained in:
2026-09-23 12:22:31 -07:00
parent f748e695b1
commit 429f6b207e
8 changed files with 177 additions and 51 deletions
+25 -6
View File
@@ -77,9 +77,7 @@ func New(raw string, opt Options) (*Client, error) {
lookup: lookup,
refresh: opt.Refresh,
}
if err := c.resolve(context.Background()); err != nil {
return nil, err
}
_ = c.resolve(context.Background()) // DNS down: keep the client, retry in Search
d := &net.Dialer{
Timeout: opt.Timeout,
Control: c.control,
@@ -204,17 +202,38 @@ func (c *Client) checkRedirect(req *http.Request, via []*http.Request) error {
return nil
}
// RefreshIfStale re-resolves when the pin is older than Refresh.
// RefreshIfStale re-resolves when unpinned or the pin is older than Refresh.
// A failed refresh leaves existing pins in place.
func (c *Client) RefreshIfStale(ctx context.Context) error {
c.mu.Lock()
stale := time.Since(c.resolved) >= c.refresh
need := len(c.ips) == 0 || time.Since(c.resolved) >= c.refresh
c.mu.Unlock()
if !stale {
if !need {
return nil
}
return c.resolve(ctx)
}
// Ensure resolves if needed. Error only when still unpinned afterwards.
func (c *Client) Ensure(ctx context.Context) error {
if c == nil {
return fmt.Errorf("search dial: nil")
}
if err := c.RefreshIfStale(ctx); err != nil && !c.Ready() {
return err
}
return nil
}
func (c *Client) Ready() bool {
if c == nil {
return false
}
c.mu.Lock()
defer c.mu.Unlock()
return len(c.ips) > 0
}
func (c *Client) IPs() []netip.Addr {
c.mu.Lock()
defer c.mu.Unlock()